Particles - LodestarMC/Lodestone GitHub Wiki

Overview

One of the most refined features in the Lodestone library is its particle system. Lodestone particles aren't an entirely new system, but instead are built on top of Minecraft's base particle engine. They are fully configurable, far more flexible, free from hardcoded behavior, and much more elaborate. This allows you to create far richer effects.

Getting Started:

To create particles, you instantiate a particle builder. There are two main builders depending on where you want to render the particle:

  • WorldParticleBuilder — for particles placed in the game world.

  • ScreenParticleBuilder — for particles rendered on the GUI layer, ideal for HUDs and menus.

Both builders follow a similar pattern: you create a builder for the particle type you want, configure its properties, and call .spawn(...) to create it.

// InWorld particle example
var builder = WorldParticleBuilder.create(LodestoneParticleRegistry.WISP_PARTICLE);

// Screen particle example
var builder = ScreenParticleBuilder.create(LodestoneScreenParticleRegistry.WISP, new ScreenParticleHolder());

Data Types:

Some particle properties use data classes to define their transitions over time. These are built using their respective 'create(...)' methods, then finalized with 'build()':

  • GenericParticleData - Used for scale and transparency
  • ColorParticleData - Controls color transitions
  • SpinParticleData - Handles spin and rotation

Particle Actors:

Advanced customization is possible by adding particle actors — functions that inject logic to modify particles dynamically.

InWorld:

  • addTickActor(...) — Adds a function that runs each tick while the particle is alive.
  • addSpawnActor(...) — Adds a function that runs once when the particle spawns.
  • addRenderActor(...) — Adds a function that modifies how the particle renders each frame.

Screen:

  • addActor(...) — Adds a generic actor.

InWorld Particles

InWorld Particles are rendered in the game world and interact with the geometry, created with WorldParticleBuilder

Here's a rundown of everything WorldParticleBuilder lets you configure:

Note: The (...) after method names indicates the method accepts one or more parameters.

Visuals
  • setColorData(...) - Controls color transitions over time
  • setTransparencyData(...) - Alpha fade in/out
  • setScaleData(...) - Size over time
  • setSpinData(...) - Rotation speed and easing
  • setRenderType(...) - Choose render style (transparent, additive, etc.)
Motion and Behavior
  • addMotion(...) - Apply constant velocity
  • setMotion(...) - Override motion directly
  • setRandomMotion(...) — Sets maximum random velocity components on spawn
  • setRandomOffset(...) — Adds random positional offset on spawn
  • enableNoClip() / disableNoClip() - Allows particles to pass through blocks or not
Timing
  • setLifetime(...) - How long it lasts
  • modifyLifetime(...) / multiplyLifetime(...) — Adjust lifetime dynamically or proportionally
  • setLifeDelay(...) - Spawn delay before it appears
  • modifyLifeDelay(...) / multiplyLifeDelay(...) — Adjust spawn delay dynamically or proportionally
Spawning
  • spawn(...) — Spawns the particle at the specified position in the given world.
  • repeat(...) — Spawns the particle n times at the position.
  • surroundBlock(...) — Spawns particles around a block face or multiple faces.
  • surroundVoxelShape(...) — Spawns particles along a voxel shape's edges.
  • spawnAtRandomFace(...) — Spawns a particle on a random face of a block.
  • createCircle(...) — Spawns a particle in a circular pattern.
  • createBlockOutline(...) — Spawns particles outlining the block's shape.

To spawn particles on an entity or player, call spawn() with their position coordinates.

Example Effect

The following class spawns a particle at the player's position every tick:

import net.minecraft.client.*;
import net.minecraft.client.player.*;
import net.minecraft.world.level.*;
import net.minecraft.world.phys.*;
import net.minecraftforge.api.distmarker.*;
import net.minecraftforge.event.*;
import net.minecraftforge.eventbus.api.*;
import net.minecraftforge.fml.common.*;
import team.lodestar.lodestone.registry.common.particle.*;
import team.lodestar.lodestone.systems.easing.*;
import team.lodestar.lodestone.systems.particle.builder.*;
import team.lodestar.lodestone.systems.particle.data.*;
import team.lodestar.lodestone.systems.particle.data.color.*;
import team.lodestar.lodestone.systems.particle.data.spin.*;

import java.awt.Color;

@Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class ExampleParticleEffect {
    
    @SubscribeEvent
    public static void clientTick(TickEvent.ClientTickEvent event) {
        LocalPlayer player = Minecraft.getInstance().player;
        if (player != null) {
            spawnExampleParticles(player.level(), player.position());
        }
    }

    public static void spawnExampleParticles(Level level, Vec3 pos) {
        Color startingColor = new Color(100, 0, 100);
        Color endingColor = new Color(0, 100, 200);
        WorldParticleBuilder.create(LodestoneParticleRegistry.WISP_PARTICLE)
                .setScaleData(GenericParticleData.create(0.5f, 0).build())
                .setTransparencyData(GenericParticleData.create(0.75f, 0.25f).build())
                .setColorData(ColorParticleData.create(startingColor, endingColor).setCoefficient(1.4f).setEasing(Easing.BOUNCE_IN_OUT).build())
                .setSpinData(SpinParticleData.create(0.2f, 0.4f).setSpinOffset((level.getGameTime() * 0.2f) % 6.28f).setEasing(Easing.QUARTIC_IN).build())
                .setLifetime(40)
                .addMotion(0, 0.01f, 0)
                .enableNoClip()
                .spawn(level, pos.x, pos.y, pos.z);
    }
}

Screen Particles

Screen Particles are rendered on the screen, created with ScreenParticleBuilder

Here's a rundown of everything ScreenParticleBuilder lets you configure:

Note: The (...) after method names indicates the method accepts one or more parameters.

Visuals
  • setColorData(...) - Controls color transitions over time
  • setTransparencyData(...) - Alpha fade in/out
  • setScaleData(...) - Size over time
  • setSpinData(...) - Rotation speed and easing
  • setRenderType(...) - Choose render style (transparent, additive, etc.)
Motion and Behavior
  • addMotion(...) - Apply constant velocity
  • setMotion(...) - Override motion directly
  • setRandomMotion(...) — Sets maximum random velocity components on spawn
  • setRandomOffset(...) — Adds random positional offset on spawn
  • addActor - Adds a generic actor
Timing
  • setLifetime(...) - How long it lasts
  • modifyLifetime(...) / multiplyLifetime(...) — Adjust lifetime dynamically or proportionally
  • setLifeDelay(...) - Spawn delay before it appears
  • modifyLifeDelay(...) / multiplyLifeDelay(...) — Adjust spawn delay dynamically or proportionally
Spawning
  • spawn(...) — Spawns the particle at the specified position on the screen.
  • repeat(...) — Spawns the particle n times at the position.
  • spawnOnStack(...) - Spawns the particle at a given itemStack
  • repeatOnStack(...) - Spawns the particle n times at a given itemStack

Example Effect

The following class shows how to create particles that follow the mouse, defaulting to screen center when no GUI is open:

import net.minecraft.client.*;
import net.minecraftforge.api.distmarker.*;
import net.minecraftforge.client.event.*;
import net.minecraftforge.client.gui.overlay.*;
import net.minecraftforge.eventbus.api.*;
import net.minecraftforge.fml.common.*;
import team.lodestar.lodestone.handlers.screenparticle.*;
import team.lodestar.lodestone.registry.common.particle.*;
import team.lodestar.lodestone.systems.easing.*;
import team.lodestar.lodestone.systems.particle.builder.*;
import team.lodestar.lodestone.systems.particle.data.*;
import team.lodestar.lodestone.systems.particle.data.color.*;
import team.lodestar.lodestone.systems.particle.data.spin.*;
import team.lodestar.lodestone.systems.particle.render_types.*;
import team.lodestar.lodestone.systems.particle.screen.*;
import java.awt.Color;

@Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class ScreenParticles {

    private static final ScreenParticleHolder PARTICLE_HOLDER = new ScreenParticleHolder();

    private static double[] getMousePos() {
        Minecraft mc = Minecraft.getInstance();

        double mouseX, mouseY;

        if (mc.screen != null) {
            mouseX = mc.mouseHandler.xpos() * mc.getWindow().getGuiScaledWidth() / mc.getWindow().getScreenWidth();
            mouseY = mc.mouseHandler.ypos() * mc.getWindow().getGuiScaledHeight() / mc.getWindow().getScreenHeight();
        } else {
            mouseX = mc.getWindow().getGuiScaledWidth() / 2.0;
            mouseY = mc.getWindow().getGuiScaledHeight() / 2.0;
        }
        return new double[]{mouseX, mouseY};
    }

    @SubscribeEvent
    public static void onRenderOverlay(RenderGuiOverlayEvent.Pre event) {
        if (event.getOverlay() != VanillaGuiOverlay.HOTBAR.type()) return;

        Minecraft mc = Minecraft.getInstance();


        if (ScreenParticleHandler.canSpawnParticles && mc.level != null) {
            float gameTime = mc.level.getGameTime();
            float spin = (gameTime * 0.2f) % ((float) (2 * Math.PI));

            Color startColor = new Color(255, 35, 241);
            Color endColor = new Color(0, 179, 255);

            ScreenParticleBuilder.create(LodestoneScreenParticleRegistry.WISP, PARTICLE_HOLDER)
                    .setColorData(ColorParticleData.create(startColor, endColor).build())
                    .setTransparencyData(GenericParticleData.create(1f, 0f).build())
                    .setScaleData(GenericParticleData.create(1f, 0f).build())
                    .setLifetime(20)
                    .setSpinData(SpinParticleData.create(0.2f, 0.4f).setSpinOffset(spin).setEasing(Easing.QUARTIC_IN).build())
                    .setRenderType(LodestoneScreenParticleRenderType.ADDITIVE)
                    .spawn((float) getMousePos()[0], (float) getMousePos()[1]);

            PARTICLE_HOLDER.tick();
        }

        ScreenParticleHandler.renderParticles(PARTICLE_HOLDER);
    }

    @SubscribeEvent
    public static void onRenderScreen(ScreenEvent.Render event) {
        ScreenParticleHandler.renderParticles(PARTICLE_HOLDER);
    }
}

Further Examples

For more examples of particle effects, please look at this section of the Malum Repository: Example Visual Effects

⚠️ **GitHub.com Fallback** ⚠️