Friday, 31 October 2014

Phase 3a: Snake Enhanced

The last couple of weeks has seen a continuation of the approach I took developing Snake. Since there is still more to be done getting the Engine to a functional state, enhancing the existing game seemed like the quickest way of those enhancements in.

The two major enhancements I had for the system were to get animation working, and to get level as its own concept. For the first cut of snake, these were two areas where there was a lot of manual code.

The need for an AnimationObject arose out of the unsuitability of RenderObject and TextureObject to be complete for use as the game object. Each game object was loading its correct texture, then using that to populate the RenderObject associated with rendering. That was fine except for the snake itself, which changed whether the body part was a head, body part, or tail. So the snake had to keep track of both the texture and the render object so it could manually shift the frame corresponding to the right body part.

AnimationObject was my solution. By taking the texture, there was no need for the game object to have continual reference to the texture object (or even retrieve it) in order to change frames. Any animation in the original version was simply the illusion of movement. With AnimationObject, I’d have actual animation at the speed I desired simply by creating the object. It also meant that if the object moved, I could update the game coordinates without having to write the code to manually update the destination rect on the RenderObject.

Instead of trying to put absolutely every scenario into the single AnimationObject, I instead made use of inheritance and polymorphism to get specific functions. I made a RunOnceAnimationObject and a LoopAnimationObject – both of which override the render() method to allow for timer-based animation. The base AnimationObject would be for pointing to a single frame of a texture, while RunOnce would go from start to finish and hold on the final frame, and Loop would loop indefinitely.

The satisfying thing about the AnimationObject classes was that it took no more than an evening to write the code and refactor the game code. The whole process was seamless, and worked with very little effort. Building the level code was more effort, and one of those points where I had to remind myself to design for the game rather than design for some abstract ideal of what I wanted to achieve. The problem is this: Level ideally loads from an XML file (in my case, I wanted those XML files generated by Tiled!), and what is in the XML file on any given tile conforms to a game object.

To solve this problem, I implemented the abstract factory pattern. Level takes a TileFactory (an abstract type) that has a generateTile(int id) method which returns a Tile pointer (also an abstract). Level populates a Vector, where tiles can be retrieved with an x and y coordinate. So what kinds of tiles are produced depends on the concrete TileFactory, and all the SnakeGameObjects that could be Tile had to do was inherit from Tile. This approach does have a limitation, using Level like this means accessing any Tile will only have the methods that exist on the Tile contract. For now, I put the method bool isCollision() onto Tile to allow for collision detection within the game object. In the future, enhancing Tile to cater for all iterations of what Level could do might prove to be unwieldy. For now, the class does enough to work.

Here’s the enhanced game.

It might not look like much, but there are those enhancements that went on in the backing code – animation was handled automatically, and the world itself was controlled by an XML generated by the Tiled! application. In the spirit of prototype-driven design, I feel I was able to accomplish a number of vital tasks in a short period of time. Of course, there is still more to go, but that requires a different kind of prototype.

No comments: