/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.tests.superkoalio;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.tests.utils.GdxTest;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Pool;
/** Super Mario Brothers-like very basic platformer, using a tile map built using <a href="http://www.mapeditor.org/">Tiled</a> and a
* tileset and sprites by <a href="http://www.vickiwenderlich.com/">Vicky Wenderlich</a></p>
*
* Shows simple platformer collision detection as well as on-the-fly map modifications through destructible blocks!
* @author mzechner */
public class SuperKoalio extends GdxTest {
/** The player character, has state and state time, */
static class Koala {
static float WIDTH;
static float HEIGHT;
static float MAX_VELOCITY = 10f;
static float JUMP_VELOCITY = 40f;
static float DAMPING = 0.87f;
enum State {
Standing, Walking, Jumping
}
final Vector2 position = new Vector2();
final Vector2 velocity = new Vector2();
State state = State.Walking;
float stateTime = 0;
boolean facesRight = true;
boolean grounded = false;
}
private TiledMap map;
private OrthogonalTiledMapRenderer renderer;
private OrthographicCamera camera;
private Texture koalaTexture;
private Animation<TextureRegion> stand;
private Animation<TextureRegion> walk;
private Animation<TextureRegion> jump;
private Koala koala;
private Pool<Rectangle> rectPool = new Pool<Rectangle>() {
@Override
protected Rectangle newObject () {
return new Rectangle();
}
};
private Array<Rectangle> tiles = new Array<Rectangle>();
private static final float GRAVITY = -2.5f;
private boolean debug = false;
private ShapeRenderer debugRenderer;
@Override
public void create () {
// load the koala frames, split them, and assign them to Animations
koalaTexture = new Texture("data/maps/tiled/super-koalio/koalio.png");
TextureRegion[] regions = TextureRegion.split(koalaTexture, 18, 26)[0];
stand = new Animation(0, regions[0]);
jump = new Animation(0, regions[1]);
walk = new Animation(0.15f, regions[2], regions[3], regions[4]);
walk.setPlayMode(Animation.PlayMode.LOOP_PINGPONG);
// figure out the width and height of the koala for collision
// detection and rendering by converting a koala frames pixel
// size into world units (1 unit == 16 pixels)
Koala.WIDTH = 1 / 16f * regions[0].getRegionWidth();
Koala.HEIGHT = 1 / 16f * regions[0].getRegionHeight();
// load the map, set the unit scale to 1/16 (1 unit == 16 pixels)
map = new TmxMapLoader().load("data/maps/tiled/super-koalio/level1.tmx");
renderer = new OrthogonalTiledMapRenderer(map, 1 / 16f);
// create an orthographic camera, shows us 30x20 units of the world
camera = new OrthographicCamera();
camera.setToOrtho(false, 30, 20);
camera.update();
// create the Koala we want to move around the world
koala = new Koala();
koala.position.set(20, 20);
debugRenderer = new ShapeRenderer();
}
@Override
public void render () {
// clear the screen
Gdx.gl.glClearColor(0.7f, 0.7f, 1.0f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// get the delta time
float deltaTime = Gdx.graphics.getDeltaTime();
// update the koala (process input, collision detection, position update)
updateKoala(deltaTime);
// let the camera follow the koala, x-axis only
camera.position.x = koala.position.x;
camera.update();
// set the TiledMapRenderer view based on what the
// camera sees, and render the map
renderer.setView(camera);
renderer.render();
// render the koala
renderKoala(deltaTime);
// render debug rectangles
if (debug) renderDebug();
}
private void updateKoala (float deltaTime) {
if (deltaTime == 0) return;
if (deltaTime > 0.1f)
deltaTime = 0.1f;
koala.stateTime += deltaTime;
// check input and apply to velocity & state
if ((Gdx.input.isKeyPressed(Keys.SPACE) || isTouched(0.5f, 1)) && koala.grounded) {
koala.velocity.y += Koala.JUMP_VELOCITY;
koala.state = Koala.State.Jumping;
koala.grounded = false;
}
if (Gdx.input.isKeyPressed(Keys.LEFT) || Gdx.input.isKeyPressed(Keys.A) || isTouched(0, 0.25f)) {
koala.velocity.x = -Koala.MAX_VELOCITY;
if (koala.grounded) koala.state = Koala.State.Walking;
koala.facesRight = false;
}
if (Gdx.input.isKeyPressed(Keys.RIGHT) || Gdx.input.isKeyPressed(Keys.D) || isTouched(0.25f, 0.5f)) {
koala.velocity.x = Koala.MAX_VELOCITY;
if (koala.grounded) koala.state = Koala.State.Walking;
koala.facesRight = true;
}
if (Gdx.input.isKeyJustPressed(Keys.B))
debug = !debug;
// apply gravity if we are falling
koala.velocity.add(0, GRAVITY);
// clamp the velocity to the maximum, x-axis only
koala.velocity.x = MathUtils.clamp(koala.velocity.x,
-Koala.MAX_VELOCITY, Koala.MAX_VELOCITY);
// If the velocity is < 1, set it to 0 and set state to Standing
if (Math.abs(koala.velocity.x) < 1) {
koala.velocity.x = 0;
if (koala.grounded) koala.state = Koala.State.Standing;
}
// multiply by delta time so we know how far we go
// in this frame
koala.velocity.scl(deltaTime);
// perform collision detection & response, on each axis, separately
// if the koala is moving right, check the tiles to the right of it's
// right bounding box edge, otherwise check the ones to the left
Rectangle koalaRect = rectPool.obtain();
koalaRect.set(koala.position.x, koala.position.y, Koala.WIDTH, Koala.HEIGHT);
int startX, startY, endX, endY;
if (koala.velocity.x > 0) {
startX = endX = (int)(koala.position.x + Koala.WIDTH + koala.velocity.x);
} else {
startX = endX = (int)(koala.position.x + koala.velocity.x);
}
startY = (int)(koala.position.y);
endY = (int)(koala.position.y + Koala.HEIGHT);
getTiles(startX, startY, endX, endY, tiles);
koalaRect.x += koala.velocity.x;
for (Rectangle tile : tiles) {
if (koalaRect.overlaps(tile)) {
koala.velocity.x = 0;
break;
}
}
koalaRect.x = koala.position.x;
// if the koala is moving upwards, check the tiles to the top of its
// top bounding box edge, otherwise check the ones to the bottom
if (koala.velocity.y > 0) {
startY = endY = (int)(koala.position.y + Koala.HEIGHT + koala.velocity.y);
} else {
startY = endY = (int)(koala.position.y + koala.velocity.y);
}
startX = (int)(koala.position.x);
endX = (int)(koala.position.x + Koala.WIDTH);
getTiles(startX, startY, endX, endY, tiles);
koalaRect.y += koala.velocity.y;
for (Rectangle tile : tiles) {
if (koalaRect.overlaps(tile)) {
// we actually reset the koala y-position here
// so it is just below/above the tile we collided with
// this removes bouncing :)
if (koala.velocity.y > 0) {
koala.position.y = tile.y - Koala.HEIGHT;
// we hit a block jumping upwards, let's destroy it!
TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get("walls");
layer.setCell((int)tile.x, (int)tile.y, null);
} else {
koala.position.y = tile.y + tile.height;
// if we hit the ground, mark us as grounded so we can jump
koala.grounded = true;
}
koala.velocity.y = 0;
break;
}
}
rectPool.free(koalaRect);
// unscale the velocity by the inverse delta time and set
// the latest position
koala.position.add(koala.velocity);
koala.velocity.scl(1 / deltaTime);
// Apply damping to the velocity on the x-axis so we don't
// walk infinitely once a key was pressed
koala.velocity.x *= Koala.DAMPING;
}
private boolean isTouched (float startX, float endX) {
// Check for touch inputs between startX and endX
// startX/endX are given between 0 (left edge of the screen) and 1 (right edge of the screen)
for (int i = 0; i < 2; i++) {
float x = Gdx.input.getX(i) / (float)Gdx.graphics.getWidth();
if (Gdx.input.isTouched(i) && (x >= startX && x <= endX)) {
return true;
}
}
return false;
}
private void getTiles (int startX, int startY, int endX, int endY, Array<Rectangle> tiles) {
TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get("walls");
rectPool.freeAll(tiles);
tiles.clear();
for (int y = startY; y <= endY; y++) {
for (int x = startX; x <= endX; x++) {
Cell cell = layer.getCell(x, y);
if (cell != null) {
Rectangle rect = rectPool.obtain();
rect.set(x, y, 1, 1);
tiles.add(rect);
}
}
}
}
private void renderKoala (float deltaTime) {
// based on the koala state, get the animation frame
TextureRegion frame = null;
switch (koala.state) {
case Standing:
frame = stand.getKeyFrame(koala.stateTime);
break;
case Walking:
frame = walk.getKeyFrame(koala.stateTime);
break;
case Jumping:
frame = jump.getKeyFrame(koala.stateTime);
break;
}
// draw the koala, depending on the current velocity
// on the x-axis, draw the koala facing either right
// or left
Batch batch = renderer.getBatch();
batch.begin();
if (koala.facesRight) {
batch.draw(frame, koala.position.x, koala.position.y, Koala.WIDTH, Koala.HEIGHT);
} else {
batch.draw(frame, koala.position.x + Koala.WIDTH, koala.position.y, -Koala.WIDTH, Koala.HEIGHT);
}
batch.end();
}
private void renderDebug () {
debugRenderer.setProjectionMatrix(camera.combined);
debugRenderer.begin(ShapeType.Line);
debugRenderer.setColor(Color.RED);
debugRenderer.rect(koala.position.x, koala.position.y, Koala.WIDTH, Koala.HEIGHT);
debugRenderer.setColor(Color.YELLOW);
TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get("walls");
for (int y = 0; y <= layer.getHeight(); y++) {
for (int x = 0; x <= layer.getWidth(); x++) {
Cell cell = layer.getCell(x, y);
if (cell != null) {
if (camera.frustum.boundsInFrustum(x + 0.5f, y + 0.5f, 0, 1, 1, 0))
debugRenderer.rect(x, y, 1, 1);
}
}
}
debugRenderer.end();
}
@Override
public void dispose () {
}
}