Particle Effects Component - UQcsse3200/2026-studio-2 GitHub Wiki
Overview
ParticleEffectsRenderingComponent is a visual effect renderer that draws procedural fire, ice, and poison particles above an entity using LibGDX ShapeRenderer instead of sprite-based animations. It is designed for status-based effects and creates a layered look using small, animated rectangles that rise upward and sway slightly over time.
This component is useful for:
- Burning effects
- Slow/ice effects
- Poisoned effects
It sits above the base entity rendering by slightly increasing its draw order, ensuring the effect appears visually on top of the entity.
Purpose
The component listens to status flags from related stat components:
BurnStatsComponentSlowStatsComponentPoisonStatsComponent
When one or more of these are active, the renderer creates a stylized particle plume above the entity with a matching color palette.
Dependencies
This class depends on:
RenderComponentShapeRendererSpriteBatchGameTimeServiceLocatorPointLightComponentBurnStatsComponentSlowStatsComponentPoisonStatsComponent- 'Lighting Engine'
Class Fields
Effect configuration
pixelCount = 5- Number of particle streaks rendered per effect.
segmentsPerPixel = 6- Number of vertical segments in each streak.
Color palettes
These define the look of each effect:
-
Fire:
- base: dark red
- mid: orange
- tip: bright yellow
-
Ice:
- base: blue
- mid: cyan
- tip: pale white-blue
-
Poison:
- base: purple
- mid: violet
- tip: greenish-yellow
Runtime state
burnStatsslowStatspoisonStatsshapeRendererworkingColourpointLightComponent
Lifecycle
create()
During creation, the component retrieves the relevant entity components:
burnStats = entity.getComponent(BurnStatsComponent.class);
slowStats = entity.getComponent(SlowStatsComponent.class);
poisonStats = entity.getComponent(PoisonStatsComponent.class);
It also tries to fetch an optional PointLightComponent:
try {
pointLightComponent = entity.getComponent(PointLightComponent.class);
} catch (Exception e) {
pointLightComponent = null;
}
This means the effect can tint or control a light attached to the same entity if present.
Rendering Flow
draw(SpriteBatch batch)
This method is responsible for deciding whether the effect should be drawn.
- Gets the current game time from
ServiceLocator. - Exits early if no time source is available.
- Checks the active status flags:
burningslowedpoisoned
- If none are active:
- clears the point light color
- returns without drawing
If any effect is active:
- obtains entity center position
- gets entity scale
- computes
baseSize - converts current time to seconds
Then it does the following:
batch.end();
shapeRenderer.setProjectionMatrix(batch.getProjectionMatrix());
shapeRenderer.begin(ShapeType.Filled);
It calls drawEffect(...) for each active effect in order:
- fire
- ice
- poison
After drawing, it resumes the sprite batch:
shapeRenderer.end();
batch.begin();
Effect Generation
drawEffect(...)
This method creates a procedural plume for one effect type.
Parameters:
centre: entity center positionbaseSize: base scale used for effect sizeseconds: elapsed time in secondsbase,mid,tip: the effect’s color gradient
For each particle streak:
- Random noise is used to vary motion
- The streak sways using sine waves
- The effect height changes subtly over time
- The particle width tapers as it rises
- Each segment is color-interpolated from base to mid to tip
Core loop:
for (int i = 0; i < pixelCount; i++) {
float hash = MathUtils.random((float) 11.0 + i * 17.3f);
float sway = MathUtils.sin(seconds * (1.6f + hash) + i) * baseSize * 0.08f;
float height = baseSize * (0.55f + 0.4f * MathUtils.random((float) 11.0 + i));
float flicker = 0.85f + 0.15f * MathUtils.sin(seconds * (4f + hash * 3f) + i * 2f);
height *= flicker;
float pixel = Math.max(0.15f, baseSize * 0.05f);
float startX = centre.x;
float startY = centre.y - (centre.y / 2);
for (int s = 0; s < segmentsPerPixel; s++) {
float t = s / (float) (segmentsPerPixel - 1);
float segY = startY + height * t;
float segSway = sway * t + MathUtils.sin(seconds * 3f + s + i) * pixel;
float segX = startX + segSway;
float segWidth = pixel * (1.6f - 1.1f * t);
colourGradient(base, mid, tip, t, workingColour);
shapeRenderer.setColor(workingColour);
shapeRenderer.rect(segX - segWidth / 2f, segY, segWidth, pixel * 1.3f);
}
}
This creates a tapered, rising vertical streak with subtle motion and animated flickering.
Color Gradient Logic
colourGradient(...)
This helper smoothly interpolates between three colors depending on the segment’s progress:
private static void colourGradient(Color base, Color mid, Color tip, float t, Color out) {
if (t < 0.5f) {
out.set(base).lerp(mid, t / 0.5f);
} else {
out.set(mid).lerp(tip, (t - 0.5f) / 0.5f);
}
}
This produces a nice gradient from:
- dark base color
- to highlighted middle
- to bright tip color
Lighting Integration
If the entity has a PointLightComponent, the component updates its color to match the current effect’s base color:
if (pointLightComponent != null) {
pointLightComponent.setColor(base);
}
This helps create a stronger visual connection between the particle effect and the light it emits.
When no effects are active, it clears the light color:
if (pointLightComponent != null) {
pointLightComponent.setColor(Color.CLEAR);
}
Z-Order
The component increases the render order slightly above its parent entity:
@Override
public float getZIndex() {
return super.getZIndex() + 0.01f;
}
This ensures the particle effect appears above the entity’s main sprite/mesh.
Disposal
On cleanup, the component disposes of the light and renderer:
@Override
public void dispose() {
if (pointLightComponent != null) {
pointLightComponent.dispose();
}
shapeRenderer.dispose();
super.dispose();
}
Multiple effects
If an entity is simultaneously burning, slowed, and poisoned, all three effects render together. This may create a visually dense or layered effect.
Randomized motion
The use of MathUtils.random(...) creates variation in each particle plume, making the effect feel organic and less repetitive.
Performance
This is a lightweight procedural effect, but it does allocate and render many quads each frame. It is suitable for a few active effects at once, but may need optimization if used on large numbers of entities.
Potential caveat
The y-position calculation:
float startY = centre.y - (centre.y / 2);
is effectively relative to the entity center and may be tuned depending on the desired visual height. It creates a base starting point near the lower half of the entity, making the effect appear to rise upward from the entity.
Example Usage
Attach this component to an entity that also contains effect status components:
entity.addComponent(new ParticleEffectsRenderingComponent());
If the entity has a BurnStatsComponent, SlowStatsComponent, or PoisonStatsComponent, the renderer will automatically display matching particle effects when those status flags become active.