Home | History | Annotate | Download | only in superkoalio
      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