Skip to main content

My First Effect

In this tutorial you will learn how to create particle effects (Particle Effects, PFX) - from simple smoke, through fire, to rain and snow.

How Do Particle Effects Work?

The PFX system in Gothic emits particles (small textured sprites) from an emitter with a defined shape. Each particle has its own direction, speed, lifespan, and appearance.

Effects are defined as instances of the C_ParticleFX class in files in the System/PFX/ directory:

FileDescription
PfxInst.dGeneral effects (fire, smoke, sparks, water, weather)
PfxInstEngine.dEngine-required effects (blood, dust, water splash)
PfxInstMagic.dMagic effects (spells, runes, auras)

The C_ParticleFX Class - Overview

The class has 49 fields divided into 7 categories. You don't need to set them all - fields you don't set will use default values (usually 0 or empty string).

1. Emission Rate - How Many Particles and When

FieldTypeDescription
ppsValuefloatBase particles emitted per second
ppsScaleKeys_SstringTime-varying multipliers, e.g., "1 2 3"
ppsIsLoopingint1 = looping, 0 = one-shot
ppsIsSmoothint1 = smooth interpolation between keys
ppsFPSfloatKey playback speed (frames/sec)
ppsCreateEm_SstringChild effect name (spawned per particle)
ppsCreateEmDelayfloatChild effect delay

2. Emitter Shape - Where Particles Come From

FieldTypeDescription
shpType_SstringShape: "POINT", "LINE", "BOX", "CIRCLE", "SPHERE", "MESH"
shpFOR_SstringFrame of reference: "OBJECT" or "WORLD"
shpOffsetVec_SstringOffset: "X Y Z"
shpDistribType_SstringDistribution: "RAND", "UNIFORM", "WALK", "DIR"
shpIsVolumeint1 = emit from volume, 0 = from surface only
shpDim_SstringDimensions (shape-dependent)
shpMesh_SstringEmitter mesh (when shpType_S = "MESH")
shpMeshRender_Bint1 = render the emitter mesh

3. Direction and Speed

FieldTypeDescription
dirMode_SstringMode: "DIR", "TARGET", "MESH_POLY", "RAND", "NONE"
dirFOR_SstringDirection frame of reference
dirAngleHeadfloatHorizontal rotation angle (°)
dirAngleHeadVarfloatAngle variance (±°)
dirAngleElevfloatElevation angle (°); 90 = up, -90 = down
dirAngleElevVarfloatElevation variance (±°)
velAvgfloatAverage initial velocity
velVarfloatVelocity variance (±)

4. Particle Lifespan

FieldTypeDescription
lspPartAvgfloatAverage lifespan (ms)
lspPartVarfloatLifespan variance (±ms)

5. Flight Behavior

FieldTypeDescription
flyGravity_SstringGravity vector: "X Y Z"
flyCollDet_Bint0 = no collision, 1 = collisions, 3 = collisions + marks

6. Visualization

FieldTypeDescription
visName_SstringTexture (.TGA) or model (.3DS)
visOrientation_SstringBillboard: "NONE", "VELO", "VELO3D", "VOB"
visTexIsQuadPolyint0 = triangle, 1 = quad mesh
visTexAniFPSfloatTexture animation FPS
visTexAniIsLoopingint0 = once, 1 = loop, 2 = ping-pong
visTexColorStart_SstringStart color: "R G B" (0–255)
visTexColorEnd_SstringEnd color (interpolated over lifespan)
visSizeStart_SstringStart size: "W H"
visSizeEndScalefloatEnd size multiplier
visAlphaFunc_SstringBlending: "BLEND", "ADD", "MUL"
visAlphaStartfloatStart alpha (0–255)
visAlphaEndfloatEnd alpha (0–255)

7. Additional Effects

FieldTypeDescription
trlFadeSpeedfloatTrail fade speed
trlTexture_SstringTrail texture
trlWidthfloatTrail width
mrkFadeSpeedfloatMark (decal) fade speed
mrkTexture_SstringMark texture
mrkSizefloatMark size
flockModestringFlocking mode: "WIND"
flockStrengthfloatFlocking strength
useEmittersFORint1 = particles follow emitter position
timeStartEnd_SstringRender time window: "8 22" (8am–10pm)
m_bIsAmbientPFXint1 = ambient effect (can be disabled in gothic.ini)

