Switch Hitter Development Log

Behold as I pretend that I am a game developer.

Previous Page

Sprint #14: "Bending--okay, breaking--the rules."

October 21st, 2018

After spending some magic number of years writing code, you start to pick some of software development's lore. One of those nuggets shows up in Michael A. Jackson's Principles of Program Design, wherein he says that his teams follow two rules "in the matter of optimization. Rule 1: Don't do it. Rule 2 (for experts only): Don't do it yet, that is, not until you have a perfectly clear and unoptimized solution." Broadly speaking, code that performs well--and it's worth defining that performant code either doesn't take long to run, doesn't need much memory to run, or some combination of both--is at odds with code that reads well. It's often faster to make many assumptions but it's also often far more verbose. Jackson, and many other extremely smart people, insist that being able to read and understand code is of paramount importance during the development process while having that code perform well is secondary until it's absolutely necessary. Well, I sort of broke the rules this sprint and decided to indulge my performance instincts a little bit.

That's okay, right?

Though I wouldn't really say that getting Ensue to perform was absolutely necessary, I've definitely noticed that it's slowing down significantly in the bigger levels that I've been trying to produce lately. The main reason I decided to make this sprint performance-themed was that I figured there were a lot of easy things to change that would make big changes in performance. I was sort of right but also sort of wrong: the various performance profiling tools I've used certainly indicate that my numbers are better but, honestly, the frame rate feels about the same to me.

In any case, I thought it'd be good to give back a little bit of my knowledge, so here are the parameters. All of these optimizations were made in JavaScript. Although I'm very familiar with JavaScript through a career spent as a web developer, it dawned on me in these last couple of weeks that I've never been all that concerned with the actual performance of the code (generally because the time that HTTP requests need dwarf even the most sluggish of client-side scripts). As such, many of these tips and tricks were new to me. Maybe they'll be new to you, too.

For each forEach

One of the first things you learn in programming is the concept of the for loop, the notion of being able to do something a defined (but still dynamic) number of times.

var list = [1, 2, 3, 4, 5];

for (var i = 0; i < list.length; i++) {
  var item = list[i]
  console.log(item);
}

In modern JavaScript, though, the much more usual way of writing a for-style loop that iterates over an array is to use Array.prototype.forEach(). This snippet accomplishes exactly the same thing as the last.

var list = [1, 2, 3, 4, 5];

list.forEach(function(item) {
  console.log(item);
});

I happen to think the latter forEach() version is a lot more readable and straightforward than the somewhat archaic-looking classic for loop but what I didn't know is that it's definitely much slower. I don't really know the specifics here but I believe it has a lot to do with repeatedly calling an anonymous function, along with having to create that anonymous function it the first place. Regardless, in video games, you're constantly running all sorts of loops to make sure every entity in the game gets its chance to handle the input the player is giving you, update its state based on that input (and myriad other factors), and then eventually get drawn to the screen. If you're slowing everything down with something that ultimately just boils down to using a more convenient syntax, well, that's a pretty easy win to get. Converting my code that looked like that latter into code that looks like the former was the vast majority of the performance benefit I saw.

We can do better

Even the humble for loop has a problem, though. It's all about that list.length thing. length is a property on the list object (and, indeed, on all Arrays). Again, I'm not super-familiar with the specifics here but it was made very clear to me that it takes a little bit of extra time to access an object property than it does to reference, say, a variable that has the same value as that property. Specifically, there ended up being a bunch of places where I was able to do something like this and see clear performance improvement:

var list = [1, 2, 3, 4, 5];
var listLength = list.length;

for (var i = 0; i < listLength; i++) {
  var item = list[i];
  console.log(item);
}

This technique represents the very basic concept of caching. When list.length shows up in the for loop's condition, it gets referenced once per iteration, so it can be significantly faster to cache its value in this way. Instead of constantly asking a question--in this case, "How many elements are in the array called list?"--you ask the question once and remember the answer.

Thrashing around

In JavaScript, as well as Java, memory is sort of managed for you. You get to just use however much memory you need and, periodically, the engine will run a garbage collector that frees up all the memory you're no longer using. This is generally pretty convenient, not having to meticulously ensure that every malloc() is paired with a free(), the way you need to in C, or every new with a delete, as in C++. It's not necessarily preferable in video games, though.

