Despite a bit of a late start today (which pushes this blog entry a little later than I wanted it to be, it should be done by now) I have managed to get the first parts of the random level generation done. This required some tweaking of existing code, and I have added a new class to the game which may eventually be pushed upwards into the engine itself.
The gist of today’s tasks were to start on level generation. The way the levels are currently set up is just a big array of entity objects. Some entities are static, such as the outer walls, so there is no need to have more than one of them. Then there are entities that animate, but they animate as a group, such as the Gray bricks which all vanish at once near the end of the game. For these is is enough to have a single entity that is used in all places, since the state is always the same.
However when it comes to other entities, such as Arrows and bonus tiles, this does not hold true. These may change state independently of each other, so we need to create unique instances for each and we need a way to track them. Also, since they’re animated, we need to invoke their update() method to ensure they visually update as needed. To get around both items easier, I created a new class which I’m tentatively calling EntityPool.
This is basically just a glorified interface over an array; entities can be added to the list, and then marked as either dead or alive. There is an update() method which invokes the similar method in all entities in the pool, so long as they are currently alive. Thus for our level creation purposes we can add them to the class and update them all at once, while using references from the maze array to distinguish between them as needed.
This class is overkill for the current task, but the idea is that it would act as a simple object pool for often recycled entities to save on the cost of having to create and collect them all the time. If this gets pushed into the engine, it needs a little more work. I’ve added it directly to this project to see what other changes I deem necessary before all is said and done.
At the very least the class is crude enough that it assumes it stores Entity instances, when it should really be Actor. Additionally, it should be made generic (pretty sure TypeScript supports that) so that you don’t have to perform casts as you remove objects from the pool.
Regardless, with that out of the way I was able to begin actually starting the level generation. To begin with I added generation of Black Holes. There are a set number of them which are randomly placed into the level as a first step. We also ensure that no other entity (including the walls) is within two tiles of a black hole when we place it. This keeps them away from the edges of the maze and away from each other a bit, which seems to work out better.
Using the new class, we now also generate arrows into the maze. For now it’s randomly selecting between both styles of arrow, although for our actual uses (if we’re remaining true to the original) the first couple of levels will never have arrows that automatically swap positions. This is good enough for ensuring that everything works as we want it to, though.
For arrow generation, instead of just randomly selecting arrow locations, we instead generate a random number of arrows per row, where the number falls within a predefined range (currently between three and eight inclusive). I did some experimentation and discovered that this seems to generate a more visually pleasing result.
In order to support this better, I did a little refactoring on the Arrow entity itself, changing the constructor to generate generic arrows and adding the ability to alter the type and direction of the arrow directly. This allows the generation code to extract a pre-created arrow from the pool and configure it as needed.
So far I’m fairly pleased with the results. Once I get the last two level parts in I’ll look a bit more into tweaking the generation rules in order to make things work a little better. Having played a few games of the original, I’m not convinced any such rules were applied in the creation of the original, although I may be wrong.
Something to note here is that we make sure that nothing gets generated into the two two rows of the maze, or into the bottom row. At the top, we need the first row to be the location where the balls originally start, with the row under it being empty so that all balls have a fair chance of actually entering the maze (although actions by the other player may still block this). At the bottom we need the last row clear as a “goal” row; balls that get this far score bonus points, so it’s important that they all have a fair chance to get that far.
I’ve noticed that if I take these screenshots on my Macbook (such as this one), the tiles seem to be sized slightly incorrectly. This doesn’t happen on my development machine, so I’m not sure if this has to do with canvas scaling or what. That’s something on the list to look into closely later.
For those interested, a reminder that at the end of every day I’m uploading that day’s changes to http://gamedev.nurdz.com/amazeballs/, so you can take a look and generate some levels.