Example 1: Simple Smoke

Let's start with something simple - a column of smoke rising upward:

instance PFX_MySmoke (C_ParticleFX)
{
// --- Emission: 30 particles/sec, continuous ---
ppsValue = 30;
ppsScaleKeys_S = "1";
ppsIsLooping = 1;

// --- Shape: point ---
shpType_S = "POINT";
shpFOR_S = "OBJECT";

// --- Direction: upward with random variance ---
dirMode_S = "DIR";
dirFOR_S = "OBJECT";
dirAngleElev = 90;
dirAngleElevVar = 15;
dirAngleHeadVar = 180;
velAvg = 0.02;
velVar = 0.01;

// --- Lifespan: 2–3 seconds ---
lspPartAvg = 2500;
lspPartVar = 500;

// --- No gravity (smoke rises) ---
flyGravity_S = "0 0.0001 0";

// --- Appearance ---
visName_S = "SMOKE1.TGA";
visOrientation_S = "NONE";
visTexColorStart_S = "150 150 150";
visTexColorEnd_S = "80 80 80";
visSizeStart_S = "10 10";
visSizeEndScale = 5;
visAlphaFunc_S = "BLEND";
visAlphaStart = 180;
visAlphaEnd = 0;
};
FieldValueDescription
ppsValue3030 particles per second
ppsScaleKeys_S"1"Constant rate (no scaling)
ppsIsLooping1Continuous emission
shpType_S"POINT"Emits from a single point
shpFOR_S"OBJECT"Relative to the emitter object
dirMode_S"DIR"Directional emission
dirFOR_S"OBJECT"Direction relative to object
dirAngleElev90Upward direction
dirAngleElevVar15±15° random variance
dirAngleHeadVar180Full 360° horizontal spread
velAvg0.02Slow speed
velVar0.01Slight speed variation
lspPartAvg2500Average lifespan 2.5 seconds
lspPartVar500±0.5s lifespan variance
flyGravity_S"0 0.0001 0"Slightly upward (smoke rises)
visName_S"SMOKE1.TGA"Smoke texture
visOrientation_S"NONE"Billboard facing camera
visTexColorStart_S"150 150 150"Gray at birth
visTexColorEnd_S"80 80 80"Darkens over time
visSizeStart_S"10 10"10×10 starting size
visSizeEndScale5Grows 5×
visAlphaFunc_S"BLEND"Standard blending
visAlphaStart180Semi-transparent at birth
visAlphaEnd0Fades out completely
tip

visAlphaFunc_S - blending modes:

  • "BLEND" - classic blending (smoke, fog, dust)
  • "ADD" - additive (fire, sparks, magic - bright, glowing)
  • "MUL" - multiplicative (shadows, darkening)

Example 2: Campfire

Fire combines fast emission, additive blending, and animated textures:

instance PFX_MyFire (C_ParticleFX)
{
// --- Emission: lots of particles, continuous ---
ppsValue = 80;
ppsScaleKeys_S = "1";
ppsIsLooping = 1;

// --- Shape: circle (fire base) ---
shpType_S = "CIRCLE";
shpFOR_S = "OBJECT";
shpIsVolume = 1;
shpDim_S = "15";

// --- Direction: upward ---
dirMode_S = "DIR";
dirFOR_S = "OBJECT";
dirAngleElev = 90;
dirAngleElevVar = 20;
dirAngleHeadVar = 180;
velAvg = 0.05;
velVar = 0.02;

// --- Lifespan: short (fast fire) ---
lspPartAvg = 800;
lspPartVar = 200;

// --- Slight upward gravity (hot air) ---
flyGravity_S = "0 0.0003 0";

// --- Appearance ---
visName_S = "FIREFLARE.TGA";
visOrientation_S = "NONE";
visTexAniFPS = 8;
visTexAniIsLooping = 1;
visTexColorStart_S = "255 255 255";
visTexColorEnd_S = "255 100 30";
visSizeStart_S = "5 5";
visSizeEndScale = 4;
visAlphaFunc_S = "ADD";
visAlphaStart = 255;
visAlphaEnd = 0;
};
FieldValueDescription
ppsValue8080 particles per second
ppsScaleKeys_S"1"Constant rate
ppsIsLooping1Continuous emission
shpType_S"CIRCLE"Circular emitter shape
shpFOR_S"OBJECT"Relative to the emitter object
shpIsVolume1Emits from entire disk area
shpDim_S"15"Circle radius 15 units
dirMode_S"DIR"Directional emission
dirFOR_S"OBJECT"Direction relative to object
dirAngleElev90Upward direction
dirAngleElevVar20±20° random variance
dirAngleHeadVar180Full 360° horizontal spread
velAvg0.05Moderate speed
velVar0.02Speed variation
lspPartAvg800Short lifespan (0.8s)
lspPartVar200±0.2s variance
flyGravity_S"0 0.0003 0"Slight upward pull (hot air)
visName_S"FIREFLARE.TGA"Fire texture
visOrientation_S"NONE"Billboard facing camera
visTexAniFPS8Texture animation speed
visTexAniIsLooping1Animation loops
visTexColorStart_S"255 255 255"White (overexposed center)
visTexColorEnd_S"255 100 30"Orange (flame edges)
visSizeStart_S"5 5"5×5 starting size
visSizeEndScale4Grows 4×
visAlphaFunc_S"ADD"Additive blending (glowing)
visAlphaStart255Fully opaque at birth
visAlphaEnd0Fades out completely

Example 3: Sparks

Sparks are small, fast particles with gravity and collisions:

instance PFX_MySparks (C_ParticleFX)
{
// --- Emission: one-time burst ---
ppsValue = 50;
ppsScaleKeys_S = "1 0";
ppsIsLooping = 0;
ppsFPS = 2;

// --- Shape: point ---
shpType_S = "POINT";
shpFOR_S = "OBJECT";

// --- Direction: scatter in all directions ---
dirMode_S = "DIR";
dirFOR_S = "OBJECT";
dirAngleHeadVar = 180;
dirAngleElev = 45;
dirAngleElevVar = 45;
velAvg = 0.15;
velVar = 0.08;

// --- Lifespan: short ---
lspPartAvg = 600;
lspPartVar = 300;

// --- Gravity pulls down ---
flyGravity_S = "0 -0.0005 0";
flyCollDet_B = 1;

// --- Appearance: small, bright dots ---
visName_S = "ZFLARE1.TGA";
visOrientation_S = "NONE";
visTexColorStart_S = "255 220 100";
visTexColorEnd_S = "255 80 20";
visSizeStart_S = "2 2";
visSizeEndScale = 0.5;
visAlphaFunc_S = "ADD";
visAlphaStart = 255;
visAlphaEnd = 0;
};
FieldValueDescription
ppsValue5050 particles in burst
ppsScaleKeys_S"1 0"Instant burst, then nothing
ppsIsLooping0One-shot (not looping)
ppsFPS2Scale key playback speed
shpType_S"POINT"Emits from a single point
shpFOR_S"OBJECT"Relative to the emitter object
dirMode_S"DIR"Directional emission
dirFOR_S"OBJECT"Direction relative to object
dirAngleHeadVar180Full 360° scatter
dirAngleElev45Slightly upward
dirAngleElevVar45Wide vertical variance
velAvg0.15Fast speed
velVar0.08High speed variance
lspPartAvg600Short lifespan (0.6s)
lspPartVar300±0.3s variance
flyGravity_S"0 -0.0005 0"Gravity pulls down
flyCollDet_B1Collides with world geometry
visName_S"ZFLARE1.TGA"Small flare texture
visOrientation_S"NONE"Billboard facing camera
visTexColorStart_S"255 220 100"Yellow
visTexColorEnd_S"255 80 20"Dark orange
visSizeStart_S"2 2"2×2 small size
visSizeEndScale0.5Shrinks to half size
visAlphaFunc_S"ADD"Additive blending (glowing)
visAlphaStart255Fully opaque at birth
visAlphaEnd0Fades out completely

Example 4: Snow

Snow uses a large emitter high above, with slowly falling particles:

instance PFX_MySnow (C_ParticleFX)
{
// --- Emission: continuous ---
ppsValue = 50;
ppsScaleKeys_S = "1";
ppsIsLooping = 1;

// --- Shape: large circle high above the player ---
shpType_S = "CIRCLE";
shpFOR_S = "OBJECT";
shpOffsetVec_S = "0 500 0";
shpIsVolume = 1;
shpDim_S = "300";

// --- Direction: downward ---
dirMode_S = "DIR";
dirFOR_S = "OBJECT";
dirAngleHead = 20;
dirAngleHeadVar = 10;
dirAngleElev = -89;
velAvg = 0.05;
velVar = 0.02;

// --- Lifespan: long ---
lspPartAvg = 5000;

// --- No gravity (constant fall speed) ---
flyGravity_S = "0 0 0";

// --- Appearance: white flakes ---
visName_S = "MFX_SLEEP_STAR.TGA";
visOrientation_S = "NONE";
visTexColorStart_S = "255 255 255";
visTexColorEnd_S = "255 255 255";
visSizeStart_S = "5 5";
visSizeEndScale = 1;
visAlphaFunc_S = "ADD";
visAlphaStart = 255;
visAlphaEnd = 255;

// --- Ambient effect (can be disabled in options) ---
m_bIsAmbientPFX = 1;
};
FieldValueDescription
ppsValue5050 particles per second
ppsScaleKeys_S"1"Constant rate
ppsIsLooping1Continuous emission
shpType_S"CIRCLE"Circular emitter shape
shpFOR_S"OBJECT"Relative to the emitter object
shpOffsetVec_S"0 500 0"500 units above the emitter
shpIsVolume1Fills entire disk area
shpDim_S"300"Circle radius 300 units
dirMode_S"DIR"Directional emission
dirFOR_S"OBJECT"Direction relative to object
dirAngleHead20Slight wind direction
dirAngleHeadVar10±10° wind variance
dirAngleElev-89Nearly straight down
velAvg0.05Slow falling speed
velVar0.02Slight speed variation
lspPartAvg5000Long lifespan (5 seconds)
flyGravity_S"0 0 0"No gravity (constant fall speed)
visName_S"MFX_SLEEP_STAR.TGA"Snowflake texture
visOrientation_S"NONE"Billboard facing camera
visTexColorStart_S"255 255 255"White
visTexColorEnd_S"255 255 255"Stays white
visSizeStart_S"5 5"5×5 flake size
visSizeEndScale1No size change
visAlphaFunc_S"ADD"Additive blending (bright flakes)
visAlphaStart255Fully visible
visAlphaEnd255Stays visible (no fade)
m_bIsAmbientPFX1Ambient effect (can disable in options)

Example 5: Blood (Child Emitters)

The child emitter system lets you create complex effects. Blood in Gothic consists of two instances - the main one (blood spray) and the child one (ground splat):

// Main effect: blood spray scattering
instance PFX_MyBlood (C_ParticleFX)
{
ppsValue = 64;
ppsCreateEm_S = "PFX_MyBlood_Splat";

dirMode_S = "DIR";
dirFOR_S = "OBJECT";
dirAngleHeadVar = 30;
dirAngleElevVar = 30;
velAvg = 0.1;
velVar = 0.05;

lspPartAvg = 750;
lspPartVar = 550;

flyGravity_S = "0 -0.0001 0";
flyCollDet_B = 1;

visName_S = "BLOOD1.TGA";
visTexColorStart_S = "255 255 255";
visTexColorEnd_S = "255 255 255";
visSizeStart_S = "6 6";
visSizeEndScale = 1;
visAlphaFunc_S = "BLEND";
visAlphaStart = 255;
};

// Child effect: ground splat
instance PFX_MyBlood_Splat (C_ParticleFX)
{
ppsValue = 1;
ppsIsLooping = 0;

shpType_S = "POINT";

dirMode_S = "NONE";
velAvg = 0;

lspPartAvg = 3000;

visName_S = "YOURBLOODSPLAT.TGA";
visSizeStart_S = "10 10";
visSizeEndScale = 1;
visAlphaFunc_S = "BLEND";
visAlphaStart = 200;
visAlphaEnd = 0;
};

PFX_MyBlood:

FieldValueDescription
ppsValue6464 particles in burst
ppsCreateEm_S"PFX_MyBlood_Splat"Spawns child effect per particle
dirMode_S"DIR"Directional emission
dirFOR_S"OBJECT"Direction relative to object
dirAngleHeadVar30±30° horizontal scatter
dirAngleElevVar30±30° vertical scatter
velAvg0.1Moderate speed
velVar0.05Speed variation
lspPartAvg7500.75s lifespan
lspPartVar550High lifespan variance
flyGravity_S"0 -0.0001 0"Falls downward
flyCollDet_B1Collides with world geometry
visName_S"BLOOD1.TGA"Blood texture
visTexColorStart_S"255 255 255"White (preserves texture color)
visTexColorEnd_S"255 255 255"No color change
visSizeStart_S"6 6"6×6 starting size
visSizeEndScale1No size change
visAlphaFunc_S"BLEND"Standard blending
visAlphaStart255Fully opaque

PFX_MyBlood_Splat:

FieldValueDescription
ppsValue1Single particle (one splat)
ppsIsLooping0One-shot
shpType_S"POINT"Point emitter
dirMode_S"NONE"No movement direction
velAvg0Stationary
lspPartAvg3000Lasts 3 seconds
visName_S"YOURBLOODSPLAT.TGA"Splat texture
visSizeStart_S"10 10"10×10 splat size
visSizeEndScale1No size change
visAlphaFunc_S"BLEND"Standard blending
visAlphaStart200Slightly transparent
visAlphaEnd0Fades out completely
info

ppsCreateEm_S - each particle from the main emitter becomes a source for a new child effect. This is a powerful tool but expensive - use carefully to avoid overloading the engine.

Emitter Shapes

ShapeshpType_SshpDim_SDescription
Point"POINT"-Emission from a single point
Line"LINE""100" (length)Emission along a line
Box"BOX""W H D"Emission from a rectangular area
Circle"CIRCLE""50" (radius)Emission from a circle (or disk when shpIsVolume = 1)
Sphere"SPHERE""50" (radius)Emission from a sphere
Mesh"MESH""250" (scale)Emission from a 3D mesh surface

shpIsVolume

  • shpIsVolume = 0 - particles appear on the edge of the shape (e.g., on the circle circumference)
  • shpIsVolume = 1 - particles appear inside the shape (e.g., within the entire circle)

Particle Orientation

ModevisOrientation_SDescription
Billboard"NONE"Particles always face the camera (default)
Along velocity"VELO"Particles stretched in movement direction (rain, sparks)
3D along velocity"VELO3D"Like VELO, but with full 3D rotation
Object"VOB"Orientation matches the parent object

Registration in ParticleFX.src

Particle effects have a separate compilation from game scripts. Add your file to System/ParticleFX.src:

_intern\ParticleFx.d
Pfx\PfxInstEngine.d
Pfx\PfxInst.d
Pfx\PfxInstMagic.d
Pfx\MyPfx.d
warning

PFX effects are not compiled by Gothic.src - they use their own ParticleFX.src file in the System/ directory.

Practical Tips

Performance

  • Higher ppsValue means more particles = more computation
  • flyCollDet_B with many particles heavily loads the CPU
  • useEmittersFOR = 1 combined with flyCollDet_B is the most expensive combination
  • ppsCreateEm_S multiplies the number of effects - each particle creates a new emitter

Debugging

  • If the effect is not visible, check that visAlphaStart > 0 and visSizeStart_S is not too small
  • Verify that the texture (.TGA) exists in the Textures/ directory
  • Effects with m_bIsAmbientPFX = 1 can be disabled in game options

Common Patterns

EffectKey Settings
SmokeBLEND, large visSizeEndScale, visAlphaEnd = 0
FireADD, animated texture, short lspPartAvg
SparksADD, one-time burst, downward gravity, collisions
Rain/SnowLarge CIRCLE emitter, Y offset, dirAngleElev = -89
BloodBLEND, gravity, child emitter (splats)
Magic/AuraADD, CIRCLE emitter, useEmittersFOR = 1

Summary

Creating particle effects requires:

  1. An instance of the C_ParticleFX class with appropriate parameters
  2. An emitter shape (shpType_S) - where particles come from
  3. Direction and speed - how they move
  4. Visualization - texture, color, size, blending
  5. Registration in ParticleFX.src (not in Gothic.src!)