skills/particle-system/SKILL.md
Emit particles (fire, smoke, sparks, snow, magic, fireworks) from an entity in a Decentraland SDK7 scene with the ParticleSystem component. Covers emitter shapes (Point, Sphere, Cone, Box), continuous rate vs Burst emission, lifetime/size/color/velocity ranges, gravity and additionalForce, blend modes (ALPHA/ADD/MULTIPLY), billboard and faceTravelDirection, sprite-sheet texture animation, simulation space (local vs world), playback state, and per-scene particle budget. Use when the user asks for particles, sparks, fire, smoke, dust, fog, fireworks, magic effects, snowfall, rain, embers, trails, or atmospheric effects. Do NOT use for procedural entity motion (see animations-tweens), GLTF model effects (see add-3d-models), or 2D UI effects (see build-ui).
npx skillsauth add dcl-regenesislabs/opendcl particle-systemInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Emit particles from an entity. One ParticleSystem component per entity, attached alongside a Transform. No mesh required — particles render from the component itself.
The emitter position (entity Transform + optional GltfContainer of a torch/fire pit/fog vent) is static and belongs in main-entities.ts. ParticleSystem itself is not in the supported declarative list — attach it at runtime in src/index.ts via getEntityOrNullByName.
// main-entities.ts — emitter placement
campfire: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: { src: 'models/campfire.glb' }
}
}
// src/index.ts — attach ParticleSystem
import { engine, ParticleSystem, PBParticleSystem_BlendMode } from '@dcl/sdk/ecs'
import { Color4 } from '@dcl/sdk/math'
export function main() {
const fire = engine.getEntityOrNullByName('campfire')
if (!fire) return
ParticleSystem.create(fire, { /* ... config ... */ })
}
Particle size is controlled exclusively by initialSize and sizeOverTime (FloatRange). The ParticleSystem also sets the emitter shape's spatial dimensions when the shape has size fields (Sphere radius, Box size, Cone radius). These are not affected by the entity's Transform.scale.
Players viewing the scene from outside its parcels see nothing. Particles are not part of the scene LOD silhouette. Position emitters within parcel bounds.
The mobile Godot explorer and the Bevy explorer don't have this feature implemented. The renderer ignores the component. Design fallbacks (a glowing emissive sphere, a baked-in GLTF animation) for scenes that should look reasonable everywhere.
The engine enforces a per-scene particle budget and will scale down emission rates across all active systems if total live particles would exceed the limit. Cap each system with maxParticles and prefer fewer impactful systems over many small ones.
prewarm: true only takes effect when loop: true. On a one-shot system (loop: false) prewarm is silently ignored.
When faceTravelDirection: true, particles orient along their velocity vector and billboard is ignored. Use this for trails/streaks (asteroids, bullets, sparks). Set billboard: false explicitly to avoid confusion.
import { engine, ParticleSystem } from '@dcl/sdk/ecs'
import {
PBParticleSystem_BlendMode,
PBParticleSystem_PlaybackState,
PBParticleSystem_SimulationSpace
} from '@dcl/sdk/ecs'
import { Color4, Vector3, Quaternion } from '@dcl/sdk/math'
Aliases ParticleSystemBlendMode and ParticleSystemPlaybackState are also exported from @dcl/sdk/ecs and are interchangeable with the PB-prefixed names. There is no ParticleSystemSimulationSpace alias — only PBParticleSystem_SimulationSpace. Prefer the PB-prefixed names everywhere for consistency.
| Field | Type | Default | Notes |
|---|---|---|---|
| active | boolean | true | Master on/off for new emission. |
| rate | number | 10 | Particles emitted per second (continuous). Set to 0 when using bursts. |
| maxParticles | number | 1000 | Hard cap on simultaneous live particles for this system. |
| lifetime | number | 5 | Particle lifespan in seconds. |
| gravity | number | 0 | Multiplier on scene gravity (~ -9.81 m/s²). Negative = particles rise. |
| additionalForce | Vector3 | — | Constant force vector applied each frame (world space). |
| initialSize | FloatRange | {start:1, end:1} | Random size at spawn. |
| sizeOverTime | FloatRange | {start:1, end:1} | Size lerped start→end over particle lifetime. |
| initialRotation | Quaternion | identity | Spawn orientation. |
| rotationOverTime | Quaternion | identity | Per-axis angular velocity. |
| faceTravelDirection | boolean | false | Orient along velocity. Overrides billboard. |
| initialColor | ColorRange | {white,white} | Random color at spawn. |
| colorOverTime | ColorRange | {white,white} | Color lerped start→end. Use alpha=0 at end to fade out. |
| initialVelocitySpeed | FloatRange | {start:1, end:1} | Initial speed in m/s, randomized per particle. |
| texture | Texture | white quad | Particle sprite. Same Texture shape as Material textures. |
| blendMode | PBParticleSystem_BlendMode | PSB_ALPHA | PSB_ALPHA / PSB_ADD / PSB_MULTIPLY. |
| billboard | boolean | true | Particles always face camera. |
| spriteSheet | {tilesX, tilesY, framesPerSecond?} | — | Texture-atlas frame animation. |
| shape | oneof Point/Sphere/Cone/Box | Point | Emitter geometry. |
| loop | boolean | true | Loop emission cycle. false = one-shot. |
| prewarm | boolean | false | Start as if one full loop already simulated. Requires loop: true. |
| simulationSpace | PBParticleSystem_SimulationSpace | PSS_LOCAL | PSS_LOCAL (move with entity) / PSS_WORLD (stay put after spawn). |
| limitVelocity | {speed, dampen?} | — | Clamp top speed. dampen 0–1, default 1 = hard clamp. |
| playbackState | PBParticleSystem_PlaybackState | PS_PLAYING | PS_PLAYING / PS_PAUSED / PS_STOPPED. |
| bursts | {values: Burst[]} | — | Discrete emission events. |
FloatRange = { start: number, end: number }. ColorRange = { start: Color4, end: Color4 }.
Use ParticleSystem.Shape.* helpers — never assemble the oneof manually:
shape: ParticleSystem.Shape.Point()
shape: ParticleSystem.Shape.Sphere({ radius: 1 })
shape: ParticleSystem.Shape.Cone({ angle: 25, radius: 1 }) // angle = half-angle in degrees
shape: ParticleSystem.Shape.Box({ size: Vector3.create(1, 1, 1) })
radius.angle half-angle. Cone direction is the entity's local forward; rotate the parent Transform to aim it.size.To rotate the emission direction (snow falling, rain), rotate the parent entity's Transform.rotation (in main-entities.ts).
Discrete emission events at specific times. Set rate: 0 to use bursts only, or combine with rate > 0 for continuous + bursty.
bursts: {
values: [{ time: 0, count: 100, cycles: 1, interval: 0.01, probability: 1.0 }]
}
Burst fields: time (s from cycle start), count (particles per burst), cycles (default 1, 0 = infinite), interval (s between cycles, default 0.01), probability (0–1 chance per cycle, default 1). Multiple bursts in one cycle = staggered ignition pattern (fireworks).
// 1. Fire ember — Point + ADD blend, slight upward drift
const fire = engine.getEntityOrNullByName('campfire')
if (fire) ParticleSystem.create(fire, {
rate: 40,
lifetime: 2,
maxParticles: 200,
initialSize: { start: 0.1, end: 0.3 },
sizeOverTime: { start: 1.0, end: 0.0 },
initialColor: { start: Color4.create(1, 0.6, 0.1, 1), end: Color4.create(1, 0.2, 0, 1) },
colorOverTime: { start: Color4.create(1, 0.5, 0.1, 1), end: Color4.create(0.2, 0, 0, 0) },
initialVelocitySpeed: { start: 1.5, end: 2.5 },
gravity: -0.3,
blendMode: PBParticleSystem_BlendMode.PSB_ADD,
shape: ParticleSystem.Shape.Point()
})
// 2. One-shot burst — explosion/pickup VFX
ParticleSystem.create(entity, {
loop: false,
rate: 0,
lifetime: 3,
maxParticles: 150,
initialSize: { start: 0.1, end: 0.25 },
sizeOverTime: { start: 1.0, end: 0.0 },
initialVelocitySpeed: { start: 2, end: 4 },
shape: ParticleSystem.Shape.Sphere({ radius: 0.5 }),
bursts: {
values: [{ time: 0, count: 100, cycles: 1, interval: 0.01, probability: 1.0 }]
}
})
const ps = ParticleSystem.getMutable(entity)
ps.playbackState = PBParticleSystem_PlaybackState.PS_PAUSED // pause + freeze particles
ps.playbackState = PBParticleSystem_PlaybackState.PS_PLAYING // resume
ps.playbackState = PBParticleSystem_PlaybackState.PS_STOPPED // hard cut, clear live particles
ps.active = false // graceful trail-off
Texture atlas with frames laid out in a grid (left-to-right, top-to-bottom). Total frames = tilesX * tilesY.
texture: { src: 'images/flame-sheet.png' },
spriteSheet: { tilesX: 4, tilesY: 3, framesPerSecond: 12 }
PSS_LOCAL (default) — particles move with the emitter. A moving emitter drags its particle cloud. Good for auras / halos on moving entities.PSS_WORLD — particles stay at their spawn position in world space. A moving emitter leaves a trail. Required for proper trails combined with Tween movement on the emitter.texture: { src: 'images/spark.png' }
The full Texture form supports filterMode/wrapMode but particle systems generally only need src. Avatar/Video textures on particles are unverified — stick with file textures.
rotationOverTime is interpreted as per-axis angular velocity. Quaternion.fromEulerDegrees(0, 90, 0) = spin 90°/s on Y. Identity = no spin.additionalForce is world-space even when simulationSpace = PSS_LOCAL. Wind/drift directions stay constant regardless of emitter rotation.limitVelocity.dampen = 1 = hard clamp. Lower values let velocity exceed cap briefly then decay.colorOverTime is the standard way to fade particles out.MeshRenderer is unrelated; particles render from the ParticleSystem component itself.maxParticles. Total scene budget across all systems is ~1000.lifetime * rate low; that product is the steady-state live count.playbackState = PS_STOPPED or active = false.PSB_ALPHA for opaque/translucent effects. PSB_ADD is best for glow/fire (it stacks visually) but multi-layer additive overdraw is the most expensive case.ParticleLab.dcl.eth (open with Decentraland client).development
Capture screenshots of the running Decentraland preview to verify scene changes visually. Covers camera movement, interaction actions, and visual debugging. Use when the preview is running and you need to check what the scene looks like, debug visual issues, or verify layout. Do NOT use for code changes (make changes first, then screenshot).
development
Cross-cutting runtime APIs for Decentraland SDK7 scenes. Use when the user needs async operations (executeTask), HTTP requests (fetch, signedFetch), WebSocket connections, timers, realm/scene detection, restricted actions (movePlayerTo, teleportTo, triggerEmote, openExternalUrl), portable experiences, or the testing framework. Do NOT use for UI (see build-ui), multiplayer sync (see multiplayer-sync), or avatar/player data (see player-avatar).
development
Apply physics forces to the player in Decentraland scenes. Impulses (one-shot pushes), knockback (push away from a point with falloff), continuous forces (wind tunnels, anti-gravity, lift, levitation, hover), timed forces, and repulsion fields. Use when the user wants launch pads, knockback on hit, wind zones, gravity fields, jumps, lifting/floating the player, pushing the player up/sideways/back, hover effects, or any scene-applied force on the player. THIS is also the right skill when an agent's first instinct is to mutate `Transform` on `engine.PlayerEntity` to move/lift/push the player — that does NOT work (the player Transform is engine-controlled and read-only); use the Physics API instead. Do NOT use for player movement speed (see player-avatar AvatarLocomotionSettings) or platform movement (see animations-tweens).
data-ai
Player and avatar system in Decentraland. Read player position/profile, customize appearance (AvatarBase), trigger emotes (triggerEmote/triggerSceneEmote), read equipped wearables (AvatarEquippedData), attach objects to players (AvatarAttach), create NPC avatars (AvatarShape), avatar modifier areas, and locomotion settings. Use when the user wants player data, emotes, wearables, NPC avatars, avatar attachments, or movement speed changes. Do NOT use for wallet/blockchain interactions (see nft-blockchain).