When the garbage collector runs, you definitely know it, especially when you're working on, you know, just for instance, an action-platformer that you want to be running silky smooth all the time. You don't really have any control over when the garbage collector runs so the best you can do is to try to minimize the amount of memory you allocate and subsequently throw away. This concept many, many faces that go well beyond the scope of a humble devlog but I wanted to give at least a few examples of how I was able to significantly reduce Ensue's memory footprint and, thus, prevent it from invoking the garbage collector as often as it had been.

A very basic syntax to avoid is this one:

[1, 2, 3, 4, 5].forEach(function(item) {
  console.log(item);
});

I've already talked about the virtues of moving away from forEach() but it turns out that it's also useful to just store a static array somewhere (even if globally) if you know that you're going to be using it a lot. Unfortunately, you forfeit the cool and hip look of defining arrays in place but each of those definitions is a new memory allocation and then an immediate tossing of its reference. I was guilty of liberally using this syntax.

Another easy but wasteful habit to fall into in JavaScript is writing a function that returns a new object. For example:

function arithmetic(x, y) {
  return { plus: x + y, minus: x - y, times: x * y, dividedBy: x / y };
}

This (admittedly very contrived) function returns the results of the big four arithmetic operators when given two numbers x and y. Specifically, every time it's called, it creates an entirely new object to pass back to the caller. You can certainly avoid this by breaking the one function into several and just insisting that the caller doesn't get all four in one:

function plus(x, y) {
  return x + y;
}

function minus(x, y) {
  return x - y;
}

function times(x, y) {
  return x * y;
}

function dividedBy(x, y) {
  return x / y;
}

Alternatively, though, you can have the caller pass an argument that's a reference to an object that they're responsible for making sure is already allocated and ready to be modified. The function could then look like this:

function arithmetic(x, y, result) {
  result.plus = x + y;
  result.minus = x - y;
  result.times = x * y;
  result.dividedBy = x / y;
}

In this scheme, you have to trust the caller to manage the result object in a smart way such that it never gets its reference tossed but the pay-off is that you're not creating a brand new object every time arithmetic() is called. In video games, this technique can get very dicey because you sometimes want, or even need, to call a function like this multiple times per frame and it can be tough to keep track of which values were what at which times. If you're able to cache data and reuse objects like this, though, it can be a major improvement in terms of your game's memory usage.

The elephant in the room

All these code snippets aside, though, the thing that is probably making your game run slow is the time you spend rendering everything to the screen. In mine, even after making a ton of the optimizations I've described, my big fat draw() method was still the main bottleneck. It turns out that it's quite expensive to put something on the screen, which is a concept that I was well aware of from the web ecosystem. In that land, if you're constantly making updates to a page's markup, you eventually learn to start operating on a copy of that markup that you then replace the full DOM with to avoid forcing the web browser to redraw the screen for every single change. Redrawing one change is just as expensive as redrawing all of them at once, both of which are way less expensive that redrawing a bunch of changes in sequence.

In the video game world, this manifests as going out of your way to not redraw anything that doesn't have to be redrawn. A common technique (that I have yet to implement) involves having two layers: one for your background and one for everything else. If you don't need your background to scroll as the player moves around the world, you get to draw it only once! Everything else on the other (mostly transparent) layer will check to see if it needs to be redrawn and, if so, it'll undraw its former state and draw its new one.

I bring this up mostly to underscore a common trap that developers fall into when optimizing their code: they often aren't optimizing the most relevant things. In my case, I sort of knowingly gave myself carte blanche to just work on whatever optimization seemed interesting to me at the time and it didn't take long for me to know that I wasn't attacking the big behemoth. Memory management and saving cycles are important, for sure, but my profilers were consistently telling me that I was simply drawing too much to the screen too often. I'd already long ago made the enhancement where you only draw things if they're within some magic number of pixels of the camera--which, of course, is a huge win if you're otherwise just always drawing everything to the screen, visible or not--but the next level was to start using multiple canvas layers to much more deliberate manage everything's visual representation. I didn't get there this sprint but it's becoming pretty clear that I'll have to get there at some point.

Closing thoughts

Throughout these last two weeks, there were a few resources that I found to be very helpful. No matter what stack you're working with, the most helpful tool is the one that will help you identify where your code is slow and/or bloated. In my universe, that's the performance tool that both Firefox and Chrome make available as a standard feature. Once you know where your code is slow, you need to know the best ways to make it faster. Though there was no single link that that me everything I needed to know about JavaScript optimization, there are many, many things out there that are just a simple search away. Specifically in the realm of video games, though, I found this thread on the HTML5 Game Devs Forum to be very useful, particularly the original post from rafinskipg and the reply by Sawamara. Hopefully, all of this will help somebody else who's looking to squeeze a few more frames per second out of their game!

