BreakoutGdx Week 3: Minimum Viable Product
Today marks the end of the third week of working on this breakout clone project. When I started the project, I had set some ideal dates that I’d be finished with various parts of the project (part of the process of making this in a semi professional way was setting timelines/crunch times for myself, as I believe that no real product gets made without a schedule). Specifically, I had originally written that by the 17th of October I should be done with Phase 1 of the project. Phase 1 of the project consisted of a number of features that I felt would lead to a minimum viable product; as in a game with basic functionality, but complete in that it could be played and enjoyed by an outside user if it was demanded. Now that we’re actually at the 17th, I think I have in fact accomplished what I originally set out to, and that I have an MVP.
The first task I set off to this week was fixing the TiledMapRenderer object. I had been having an issue with this object in that it would display properly if I had the world’s debug renderer turned on, but would not display properly at all if I had it turned off. I eventually found the cause of the problem, the debug renderer was quitely scaling the TiledMapRenderer by the Const.PPM pixel per meter value (32.0f in my case) when it was on. When it was off it wouldn’t quietly perform this scaling, and thus the TiledMapRenderer would end up rendering significantly larger then other assets, and in the wrong location. I never found a good way to turn off this scaling (while leaving all other assets unaffected), but I did find a way to correct for it when the DebugRenderer was turned off, and just left two TiledMap setter lines in the code for future use (one for if the debug renderer was turned on, and one for if it was turned off), and I manually switch between these when I want the debug renderer on or off.
I messed around a bit after this, pulling features off of the kanban board and implementing as I was interested in them. Unfortunately not all of these features are very demonstratable in this format, so at this end of this post I’m going to go ahead and provide a download to 0.0.2 of the application. To begin I added music to the game, and made that music coorelate to the preferences set by the user in the preferences page. One cool thing that I honestly didn’t realize until I had implemented this, the ‘preferences’ class that I had been using is persistant between application sessions, and apparently this persistance works between all the releasable OSs (digging a little into this, I found that the LibGDX framework was saving these persistant preferences files as XML documents, and the Preferences class had all the serialization/deserialization built in so tightly that I hadn’t even noticed it!). I also implemented sound effects for a number of game events, namely: ball hitting wall, ball hitting paddle, ball hitting simple brick, ball hitting indestructible brick, game over, and level complete. There’s a couple of new things in there that I’ll explain later, but I wanted to gloss over all of the sound features rather quickly as I can’t really demonstrate that in a blog post.
Next I implemented a ‘GameMaster’ class. The purpose of this class was basically to control the logic and objects of ‘the game’. In this capacity, this class stores a ‘player lives’ variable that it decrements from when the player loses a life, and it stores reference to all game object (paddle, ball, brick manager, etc). This object has an update/render method just like any other game object, and in addition to rendering the game objects, it also renders UI data that is useful to the user. For now it just renders the players life total (in terms of balls remaining). I also built in a score system, but I couldn’t find a way to make the score interesting so I prevented the object from rendering that.
One of the notable changes of the week, this GameMaster class also tracks whether or not the player has won, lost, or if the game is still ongoing. The GameMaster keeps two booleans in memory called “levelcomplete” and “gameover”, both of which are initialized to false. The GameMaster has setters that other objects can call to set these variables as they see fit. For “gameover”, the GameMaster itself normally sets this. Anytime a ball dies, the ball object reports that status to the GameMaster, who checks the players life total. If the player has at least one life left, it decrements the players life total and gives him a new ball. If the player has no lives left, it sets the ‘gameover’ condition. ‘LevelComplete’ was a little more complicated. I put a boolean variable into the abstract Brick object called ‘isDestructible’ that each instantiatable brick should set in it’s definition. For bricks that can be destroyed, such as the SimpleBrick that was made in the last post, this is set to true. For bricks that can’t be destroyed this is set to false. Then, on every “hitDetected()” call (which is part of the IHit callback), the brickmanager checks the entire brick structure to see if every “isDestructible” brick has been destroyed. This is the definition of a “levelComplete” state, every “isDestructible” brick is destroyed, and we don’t care what the state is for the indestructible bricks. Once the GameMaster is set to a GameOver or LevelComplete situation, it displays the appropriate text on screen and stops stepping the game world forward.
You’ll notice a string in there saying “Press ‘r’ to retry”. That was the other big thing I did this week, implement retry/reset functionality. There is a whole story behind this feature. I played around with this functionality all night. The first thing I tried was implementing ‘reset’ features in each of the objects that the GameMaster interfaces with, namely the Ball and BrickManager. This became problematic with the BrickManager. In order for the BrickManager to destroy the brick bodies from the world it has to register them to the ‘destroyLater’ list maintained by the GameScreen, which get’s processed on the GameScreen’s next update cycle. I tried adding all of the present bodies onto the destroyLater list, clearing the list in the BrickManager, and then reproducing the level, but I could not get this timing to line up properly. Invariably I’d have at least a couple bricks bodies that wouldn’t be destroyed until after the new brick structure was generated, and then they’d be destroyed on the new structure, leaving it without some bricks.
Eventually I found a solution. Instead of trying to reset everything piecemeal, just dereference the entire GameScreen and make a new one. A GameScreen instance comes with it’s own world, brickmanager, gamemaster, everything. So just instantiate a new one of all of those. This would work great… most of the time. After 12-16 resets I found that I would end up crashing Java (core dumping the whole JRE). This was late Saturday evening that I came to this realization, but I took a guess as to what may be causing it. My thought was, maybe on some instances we’re threading the needle between processing threads, where sometimes we are trying to render the GameScreen instance after we dereference it but before we create a new object. With this thought in mind, around midnight on Saturday evening I made a new screen called “BlankScreen” that’s literally just a blank screen. I then refactored my game reset code to first change the screen view to the BlankScreen, then dereference the GameScreen, then instantiate a new GameScreen, and then switch the screen view back to the GameScreen from the BlankScreen. This way, if we do end up accidentally rendering mid operation every so often, there’s a legit, instantiated screen to render, which would prevent any kind of NullPointerException. I put some test code in so that the game would automatically reset if a gameover or levelcomplete condition was experienced, and let the program run without user input overnight in order to test my theory.
And it worked! I had guessed right in what was causing the JRE crash. Before this change, the program could only go 12-16 resets before crashing. With my changes and test code, and letting it run overnight, it went 285 resets without crashing by the time I woke up Sunday morning and got to my computer.
One small change, I implemented an AssetManager into the game following this guide. Previously I had been loading assets when I needed them, and taking special care to dispose of them when I dereferenced the object they were associated with. The AssetManager is awesome though in that I don’t have to think about that anymore. You load all assets one time with the AssetManager, and then you just pass around references to them to the various game objects. Since they’re just references they get cleaned up with the Java garbage collector and you don’t need to worry about explicitly derefencing them in VRAM. Up until this point I had split all of the game’s sprites into multiple texture atlases as I didn’t want to load in a big texture atlas where I wouldn’t use most of it. Now though, since I was only actually instantiating each asset once, I combined all game sprites into one big texture atlas that gets loaded on game startup, is owned by the BreakoutGdx singleton, and get’s destroyed when the game closes.
You’ll also see some sprites in there for power ups. These aren’t currently used in the game. These sprites I actually made by hand in Gimp instead of using anything from an existing sprite pack. They don’t look great, but I’m pretty proud of them since, after all, I’m not really much of an artist.
The last thing I did (kind of offhandedly, this only took a couple of minutes) was implement two new brick types. These are the “NoBrick” and the “IndestructibleBrick”. The NoBrick isn’t a brick at all, just a brick shaped hole in the map that doesn’t interact with anything. The “IndestructibleBrick” is a brick that renders as a gray, metallic looking thing. This brick cannot be destroyed, no matter how many times you hit it. Implementing both of these new brick types only took 15-20 minutes combined, which really gives credence to the idea of making your tools early and then using them throughout your development process. Since I had set up the idea of a “Brick” in the previous week to be a fairly easily defined and expandable thing, implementing this new content was simple. In addition to the two new bricks, I also implemented two new maps that the BrickManager could make, both of which incorporated the new brick types. In the gif below you can see brick structure number three, which uses SimpleBricks, IndestructibleBricks, and NoBricks. It also demonstrates the idea of reseting after a “levelcomplete” condition.
The game is still far, far from complete. I have many Phase 2 features to incorporate, the first of which I think will be a new system for paddle/ball physics interactions. But, it’s a game, it has an objective, it has user input, it has win/lose conditions, it has sound effects, it even has music. To me, this seems like a minimum viable product, which was my goal for this phase! The game in it’s current state (0.0.2) can be downloaded here.