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

789 lines
20 KiB
Markdown

---
title: Particles
type: docs
weight: 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 >}}
```java
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 >}}
```java
// 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 >}}
```java
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:
```java
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 >}}
```java
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:**
```java
// 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:**
```java
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:
```java
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:
```java
// 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:
```java
// 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:
```java
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
```java
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
```java
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
```java
spawner.setRenderMode(FXRenderMode.Erosion);
```
{{< /tab >}}
{{< tab >}}
### Distortion
Refracts the background, creating heat-shimmer effects. Best for:
- Heat waves
- Portals
- Energy fields
- Underwater effects
```java
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:
```java
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:
```java
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:
```java
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):
```java
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:
```java
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:
```java
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
```java
public enum ParticleUVOption {
None, // Single static frame
Animated, // Play through frames in sequence
Random // Random frame per particle
}
```
### Animation Frames
```java
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
```java
// 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:
```java
TransformComponent transform = store.getComponent(entityRef, TransformComponent.getComponentType());
Vector3d position = transform.getPosition();
```
{{< /callout >}}
{{< tabs items="Block Break,Combat,Death" >}}
{{< tab >}}
```java
@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 >}}
```java
@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 >}}
```java
@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 >}}
```java
// 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 >}}
```java
// 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 >}}
```java
// 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 |
```java
// 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
);
}
}
```
---
## Related Topics
- [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