Breath Weapon
This is a library/api mod to render geckolib models as particles
Quick challenge
How far can you run before the mobs catch you?
Minecraft check
Confirm your run
Complete the quick check to get your code.
Breath Weapon
Using BreathWeapon
This guide explains how to integrate the BreathWeapon particle system into your Minecraft mod to create custom particles without writing complex rendering code.
Setup
1. Add Dependency
In your `build.gradle`, add BreathWeapon to your dependencies:
```gradle mavenCentral() exclusiveContent { forRepository { maven { name = "Modrinth" url = "https://api.modrinth.com/maven" } } forRepositories(fg.repository) filter { includeGroup "maven.modrinth" } }
dependencies { implementation fg.deobf("maven.modrinth:breath-weapon:0.0.1") } ```
2. Import the API
In your code, import the spawning utilities:
```java import com.dracolich777.breathweapon.util.ParticleSpawner; import com.dracolich777.breathweapon.api.particle.ParticleRegistry; import net.minecraft.resources.ResourceLocation; ```
---
Quick Start
Minimal Setup
1. Create a particle definition JSON:
``` src/main/resources/data/yourmodid/particles/my_effect.json ```
```json { "type": "sprite", "lifetime": 50 } ```
2. Spawn it in code:
```java ParticleSpawner.spawn(level, new ResourceLocation("yourmodid", "my_effect"), x, y, z, vx, vy, vz); ```
That's it! The particle will render with default texture and physics.
---
Creating Particle Definitions
Particle definitions are JSON files placed in your mod's datapacks. Create them at:
``` src/main/resources/data/yourmodid/particles/particle_name.json ```
File Structure
``` src/main/resources/ ├── assets/yourmodid/ │ └── textures/ │ └── particle/ │ ├── beam.png │ ├── spark.png │ └── smoke.png └── data/yourmodid/ └── particles/ ├── my_beam.json ├── my_spark.json └── my_smoke.json ```
Basic JSON Structure
Every particle definition has this structure:
```json { "type": "sprite | beam | geo", "texture": "yourmodid:textures/particle/name", "lifetime": 200, "scale": 1.0, "gravity": 0.0, "drag": 1.0, "light": { ... }, "animation": { ... } } ```
Common Properties
All particle types support:
| Property | Type | Default | Description | |----------|------|---------|-------------| | `type` | string | "sprite" | Particle type: sprite, beam, or geo | | `lifetime` | int | 200 | Duration in ticks (1 tick = 50ms) | | `scale` | float | 1.0 | Size multiplier (0.1 - 10.0) | | `gravity` | float | 0.0 | Downward acceleration (negative = upward) | | `drag` | float | 1.0 | Velocity damping (0.9 = 10% slowdown/tick) | | `texture` | string | null | Texture path in format `modid:path` |
Light Configuration
Add glow/dynamic lighting to particles:
```json { "type": "sprite", "light": { "mode": "glow", "color": [1.0, 0.5, 0.0], "intensity": 1.0 } } ```
Light Modes: - `"none"` - No special lighting - `"glow"` - Constant glow effect - `"pulse"` - Pulsing brightness
For Glow/Pulse: ```json "light": { "mode": "glow", "color": [r, g, b], "intensity": brightness_0_to_2 } ```
For Pulse: ```json "light": { "mode": "pulse", "color": [r, g, b], "intensity": 1.0, "pulse_min": 0.3, "pulse_max": 1.0, "pulse_speed": 0.1 } ```
---
Spawning Particles
Basic Spawning
Single particle:
```java ParticleSpawner.spawn(level, new ResourceLocation("yourmodid", "my_particle"), x, y, z, velocityX, velocityY, velocityZ); ```
Pattern Spawning
Burst (random directions):
```java ParticleSpawner.burst(level, new ResourceLocation("yourmodid", "spark"), center, // Vec3 position count, // Number of particles (e.g., 10) speed); // Speed in blocks/tick (e.g., 0.5) ```
Stream (directional):
```java ParticleSpawner.stream(level, new ResourceLocation("yourmodid", "smoke"), entity, // Entity to spawn from count, // Number of particles speed, // Particle speed spread); // Angular spread in radians ```
Beam Spawning
Uniform beam:
```java ParticleSpawner.spawnBeam(level, new ResourceLocation("yourmodid", "laser"), from, // Vec3 start position to, // Vec3 end position width); // Beam width (e.g., 0.3f) ```
Tapered beam (width changes along length):
```java ParticleSpawner.spawnBeam(level, new ResourceLocation("yourmodid", "laser"), from, // Vec3 start to, // Vec3 end startWidth, // Width at start (e.g., 0.5f) endWidth); // Width at end (e.g., 0.1f) ```
---
Particle Types
Type 1: Sprite (Default)
Simple 2D particles. Fast and lightweight.
Definition:
```json { "type": "sprite", "texture": "yourmodid:particle/spark", "lifetime": 30, "scale": 0.5, "gravity": 0.05, "drag": 0.98, "light": { "mode": "glow", "color": [1.0, 1.0, 0.0], "intensity": 0.8 } } ```
Best for: Sparks, sparkles, dust, simple effects
Type 2: Beam
Billboard between two points. Supports dynamic endpoints.
Definition:
```json { "type": "beam", "texture": "yourmodid:particle/laser", "lifetime": 100, "scale": 1.0, "light": { "mode": "glow", "color": [1.0, 0.0, 0.0], "intensity": 1.5 } } ```
Best for: Lasers, energy blasts, directed attacks, tracers
Type 3: Geo (GeckoLib Model)
Full 3D models with animation support.
Definition:
```json { "type": "geo", "model": "yourmodid:geo/explosion.geo.json", "texture": "yourmodid:textures/particle/explosion.png", "animation": "yourmodid:animations/explosion.animation.json", "anim_name": "animation.explosion.burst", "lifetime": 50, "scale": 0.8, "gravity": -0.05, "drag": 0.95, "spin": { "yaw": 5.0, "pitch": 2.0, "roll": 3.0 } } ```
Properties: - `model` - Path to your GeckoLib `.geo.json` file - `texture` - Texture for the model - `animation` - Path to animation file (optional) - `anim_name` - Which animation to play (optional) - `spin` - Rotation speed in degrees/tick (optional)
Best for: Complex effects, explosions, magical effects, custom models
---
Advanced Features
Dynamic Beam Endpoints
Make beams that track targets or move over time:
```java import com.dracolich777.breathweapon.client.particle.SimpleBeamParticle;
// When spawning, you need to access the particle directly // This requires a custom spawning approach or event listener
SimpleBeamParticle.EndpointProvider provider = (age, lifetime) -> { // Calculate new position based on age float progress = age / (float) lifetime;
// Example: Spiral endpoint double angle = progress * Math.PI * 4; // 2 rotations double newX = centerX + Math.cos(angle) * radius; double newY = centerY + Math.sin(angle) * radius; double newZ = centerZ + Math.sin(progress * Math.PI) * 5;
return new SimpleBeamParticle.BeamEndpoint(newX, newY, newZ); };
// Set on particle (requires direct particle access via event or hook) particle.setEndpointProvider(provider); ```
Physics Customization
Control how particles move:
```json { "gravity": 0.1, // Fall speed (positive = down, negative = up) "drag": 0.95, // Slow down by 5% each tick "scale": 1.5 // Make it 1.5x bigger } ```
Examples:
- Fast zoom: `"gravity": -0.01, "drag": 1.0` - Fast zoom: `"gravity": 0.02, "drag": 0.99` - Fast zoom: Use short lifetime + high drag - Fast zoom: Use high initial velocity + low gravity
---
Complete Examples
Example 1: Energy Beam Attack
JSON Definition (`data/mymod/particles/energy_blast.json`):
```json { "type": "beam", "texture": "mymod:particle/energy_beam", "lifetime": 80, "scale": 0.9, "light": { "mode": "glow", "color": [0.2, 0.7, 1.0], "intensity": 1.3 } } ```
Spawning Code (in an ability handler):
```java @SubscribeEvent public static void onPlayerAttack(LivingAttackEvent event) { if (event.getEntity() instanceof Player player && player.level() instanceof ServerLevel level) {
Vec3 from = player.getEyePosition(); Vec3 to = from.add(player.getLookAngle().scale(50));
ParticleSpawner.spawnBeam(level, new ResourceLocation("mymod", "energy_blast"), from, to, 0.2f); } } ```
Example 2: Explosion Effect
JSON Definition (`data/mymod/particles/explosion_burst.json`):
```json { "type": "sprite", "texture": "mymod:particle/explosion", "lifetime": 40, "scale": 1.2, "gravity": -0.02, "drag": 0.96, "light": { "mode": "pulse", "color": [1.0, 0.6, 0.0], "intensity": 1.5, "pulse_min": 0.5, "pulse_max": 1.5, "pulse_speed": 0.15 } } ```
Spawning Code:
```java public static void createExplosion(ServerLevel level, Vec3 center, int power) { ParticleSpawner.burst(level, new ResourceLocation("mymod", "explosion_burst"), center, power * 3, // More particles for bigger explosion power * 0.1); // Faster spread for bigger explosion } ```
Example 3: Magical Aura
JSON Definition (`data/mymod/particles/mana_dust.json`):
```json { "type": "sprite", "texture": "mymod:particle/mana", "lifetime": 100, "scale": 0.6, "gravity": -0.005, "drag": 0.98, "light": { "mode": "glow", "color": [0.7, 0.3, 1.0], "intensity": 0.9 } } ```
Spawning Code (in entity tick):
```java public void createManaAura(Entity entity, ServerLevel level) { Vec3 pos = entity.position().add(0, entity.getBbHeight() / 2, 0);
// Small burst of particles around entity ParticleSpawner.burst(level, new ResourceLocation("mymod", "mana_dust"), pos, 5, // 5 particles 0.3); // Slow spread } ```
Example 4: Status Effect Indicator
JSON Definition (`data/mymod/particles/poison_cloud.json`):
```json { "type": "sprite", "texture": "mymod:particle/poison", "lifetime": 60, "scale": 0.8, "gravity": 0.01, "drag": 0.99, "light": { "mode": "glow", "color": [0.3, 0.8, 0.2], "intensity": 0.7 } } ```
Spawning Code (while effect active):
```java @SubscribeEvent public static void onLivingUpdate(LivingTickEvent event) { LivingEntity entity = event.getEntity();
if (entity.hasEffect(MobEffects.POISON) && entity.level() instanceof ServerLevel level && entity.tickCount % 5 == 0) { // Every 5 ticks
Vec3 pos = entity.position().add( (Math.random() - 0.5) * 0.5, Math.random() * entity.getBbHeight(), (Math.random() - 0.5) * 0.5 );
ParticleSpawner.spawn(level, new ResourceLocation("mymod", "poison_cloud"), pos.x, pos.y, pos.z, 0, -0.05, 0); // Slight upward drift } } ```
---
Checking Particle Availability
Before spawning, verify the particle is registered:
```java import com.dracolich777.breathweapon.api.particle.ParticleRegistry;
ResourceLocation particleId = new ResourceLocation("yourmodid", "my_particle");
if (ParticleRegistry.has(particleId)) { // Safe to spawn ParticleSpawner.spawn(level, particleId, x, y, z, 0, 0, 0); } else { LOGGER.warn("Particle not found: {}", particleId); } ```
Get particle info:
```java var definition = ParticleRegistry.get("yourmodid:my_particle"); if (definition != null) { int lifetime = definition.getLifetime(); float scale = definition.getScale(); String type = definition.getType(); // "beam", "geo", "sprite" } ```
---
Best Practices
1. Organize Your Particles
``` data/yourmodid/particles/ ├── abilities/ │ ├── fireball.json │ ├── lightning.json │ └── healing_circle.json ├── effects/ │ ├── hit_effect.json │ ├── death_burst.json │ └── buff_indicator.json └── ambient/ ├── magical_dust.json ├── fire_embers.json └── water_splash.json ```
2. Keep Lifetimes Short
Don't make particles last longer than needed: - Sparks: 20-60 ticks - Beams: 50-150 ticks - Explosions: 30-100 ticks - Ambient: 60-200 ticks
3. Test Physics
```json { "gravity": 0.0, // Start with no gravity "drag": 1.0 // Start with no drag } ```
Then adjust based on visual appearance.
4. Use Appropriate Types
- Geo - Most effects (fast) - Geo - Targeted effects, attacks - Geo - Special occasions (slower)
5. Reuse Definitions
Create one definition and spawn it multiple times:
```java for (int i = 0; i < 10; i++) { ParticleSpawner.spawn(level, new ResourceLocation("mymod", "explosion"), x, y, z, randomVelX, randomVelY, randomVelZ); } ```
6. Add Descriptions to JSONs
```json { "_comment": "Blue energy beam - used for mage spell attacks", "type": "beam", ... } ```
---
Troubleshooting
Particles Not Showing
Problem: I spawned a particle but it doesn't appear in-game.
Solutions: 1. Check particle definition is in correct folder: `data/yourmodid/particles/name.json` 2. Verify the particle ID matches the JSON filename 3. Use `/reload` to reload data 4. Check texture path exists: `assets/yourmodid/textures/particle/name.png` 5. Verify you're on the correct level (ServerLevel, not ClientLevel)
Debug code: ```java var def = ParticleRegistry.get("yourmodid:my_particle"); if (def == null) { LOGGER.error("Particle not found in registry!"); } else { LOGGER.info("Particle found: {}", def); } ```
Texture Not Loading
Problem: Particle appears but texture is wrong.
Solutions: 1. Verify texture path uses forward slashes: `"mymod:particle/name"` 2. Check texture file is PNG format 3. Texture path should NOT include `.png` extension 4. Texture should be in `assets/yourmodid/textures/`
Beam Position Wrong
Problem: Beam starts/ends at wrong position.
Solutions: 1. Use world space coordinates (not relative) 2. For entities: `entity.getEyePosition()` for eye level 3. For blocks: `new Vec3(x + 0.5, y + 0.5, z + 0.5)` for center 4. Verify `from` and `to` are not the same position
Particle Too Slow/Fast
Problem: Particle movement doesn't match expected velocity.
Solutions: 1. Increase `speed` parameter in `burst()` or `stream()` 2. Adjust `drag` - lower values = keeps velocity longer 3. Check particle spawn velocity is correct 4. Use `gravity` to add momentum over time
GeckoLib Model Not Loading
Problem: Type is "geo" but model doesn't render.
Solutions: 1. Verify model file path is correct 2. Check `.geo.json` file exists 3. Model path should NOT include `.geo.json` in JSON (just `path` without extension) 4. Texture path must match model's texture reference 5. Animation name must match exactly (case-sensitive)
---
API Reference
ParticleSpawner Methods
```java // Single particle ParticleSpawner.spawn(level, id, x, y, z, vx, vy, vz); ParticleSpawner.spawn(level, id, x, y, z, vx, vy, vz, extraData);
// Patterns ParticleSpawner.burst(level, id, center, count, speed); ParticleSpawner.stream(level, id, entity, count, speed, spread);
// Beams ParticleSpawner.spawnBeam(level, id, from, to, width); ParticleSpawner.spawnBeam(level, id, from, to, startWidth, endWidth); ```
ParticleRegistry Methods
```java ParticleRegistry.register(id, definition); ParticleRegistry.get(id); ParticleRegistry.has(id); ParticleRegistry.getAll(); ParticleRegistry.getAllFromMod(namespace); ```
---
Need Help?
- DM me on discord or join my discord server: - Discord Server - `.themrsuit`
Good luck creating amazing particles!