Next sprint

It feels like it's time for another asset sprint. I'm going to be focusing on things that I've been putting off: background art, sound effects, new tiles, and level design. If nothing else, I'll actually have some thing to, you know, show in my next devlog post. Oh, also! I'm going to try to make it a weekly thing to do a Friday dev stream over at my personal Twitch account. I did a test run last Friday which I thought went pretty well. Tune in for that! I should be starting around 4pm ET or so.

Sprint #13: "The kitchen sink."

October 7th, 2018

Well, last week, I said that this would be a free-form sprint touching all kinds of systems. I'd introduce a new mechanic here, fix a bug there, maybe even create some new visuals. I did all that and more and, frankly, I don't even know where to start. If you like screenshots and videos, this is the devlog post for you.

A different kind of bug

Through watching some unthinkably large number of hours of televised sporting events, I've been made aware that the main graphic you see on the screen these days--the thing that tells you the score, the baserunning situation, the count, etc.--is called the "score bug". Until this week, I've had a very bare bones version of a score bug that tries to keep you updated on your balls, strikes, and outs.

Generally speaking, though, people don't seem to actually notice it. That's fine because it's so far from my bigger vision for it, I had an excuse to actually work on some new art for it this week. In my mind, the final score bug will be very reminiscent of an actual ballpark scoreboard, sporting a look that reminds you of hundreds or thousands of individual light-bulbs creating simple but effective imagery. After a little time spent working on it this sprint, here's what I've come up with, in four different states.

I really like how this came out and, in the longer term, I very much want to add animation to it. I could even see the score bug becoming almost its own character within the game that offers help when you need it or just adds a cool flourish to your actions. I'm very excited to go deeper and really see what I can do with this thing.

Back to basics

Even though I'm to the point where I've spent twelve months of effort on this game, the fundamental rules of my platforming universe are still being tweaked. One thing I released but didn't mention in the devlog post from last sprint was that you now have to hit every checkpoint in a level before being allowed to exit it. I sort of think of this as having to touch all the bases before you're allowed to touch home, though I don't actually require that you touch them in a particular order. Anyway, for the levels that exist right now, it doesn't make a huge difference because the checkpoints are laid out in a pretty linear fashion. I look forward to experimenting with this a little, though, and making some levels that afford you the ability to collect checkpoints in whatever order you like.

The biggest fundamental change I made, though, was something I've been kicking around a lot lately: the idea that you could convert an already-collected ball into a projectile. Think of it as ammunition but, to use the ammunition, you cost yourself a ball that you've collected and that could presumbly go towards nullifying one or two strikes later on. It's a trade-off that's very interesting to me and I spent a good amount of time implementing it this week.

As you can see, when the hero collects the ball, their count goes to 1-0 but, when the hero elects to use the ball as projectile, it not only decreases the count back to 0-0, it actually manifests as a strike. This introduces a good trade-off for being able to have ammunition along for the ride. If you swing and miss at the newly introduced strike, you'll end up collecting it, hugely affecting your count. This took the better part of a day to implement decently and, in many ways, the toughest thing was figuring out how on Earth the player should control this. I landed on: whenever the player holds up and presses the glove button while they have at least a one-ball count, that ball will be converted into a strike and peppered right in front of the hero. I think it actually works pretty well.

Watch out for the rebound

At some point, I started thinking the projectile system might be a little too generous to the player. Specifically, there didn't really seem to be any downside to just swinging your bat as often as possible to try to avoid having to actually confront an enemy head-on. Well, that changed quite a bit when I made a very simple modification to the bat mechanic: enemies can now hit you as they rebound off of walls.

On the other hand, you can also bounce off of rebounding enemies. This is a fairly precise maneuver, very much like a shell jump in Super Mario World and I don't think I would ever hide a checkpoint behind this mechanic. I could, however, envision hiding something optional behind it.

Special modifications

It's funny how just small tweaks can make a huge difference. This sprint, I've been experimenting a lot with modifiers to standard objects and processes. Even though these three things I'll talk about aren't strictly related, I view them as all part of the same "take a basic system and play with it" group of things I was goofing around with.

