1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.tests.superkoalio; 18 19 import com.badlogic.gdx.Gdx; 20 import com.badlogic.gdx.Input.Keys; 21 import com.badlogic.gdx.graphics.Color; 22 import com.badlogic.gdx.graphics.GL20; 23 import com.badlogic.gdx.graphics.OrthographicCamera; 24 import com.badlogic.gdx.graphics.Texture; 25 import com.badlogic.gdx.graphics.g2d.Animation; 26 import com.badlogic.gdx.graphics.g2d.Batch; 27 import com.badlogic.gdx.graphics.g2d.TextureRegion; 28 import com.badlogic.gdx.graphics.glutils.ShapeRenderer; 29 import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; 30 import com.badlogic.gdx.maps.tiled.TiledMap; 31 import com.badlogic.gdx.maps.tiled.TiledMapTileLayer; 32 import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell; 33 import com.badlogic.gdx.maps.tiled.TmxMapLoader; 34 import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer; 35 import com.badlogic.gdx.math.MathUtils; 36 import com.badlogic.gdx.math.Rectangle; 37 import com.badlogic.gdx.math.Vector2; 38 import com.badlogic.gdx.tests.utils.GdxTest; 39 import com.badlogic.gdx.utils.Array; 40 import com.badlogic.gdx.utils.Pool; 41 42 /** Super Mario Brothers-like very basic platformer, using a tile map built using <a href="http://www.mapeditor.org/">Tiled</a> and a 43 * tileset and sprites by <a href="http://www.vickiwenderlich.com/">Vicky Wenderlich</a></p> 44 * 45 * Shows simple platformer collision detection as well as on-the-fly map modifications through destructible blocks! 46 * @author mzechner */ 47 public class SuperKoalio extends GdxTest { 48 /** The player character, has state and state time, */ 49 static class Koala { 50 static float WIDTH; 51 static float HEIGHT; 52 static float MAX_VELOCITY = 10f; 53 static float JUMP_VELOCITY = 40f; 54 static float DAMPING = 0.87f; 55 56 enum State { 57 Standing, Walking, Jumping 58 } 59 60 final Vector2 position = new Vector2(); 61 final Vector2 velocity = new Vector2(); 62 State state = State.Walking; 63 float stateTime = 0; 64 boolean facesRight = true; 65 boolean grounded = false; 66 } 67 68 private TiledMap map; 69 private OrthogonalTiledMapRenderer renderer; 70 private OrthographicCamera camera; 71 private Texture koalaTexture; 72 private Animation stand; 73 private Animation walk; 74 private Animation jump; 75 private Koala koala; 76 private Pool<Rectangle> rectPool = new Pool<Rectangle>() { 77 @Override 78 protected Rectangle newObject () { 79 return new Rectangle(); 80 } 81 }; 82 private Array<Rectangle> tiles = new Array<Rectangle>(); 83 84 private static final float GRAVITY = -2.5f; 85 86 private boolean debug = false; 87 private ShapeRenderer debugRenderer; 88 89 @Override 90 public void create () { 91 // load the koala frames, split them, and assign them to Animations 92 koalaTexture = new Texture("data/maps/tiled/super-koalio/koalio.png"); 93 TextureRegion[] regions = TextureRegion.split(koalaTexture, 18, 26)[0]; 94 stand = new Animation(0, regions[0]); 95 jump = new Animation(0, regions[1]); 96 walk = new Animation(0.15f, regions[2], regions[3], regions[4]); 97 walk.setPlayMode(Animation.PlayMode.LOOP_PINGPONG); 98 99 // figure out the width and height of the koala for collision 100 // detection and rendering by converting a koala frames pixel 101 // size into world units (1 unit == 16 pixels) 102 Koala.WIDTH = 1 / 16f * regions[0].getRegionWidth(); 103 Koala.HEIGHT = 1 / 16f * regions[0].getRegionHeight(); 104 105 // load the map, set the unit scale to 1/16 (1 unit == 16 pixels) 106 map = new TmxMapLoader().load("data/maps/tiled/super-koalio/level1.tmx"); 107 renderer = new OrthogonalTiledMapRenderer(map, 1 / 16f); 108 109 // create an orthographic camera, shows us 30x20 units of the world 110 camera = new OrthographicCamera(); 111 camera.setToOrtho(false, 30, 20); 112 camera.update(); 113 114 // create the Koala we want to move around the world 115 koala = new Koala(); 116 koala.position.set(20, 20); 117 118 debugRenderer = new ShapeRenderer(); 119 } 120 121 @Override 122 public void render () { 123 // clear the screen 124 Gdx.gl.glClearColor(0.7f, 0.7f, 1.0f, 1); 125 Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 126 127 // get the delta time 128 float deltaTime = Gdx.graphics.getDeltaTime(); 129 130 // update the koala (process input, collision detection, position update) 131 updateKoala(deltaTime); 132 133 // let the camera follow the koala, x-axis only 134 camera.position.x = koala.position.x; 135 camera.update(); 136 137 // set the TiledMapRenderer view based on what the 138 // camera sees, and render the map 139 renderer.setView(camera); 140 renderer.render(); 141 142 // render the koala 143 renderKoala(deltaTime); 144 145 // render debug rectangles 146 if (debug) renderDebug(); 147 } 148 149 private void updateKoala (float deltaTime) { 150 if (deltaTime == 0) return; 151 152 if (deltaTime > 0.1f) 153 deltaTime = 0.1f; 154 155 koala.stateTime += deltaTime; 156 157 // check input and apply to velocity & state 158 if ((Gdx.input.isKeyPressed(Keys.SPACE) || isTouched(0.5f, 1)) && koala.grounded) { 159 koala.velocity.y += Koala.JUMP_VELOCITY; 160 koala.state = Koala.State.Jumping; 161 koala.grounded = false; 162 } 163 164 if (Gdx.input.isKeyPressed(Keys.LEFT) || Gdx.input.isKeyPressed(Keys.A) || isTouched(0, 0.25f)) { 165 koala.velocity.x = -Koala.MAX_VELOCITY; 166 if (koala.grounded) koala.state = Koala.State.Walking; 167 koala.facesRight = false; 168 } 169 170 if (Gdx.input.isKeyPressed(Keys.RIGHT) || Gdx.input.isKeyPressed(Keys.D) || isTouched(0.25f, 0.5f)) { 171 koala.velocity.x = Koala.MAX_VELOCITY; 172 if (koala.grounded) koala.state = Koala.State.Walking; 173 koala.facesRight = true; 174 } 175 176 if (Gdx.input.isKeyJustPressed(Keys.B)) 177 debug = !debug; 178 179 // apply gravity if we are falling 180 koala.velocity.add(0, GRAVITY); 181 182 // clamp the velocity to the maximum, x-axis only 183 koala.velocity.x = MathUtils.clamp(koala.velocity.x, 184 -Koala.MAX_VELOCITY, Koala.MAX_VELOCITY); 185 186 // If the velocity is < 1, set it to 0 and set state to Standing 187 if (Math.abs(koala.velocity.x) < 1) { 188 koala.velocity.x = 0; 189 if (koala.grounded) koala.state = Koala.State.Standing; 190 } 191 192 // multiply by delta time so we know how far we go 193 // in this frame 194 koala.velocity.scl(deltaTime); 195 196 // perform collision detection & response, on each axis, separately 197 // if the koala is moving right, check the tiles to the right of it's 198 // right bounding box edge, otherwise check the ones to the left 199 Rectangle koalaRect = rectPool.obtain(); 200 koalaRect.set(koala.position.x, koala.position.y, Koala.WIDTH, Koala.HEIGHT); 201 int startX, startY, endX, endY; 202 if (koala.velocity.x > 0) { 203 startX = endX = (int)(koala.position.x + Koala.WIDTH + koala.velocity.x); 204 } else { 205 startX = endX = (int)(koala.position.x + koala.velocity.x); 206 } 207 startY = (int)(koala.position.y); 208 endY = (int)(koala.position.y + Koala.HEIGHT); 209 getTiles(startX, startY, endX, endY, tiles); 210 koalaRect.x += koala.velocity.x; 211 for (Rectangle tile : tiles) { 212 if (koalaRect.overlaps(tile)) { 213 koala.velocity.x = 0; 214 break; 215 } 216 } 217 koalaRect.x = koala.position.x; 218 219 // if the koala is moving upwards, check the tiles to the top of its 220 // top bounding box edge, otherwise check the ones to the bottom 221 if (koala.velocity.y > 0) { 222 startY = endY = (int)(koala.position.y + Koala.HEIGHT + koala.velocity.y); 223 } else { 224 startY = endY = (int)(koala.position.y + koala.velocity.y); 225 } 226 startX = (int)(koala.position.x); 227 endX = (int)(koala.position.x + Koala.WIDTH); 228 getTiles(startX, startY, endX, endY, tiles); 229 koalaRect.y += koala.velocity.y; 230 for (Rectangle tile : tiles) { 231 if (koalaRect.overlaps(tile)) { 232 // we actually reset the koala y-position here 233 // so it is just below/above the tile we collided with 234 // this removes bouncing :) 235 if (koala.velocity.y > 0) { 236 koala.position.y = tile.y - Koala.HEIGHT; 237 // we hit a block jumping upwards, let's destroy it! 238 TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get("walls"); 239 layer.setCell((int)tile.x, (int)tile.y, null); 240 } else { 241 koala.position.y = tile.y + tile.height; 242 // if we hit the ground, mark us as grounded so we can jump 243 koala.grounded = true; 244 } 245 koala.velocity.y = 0; 246 break; 247 } 248 } 249 rectPool.free(koalaRect); 250 251 // unscale the velocity by the inverse delta time and set 252 // the latest position 253 koala.position.add(koala.velocity); 254 koala.velocity.scl(1 / deltaTime); 255 256 // Apply damping to the velocity on the x-axis so we don't 257 // walk infinitely once a key was pressed 258 koala.velocity.x *= Koala.DAMPING; 259 } 260 261 private boolean isTouched (float startX, float endX) { 262 // Check for touch inputs between startX and endX 263 // startX/endX are given between 0 (left edge of the screen) and 1 (right edge of the screen) 264 for (int i = 0; i < 2; i++) { 265 float x = Gdx.input.getX(i) / (float)Gdx.graphics.getWidth(); 266 if (Gdx.input.isTouched(i) && (x >= startX && x <= endX)) { 267 return true; 268 } 269 } 270 return false; 271 } 272 273 private void getTiles (int startX, int startY, int endX, int endY, Array<Rectangle> tiles) { 274 TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get("walls"); 275 rectPool.freeAll(tiles); 276 tiles.clear(); 277 for (int y = startY; y <= endY; y++) { 278 for (int x = startX; x <= endX; x++) { 279 Cell cell = layer.getCell(x, y); 280 if (cell != null) { 281 Rectangle rect = rectPool.obtain(); 282 rect.set(x, y, 1, 1); 283 tiles.add(rect); 284 } 285 } 286 } 287 } 288 289 private void renderKoala (float deltaTime) { 290 // based on the koala state, get the animation frame 291 TextureRegion frame = null; 292 switch (koala.state) { 293 case Standing: 294 frame = stand.getKeyFrame(koala.stateTime); 295 break; 296 case Walking: 297 frame = walk.getKeyFrame(koala.stateTime); 298 break; 299 case Jumping: 300 frame = jump.getKeyFrame(koala.stateTime); 301 break; 302 } 303 304 // draw the koala, depending on the current velocity 305 // on the x-axis, draw the koala facing either right 306 // or left 307 Batch batch = renderer.getBatch(); 308 batch.begin(); 309 if (koala.facesRight) { 310 batch.draw(frame, koala.position.x, koala.position.y, Koala.WIDTH, Koala.HEIGHT); 311 } else { 312 batch.draw(frame, koala.position.x + Koala.WIDTH, koala.position.y, -Koala.WIDTH, Koala.HEIGHT); 313 } 314 batch.end(); 315 } 316 317 private void renderDebug () { 318 debugRenderer.setProjectionMatrix(camera.combined); 319 debugRenderer.begin(ShapeType.Line); 320 321 debugRenderer.setColor(Color.RED); 322 debugRenderer.rect(koala.position.x, koala.position.y, Koala.WIDTH, Koala.HEIGHT); 323 324 debugRenderer.setColor(Color.YELLOW); 325 TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get("walls"); 326 for (int y = 0; y <= layer.getHeight(); y++) { 327 for (int x = 0; x <= layer.getWidth(); x++) { 328 Cell cell = layer.getCell(x, y); 329 if (cell != null) { 330 if (camera.frustum.boundsInFrustum(x + 0.5f, y + 0.5f, 0, 1, 1, 0)) 331 debugRenderer.rect(x, y, 1, 1); 332 } 333 } 334 } 335 debugRenderer.end(); 336 } 337 338 @Override 339 public void dispose () { 340 } 341 } 342