Files
Documentation/content/advanced/effects/particles.en.md
2026-01-20 20:33:59 +01:00

20 KiB

title, type, weight
title type weight
Particles docs 1

The particle system in Hytale provides rich visual effects through configurable emitters and renderers. Create explosions, trails, auras, and complex visual effects with precise control over behavior and appearance.

Package: com.hypixel.hytale.server.core.asset.type.particle

{{< cards cols="3" >}} {{< card link="#spawning-particles" title="Spawning" subtitle="Create particle effects" icon="sparkles" >}} {{< card link="#particle-system-configuration" title="Configuration" subtitle="System and spawner setup" icon="adjustments" >}} {{< card link="#particle-attractors" title="Attractors" subtitle="Forces and movement" icon="arrow-circle-right" >}} {{< /cards >}}


Spawning Particles

ParticleUtil Methods

{{< tabs items="Basic,Advanced,WorldParticle" >}} {{< tab >}}

import com.hypixel.hytale.server.core.universe.world.ParticleUtil;

// Basic spawn at position (auto-finds nearby players)
ParticleUtil.spawnParticleEffect(
    "explosion_small",           // Particle system ID
    position,                    // Vector3d position
    componentAccessor
);

// Spawn for specific players
ParticleUtil.spawnParticleEffect(
    "magic_trail",
    position,
    playerRefs,                  // List<Ref<EntityStore>>
    componentAccessor
);

Broadcast Distance: DEFAULT_PARTICLE_DISTANCE = 75 blocks

{{< /tab >}} {{< tab >}}

// With source entity reference
ParticleUtil.spawnParticleEffect(
    "attack_swing",
    position,
    sourceEntityRef,             // Entity that spawned this
    playerRefs,
    componentAccessor
);

// With rotation control
ParticleUtil.spawnParticleEffect(
    "directional_beam",
    position,
    yaw, pitch, roll,            // Rotation angles
    sourceEntityRef,
    playerRefs,
    componentAccessor
);

// With scale and color
ParticleUtil.spawnParticleEffect(
    "colored_burst",
    position,
    yaw, pitch, roll,
    2.0f,                        // Scale multiplier
    new Color(255, 100, 50, 255), // RGBA color
    playerRefs,
    componentAccessor
);

{{< /tab >}} {{< tab >}}

import com.hypixel.hytale.server.core.asset.type.particle.config.WorldParticle;

// Full control with WorldParticle wrapper
WorldParticle worldParticle = new WorldParticle(
    "my_particle_system",
    new Color(255, 100, 50, 255),  // RGBA color override
    1.5f,                          // Scale multiplier
    new Vector3f(0, 1, 0),         // Position offset
    new Direction(0, 0, 0)         // Rotation offset
);

ParticleUtil.spawnParticleEffect(
    worldParticle,
    position,
    sourceRef,
    playerRefs,
    componentAccessor
);

{{< /tab >}} {{< /tabs >}}


Particle System Configuration

ParticleSystem Class

The root configuration for a particle effect:

public class ParticleSystem {
    protected String id;                        // Unique identifier
    protected ParticleSpawnerGroup[] spawners;  // Spawner configurations
    protected float lifeSpan;                   // System lifetime (seconds)
    protected float cullDistance;               // Distance before culling
    protected float boundingRadius;             // Collision bounding box
    protected boolean isImportant;              // Network priority flag
}
Field Type Description
id String Unique identifier for the particle system
spawners ParticleSpawnerGroup[] Array of spawner configurations
lifeSpan float How long the system lives (seconds)
cullDistance float Distance at which particles are culled
boundingRadius float Bounding sphere for culling calculations
isImportant boolean If true, prioritized in network sync

ParticleSpawner Configuration

Controls how individual particles are emitted:

{{< tabs items="Properties,Emission,Movement,Rendering" >}} {{< tab >}}

public class ParticleSpawner {
    protected String id;
    protected EmitShape shape;
    protected RangeVector3f emitOffset;
    protected boolean useEmitDirection;
    protected Range totalParticles;
    protected float lifeSpan;
    protected int maxConcurrentParticles;
    protected Rangef particleLifeSpan;
    protected Rangef spawnRate;
    protected boolean spawnBurst;
    protected InitialVelocity initialVelocity;
    protected ParticleAttractor[] attractors;
    protected FXRenderMode renderMode;
    protected float lightInfluence;
    protected Particle particle;
}

{{< /tab >}} {{< tab >}}

Emission Properties:

Property Type Description
shape EmitShape Sphere or Cube emission shape
emitOffset RangeVector3f Random offset range from origin
useEmitDirection boolean Use spawn direction for velocity
totalParticles Range Min/max total particles to emit
spawnRate Rangef Particles per second
spawnBurst boolean Emit all at once vs over time

Example emission setup:

// Burst explosion - all at once
spawner.setSpawnBurst(true);
spawner.setTotalParticles(new Range(50, 100));

// Continuous stream
spawner.setSpawnBurst(false);
spawner.setSpawnRate(new Rangef(10, 20)); // 10-20 per second
spawner.setLifeSpan(5.0f); // Emit for 5 seconds

{{< /tab >}} {{< tab >}}

Movement Properties:

Property Type Description
initialVelocity InitialVelocity Starting velocity configuration
attractors ParticleAttractor[] Forces applied to particles

Initial Velocity:

public class InitialVelocity {
    protected float speed;           // Base speed
    protected float speedVariance;   // Random variance
    protected Vector3f direction;    // Base direction
    protected float coneAngle;       // Spread angle (degrees)
}

{{< /tab >}} {{< tab >}}

Rendering Properties:

Property Type Description
renderMode FXRenderMode How particles are blended
lightInfluence float How much lighting affects particles
particle Particle Texture and animation config

{{< /tab >}} {{< /tabs >}}


Emit Shapes

Control the volume from which particles spawn:

public enum EmitShape {
    Sphere,     // Emit from spherical volume
    Cube        // Emit from cubic volume
}

{{< tabs items="Sphere,Cube" >}} {{< tab >}}

Spherical Emission:

Particles spawn within a spherical volume, with direction pointing outward from center:

// Configure spherical emission
spawner.setShape(EmitShape.Sphere);
spawner.setEmitOffset(new RangeVector3f(
    new Rangef(-1, 1),  // X range (radius)
    new Rangef(-1, 1),  // Y range (radius)
    new Rangef(-1, 1)   // Z range (radius)
));

// Use emit direction for outward velocity
spawner.setUseEmitDirection(true);

Best for: Explosions, bursts, radial effects

{{< /tab >}} {{< tab >}}

Cubic Emission:

Particles spawn within an axis-aligned box volume:

// Configure cubic emission
spawner.setShape(EmitShape.Cube);
spawner.setEmitOffset(new RangeVector3f(
    new Rangef(-2, 2),  // X range
    new Rangef(0, 3),   // Y range (above ground)
    new Rangef(-2, 2)   // Z range
));

Best for: Area effects, rain, ground effects

{{< /tab >}} {{< /tabs >}}


Render Modes

Determine how particles blend with the scene:

public enum FXRenderMode {
    BlendLinear,   // Standard transparency blending
    BlendAdd,      // Additive blending (bright/glowing)
    Erosion,       // Erosion/dissolve effect
    Distortion     // Distortion/refraction effect
}

{{< tabs items="BlendLinear,BlendAdd,Erosion,Distortion" >}} {{< tab >}}

BlendLinear

Standard alpha blending. Best for:

  • Smoke
  • Dust
  • Clouds
  • Solid-looking particles
spawner.setRenderMode(FXRenderMode.BlendLinear);

{{< /tab >}} {{< tab >}}

BlendAdd

Additive blending - particles add light to the scene. Best for:

  • Fire
  • Sparks
  • Magic effects
  • Glowing particles
  • Light beams
