Magelusion - LOWREZJAM 2020, Day Five - Fog, Fire, and Water

Fogged Up My Mind!

A couple more days down, a few more things added. If you’ve been following along, or, at least, reading this series of posts in hinde-sight, I like to give credit to those from whom I borrow ideas for use in my game, and the new fog is definitely no exception to that!

The game’s setting is a dungeon! I wanted a good fog effect, but, with the low resolution, I didn’t feel like particle fog was the way to go… or, at least, not for me because I didn’t think I would have the chops to come up with a solid “fog” particle that would look interesting at such a low resolution. To be fair, I probably would have had the same reservations regardless of the resolution of the game, but I digress.

So, obviously, the next avenue was to use shaders (I do tend to have a soft spot for shaders). My hunt for some shader tutorial on fog begins… and ends with a wonderful tutorial by Gonkee for 2D procedural fog! It was exactly what I was looking for! One things before I continue here, in his tutorial he mentions that he pulled a lot of his methods for his shader from The Book of Shaders and, that is why I put that link there like that… HA! Some day I, too, may read everything on that site, but in a game jam, I don’t want to end up falling into (any more of) a rabbit whole, so…

Yet another free shader!

shader_type canvas_item;

uniform float seed = 0.0;
uniform vec3 color = vec3(1.0, 1.0, 1.0);
uniform int octaves = 4;
uniform float speed = 0.5;
uniform float scale_a = 20.0;
uniform float scale_b = 20.0;
uniform vec2 offset = vec2(0.0, 0.0);


float rand(vec2 coord){
	float vx = (sin(seed) - 2.0) * 15.239823472956;
	float vy = (1.0 + sin(seed)) * 87.238742634592;
	float ma = 12367.31276531276;
	float mb = 67273.12836498882;
	return fract(sin(dot(coord, vec2(vx, vy)) * ma) * mb);
}


float noise(vec2 coord){
	vec2 i = floor(coord);
	vec2 f = fract(coord);
	
	float a = rand(i);
	float b = rand(i + vec2(1.0, 0.0));
	float c = rand(i + vec2(0.0, 1.0));
	float d = rand(i + vec2(1.0, 1.0));
	
	vec2 cubic = f * f * (3.0 - 2.0 * f);
	
	return mix(a, b, cubic.x) + ((c - a) * cubic.y * (1.0 - cubic.x)) + ((d - b) * cubic.x * cubic.y);
}

// Fractal Brownian Motion
float fbm(vec2 coord){
	float value = 0.0;
	float scale = 0.5;
	
	for(int i = 0; i < octaves; i++){
		value += noise(coord) * scale;
		coord *= 2.0;
		scale *= 0.5;
	}
	return value;
}

void fragment(){
	vec2 coord = (UV + offset);
	float motion = fbm((coord*scale_b) + fbm((coord*scale_a) + (TIME * speed)));
	COLOR = vec4(color, motion);
}

I did add a few little tweaks of my own here. First, I mucked with the ‘rand()’ function a bit. Instead of using static values for the vector used to multiply against the coord value, and static multipliers on top of that, I decided to adjust things a little so that the numbers used to generate the noise are a bit more random-ish. Or… at least I think so. I’m sure someone smarter than I will see this and be like… “Yeah, class, you see what ObsidianBlk did here? Yeah, don’t be that stupid.". None the less I made my changes and by entering a ‘seed’ value (one of the uniform values I added to this shader), the output noise can be a little more unique. Honestly, though, I probably really over-thought the rand() modifications.

The next thing I added was ‘scale_a’ and ‘scale_b’! When adding the distortions to the fractal brownian motion results, there is a scale multiplier used because a square of size one (which a UV coordinate is) is too damn small to see anything of consequence anyway. In the tutorial I used, a value of 20 was used as the multiplier, but I decided I wanted to inject a little customizability and now the scale of the base value and the scale of the TIME adjusted distortion can be change.

Here I was with a beautiful looking fog shader that has the added benefit of still looking gorgeous even at 64x64 resolution, and I’m stuck for… probably longer than I’d like to admit. See… if I simply attach the script to the camera, then, even though the fog is animated, it will become very obvious that it’s just a texture attached to the camera! No good! I could scale the sprite to cover the level, but I’m thinking of making sizable levels and, to be honest, I’m not sure how big a hit that would have on the speed of the shader (in reality, it’d probably be fine… in fact, I’m 99% sure it’d be fine). Even if the shader rendering speed wasn’t going to be an issue at all, another potential problem may arise if I forget to properly resize the sprite (which is an easy fix, true, but why risk accidently releasing with such a stupid oopsie if I can avoid it?).

This lead me, (mumbles)after two hours(mumbles), to the realization that I could just add an offset vector to the shader! Add the offset value to the UV coords and I can easily shift the fog around. Now I can attach the fog sprite to the camera, resize it to the resolution of the game, and, in the camera script, just keep changing the shader offset value to the position of the camera divided by the resolution of the screen and !!BAM!! it now looks like the entire level is covered in fog!

Burning the Fog

