> ## Documentation Index
> Fetch the complete documentation index at: https://lua.starline.one/llms.txt
> Use this file to discover all available pages before exploring further.

# Particles

> Load your own compiled particle effects, then spawn, attach, and drive them from Lua.

Spawn stock or custom particle effects anywhere in the world.

```lua theme={"dark"}
local impact = assert(particles.Load("particles/myfx/impact.vpcf"))

events.On("bullet_impact", function(e)
    local pos = Vector(e:GetFloat("x"), e:GetFloat("y"), e:GetFloat("z"))
    local p = particles.Spawn(impact, pos, 2)
    p:SetControlPoint(1, Vector(255, 80, 80))
end)
```

## Custom effects

Build the effect in the CS2 Workshop Tools. Copy the compiled files from your addon's `game` folder into `Documents\Starline\scripts\assets`, keeping the folder layout:

```
scripts\assets\particles\myfx\impact.vpcf_c
scripts\assets\particles\myfx\impact_child.vpcf_c
scripts\assets\materials\myfx\spark.vtex_c
```

To tint an effect from Lua, make it read its color from a control point, for example `C_OP_RemapCPtoVector` into the color field, or a renderer color scale bound to a control point. Then set that control point with `SetControlPoint`.

## particles.Load

```lua theme={"dark"}
local effect, err = particles.Load("particles/myfx/impact.vpcf")
```

Load-time only. Returns the name to spawn the effect with, or `nil` and an error message.

Every file the effect references is checked before anything loads. References found in `scripts\assets` load with it. Everything else must exist in the game. A missing reference fails the load.

Reloading the script picks up edits to the effect file itself. Edits to children, materials, and textures need a game restart. Files in `scripts\assets` take priority over game files with the same name.

Files are capped at 64 MB each and 256 MB in total.

## particles.Spawn

```lua theme={"dark"}
local p = particles.Spawn(effect, position [, lifetime])
```

Spawns at a world position, written to control point 0. `effect` is a name returned by `particles.Load` or any stock `.vpcf` path. `lifetime` is in seconds. Without it the particle lives until you destroy it.

## particles.Attach

```lua theme={"dark"}
local p = particles.Attach(effect, entity [, attachment [, lifetime]])
```

Spawns on an entity and moves with it. `attachment` is a model attachment name. Without it the particle follows the entity itself.

## Particle

| Method | Description |
| - | - |
| `p:SetControlPoint(index, vector)` | Writes control point `index`, `0` to `63` |
| `p:SetControlPointEntity(index, entity [, attachment])` | Makes a control point follow an entity |
| `p:Destroy()` | Removes the particle |
| `p:IsValid()` | `false` once it finished, was destroyed, or was cleared |

Control points carry whatever the effect reads from them: positions, colors, counts. A control point the effect never reads is ignored. Children share their parent's control points.

## Lifetime

Particles are cleared at round start, on map change, when their lifetime runs out, and when the script unloads. Outside a match nothing spawns and the handle turns invalid.

For anything that should stay up, like weather, check `IsValid()` and spawn again:

```lua theme={"dark"}
local snow = assert(particles.Load("particles/myfx/snow.vpcf"))
local p

events.On("frame_stage", function()
    local pawn = entity.GetLocalPlayer()
    if pawn and not (p and p:IsValid()) then
        p = particles.Attach(snow, pawn)
    end
end)
```

At most 1024 particles exist at once across all scripts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.