Pixel Art Lava Plumes with Particles in Godot
As an exercise in developing visuals with Godot, I attempted to recreate the Anakin vs Obi-Wan duel from Star Wars: Revenge of the Sith: https://murphysdad.itch.io/star-wars-mustafar-demo
This post outlines the technique I used to create the "lava bursts" (aka magma plumes, volcanic eruptions...I'm not sure what the technical term is haha) with Godot's 2D Particle System. I'm going to walk through steps for creating the pixel art lava plumes(aka magma eruptions, volcanic bursts, lava explosions...I'm not sure what the technical term is) using Godot's 2d particle systems:
The first thing is creating a Particles2D node. The primary Texture that I used was this long, vertical 2x10 yellow block:
I favored something vertical like this over something square because I thought it made the plumes more like a trailing burst of liquid, whereas square Textures looked a little more like a solid.
I then applied the following settings to the Particles2D node:
Now, I wanted to apply both a little more color to the plume, as well as a bit more width (to represent tiny little spatters that explode more horizontally).
To accomplish this, I added two more layers of particles: both an orange and red layer. I gave them similar settings to the yellow ones, but used tiny square Textures, made the Initial Velocity somewhat less, and increased the Spread and Box Extents more dramatically.
I used a 1x1 orange pixel, and here’s the Particles2D settings and the orange plume by itself:
and a 1x1 red pixel, its Particles2D settings, and the red plume by itself:
Next, I put all 3 layers together in a LavaPlume.tscn scene, where the root parent is just a plain Node2D with all 3 Particles2D.
There's a script attached to the LavaPlume scene that simply restarts all 3 Particles2D:
extends Node2D func restart(): $LavaPlumeYellow.restart() $LavaPlumeOrange.restart() $LavaPlumeRed.restart()
The combined result:
Now that we have an individual LavaPlume, we want to have multiple bursts happening at staggered times, different random scales, x-positions, and different layers too.
To accomplish this, I introduced a LavaPlumeManager node that has a bunch of LavaPlumes as its children, as well as a Timer:
This LavaPlumeManager has a script attached that does the following:
Manages a rotating queue of LavaPlume children, restarting the next LavaPlume whenever the Timer goes off.
Randomly sets the position.x of the LavaPlume, bound by the width of the level
Randomly sets a scale for the LavaPlume (I've set it between 0.5 and 2.0)
The LavaPlumeManager.gd script:
extends Node2D var limit_left : float var limit_right : float var lava_plumes : Array var next_lava_plume_index := 0 func _ready(): for child in get_children(): if child.is_in_group("lava_plumes"): lava_plumes.append(child) func _on_Timer_timeout(): var lava_plume = lava_plumes[next_lava_plume_index] if next_lava_plume_index == len(lava_plumes) - 1: next_lava_plume_index = 0 else: next_lava_plume_index += 1 var randx = rand_range(limit_left, limit_right) var scale = rand_range(0.5, 2.0) lava_plume.scale = Vector2(scale, scale) lava_plume.position = Vector2(randx, lava_plume.position.y) lava_plume.restart()
The last thing I'll note is how I did the layering (making some lava plumes appear in front of the bridge, behind the bride, and behind the mountains).
I did two things:
For some of the LavaPlumes, I set the Z Index = 0 and some Z Index = 2. This made some LavaPlumes appear in front of the bridge, and others behind.
Duplicated the entire LavaPlumeManager and child LavaPlumes & Timer to the ParallaxBackground layer. This made a whole set of lava plumes appear in the background with the mountains.
The final result: