BreakoutGdx Week 2: Bricks and Bodies
Lots of work done on the BreakoutGdx game this week, very excited to make a post about it. Last week we left off with the LibGDX project having been started. I had done quite a bit of prep work in scope decisions of what should/should not be included in the project, and of the things that should be in the project whether or not they fit into Phase 1 of the game. I had set up a kanban board to help plan the work, and had set up source control management and a CI pipeline to help deploy builds. I cleaned up the project I was bringing forward, getting it up to date with LibGDX and LWJGL3, and threw in some quick and dirty code to get it to render a paddle and a tiled map to the screen. For reference, this is what the game looked like at the end of the first week.
It’s not super apparent in the gif, but at the time the game was just painting the paddle texture to the screen and deciding where it should paint based on the X position of the mouse’s cursor. There was no Box2D body that existed and it would also mess up if I resized the screen due to the change in game screen width, etc. So to start off this week, getting the paddle and game walls rendering properly and colliding, etc was my first priority.
First thing I decided to do was encapsulate the screen inside a viewport. LibGDX comes with a number of viewports that are useful in different situations. Previously on the PongGdx and PlatformerGdx projects I had used an ExtendedViewport. This viewport will scale each asset vertically and horizontally to stretch/shrink the assets onto the new screen. The assets will fill the screen the best they can but this can sometimes make the assets look funky, especially if the screen resize involves an aspect ratio change. I’ve found that these ExtendedViewports work best if your game world extends further than just one screen worth of area. For this particular project, I decided to use a FitViewport. This viewport will resize elements on screen resize events (both shrink and stretch), but it maintains the aspect ratio defined in code by letterboxing the picture when necessary. This seemed great for my purposes, as the project is already contained in a game wall made out of sprites, so it seemed that the letterboxing wouldn’t be too distracting.
Second thing was getting bodies added and working in the world for the game walls, paddle, etc. This part of the week is a little difficult to write about, as it was almost a whole afternoon of trial and error. Basically the problem was, I had added a PPM constant of 32 to scale everything in the game by. This is important, especially for projects using Box2D, as Box2D comes with some default restrictions in terms of max speed for a body, etc. Basically the idea here is to scale everything in your project by some scalar value, so that the objects, camera, etc, all happen inside a reduced play space. I eventually got this working so that it applied to the camera, all game objects, the TiledMapRenderer, and all the Box2D bodies, but it took quite a bit of trial and error. Sometimes the paddle would work but the walls would be huge, sometimes the wall sprites would work but the Box2D bodies would be huge, and then everything would be tiny cause the camera was huge, etc. A gif of everything working (with the debug renderer turned on) is below. For the paddle Box2D body, I made a complex body sprite using the LibGDX Body Editor Tool that I used in the PlatformerGDX Part 2 post.
A crucial leaning step with Box2D/LibGDX happened at this point. Previously when I had made Breakout type games, I simply programmed the paddle to draw/exist wherever the user’s mouse cursor X coordinate was. If the cursor moved I’d relocate the paddle to follow it. Since this paddle includes a Box2D physics object, there wasn’t an obvious way to make the paddle “exist” where the user’s mouse cursor said it should. Just telling an object to “be somewhere” works in the programming sense, but when you consider that object to be a physical object, that’d mean it would have to get to that point with infinite velocity, so Box2D just doesn’t have those methods built in. I realized I was thinking about the paddle’s position the wrong way. In the implementation above then, on every update call the paddle tracks where the mouse pointer is, and if it’s X position is too far away from the paddle, it accelerates on the X axis towards the cursor. Once it arrives within a certain proximity of the cursor’s X position, it stops. (Note that all X coordinates I’m talking about here are in relation to the viewport, not the screen itself, that way this functionality also works after a window resize event).
Next I decided to get the ball into the game. I had originally handled ball movement similarly to how we did in the PongGDX project. For each body that was loaded into the game, I assigned a ContactType enum. I then created a GameContactListener object that listened for all body contacts in the world. If it found one that affected the ball, it would modify the balls velocity accordingly. This did work well, but after setting all of this up I realized that I didn’t like this implementation for a few reasons:
- We were barely using the Box2D framework for anything other then registering collisions, which seemed like a lot of power to leave on the table.
- I hope to expand the ball/paddle physics quite a bit in Phase 2, and it seemed like it’d be harder to do that after reducing all physical interactions to “reverse velocity on x or y axis”.
- This seemed like it’d be hard to implement when we brought bricks into the game.
You see, for the walls it is always an easy determination in how the ball should react upon hitting them. If the ball is hitting the top wall it has to be hitting it from the bottom, therefore it should reverse it’s Y velocity. If the ball is hitting a side wall it has to be hitting from the side, therefore it should reverse it’s X velocity. When bricks get brought in however, the ball could be hitting them from the top, side, corner, etc.
In an attempt to get Box2D working then I removed all references to velocity changes from the ContactListener. Then, instead of setting the linear velocity of the ball to a value, I applied a force to the center of the ball’s Box2D body in the constructor of the ball object. I removed all friction from all objects in the world, set each object’s restitution to 1.0, and set the ball’s density to 0.0 so it didn’t knock around other dynamic bodies. There’s still a ton of work to do in getting the ball’s motion working properly, but it works great for the time being.
I took a detour from game functionality at this point and decided I wanted to do something with the version number that I had previously built into the game’s JAR manifest file. Nothing super complicated here, I built a helper method to return this value as a String, and then built a label into the game’s Preferences screen where it is displayed.
Now that the ball/walls were working as desired (at least for Phase 1), I decided it was now time to get bricks in there. This part of the project merited a little more planning than any previous point, as it will ultimately involve a lot more communication between various segments of the game, and it needed to be somewhat expandable for future features. I whiteboarded out a design below.
I decided that there should be an abstract, parent class called “Brick” that encapsulates all standard brick functionality (position, Box2D body, etc). This Brick class would be abstract in that it could not be instantiated directly. In order to instantiate an actual brick, you would have to extend the brick class out to a child implementation that provided brick specific functionality, and instantiate that. I created a class called “SimpleBrick” that extended the abstract brick, and provided the specific functionality of having one ‘life’ (ie this ‘SimpleBrick’ would be hit once, then destroyed).
The Brick abstract class implements an interface I created called ‘IHitCallback’, that is called whenever the brick is hit. In this callback, you define whatever special functionality you want to happen when the brick is hit. Each child brick type would override this implementation with their own functionality. For the SimpleBrick it simply reduces an integer holding it’s life total by one, checks to see if the total is zero, and if it is then it stop’s rendering itself and removes it’s Box2D body from the game world. My thought is to add other kinds of bricks in the world during Phase 2, such as bricks that take multiple hits to die, bricks that never die no matter how many times you hit them, bricks that change their sprites, etc.
Anyway, after implementing the above design, this was the result (both in regular screen and fullscreen).
One last note about the bricks, specifically about destroying them when they’re hit. At first I simply incorporated destroying the brick body from the world within the brick’s callback. This immediately caused the game (and Java itself) to crash with a core dump. It turns out just randomly destroying Box2D objects willy nilly (such as when they may be being used for a Box2D physics calculation) is a really bad idea. It’s important that you destroy the body when it’s not being updated. To account for this, I made a method in GameScreen called “destroyLater”, that ultimately get’s called to destroy the bricks. GameScreen maintains an ArrayList of Body objects that can be added to via the “destroyLater” method. After every gameStep (in the update phase of the loop), it checks this DestroyList to see if it’s populated, and if it is then it destroys every body within. In this way we can asynchronously register bodies for destruction, but still ensure that they’re not actually destroyed until they’re done being used by Box2D. A gif of the game in it’s current state is below.