spawner.setRenderMode(FXRenderMode.BlendAdd);

{{< callout type="info" >}} Use BlendAdd for any glowing or luminous effects. Multiple overlapping particles will create brighter spots. {{< /callout >}}

{{< /tab >}} {{< tab >}}

Erosion

Creates dissolving/eroding visual effect. Best for:

  • Disintegration
  • Dissolve transitions
  • Energy dissipation
spawner.setRenderMode(FXRenderMode.Erosion);

{{< /tab >}} {{< tab >}}

Distortion

Refracts the background, creating heat-shimmer effects. Best for:

  • Heat waves
  • Portals
  • Energy fields
  • Underwater effects
spawner.setRenderMode(FXRenderMode.Distortion);

{{< callout type="warning" >}} Distortion effects are more GPU-intensive. Use sparingly. {{< /callout >}}

{{< /tab >}} {{< /tabs >}}


Particle Attractors

Apply forces to particles for dynamic movement:

public class ParticleAttractor {
    protected Vector3f position;                // Local attractor position
    protected Vector3f radialAxis;              // Radial force direction
    protected float radius;                     // Influence radius

    // Accelerations (continuous forces)
    protected float radialAcceleration;         // Outward/inward acceleration
    protected float radialTangentAcceleration;  // Tangential (orbit) acceleration
    protected Vector3f linearAcceleration;      // Direct linear acceleration

    // Impulses (one-time forces)
    protected float radialImpulse;              // Outward/inward impulse
    protected float radialTangentImpulse;       // Tangential impulse
    protected Vector3f linearImpulse;           // Direct linear impulse

    // Damping
    protected Vector3f dampingMultiplier;       // Velocity reduction per frame
}

Force Types

{{< tabs items="Radial,Tangential,Linear,Damping" >}} {{< tab >}}

Radial Forces:

Push particles toward or away from the attractor position:

ParticleAttractor attractor = new ParticleAttractor();
attractor.setPosition(new Vector3f(0, 0, 0)); // Center of system

// Outward explosion
attractor.setRadialAcceleration(10.0f);  // Positive = outward

// Inward pull (black hole effect)
attractor.setRadialAcceleration(-5.0f);  // Negative = inward

// Instant outward burst
attractor.setRadialImpulse(20.0f);

{{< /tab >}} {{< tab >}}

Tangential Forces:

Create swirling/orbiting motion around the attractor:

ParticleAttractor attractor = new ParticleAttractor();
attractor.setPosition(new Vector3f(0, 0, 0));
attractor.setRadialAxis(new Vector3f(0, 1, 0)); // Orbit around Y axis

// Clockwise swirl
attractor.setRadialTangentAcceleration(5.0f);

// Counter-clockwise
attractor.setRadialTangentAcceleration(-5.0f);

{{< /tab >}} {{< tab >}}

Linear Forces:

Apply constant directional force (like gravity or wind):

ParticleAttractor attractor = new ParticleAttractor();

// Gravity (downward)
attractor.setLinearAcceleration(new Vector3f(0, -9.8f, 0));

// Wind (horizontal)
attractor.setLinearAcceleration(new Vector3f(2.0f, 0, 0));

// Upward buoyancy
attractor.setLinearAcceleration(new Vector3f(0, 3.0f, 0));

{{< /tab >}} {{< tab >}}

Damping:

Slow particles down over time:

ParticleAttractor attractor = new ParticleAttractor();

// Uniform damping (air resistance)
attractor.setDampingMultiplier(new Vector3f(0.98f, 0.98f, 0.98f));

// Strong horizontal damping, weak vertical
attractor.setDampingMultiplier(new Vector3f(0.9f, 0.99f, 0.9f));

// No damping (particles maintain velocity)
attractor.setDampingMultiplier(new Vector3f(1.0f, 1.0f, 1.0f));