So now I have a fog effect that looks great. A normal person, having accomplished this, would simply move on to the next thing. Me? I start thinking to myself, “Self," I begin, “doesn’t fire usually burn away fog? Fog is just moisture after all, right?" to which I respond, “That’s absolutely true, Self! Maybe we should spend the better part of a day figuring out how to do that?!". At this point, you’d think my Self would counter, “But Self… the better part of a day?! Isn’t that just too much?", but that’s not what Self does. Oh NO! Instead I get “Brilliant! The better part of a day is perfect!"

Long story short, it took over a day, and a brief discussion with one poor soul on the Godot reddit forum.

Initially, I really wanted to use the Godot shader system to get the lighting information and adjust the fog’s alpha by the amount of alpha at the point being calculated. The higher the light, the lower the alpha. No matter where I looked, though, it seemed that Godot (or, perhaps it’s GLSL’s fault) doesn’t have lighting information for fragment shaders in 2D. Perhaps I’m wrong about this, but I couldn’t find a damn thing useful about lighting in Godot for 2D. Much more seems to be out there about 3D when it comes to lighting, but not 2D, unfortunately.

In the end, I when into my “fire” scene (the candles in the background) and I add a second Light2D node. The difference with this Light2D node is I set it to affect the second lighting layer (bit 1) instead of the first (bit 0). The texture used, instead of being a white gradient, is a black gradient. I then set the fog texture to also use the second light mask level (bit 1), and… well… it looks like it’s working.

I’ll be honest here… I’m not entirely sure it’s doing what I want it to do. Things sort of look like they’re working, but…

Long story short, I hope Godot 4.0, with the new Vulkan renderer, will have much better lighting shader support for 2D. We shall see.

Throwing Smoke… and Fire… and Water!

The whole purpose of this game is (or intended to be) to use magic to solve problems and escape the dungeon. So I thought it was about time to allow the player to actually be able to cast spells!

Ok, in reality, the player is just shooting sprites, but it looks damn cool!

First things first… The built in particle system wasn’t going to work for me. I want the particles to be able to interact with other particles (like, fire particles burning water particles and creating steam particles… and I now just spoiled the next blog entry, probably), and I was getting the distinct impression that the particle system wasn’t going to cut it in that regard.

As such, I turned to the blood splatter system I spoke about a couple posts ago! This time, instead of “splatter” I wanted to do a “spray”! Easy enough (and you can clone the git repo linked above if you want to see how I built this spray system in it’s entirety)! The issue really came in when I came to the realization that, since I wasn’t using the built in particle system, I’d be responsible to handling the actual particle creation and destruction! You’d think that would simply be a matter of having the particles queue_free() thenselves like with the blood splatter, but, unlike the blood splatter, where the play doesn’t have control on exactly when it triggers, in this case, I thought “what would happen if the player just kept spewing out particles”?

What I decided to do was batch spawn all the particles the system was ever going to allow to exist in the environment all at once (actually, the value can be set per spay, so, one spay can batch 100 particles, while another could batch 1000). Then, when the sprayer is spraying, it takes a particle instance from the “pool” array and adds it to the root tree of the scene and also stores the instance in an “active” array. Then, the active array is scanned for any particles with a ‘life’ value less than or equal to zero. For any particle that has no life, it is removed from the root tree of the scene and placed back in the pool. That’s basically it! I just keep reusing the same particle instances over and over! Of course, if enough particles are used that the pool is completely emptied, then no new particles spawn… which is perfectly fine! A Mage can’t cast forever!

Now all I needed were the particles for fire and water (and smoke, but I haven’t created those yet). Both particles are RigidBody2D nodes at their core.

The “Fire” particle has a VERY low gravity and barely arcs at all when fired. They also have a small Light2D on them so that they light up the area when in the wild. There’s also color adjustments, which also affect the Light2D, so the older the particle, the different it looks and the dimmer it gets.

The “Water” particle, by contrast, has no light of it’s own (as water doesn’t emit light), and it’s fully affected by gravity! It looks decent, but a little jankie for water. It’s good enough for now, but I’ll most likely go back to it later and spend way more time than I resonably should to try tweaking it to act more like water. Fluids are very complex, though, so I have my reservations about how much better I’m going to get it in the time I have available for this game jam.

Finishing Thoughts

My upcoming goal is to get the fire particles to interact with the water particles to create steam particles! I also want the water to act as a weight of sorts, so, for instance, I can have a system where the water particles could be “poured” into something. The more water their is, the more weight and could be used to open a door, or raise/lower a lift, or some such mechanism. With steam, I want it to lift things or make things explode if too many steam particles are in a location.

Anyway… we’ll see what happens in the next post!

In the mean time, enjoy these screenshots!!


My little player sprite is getting more and more complex as time passes. As it should be!

Player Complexity

My little player sprite is getting more and more complex as time passes. As it should be!


The fog shader with the &lsquo;burn&rsquo; effect. Like I said, I&rsquo;m not totally convinced it&rsquo;s working, but still!

Everything's a Fog

The fog shader with the ‘burn’ effect. Like I said, I’m not totally convinced it’s working, but still!


Not to toot my own horn, but I really think the fire and water sprays look really nice!

Fire and Water

Not to toot my own horn, but I really think the fire and water sprays look really nice!