In the previous part of this series, I discussed the design behind PongRPG, a blend of Pong and RPG elements and how I jumped right into development. However, at the end of that we saw mistakes both apparent and hidden to the players (one prevented them from playing the game without a dev setup, one was messy, inefficient code). Finally, with the help of the XNA Platformer Starter Kit, we were given a glimmer of hope, a way to clean up our code and refactor it into a smaller, more dynamic code base.
Note, even cleaning up the code wouldn't necessarily fix the first problem where we used a library only included in the XNA dev kit. This would simply need to be fixed by a UI redesign or by finding a way to get around that specific library (which, technically, would be fixed by cleaning up the code).
Codebase 2 - Turf
After the less-than-great release of the PC version of PongRPG and knowing the problems with it, I knew the code needed to change. And the first item on the agenda for fixing was all the hardcoded sprites. In case you don't remember, every sprite on every screen had at least 1 line of code associated with loading it. In fact, to accomodate readability of the code, each of these load lines actually spanned 3 lines. To make matters worse, to have a button highlight when the user hovered over a given item, that sprite had 2 versions, a non-hovered version, and a hovered version that had a yellowish background. Thus, I needed to have 2 load statements for nearly each sprite on the screen (12 lines of code). Some screens, like the Garage screen, had hundreds of sprites before factoring in the hovered versions. Not only was this a problem with the code, but could lead to some code inefficiencies as well.
At this point, I stopped working on PongRPG and went on to start another short-lived project. While the project wasn't long for the world, the codebase I created was, from analyzing PongRPG, nothing short of amazing (though still generally simplistic). The engine was code-named Turf, after the idea that the code base would be something any game could grow off of; a dynamic engine.
Turf was based on the concept of what I call 'Load-Files'. Load-files are nothing but simple text files where each line defined a sprite; usually what most people call scripts. I developed some standards to the scripts such as requiring a specific format when defining a sprite versus a different line format when defining a sprite sheet and so on for other types. I had a simple class that would parse apart each line of the file, generate the appropriate types, and place those objects into a list I passed by reference.
The benefits from this approach were outstanding, this engine was perfect! Or was it...
After the dissolving of the Project Turf team I went back to my broken baby, PongRPG. With this new engine I could develop a better game and in less time, right?
Well, it's true I could develop the game in less time, however I was again marred by some art decisions. Primarily, the PC version of PongRPG was set to some kind of standard resolution, something like 1024x768. Any developer using XNA GS should know that the standard resolution you should use is 1280x720 (via Best Practices). Since the majority of my backgrounds were only able to fit a more standard resolution, I needed to update a lot of my sprites. Plus, in the first iteration of PongRPG every sprite was defined in it's own file. A useful feature in XNA is the ability to define a texture and then define a source rectangle from that texture so you could have 1 texture will every main menu sprite in it. This would be changed in the new PongRPG.
While all the changes were fine and dandy once the art-dust had settled, I needed to reconsider some of the Turf engine. This is where we begin seeing restrictions and problems in the new code.
First off, my script processor would grab each line of the script file and do a Switch...Case to determine which type of entity to create. This means for any new type of object I needed to create, I needed to add more and more code to the script processor. Soon it became a monster of a class with tons of overloaded methods where the only difference was the return type. And the sick controller of it all was a beast of a Switch...Case based on parsed strings.
Secondly, a new problem began to rear it's ugly head in the form of global variables. There were certain objects and variables I wanted accessible throughout my code such as the screen width and height. These were most notably used in my script processor which would take in a string like 'C100' and would parse that into 100 pixels off center on whatever axis defined. Now I've got a large class that is not re-usable because there are sections of code that specify PongGame.SCREEN_WIDTH. I also had many objects created as global static variables (however, this was before learning design patterns and the almighty Singleton).
Codebase 3 - RhinoXNA
PongRPG version 2 has been stalled in favor of a new project, a new breath of fresh air instead of that stinky ball and paddle sitting on my external harddrive. The new project, Ambush, is a remake of the classic Asteroids and using yet another refactored version of my engine, this time called RhinoXNA.
What was the design decision behind RhinoXNA? I decided after working on PongRPG version 2 that I was unintentionally working towards a specific goal. I wanted a reusable code base that I could take from project to project and not have to worry about having broken code before I literally wrote any code. In other words, I wanted a library.
RhinoXNA's goal was to create a specific divide between the engine and the game. The engine code would be generic and the piece that I had been striving for years to achieve. The game code would be the specific implementations, the pieces where I would be free to hardcode strings and sprites (though I still don't suggest it). Whereas Turf was built to cleanup the game code, RhinoXNA was built to cleanup and decouple the engine code from the rest of it all. Specifically I knew that the script processor needed to be refactored and all global static variables should be removed.
Let's start with the global static variables. Several of those variables were able to removed simply by providing the code with a better design. I had cross class dependencies all over the place, the work in RhinoXNA was cutting those dependencies. For certain variables I felt would be useful (things like screen width, height, etc...) I created the RhinoXNAAdapter class. This class serves two purposes, 1) Store useful variables, 2) Provide the game code with an interface to the rest of the RhinoXNA library. Several classes needed 2-3 variables that are common in XNA GS: SpriteBatch and ContentManager. Being able to store these in the RhinoXNAAdapter as well as other variables only requires me to pass one argument and have a useful set of variables and tools immediately at my disposal.
Compared to the variable problem, the script processor class needed much more work. Eventually I got to the point where the entire class is generic, fully possible by the awesomeness of Reflection. Using Reflection, I was able to remove that nasty Switch...Case in favor of parsing out the first part of the script and linking it to a class, similar to the way Java properties files work.
However, even though I could create objects of a given type, I still needed to return a list of generated objects of a specific type, but if the user had their own created classes, how was I supposed to know what they'd call their class? This was a simple fix through the use of inheritance, abstract classes, and interfaces included in RhinoXNA. So, to create 5 asteroids I would need 2 script files (one script, one content defining my class), one class that inherits from the RhinoXNA interface SpriteEntity, and a new level (this hasn't been explained but Levels in RhinoXNA is what stores lists of things, like sprites).
3 code bases, 4 projects, tons of headaches, and days of programming. I'm finally here with a reusable code base that will grow in the future and can be used (hopefully) for damn near anything. RhinoXNA is still a work in progress, and it's even available as an open source project. Will I find problems in it? Most definitely. Am I happy with the current result? Absolutely.
I've learned more from these code base iterations than probably anything else I've ever done, and without finishing a single project. A noticeable pattern emerges in this series, I am horrible with sticking with things until they are finished. However, the one thing I've stuck by through the entire process is my code. And maybe, the finished project is my constantly work-in-progress of an engine and all the projects were just the flavor of the engine at the time. But it was what I needed, I needed that spark every once in awhile to push me and my code to their full potential.
Whether you finish a project or not, keep taking that code and reworking it. You won't learn if you don't try. Oh, and forget reinventing the wheel and invent something new!