{{< callout type="info" >}} Damping values < 1.0 slow particles. Values of 0.98-0.99 give realistic air resistance. {{< /callout >}}

{{< /tab >}} {{< /tabs >}}


Particle Visual Configuration

Configure particle appearance and animation:

public class Particle {
    protected String texture;                   // Texture atlas path
    protected Size frameSize;                   // Frame dimensions

    protected ParticleUVOption uvOption;        // None, Animated, Random
    protected SoftParticle softParticle;        // Soft particle blending
    protected float softParticlesFadeFactor;    // 0.1 to 2.0

    // Animation
    protected ParticleAnimationFrame initialAnimationFrame;
    protected Map<Integer, ParticleAnimationFrame> animation;
}

Animation Options

public enum ParticleUVOption {
    None,       // Single static frame
    Animated,   // Play through frames in sequence
    Random      // Random frame per particle
}

Animation Frames

public class ParticleAnimationFrame {
    protected int frame;           // Frame number in atlas
    protected Rangef scale;        // Size range
    protected Rangef alpha;        // Transparency range (0-1)
    protected Color color;         // Color tint
    protected Rangef rotation;     // Rotation range (degrees)
}

Accessing Particle Assets

// Get particle system by ID
ParticleSystem system = ParticleSystem.getAssetMap().getAsset("explosion_large");

// Get spawner configuration
ParticleSpawner spawner = ParticleSpawner.getAssetMap().getAsset("fire_spawner");

// Use in command arguments
// ArgTypes.PARTICLE_SYSTEM for command parameters

Common Patterns

Event-Based Particles

{{< callout type="warning" >}} Note: The examples below are simplified. Entity doesn't have a direct getPosition() method. In real code, obtain position via TransformComponent from the entity's store. Example:

TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
Vector3d position = transform.getPosition();

{{< /callout >}}

{{< tabs items="Block Break,Combat,Death" >}} {{< tab >}}

@Subscribe
public void onBlockBreak(BreakBlockEvent event) {
    // Use getTargetBlock() - not getPosition()
    Vector3i blockPos = event.getTargetBlock();
    Vector3d pos = new Vector3d(
        blockPos.x + 0.5,
        blockPos.y + 0.5,
        blockPos.z + 0.5
    );

    // Spawn break particles at block center
    ParticleUtil.spawnParticleEffect(
        "block_break_particles",
        pos,
        componentAccessor
    );
}

{{< /tab >}} {{< tab >}}

@Subscribe
public void onEntityDamage(EntityDamageEvent event) {
    Entity target = event.getTarget();
    Vector3d hitPos = target.getPosition().add(0, 1, 0);

    // Blood/damage effect
    ParticleUtil.spawnParticleEffect(
        "damage_hit",
        hitPos,
        componentAccessor
    );

    // Critical hit special effect
    if (event.isCritical()) {
        ParticleUtil.spawnParticleEffect(
            "critical_hit_sparks",
            hitPos,
            0, 0, 0,    // rotation
            1.5f,       // larger scale
            new Color(255, 215, 0, 255), // gold color
            getNearbyPlayers(hitPos, 50),
            componentAccessor
        );
    }
}

{{< /tab >}} {{< tab >}}

@Subscribe
public void onEntityDeath(EntityDeathEvent event) {
    Entity entity = event.getEntity();
    Vector3d deathPos = entity.getPosition();

    // Death particles
    ParticleUtil.spawnParticleEffect(
        "entity_death_poof",
        deathPos,
        componentAccessor
    );

    // Soul rising effect for players
    if (entity instanceof Player) {
        ParticleUtil.spawnParticleEffect(
            "soul_ascend",
            deathPos.add(0, 0.5, 0),
            componentAccessor
        );
    }
}

{{< /tab >}} {{< /tabs >}}

Continuous Effects

{{< tabs items="Player Trail,Aura,Area Effect" >}} {{< tab >}}

