PlatformerGdx Part 2: Moving Sprites
Source code for everything covered in this post can be found here.
Well, with the amount of throwaway code I’ve written in the last couple of days, I definitely don’t feel entitled to write about any of this, but I do think that I’ve arrived at an important step in the process so I figure I may as well. Over the last couple of days I have implemented a couple of my goals from my previous post. Namely, I have added a player sprite to the world that is controllable and can traverse the level. It can move left or right and it can jump. It has physical properties that are acted upon by the Box2D engine. It has a sprite that changes based on what it is doing (different animations for standing, moving right, and moving left). It has a sound effect that occurs when jumping. And, finally, the debug camera code has been stripped away and the camera now follows the character on the X axis (and is also restricted on the Y axis).
Fair warning, most of this has been performed by writting “screw it, figure it out first and then optimize it later” code. I created a ‘Player’ class where I built all of this functionality, from the rendering, to the updating, to the keyboard controls. Continuing where I left off in the last post, I first went into the Kenney asset pack I had previously downloaded looking for sprites to use as a ‘main character’. I found one in this green alien that I believe was the Kenney mascot for some time.
The assets were provided in 128x256 resolution which left a significant amount of blank space at the top of the canvas, so the first thing I did was shrink the canvas on the Y axis down on each asset so that it only contained the space that it needed. This ended up being unnecessary in the final revision of this post, but it was important during my first attempt at modeling a character.
The ultimate goal here is to create a Box2D body in the game world that is dynamic and controlled by user input. This body would then interact with the rest of the world’s Box2D bodies, following the physics that we have specified the Box2D world to have (gravity, etc). This body is the object that is actually interacting with the world, though it is invisible to the user. Once we have this body we then overlay a sprite (or TextureRegion) onto it. This is a little bit of a mental stretch, as the whole point of this operation is to give the user the perspective that they are playing as the sprite and that the sprite is the thing that’s interacting with the world.
Creating a Box2D body that was the same shape/size as the sprite seemed like a complicated step, so I first modeled the main character as a simple square that was the same height and width as the sprite object.
Now that the body was in the world, it was time to get the sprite to overlay it. I put all of the textures together as a TextureAtlas using the TexturePacker tool, as this is generally the most efficient way to load multiple textures into a game, and then pulled each texture when needed as a TextureRegion. To control the body, I added some Gdx.Input checks in the update method for the player class that check for ‘KeyPressed’ for the right or left arrow keys, and a ‘KeyJustPressed’ check for the space bar for jump. I’m still using the body ‘setLinearVelocity’ method to handle body movement, just like we did for the Pong game.
To handle animations, I made an enum called “MovementState” that got set based on user input. I also made an enum called “FacingDirection” to indicate whether the character was facing right or left, and would mirror the requisite character sprite on the vertical axis if the user was facing left. Note that the below GIF was taken with the debug renderer turned on, so the faint outlines you see around the terrain and player are the Box2D objects.
Finally it came time to make a Box2D object that actually modelled the character sprite, instead of just using a square. In researching this task, I came across the LibGDX Body Editor Tool. This is a very old tool, and as of this writing the last commit on the project’s GitHub was in 2018, around three years ago. Also, the project has been archived, so you cannot submit an issue ticket or a PR against it. This tool provides a canvas to create complex geometry Box2D objects out of a collection of points. It exports these body definitions as JSON files. The tool also comes with a Java class for you to include in your application called a ‘BodyEditorLoader’ that is used to load in the JSON file as a world body. The tool comes with full source and a compiled JAR to run, which I found still works with JDK11.
That said, I ran into issues when I attempted to utilize the BodyEditorLoader helper class. In order to load the fixture into the world, the class has to scale the provided JSON against a float. To do this it was still using an old, deprecated Vector2 method called ‘mul’, which previously in LibGDX could multiply the two dimensional vector by a scalar float. Sometime in the last couple of years this functionality has been removed from this method entirely, however, it was replaced with a method called ‘scl’ that can be used to do the same thing. Since the Body Editor project is archived and I can’t issue a fix PR against it, I forked the project and applied my fixes. The functioning version can be found here.
With the object made I simply modified my helper BodyLoader class to include a method that can load a body from a JSON file, which basically just calls the BodyEditorLoader class. After that I made the body using the new helper class, and overlaid the green alien TextureRegion over the new body.
Below there is a screenshot of the alien with the debug renderer turned on, which draws an outline of the Box2D objects.
It’s hard to see in the picture below, but I wanted to illustrate that currently the body is just modeling the alien’s standing animation, so when the character is walking, the body object at times exists outside of the sprite’s “body”.
There was actually quite a bit to this update, even a little more then I talked about here, which is why I made a post. That said, I didn’t want this post to be an hour of minutia, so I’ve left a couple things out such as how the walking animation itself works, etc.