Particle scripts in Lumix Engine define particle system behavior using a custom scripting language. Scripts are compiled to bytecode and executed on the CPU for high-performance particle simulation.
Particle script files use the .pat extension. Particle script files can import other files with .pai extension.
A particle script consists of one or more emitters. Each emitter defines the behavior of a particle system.
emitter EmitterName {
material "/path/to/material.mat"
init_emit_count 10
emit_per_second 5
// Output channels (required)
out position : float3
out scale : float
out color : float4
// Variables
var velocity : float3
var lifetime : float
// Functions
fn emit() {
// Initialize new particles
}
fn update() {
// Update existing particles
}
fn output() {
// Set output values for rendering
}
}
float - Single floating-point valuefloat3 - 3-component vector (x, y, z)float4 - 4-component vector (x, y, z, w)emitter - Defines a particle emitter. Single file can contain multiple emitters.out - Declares output channels for rendering. This can be accessed from shaders.in - Declares input parameters for emitter instantiation.var - Declares per-particle data.let - Declares block-local variables.const - Declares global constants.global - Declares global variables. This can be set from outside the particle system, e.g., by a lua script.fn - Defines a function.if / else - Conditional statementsreturn - Return from functionimport - Import other scriptsParticle scripts support conditional execution using if/else statements. else
if condition {
// Code executed if condition is true
} else {
// Code executed if condition is false (optional)
}
Conditions can use comparison operators (<, >) and boolean expressions:
fn update() {
if life > 1.0 {
kill(); // Kill particle if lifetime exceeded
} else {
scale = scale * 1.1; // Grow particle
}
}
Note: If/else statements are not SIMD-friendly, as branching is not vectorized in particle simulations.
material - Path to the material file (for sprite/ribbon particles)mesh - Path to the mesh file (for mesh particles)init_emit_count - Initial number of particles to emitemit_per_second - Particles emitted per secondemit_on_move - Emit particles when emitter movesmax_ribbons - Maximum number of ribbonsmax_ribbon_length - Maximum ribbon lengthinit_ribbons_count - Initial ribbon countInput variables (in) declare parameters that can be passed when instantiating particles from another emitter. They allow emitters to communicate data between each other.
emitter ChildEmitter {
// Input parameters
in spawn_position : float3
in particle_color : float3
// ... other declarations ...
fn emit() {
// Use input values to initialize particle
pos = spawn_position;
color = particle_color;
}
}
emitter ParentEmitter {
// ... declarations ...
fn update() {
if t > 1.5 {
// Emit particles from ChildEmitter with input values
emit(ChildEmitter) {
spawn_position = current_position;
particle_color = {1, 0, 0}; // red
};
}
}
}
Input variables are only accessible in the emit() function and are used to initialize particle state when spawned from another emitter.
Called when emitting new particles. Initialize particle state here.
Called every frame for each particle. Update particle state, check lifetime, etc.
Called before rendering. Set output channels for rendering.
Particle scripts support user-defined functions to organize and reuse code. Functions must be defined globally and can take parameters.
fn function_name(param1, param2, ...) {
// function body
return value;
}
Examples:
fn abs(x) {
return max(x, -x);
}
fn saturate(x) {
return max(0, min(1, x));
}
fn sphere(r) {
let lat = random(0, 2 * PI);
let lon = random(0, 2 * PI);
let res : float3;
let tmp = sin(lon) * r;
res.x = cos(lat) * tmp;
res.y = sin(lat) * tmp;
res.z = cos(lon) * r;
return res;
}
sin(float) - Sinecos(float) - Cosinesqrt(float) - Square rootmin(float, float) - Minimummax(float, float) - Maximumrandom(float min, float max) - Random float between min and maxnoise(float) - Perlin noiseemit(emitter) - Emit new particle in specified emitter (update function only)kill() - Kill current particle (update function only)time_delta - Time since last frametotal_time - Total elapsed timeemit_index - Index of current particle being emittedribbon_index - Index for ribbon particlesentity_position - World space position of emitting entity - useful to make world space particles+, -, *, /, %<, >=Vectors support swizzling and component-wise operations:
pos = {1, 2, 3};
x = pos.x;
xy = pos.xy;
pos.yz = {4, 5};
const horizontal_spread_from = 0.8;
const horizontal_spread_to = 1.2;
const start_vertical_velocity = 5;
const G = 9.8;
emitter Emitter0 {
mesh "/engine/models/sphere.fbx"
init_emit_count 0
emit_per_second 300
// par-particle output data, read in shaders
out i_rot_lod : float4
out i_pos_scale : float4
// per-particle runtime data, only accessed by the particle system
var pos : float3
var vel : float3
var t : float
// called every frame
fn update() {
t = t + time_delta;
pos = pos + vel * time_delta;
vel.y = vel.y - G * time_delta;
kill(pos.y < -0.01);
}
// called when emitting a particle
fn emit() {
pos = {0, 0, 0};
let angle = random(0, 2 * 3.14159265);
let h = random(horizontal_spread_from, horizontal_spread_to);
vel.x = cos(angle) * h;
vel.y = start_vertical_velocity;
vel.z = sin(angle) * h;
t = 0;
}
// called before rendering to output data to rendering system
fn output() {
i_pos_scale.xyz = pos;
i_pos_scale.w = min(pos.y * 0.1, 0.1);
i_rot_lod = {0.707, 0, 0.707, 0};
}
}
The particle script editor provides: