With another hectic year of study over and exams done, I’ve finally finished my undergraduate studies in engineering! Which means I’ve also finally got back into the groove of gamedev and have been putting some of the finishing touches on Ambience. (Don’t worry, I definitely haven’t given up on Ambience at all! I’ve just been a lot quieter than usual internet-wise, which I know needs to change – I’m thinking about ways of improving that.)
In redesigning yet another cutscene, I stumbled upon a cool application of numerical methods which I thought I’d share. (For those who aren’t familiar with the term, numerical methods are a way of finding approximate solutions to complex equations which would be really hard to solve exactly.) In my case, I wanted to make a cutscene where the player is viewing a fireball travelling over water, from the perspective of the fireball.
Here, I needed an effect with a water background moving vertically down the screen to represent the water below the fireball. I already had a 32×32 water tile – check. But these water tiles needed to stretch vertically and move faster as they moved towards the bottom of the screen (“closer” to the player). So I made some horizontal “strips” of water tiles and tried scaling them and moving them down the screen. But then I realized something else: the strips of water tiles had to scale and move while also remaining attached to the strips of tiles above and below!
To achieve this, I made a data structure containing a list of y-positions on the screen. These positions would move progressively down the screen, going faster and faster as they went. When the bottom-most position went off the bottom of the screen, it would be placed back at the top. I could then draw each horizontal strip of tiles stretched vertically between two of these vertical points, and it would always remain attached to the strips above and below it. The diagram below shows how this all looks in practice:
That only left the vertical movement itself. On every frame in-game (once every 1/60 of a second), each point had to move down the screen a bit. If a point was closer to the starting point (the “horizon”), it had to move only a small distance; if it was closer to the bottom of the screen, it had to move a larger distance. So as time progressed, each point would be falling faster and faster. I chose to model this using a parabolic relationship between position (y) and time (t), like in the graph below, which is similar to how things fall under gravity.
But there was a problem: since I was working with multiple points, I couldn’t simply use an ever-increasing time variable to work out the position of each point every step. Instead, I had to calculate the next y-position of each point () based only on the current y-position () and the difference in time between each frame (, which in this case was 1/60 s).
Warning: Heavy Maths Ahead!
It turns out I couldn’t do this easily by playing with the exact equation for the parabola, either:
This is where the numerical methods come in! I approximated the change in y using the derivative (“slope”) of the parabola at the current value of y, like this:
Then I rearranged the original formula for the parabola to get rid of that pesky, unknown time variable :
So at every step, I updated the y-position of each point simply by adding to it. (With one exception: for points at the starting point, the slope of the parabola was zero so the point wouldn’t move down at all! Instead I used the exact formula for the parabola to shift the points nicely off the starting line.)
Here’s a GIF of the numerical method in action (the points are shown as red circles):
Hey, nice! The red circles drop from the starting point and accelerate as they move downwards, as expected. And here’s what it looked like when I added the horizontal strips of water background between the points (with some other minor adjustments):
I then made some more adjustments including changing the speed and number of points and fixing that annoying “bobbing” top of the water level. And here’s the final(-ish) result:
(I guess learning all that maths in my engineering degree did come in handy, after all!)