I’ve written about BASeBlock before. Now, the main reason is I kind of like typing the name BASeBlock. it let’s me put extra capitals in because that’s how it’s supposed to go- you know, unregistered trademark and all that jazz. Another reason of course is that I really do work on it a lot.
Here I will talk about the actual architecture of the game, and my design decisions- good and bad- that I made while developing it.
At first glance, the design of a Block breaker clone should be very simple- blocks, balls- done. Now, that might be true, but I didn’t want to make a simple little Block Breaker clone- I wanted to Make something awesome. So I put a bit of thought into it. The goal here was to create something extensible, something that, hopefully, I wouldn’t be working on down the road and find certain decisions I made in good faith early on reprehensible and annoying.
The main game components are the Paddle, the Balls, and the Blocks. All Blocks derive from the abstract Block class, and the Paddle is all alone. Originally, I was considering using Derived classes of Paddle and Ball to make, say, a Paddle that can shoot stuff, or a Ball that has special effects. But then I got to thinking about it, and really there shouldn’t be a mutual exclusivity about those types of things. I got to thinking of them as “Behaviours”. the result is that I have an interface for Paddle Behaviours and an interface for Ball behaviours; multiple behaviours can be applied to either, and in some cases I use generics and further object heirarchies to condense the code as much as possible; for example I have a separate “framework” of ‘Terminator’ paddle behaviours, which is the name I game for those behaviours that shoot things. In so doing I was able to change a single class to support “charging” the shot before release.
After that, I realized that having blocks simply dissappear was simply boring. So I came up with the idea of a “Particle” or, rather, I used the already present idea of a Particle. My original implementation of the abstract class Particle was simply a “dust” particle, which is more or less a generic particle that fades away over a second or two. Over time I extended this quit easily by adding new implementations. For example, I added a “PolyDebris” particle. This was simply a polygon that rotated and faded away and had the colours as set in it’s constructor. When I added it to the game and replaced some of the uses of the “DustParticle” with it, the results were very immersing. A derived class of the PolyDebris was the BrickDebris; this is used to emulate the appearance of the brick breaking animation from Super Mario Brothers for the block that is based on it. Much like the Super Mario Brothers brick, it breaks into four pieces which fall and rotate. The effect is really quite interesting and I was rather surprised at how fluid and cool it looked at the time. I also added GameObjects, GameEnemy’s, and a GameCharacter that was user-controllable, could jump around and break blocks, etc.
I added more framework, low level classes- interfaces, and all sorts of similar stuff. One of my favourites is the “Trigger/Event” framework. The idea is fairly simple: allow for all sorts of triggers to cause all sorts of events. For example; maybe a Enemy dying triggers the level to complete; or maybe it causes certain otherwise impossible to destroy blocks to break. The core idea is that:
Events and Triggers have IDs. a Trigger with a given ID, when invoked, will cause the Events with the same ID to occur. The base Trigger class is derived from by other classes, each one which defines what actually causes it to “trigger”; a block breaking? a Enemy dying? etc. Other classes have ID elements that are activated when certain events occur, or are used to pass that data on to another class. I’ve been impressed with how well it works, actually. For example I had a level with the aforementioned GameCharacter. When the level starts, you have to work your way up to the top. This starts you in a position where you have to jump on the paddle and ride it to the other side, where you disembark and get a invincibility star (yes, I know, very mario-esque…) There are some other powerup blocks there as well, but the goal is to get to the top, and break a “spawner” block. This spawns a boss enemy, which is a snake that builds itself out of blocks. You need to hit it’s head, at which point it slowly dyes (tail first). a Few moments after it dies, the music resets, and some invincible blocks break, giving you access to the one breakable block in the level, which allows you to complete it. This sort of gameplay would have been impossible without the Events and Triggers, which are used for several purposes- for example, when the game character dies (from touching a enemy, or falling off) it causes DelayedTrigger to invoke; it delays for 3 seconds, then invokes another TriggerID; the Level has this ID, and it is set to destroy the player, which it does; resulting in a lost life. The Block that spawns a monster has data that it passes to the enemy, namely, the EnemySpawnTriggerID. The base GameEnemy class knows when it dies and invokes the trigger when it does; this causes the blocks to break and there is also an Event for resetting the game music as well.
The game also uses reflection heavily at startup to find the appropriate classes; so I don’t need to say, add a new block to some internal list of available blocks- it finds it automatically. Another plus was this allowed me to use the CodeDOM to dynamicallly compile segments of code to allow for “scripts”; that is, I can have blocks, Ball Behaviours, GameObjects, and pretty much any class type in a .cs file in the scripts folder, and it will appear in the game as if it is native to the game; I can even give it a special category with the Attributes.
One of the design decisions I really hate is my use of an enumerated type for the gamestate; the game can be stopped, running, showing game over, showing the level intro, etc. All of this is dealt with using an enumeration. ideally, the drawing and game procedure code for each case would be part of a specific interface implementation for each type. I use this for DodgerX, which uses a interface-based approach to the problem, and makes working with it a heck of a lot easier. I’d love to refactor it to use this method, but there is quite a bit of strong coupling around; the form itself stores things it shouldn’t, for example. And it is something of a core, very low-level change. It will be worth the time investment… and I’ll get to it… some day!
Have something to say about this post? Comment!