Being new to game dev while also working solo is great fun.
Most of the code was written by your least-favourite colleague: you from several months ago.
And the bigger your game gets, the more prolific that incompetent colleague seems to be.
But hey, you can always rewrite code, so that's what I did with the atom movement logic for powders (sand, coal, etc) this week.
Atom Movement Quirks
Atom movement has been "okay-ish" for a while, but as I've been adding more gameplay, the cracks were starting to show...
And you can't shoot atoms in the same direction, which limits what spells can do:
And on top of that:
- atoms would totally reverse velocity when bouncing (but only sometimes!)
- sometimes atoms would take a long time to settle into shapes that colliders could hit.
- there were a bunch of performance problems around explosions and moving bodies.
This same "powder movement" code is at the heart of fire, smoke, and explosion-visuals too, so I figured it was time to fix it.
The Big Mistake
When I added "velocity" to atoms half a year ago, I made a terrible mistake.
You see, atoms are constrained to a 2D "integer grid". You can have an atom at 0, 0
or 0, 1
but not 0, 0.5
.
But when I added velocity, I figured I needed a way for atoms to have a fractional position, because otherwise the velocity calculation would be physically incorrect: an atom would hang stationary in the air until their velocity had increased to be at least 1.0, which could take up to half a second. 1
So I added a special offset
attribute to the atom that stored X and Y coordinates from 0.0 to 1.0, and introduced all kinds of special-case handling to account for that.
Turns out, it's totally unnecessary! Who cares about physically accurate! We've already increased gravity to 2X! 2
Removing that offset
attribute allowed me to drastically simplify the entire atom movement logic.
In fairness, The Powder Toy also has a fractional position for atoms, so I had some reason to believe that it would work okay. But The Powder Toy allows atoms to overlap each other, which makes that approach far easier to reason about (at the cost of not being able to parallelize the update process easily).
In fact the higher gravity is already enough to help reduce this problem a fair bit.
Collision Hacks
Correctly working out how an atom should bounce when hitting another atom is tricky.
In theory, if two moving atoms hit each other in mid air, you can apply standard billiard-ball-like physics to them.
In practice, if you have a block of 4 or more atoms moving in the same direction, the "behind" atoms will end up boosting the "in front" atoms, because the atoms are only updated one at a time.
So I added a hack: an atom that moves in the same-ish direction another atom is not allowed to bounce; instead it just moves up next to that atom.
Atom Surfaces
Bouncing gets even more complicated: when an atom hits a line of atoms at a non-90 degree angle, you expect it to bounce realistically.
But if you treat the two colliding atoms as billiard balls in this case, the incoming atom will have its velocity totally reversed, which looks broken.
This is because the collision isn't happening between an atom and a line of atoms, but between two supposedly-spherical atoms. 3
In physics-programmer terms, the correct approach is to calculate the surface direction (the direction of the "line" of atoms.. bearing in mind the line could be crooked or an L-shape). Then you calculate the vector perpendicular to your surface direction; this is the collision normal. Then reflect the velocity about the collision normal.
What I ended up doing is looking at the atoms immediately around the atom that was hit, and using them to calculate the correct bounce.
Here's a slow-mo demo with an unusually-bouncy purple atom:
Frustratingly, after implementing this (and friction too), I realized that powders look bad if they bounce too much, so you mostly don't see atoms bouncing.
But I ended up using the underlying calculations for other bits of movement too, so I think it's still a net win.
Results
I'll skip over the many other minor fixes and just show some results (compare them with above):
Sand looks better when it hits a ledge:
And on top of atom movement being more robust, the whole game now performs about 20% better across the board. 4
That's across the whole game, not just movement. And includes some optimizations to explosions which the new atom movement code allowed.
Playable web build
For your convenience, this week's build starts off in Edit-mode (F4 to toggle), so you can fire atoms just by left-clicking:
Aside: as a rule I don't put any effort in for mobile, but this week tapping the screen to fire atoms should actually work too!
- If you want to change the speed or angle you're firing atoms at, scroll down on the left and change the
5 px/tick
and-45.0°
values.
I did have to re-tweak a great deal of "tuned" (hardcoded) numbers to work with the new atom movement code, so let me know if anything seems off!