// Spawn particle trail at player position
public void spawnTrailEffect(Player player, Store<EntityStore> store,
                              ComponentAccessor<EntityStore> componentAccessor) {
    if (hasTrailEffect(player)) {
        // Get position from TransformComponent
        Ref<EntityStore> entityRef = player.getReference();
        TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
        if (transform != null) {
            Vector3d position = transform.getPosition();
            ParticleUtil.spawnParticleEffect(
                "magic_trail",
                position,
                componentAccessor
            );
        }
    }
}

{{< callout type="warning" >}} Note: Player doesn't have a getPosition() method. Get position via TransformComponent from the entity store. {{< /callout >}}

{{< /tab >}} {{< tab >}}

// Persistent aura around player
public class AuraManager {
    private final Map<UUID, Boolean> activeAuras = new HashMap<>();
    private Store<EntityStore> store;
    private ComponentAccessor<EntityStore> componentAccessor;

    public void enableAura(Player player) {
        // Use player.getPlayerRef().getUuid() - not player.getUuid()
        activeAuras.put(player.getPlayerRef().getUuid(), true);
    }

    @Subscribe
    public void onTick(ServerTickEvent event) {
        for (Map.Entry<UUID, Boolean> entry : activeAuras.entrySet()) {
            if (entry.getValue()) {
                Player player = getPlayer(entry.getKey());
                if (player != null) {
                    // Get position from TransformComponent
                    Ref<EntityStore> entityRef = player.getReference();
                    TransformComponent transform = store.getComponent(
                        entityRef, TransformComponent.getComponentType());
                    if (transform != null) {
                        Vector3d pos = transform.getPosition();
                        ParticleUtil.spawnParticleEffect(
                            "magic_aura",
                            new Vector3d(pos.getX(), pos.getY() + 1, pos.getZ()),
                            componentAccessor
                        );
                    }
                }
            }
        }
    }
}

{{< /tab >}} {{< tab >}}

// Area healing effect
public void createHealingZone(Vector3d center, double radius, int durationTicks) {
    // Spawn ring particles
    for (int i = 0; i < 16; i++) {
        double angle = (2 * Math.PI * i) / 16;
        Vector3d pos = center.add(
            Math.cos(angle) * radius,
            0.1,
            Math.sin(angle) * radius
        );

        ParticleUtil.spawnParticleEffect(
            "healing_sparkle",
            pos,
            componentAccessor
        );
    }

    // Central pillar effect
    ParticleUtil.spawnParticleEffect(
        "healing_pillar",
        center,
        componentAccessor
    );
}

{{< /tab >}} {{< /tabs >}}


Performance Guidelines

{{< callout type="warning" >}} Performance Notes:

  • Default broadcast distance: 75 blocks (DEFAULT_PARTICLE_DISTANCE)
  • Particles use SpawnParticleSystem packet (ID: 152)
  • Set isImportant = true for critical visual feedback
  • Limit particle count for client performance
  • Use maxConcurrentParticles to cap active particles {{< /callout >}}

Optimization Tips

Tip Description
Limit total particles Keep under 100-200 for burst effects
Use appropriate cullDistance Don't render particles too far away
Batch spawns Spawn multiple particles in same tick
Use spawnBurst wisely Bursts are cheaper than continuous
Consider player count More players = more network traffic
// Good: Efficient particle spawning
public void spawnEfficiently(Vector3d position) {
    // Only spawn for nearby players
    List<PlayerRef> nearbyPlayers = getNearbyPlayers(position, 50);

    if (!nearbyPlayers.isEmpty()) {
        ParticleUtil.spawnParticleEffect(
            "my_effect",
            position,
            nearbyPlayers,  // Limited audience
            componentAccessor
        );
    }
}

  • [Entity Effects]({{< relref "entity-effects" >}}) - Status effects with visual components
  • [Networking]({{< relref "/advanced/networking" >}}) - How particles are broadcast
  • [Events]({{< relref "/core-concepts/events" >}}) - Triggering particles from events