This is part 2 of a 3 part post demonstrating how to make a simple side scrolling game using libgdx and box2d. See part 1.
Updated 2011-09-19: Heh, now uses libgdx 0.9.2. No longer has its own Box2DDebugRenderer, figured out a better way using the new Matrix4 argument to renderer. Improved the controls by removing linear damping and replacing it with friction. (I am learning as I go with this, for what it is worth.) Discovered that I made a rather embarrassing mistake in leaving out rudimentary on-screen controls for Android devices. They are still pretty cruddy, though. Maybe I’ll come up with something better, or maybe someone else will and will contribute it? :) Either way is fine.
Updated 2011-09-18: Now uses libgdx 0.9.1.
In part 1 you saw how to make what is essentially a tiled map viewer. After you are through with this part you will have a character that you can move around the screen in a “natural” way, or at least a familiar way. As in part 1, download JumperTutorialProjects.zip for part 2 and follow along as the code is explained.
Post index
- Creating map tiles and the game map
- Integrating box2d
- Final touches: Scoring, lives, game over [coming soon]
- Add breaking blocks
- Add score and score display
- Add player death and a game over screen
- Hey, you’ve got a game
- Summary
Define the collision boundaries for each type of map tile
Adding collision support to a tiled map can be done any number of ways. For example, you could create a map (using Tiled Map Editor or whatever tool) and then use a separate tool to trace over a the map, defining where the collisions should occur. You would then write some code that loads those definitions at run time. If you used this method you would need to redraw your collision boundaries every time you change your map, in effect drawing the map twice (once with the map editor and again with the collision tracer). It would be easy for the map and the collision definitions to become out of sync.
An alternative method, the one used in this example, involves describing where the collisions should occur on each type of tile and then automatically prepare the boundaries at run-time. By setting the collision boundaries per tile, you will ensure that your environmental collisions will always be consistent with your map.
For an example of this per-tile approach consider a “floor” tile, with air above and dirt below. It would have a collision boundary across its top. A ramp would have a diagonal collision boundary from the top of one side to the bottom of the other.
Highlighted collision boundaries on selected tiles, enlarged for detail
The collision boundaries will need to be saved somewhere accessible to the game. In this example, the collision boundaries will be stored in a file, with one entry per tile. Each entry will define the line segments that make up the tile’s boundaries. The boundary file will be read when the map is initialized.
A tile that has no boundaries (such as a sky tile) will have no x and y coordinate pairs. All other tiles should have at least two pairs (minimum for a line segment). Here is the boundaries file used for the tile set that was created for part 1 of this series:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 1 2 3 0x31,0x16 0x16,16x16 16x16,16x0 16x0,31x0 31x0,31x31 0x31,31x31 4 0x0,27x0 27x0,29x2 29x2,29x31 5 6 7 29x0,29x31 8 9 10 0x31,0x0 0x0,15x0 15x0,15x16 15x16,31x16 31x16,31x31 31x31,0x31 11 2x0,2x31 12 13 14 0x0,31x0 15 16 0x31,0x0 0x0,31x0 31x0,31x31 31x31,0x31 17 18 19 2x31,2x2 2x2,4x0 4x0,31x0 20 21 |
Take a look at the tile set and compare it to what you see in this list. Tile 3, the stair-step tile in the top rightcorner of the tile image has collision boundaries all around its outer edge. Tile 7, the right-facing cliff tile, has a collision boundary that follow its edge. Some tiles have no collision boundaries at all, because they’re background pieces or pieces that can never be reached by a player.
You can create these tile boundary files by hand, but it requires a lot of work, and it is easy to make mistakes.
Introducing TileCollisionTool
That’s why I wrote TileCollisionTool. With TileCollisionTool, you can upload your tile image and then draw the specific collision boundaries for each tile. The tool also ensures that the boundaries you create fit within the tile’s width and height, and it handles spacing. It requires JavaScript and has been tested on Chrome, Safari, and Firefox 3.
TileCollisionTool in action
The resulting file will look much like the example boundary file above and should be saved as “collisions.txt” in the same project directory as tiles.png.
Programmatically configure the map’s collision boundaries
Now that each type of tile has its collision boundaries configured, the code can simply loop through the tiles defined in the map file, adding static box2d bodies as it goes. As the name implies, static bodies are unmoving. When the game is running your player character will be a dynamic fixture (see below) and will be unable to pass through these static bodies. This requires no serious effort on the part of the programmer. box2d handles it all.
As you probably suspect, adding a separate body or set of bodies for every tile means the game will have to keep track of a lot of bodies. This may not be a big deal, but why make the device work harder than it has to? The problem can be somewhat mitigated by adding code that detects whether one tile simply “continues” the collision line set up by the previous tile. For example, if you have a four tiles of flat ground next to each other, a “naive” implementation would result in four separate static bodies. If the code can detect that situation, the code can make one large body encompassing all four tiles.
In the spirit of decoupling and compartmentalizing, the map collision boundary code is written as a new method for TiledMapHelper. The following snippet is the code used to populate the world with the collision boundaries (the whole method is too large to reasonably paste here, but you’ll find it in the .zip):
208 209 210 211 212 213 214 215 216 217 218 | BodyDef groundBodyDef = new BodyDef(); groundBodyDef.type = BodyDef.BodyType.StaticBody; Body groundBody = world.createBody(groundBodyDef); for (LineSegment lineSegment : collisionLineSegments) { PolygonShape environmentShape = new PolygonShape(); environmentShape.setAsEdge( lineSegment.start().mul(1 / pixelsPerMeter), lineSegment .end().mul(1 / pixelsPerMeter)); groundBody.createFixture(environmentShape, 0); environmentShape.dispose(); } |
Take special note of the pixelsPerMeter variable there. box2d works well with small numbers, and typically people use the term “meter” for box2d’s distance unit. You’ll need to pick a conversion value to use that will take you from pixels to meters and meters to pixels. In this example project, pixelsPerMeter is 60.0, which results in expected physical behavior.
Add a player character sprite
Draw a small image to use as your player character’s sprite. The sprite can be any size. I suggest making it something like the size of a single tile, at least for this example. The sprite will need to be saved inside of an image with powers-of-2 dimensions, due to the aforementioned OpenGL ES requirements. The sample image included in the project (and displayed below) meets the requirements.
Example player character sprite file
Loading the sprite in the game is simple. First, you need to know the size of your sprite and its position within the overall sprite file. In this example, the jumper sprite is at the top left corner and is 21 pixels wide and 37 pixels tall. This code in JumperTutorial’s create method does the job:
139 140 141 142 | overallTexture = new Texture(Gdx.files.internal("data/sprite.png")); overallTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear); jumperSprite = new Sprite(overallTexture, 0, 0, 21, 37); |
Basically, the entire sprite image is loaded in to the system as a Texture, and then a chunk of that Texture is defined as a Sprite. The sprite will be drawn on screen after the TiledMapRenderer.render call.
Navigate the map with the player character
As alluded earlier, the player character will be represented in the box2d world as a dynamic fixture. For simplicity and sanity, this code will assume that the character is rectangular:
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | BodyDef jumperBodyDef = new BodyDef(); jumperBodyDef.type = BodyDef.BodyType.DynamicBody; jumperBodyDef.position.set(1.0f, 3.0f); jumper = world.createBody(jumperBodyDef); /** * Boxes are defined by their "half width" and "half height", hence the * 2 multiplier. */ PolygonShape jumperShape = new PolygonShape(); jumperShape.setAsBox(21f / (2 * PIXELS_PER_METER), 37f / (2 * PIXELS_PER_METER)); /** * The character should not ever spin around on impact. */ jumper.setFixedRotation(true); /** * The density of the jumper, 70, was found experimentally. Play with * the number and watch how the character moves faster or slower. * * The linear damping was also found the same way. */ jumper.createFixture(jumperShape, 70); jumperShape.dispose(); jumper.setLinearVelocity(new Vector2(0.0f, 0.0f)); jumper.setLinearDamping(5.0f); |
The player character’s sprite will be drawn based on the coordinates maintained in the character’s dynamic body object (jumper). The coordinates must be converted from box2d world units back to pixel units:
331 332 333 334 335 336 337 338 339 | spriteBatch.setProjectionMatrix(tiledMapHelper.getCamera().combined); spriteBatch.begin(); jumperSprite.setPosition( PIXELS_PER_METER * jumper.getPosition().x - jumperSprite.getWidth() / 2, PIXELS_PER_METER * jumper.getPosition().y - jumperSprite.getHeight() / 2); jumperSprite.draw(spriteBatch); |
There are a number of possible ways to control a character in a game. In this example I am using the keyboard. Left and right arrows move the character left and right, and the space bar causes the character to jump. Character movement is performed using “impulses”, which, in physics, is a way to apply momentum to a body that results in reasonably smooth action. I’ve pasted the movement code below, however the actual code in the project differs slightly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | if (Gdx.input.isKeyPressed(Input.Keys.KEYCODE_DPAD_RIGHT)) { jumper.applyLinearImpulse(new Vector2(12.0f, 0.0f), new Vector2( jumperSprite.getWidth() / (2 * PIXELS_PER_METER), jumperSprite.getHeight() / (2 * PIXELS_PER_METER))); } else if (Gdx.input.isKeyPressed(Input.Keys.KEYCODE_DPAD_LEFT)) { jumper.applyLinearImpulse(new Vector2(-12.0f, 0.0f), new Vector2( jumperSprite.getWidth() / (2 * PIXELS_PER_METER), jumperSprite.getHeight() / (2 * PIXELS_PER_METER))); } if (Gdx.input.isKeyPressed(Input.Keys.KEYCODE_DPAD_UP) && Math.abs(jumper.getLinearVelocity().y) < 1e-9) { jumper.applyLinearImpulse(new Vector2(0.0f, 90.0f), new Vector2( jumperSprite.getWidth() / (2 * PIXELS_PER_METER), jumperSprite.getHeight() / (2 * PIXELS_PER_METER))); } |
Summary
Right on. This is looking more and more like a real game. At this point, you’ve seen how to construct a 2 dimensional game world and how to navigate a character around that world. Where to go from here? It might be worth adding some code to reset the game when it detects that the character falls between cliffs, so you don’t have to restart the whole program every time. You could also play with different, more detailed tile sets, making a more complicated and interesting world. Maybe make a few more dynamic objects, attach them to sprites, and see how they interact with your character. In part 3 you’ll see how to add kinematic bodies to the game (such as breakable blocks), how to add superimposed scoring, and a way to detect game over (although that is pretty easy!)
By the way, as Jon pointed out, the code has the debug renderer enabled. You’ll see boxes all over the place, describing the box2d fixtures. You can disable it by commenting out the renderer.render() call.
I did it!
first:
bodyDef.type = BodyDef.BodyType.KinematicBody;
then, when the input happend
Box.setLinearVelocity(new Vector2(0, 100000f/PIXELS_PER_METER));
How would I edit the input code so that the jump is still there(you have to press the top right or top left of the screen to jump), but he “walks” across the ground whenever you touch the right bottom or right left of the screen? Sorry, I’m new to Android Development and I’m trying to get the hang of the physics.
Hi!!! im having trouble on resizing the screen, the sprites of my game buttons are resized and positioned ok and the toucheds are recalculated properly but the tilemap seems to get a zoomin and do rare things.. :S
how can i resize it properly?? :S:S
I’m not sure, can you post some code (on pastebin or similar)?
i’ve solved that!! :D but now i have another problem… my tiles are backgrounded transparent but it paints white???!!
what can be causing that?
solved!
Gdx.gl10.glAlphaFunc(GL10.GL_GREATER, 0.5f);
Gdx.gl10.glEnable(GL10.GL_ALPHA_TEST);
It works when I run it on the desktop, but I get this error when I try and run it on a Android device. Could you please help me. Thanks
07-25 12:41:19.184: E/dalvikvm(21088): Could not find class ‘com.example.jumpertutorial.JumperTutorial’, referenced from method com.example.jumpertutorial.JumperTutorialAndroid.onCreate
07-25 12:41:19.189: E/AndroidRuntime(21088): FATAL EXCEPTION: main
07-25 12:41:19.189: E/AndroidRuntime(21088): java.lang.NoClassDefFoundError: com.example.jumpertutorial.JumperTutorial
07-25 12:41:19.189: E/AndroidRuntime(21088): at com.example.jumpertutorial.JumperTutorialAndroid.onCreate(JumperTutorialAndroid.java:35)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.app.Activity.performCreate(Activity.java:4465)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2033)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2104)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.app.ActivityThread.access$600(ActivityThread.java:132)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1157)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.os.Handler.dispatchMessage(Handler.java:99)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.os.Looper.loop(Looper.java:137)
07-25 12:41:19.189: E/AndroidRuntime(21088): at android.app.ActivityThread.main(ActivityThread.java:4575)
07-25 12:41:19.189: E/AndroidRuntime(21088): at java.lang.reflect.Method.invokeNative(Native Method)
07-25 12:41:19.189: E/AndroidRuntime(21088): at java.lang.reflect.Method.invoke(Method.java:511)
07-25 12:41:19.189: E/AndroidRuntime(21088): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
07-25 12:41:19.189: E/AndroidRuntime(21088): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:556)
07-25 12:41:19.189: E/AndroidRuntime(21088): at dalvik.system.NativeStart.main(Native Method)
Having the same problem:
W/dalvikvm(3317): Unable to resolve superclass of Lcom/example/jumpertutorial/android/JumperTutorialAndroid; (520)
…well maybe not EXACTLY the same error but my guess the fix is the same.
If you haven’t already, open Properties for JumperTutorialAndroid and add the folder where the JumperTutorial class is located to the Libraries as a class folder (Should be JumperTutorial/src).
No part 3??
Thanks for the great tutorial. I’m just having one small problem. The collision lines of my map are all about 10 pixels too low. Any ideas?
Nevermind, answered my own question. I copied your loadCollisions method from the zip and it has 32 hard coded as part of the parameter to addOrExtendCollisionLineSegment. My tiles are 50×50. It’s working perfectly now. :)
Could you please share the Tile Collision Tool
Just one question…
1.) How do I edit the tiles with the Box2D editor tool? (http://code.google.com/p/box2d-editor/)
NEXT PART PLEASE! ;)
Huau, almost, 12:12!! (2012/12/12 at 12:02 am)… Are u the boy of New York? >> http://www.nydailynews.com/news/national/alabama-boy-turns-12-12-12-12-12-12-article-1.1218357
[...] http://dpk.net/2011/05/01/libgdx-box2d-tiled-maps-full-working-example-part-1/ a not yet finished tutorial, that will cover a complete example. Unknown to you should be the code from this passage on. [...]
No part 3 out? So sad… :(
The tile collision tool only works for me when I set the spacing to zero. However, whenever I do that I get an offset in the tiles. When I set the spacing to two it says the “number of tiles” is zero, and and I’m unable to draw the collision boundary: the collision marker just stays stuck to my mouse wherever I move it.
Has anyone else had this problem? Can anyone suggest another tool for collision detection?