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; // upward
dirAngleElevVar = 15; // ±15° variance
dirAngleHeadVar = 180; // spread sideways
velAvg = 0.02; // slow
velVar = 0.01;

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

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

// --- Appearance ---
visName_S = "SMOKE1.TGA";
visOrientation_S = "NONE"; // billboard facing camera
visTexColorStart_S = "150 150 150"; // gray
visTexColorEnd_S = "80 80 80"; // darkens over time
visSizeStart_S = "10 10";
visSizeEndScale = 5; // grows 5x
visAlphaFunc_S = "BLEND"; // standard blending
visAlphaStart = 180;
visAlphaEnd = 0; // fades out
};
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"; // radius 15 units

// --- 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; // texture animation
visTexAniIsLooping = 1; // looped
visTexColorStart_S = "255 255 255"; // white (overexposed center)
visTexColorEnd_S = "255 100 30"; // orange (edges)
visSizeStart_S = "5 5";
visSizeEndScale = 4; // grows
visAlphaFunc_S = "ADD"; // additive = glowing
visAlphaStart = 255;
visAlphaEnd = 0;
};

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"; // instant burst, then nothing
ppsIsLooping = 0; // one-shot
ppsFPS = 2;

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

// --- Direction: scatter in all directions ---
dirMode_S = "DIR";
dirFOR_S = "OBJECT";
dirAngleHeadVar = 180; // full 360°
dirAngleElev = 45; // slightly upward
dirAngleElevVar = 45; // but with wide variance
velAvg = 0.15; // fast
velVar = 0.08;

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

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

// --- Appearance: small, bright dots ---
visName_S = "ZFLARE1.TGA";
visOrientation_S = "NONE";
visTexColorStart_S = "255 220 100"; // yellow
visTexColorEnd_S = "255 80 20"; // dark orange
visSizeStart_S = "2 2";
visSizeEndScale = 0.5; // shrink
visAlphaFunc_S = "ADD";
visAlphaStart = 255;
visAlphaEnd = 0;
};

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"; // 500 units above emitter
shpIsVolume = 1;
shpDim_S = "300"; // radius 300

// --- Direction: downward ---
dirMode_S = "DIR";
dirFOR_S = "OBJECT";
dirAngleHead = 20; // slight wind
dirAngleHeadVar = 10;
dirAngleElev = -89; // nearly straight down
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;
};

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"; // child effect!

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"; // falls
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; // single splat

shpType_S = "POINT";

dirMode_S = "NONE"; // no movement
velAvg = 0;

lspPartAvg = 3000; // 3 seconds

visName_S = "YOURBLOODSPLAT.TGA";
visSizeStart_S = "10 10";
visSizeEndScale = 1;
visAlphaFunc_S = "BLEND";
visAlphaStart = 200;
visAlphaEnd = 0; // fades out
};
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!)