It's been a long while since I've written any updates on Spryke, so I figured I'd better hop to it. I've spent the past couple of months overhauling Spryke's code to make it more optimised. I'm doing this for a few reasons. Firstly, the performance was starting to creep into an area where even on my relatively high-end gaming PC it would sometimes dip into subpar territory (ie. less than 60 frames per second). I always told myself that this would be the point where I'd stop and start looking closer at how I'm doing things. Spryke will never be the sort of game that runs blisteringly fast on a tinny laptop with integrated graphics, but I'd certainly like it to run well on anything even resembling a gaming PC.
Secondly, I'm determined to make Spryke feel like more of a game this year. Not just an assemblage of levels, but an actual game, with a solid skeleton and all the necessary stuff, like proper loading screens, graphical options, robust camera, and so on. Part of that also means cleaning up the code and getting it into something resembling a publishable state.
Also, some of the really core code (eg. Spryke's unique movement engine) was 3 years old, written when I was a beginner with Fusion. This code was visibly inefficient, and all but completely incomprehensible to present-day me. I was getting tired of breaking things when I added a new feature and not even being able to tell how it was broken.
Redoing all that stuff was a real drag. Merely figuring out how it worked was time-consuming, as has been untangling it and reshaping it into something more modular, optimised and manageable. I've refreshed a lot of the code already, but I'm still knee-deep in the process. And, of course, I've broken lots of little things along the way, so after all this my next step will be to repair all of the 'optimisations' ;)
To be honest, I'm getting pretty sick of this process, but it's important, and I know I'll be very grateful to myself when I'm done. Up until now, I'd taken to heart that silly adage about premature optimisation being the root of all evil. I always knew I'd eventually do a wave of optimisation, so I was never too fussy about how I made my code. I was never sloppy, but tried not to be too fastidious either. I'm now questioning the wisdom of that. Though I know so much more now than I did when I started, so it's probably best that I left the fastidious stuff till now. However, cleaning up after old code is proving so time-consuming that I'm determined to make all my code from now on as optimised as possible. I've done a lot of benchmark testing to see where I can most beneficially streamline the code, and I'm developing a lot of good habits that I'm going to keep as I go forward.
Here's a little sample of the sort of optimisation work I've been doing:
Spryke generates animated smoke clouds out of her side vent as she moves. These are hand-drawn and animated in Toon Boom Harmony, but to make them look extra believable and fun, they are also generated and manipulated programmatically. For instance, they come out semi-randomly, at slightly different speeds and sizes, and each smoke cloud randomly chooses from 7 separate handmade animations. They're also more numerous when Spryke's jumping (since that takes more energy), and their angle naturally adapts to the arc of Spryke's jump as they come out of her.
Previously, I had 4 separate events for 4 different scenarios (jumping left, jumping right, ground/ceiling left, ground/ceiling right - all shown in the image), with each event taking care of that scenario's particular requirements for smoke angle, direction, and probability, as well as positioning the smoke just right so that it comes out of her vent. So one event basically said if Spryke's jumping left, generate some smoke and do this, this and this to it, while another said if Spryke's on the ground moving right, generate some smoke and do that, that, and that to it...and so on.
But with some careful maths using some pre-existing variables, I was able to consolidate all 4 events into a single event. The code no longer needs to check whether Spryke's moving right or left, airborne or not. The unified event simply runs in all of those circumstances, and adjusts the smoke's position, angle, direction, and probability as needed on the fly.
For example, this equation takes care of the angle for all 4 scenarios:
set angle to inAir * (VAngle(XinputSpeed, YmainSpeed) + 180 * Abs(Xdirection-1)*.05)
the VAngle bit uses Spryke's horizontal and vertical movement (XinputSpeed and YmainSpeed) to set the correct angle for the smoke...if she's jumping diagonally right. However, if she's jumping diagonally left, we need to spin the angle of the smoke around by 180 degrees. So I needed the equation to subtract 180, but only when she's facing left.
Depending on which way Spryke's facing, my variable Xdirection is either 1 (right) or -1 (left). So I used this in the equation, by subtracting 1 and halving the result. So when Spryke's facing right, that results in (1 minus 1) * 0.5 = 0, and when she's facing left, it's (-1 minus 1) *0.5 = -1 (but because I also used Abs, which forces a positive number, the final result is actually 1 instead of -1). So now, when we multiply 180 by this result, we end up with either 180 * 0 = 0 when facing right or 180 * 1 = 180 when facing left. Which is just what we needed.
Finally, we multiply the whole lot by inAir (which is either 1 or 0), which will leave the result unchanged when Spryke's airborne, but force it to 0 when she's on the ground/ceiling and her smoke doesn't need any angle change at all. So now we don't even need to test for Spryke's direction or whether she's airborne. We simply run the code each time, and it will figure out the rest on its own.
With this kind of convoluted gymnastics, I was able to dynamically work out the smoke's direction, movement, position and probability as well. The end result is a bit harder to read, but I've commented it well, so future-me shouldn't have too much trouble understanding it. And despite the extra computation required by the slightly more complex actions, the reduction in conditions (from 14 to just 3!) more than makes up for that. I did some performance testing to make sure, and indeed the new version runs about 10% quicker.
On top of this, I improved performance further by turning the smoke generation code off a portion of the time, preventing it running at all when Spryke isn't eligible for smoke (ie. when falling, floating, or on a wall), and switching from animation sequences to animation directions (which is significantly faster, as I discovered in some tests I did recently). All up, this new vent smoke looks better, yet runs up to 50% faster in stress tests.
But stress-tests aside, what's the real-world impact of the change, during normal gameplay? Not much. 1 or 2 FPS on my system, though it could be more on a slower system. But that's the nature of the beast when it comes to this optimisation stuff: few single changes will make a significant difference, and one can only hope for cumulative benefits, from the systematic weeding out of such inefficiencies across the whole code. And that process is what I'm knee-deep in right now.