Firstly, I've introduced several variations of balls and strikes, along with two special collectibles that actually change the number of balls and strikes you're allowed to collect before your count is reset or you make an out, respectively. Keep an eye on how the scoreboard changes as the hero collects the MinusBall and PlusStrike.

I'm also equipped now to have one collectible item give you different numbers of balls or strikes. In some cases, this just replaces, say, a cluster of four individual balls. In others, though, I can more effectively charge the player two strikes for making a mistake without having to clumsily have two distinct objects on the screen.

Another type of modifier I messed around with was a simple enemy trait modifier. Put simply, I wanted to be able to create some enemies that you could never actually bounce off with a standard jump attack, as well as some enemies that wouldn't get damaged whenever you executed a standard jump attack. I named these traits "unbounceable" and "undamageable", respectively, and here are a couple of videos showing them in action. These enemies are otherwise just a normal Sitter (despite the very cool-looking horns, I know) and a normal Pacer.

Finally, I spent more time than I'd probably like to admit tweaking some magic numbers and controls related to the launch angle that results when you hit something with your bat. The main thing I really wanted to accomplish with this was to allow a projectile to contact pretty much anything on the same vertical parallel with it in close range. That is to say, if you have two enemies close to each other and you swing the bat to hit one, I wanted its natural path to hit the other.

This actually needed a bit more work than you might think. My default gravitational acceleration is far too strong to allow such a shallow launch angle to do much, so I have to temporarily nullify it and build it back up gradually. I actually consider myself kind of lucky that one of my first two or three guesses at how best to implement it ended up feeling pretty good to me.

I didn't stop there, though. I also wanted to be able to launch something at a much sharper angle while ideally preserving at least as many angles as I already had in the game. This is where the L button on the SNES controller (Q on the keyboard) came in. Now, if you're holding that button in addition to either up or down when you contact something with a bat, you'll launch it at a sharper angle up, if you're holding up, or down, if you're holding down. Here's an example of a big lob.

New faces, new names

The experimentation with enemies didn't stop at simple traits. They haven't yet shown up in a level but I created four completely new enemies. The first three all use a notion of proximity to the hero in order to dictate their behavior and the fourth actually integrates with the projectile system.

First up, we have the Chaser. This simple variation of a Walker plays dead when you're at a distance, tries to slowly walk away from you as you get closer, and pursues you at double speed when you're not looking.

Next, the Napper, who acts similarly but never retreats.

Then, we've got the Diver, who patrols a certain territory from the air. If you enter a Diver's space, it'll dive at you--with a little extra logic to estimate your position once it reaches the ground--at a pretty quick speed.

Finally, and, unfortunately, with no artwork yet, we have the Comebacker, who will automatically relaunch anything that's been launched at it. I've actually rigged up a little level where you must make use of a Comebacker to progress.

Breaking it down

Another new mechanic that got implemented this sprint was one I've had my eye on for pretty much all summer: breakable terrain. In this particular example, this terrain acts like a normal piece of solid ground except that it will disappear whenever it encounters either your bat or an enemy that's been launched at it. There are some really nasty things that you can do with this concept that combine both platforming expertise and puzzle solving. I'm sure those will show up in the main game in some form.

Visual reinforcement

Finally, I made a few visual changes that all make a huge difference. The first is that I've made switches change color once they're activated. The second is that platforms now also change color if transition from a deactivated state to an activated one. Both of these combine to really help the player realize that the two things are connected. Lastly, I've added a faint line that shows the path that a platform will travel. This does wonders for helping the player know when the right time to jump is, especially in what currently exists as 1-2 over in the prototype.

Closing thoughts

Whew! I got a lot of stuff done these past two weeks. Early on, I actually made a note to myself that I thought I was putting way too much in the queue than I could possibly accomplish in two weeks' time but, on the whole, I actually did get through it. It was a really fun period of experimentation and, I think, very good iteration on visuals and mechanics. By the way, I've decided to just go ahead and merge it all in so everything I've talked about in this post--to the extent that it was already in the existing levels--can be played around with in the prototype.

Next sprint

Sprint #14 will certainly not have as much media associated with it. I'm going to be focusing specifically on refactorization and optimization. Now that I've had some good time creating many different objects and entities within my codebase, I feel like I have a good idea of redundancies I'd like to get rid of. I've also been noticing somewhat lower framerates lately and I'd like to go take a good look at getting things nice and smooth. It should be a fun and/or mind-numbing two weeks.

Next Page