/*******************************************************************************
* 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;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.EdgeShape;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.WorldManifold;
import com.badlogic.gdx.tests.utils.GdxTest;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;
public class Box2DCharacterControllerTest extends GdxTest implements ApplicationListener {
final static float MAX_VELOCITY = 14f;
boolean jump = false;
World world;
Body player;
Fixture playerPhysicsFixture;
Fixture playerSensorFixture;
OrthographicCamera cam;
Box2DDebugRenderer renderer;
Array<Platform> platforms = new Array<Platform>();
Platform groundedPlatform = null;
float stillTime = 0;
long lastGroundTime = 0;
SpriteBatch batch;
BitmapFont font;
float accum = 0;
float TICK = 1 / 60f;
@Override
public void create () {
world = new World(new Vector2(0, -40), true);
renderer = new Box2DDebugRenderer();
cam = new OrthographicCamera(28, 20);
createWorld();
Gdx.input.setInputProcessor(this);
batch = new SpriteBatch();
font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"), false);
}
@Override
public void dispose () {
world.dispose();
renderer.dispose();
batch.dispose();
font.dispose();
}
private void createWorld () {
float y1 = 1; // (float)Math.random() * 0.1f + 1;
float y2 = y1;
for (int i = 0; i < 50; i++) {
Body ground = createEdge(BodyType.StaticBody, -50 + i * 2, y1, -50 + i * 2 + 2, y2, 0);
y1 = y2;
y2 = 1; // (float)Math.random() + 1;
}
Body box = createBox(BodyType.StaticBody, 1, 1, 0);
box.setTransform(30, 3, 0);
box = createBox(BodyType.StaticBody, 1.2f, 1.2f, 0);
box.setTransform(5, 2.4f, 0);
player = createPlayer();
player.setTransform(-40.0f, 4.0f, 0);
player.setFixedRotation(true);
for (int i = 0; i < 20; i++) {
box = createBox(BodyType.DynamicBody, (float)Math.random(), (float)Math.random(), 3);
box.setTransform((float)Math.random() * 10f - (float)Math.random() * 10f, (float)Math.random() * 10 + 6,
(float)(Math.random() * 2 * Math.PI));
}
for (int i = 0; i < 20; i++) {
Body circle = createCircle(BodyType.DynamicBody, (float)Math.random() * 0.5f, 3);
circle.setTransform((float)Math.random() * 10f - (float)Math.random() * 10f, (float)Math.random() * 10 + 6,
(float)(Math.random() * 2 * Math.PI));
}
platforms.add(new CirclePlatform(-24, -5, 10, (float)Math.PI / 4));
platforms.add(new MovingPlatform(-2, 3, 2, 0.5f, 2, 0, (float)Math.PI / 10f, 4));
platforms.add(new MovingPlatform(17, 2, 5, 0.5f, 2, 0, 0, 5));
platforms.add(new MovingPlatform(-7, 5, 2, 0.5f, -2, 2, 0, 8));
// platforms.add(new MovingPlatform(40, 3, 20, 0.5f, 0, 2, 5));
}
Body createBox (BodyType type, float width, float height, float density) {
BodyDef def = new BodyDef();
def.type = type;
Body box = world.createBody(def);
PolygonShape poly = new PolygonShape();
poly.setAsBox(width, height);
box.createFixture(poly, density);
poly.dispose();
return box;
}
private Body createEdge (BodyType type, float x1, float y1, float x2, float y2, float density) {
BodyDef def = new BodyDef();
def.type = type;
Body box = world.createBody(def);
EdgeShape poly = new EdgeShape();
poly.set(new Vector2(0, 0), new Vector2(x2 - x1, y2 - y1));
box.createFixture(poly, density);
box.setTransform(x1, y1, 0);
poly.dispose();
return box;
}
Body createCircle (BodyType type, float radius, float density) {
BodyDef def = new BodyDef();
def.type = type;
Body box = world.createBody(def);
CircleShape poly = new CircleShape();
poly.setRadius(radius);
box.createFixture(poly, density);
poly.dispose();
return box;
}
private Body createPlayer () {
BodyDef def = new BodyDef();
def.type = BodyType.DynamicBody;
Body box = world.createBody(def);
PolygonShape poly = new PolygonShape();
poly.setAsBox(0.45f, 1.4f);
playerPhysicsFixture = box.createFixture(poly, 1);
poly.dispose();
CircleShape circle = new CircleShape();
circle.setRadius(0.45f);
circle.setPosition(new Vector2(0, -1.4f));
playerSensorFixture = box.createFixture(circle, 0);
circle.dispose();
box.setBullet(true);
return box;
}
@Override
public void resume () {
}
@Override
public void render () {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
cam.position.set(player.getPosition().x, player.getPosition().y, 0);
cam.update();
renderer.render(world, cam.combined);
Vector2 vel = player.getLinearVelocity();
Vector2 pos = player.getPosition();
boolean grounded = isPlayerGrounded(Gdx.graphics.getDeltaTime());
if (grounded) {
lastGroundTime = TimeUtils.nanoTime();
} else {
if (TimeUtils.nanoTime() - lastGroundTime < 100000000) {
grounded = true;
}
}
// cap max velocity on x
if (Math.abs(vel.x) > MAX_VELOCITY) {
vel.x = Math.signum(vel.x) * MAX_VELOCITY;
player.setLinearVelocity(vel.x, vel.y);
}
// calculate stilltime & damp
if (!Gdx.input.isKeyPressed(Keys.A) && !Gdx.input.isKeyPressed(Keys.D)) {
stillTime += Gdx.graphics.getDeltaTime();
player.setLinearVelocity(vel.x * 0.9f, vel.y);
} else {
stillTime = 0;
}
// disable friction while jumping
if (!grounded) {
playerPhysicsFixture.setFriction(0f);
playerSensorFixture.setFriction(0f);
} else {
if (!Gdx.input.isKeyPressed(Keys.A) && !Gdx.input.isKeyPressed(Keys.D) && stillTime > 0.2) {
playerPhysicsFixture.setFriction(1000f);
playerSensorFixture.setFriction(1000f);
} else {
playerPhysicsFixture.setFriction(0.2f);
playerSensorFixture.setFriction(0.2f);
}
// dampen sudden changes in x/y of a MovingPlatform a little bit, otherwise
// character hops :)
if (groundedPlatform != null && groundedPlatform instanceof MovingPlatform
&& ((MovingPlatform)groundedPlatform).dist == 0) {
player.applyLinearImpulse(0, -24, pos.x, pos.y, true);
}
}
// since Box2D 2.2 we need to reset the friction of any existing contacts
Array<Contact> contacts = world.getContactList();
for (int i = 0; i < world.getContactCount(); i++) {
Contact contact = contacts.get(i);
contact.resetFriction();
}
// apply left impulse, but only if max velocity is not reached yet
if (Gdx.input.isKeyPressed(Keys.A) && vel.x > -MAX_VELOCITY) {
player.applyLinearImpulse(-2f, 0, pos.x, pos.y, true);
}
// apply right impulse, but only if max velocity is not reached yet
if (Gdx.input.isKeyPressed(Keys.D) && vel.x < MAX_VELOCITY) {
player.applyLinearImpulse(2f, 0, pos.x, pos.y, true);
}
// jump, but only when grounded
if (jump) {
jump = false;
if (grounded) {
player.setLinearVelocity(vel.x, 0);
System.out.println("jump before: " + player.getLinearVelocity());
player.setTransform(pos.x, pos.y + 0.01f, 0);
player.applyLinearImpulse(0, 40, pos.x, pos.y, true);
System.out.println("jump, " + player.getLinearVelocity());
}
}
// update platforms
for (int i = 0; i < platforms.size; i++) {
Platform platform = platforms.get(i);
platform.update(Math.max(1 / 30.0f, Gdx.graphics.getDeltaTime()));
}
// le step...
world.step(Gdx.graphics.getDeltaTime(), 4, 4);
// accum += Gdx.graphics.getDeltaTime();
// while(accum > TICK) {
// accum -= TICK;
// world.step(TICK, 4, 4);
// }
player.setAwake(true);
cam.project(point.set(pos.x, pos.y, 0));
batch.begin();
font.draw(batch, "friction: " + playerPhysicsFixture.getFriction() + "\ngrounded: " + grounded, point.x + 20, point.y);
batch.end();
}
private boolean isPlayerGrounded (float deltaTime) {
groundedPlatform = null;
Array<Contact> contactList = world.getContactList();
for (int i = 0; i < contactList.size; i++) {
Contact contact = contactList.get(i);
if (contact.isTouching()
&& (contact.getFixtureA() == playerSensorFixture || contact.getFixtureB() == playerSensorFixture)) {
Vector2 pos = player.getPosition();
WorldManifold manifold = contact.getWorldManifold();
boolean below = true;
for (int j = 0; j < manifold.getNumberOfContactPoints(); j++) {
below &= (manifold.getPoints()[j].y < pos.y - 1.5f);
}
if (below) {
if (contact.getFixtureA().getUserData() != null && contact.getFixtureA().getUserData().equals("p")) {
groundedPlatform = (Platform)contact.getFixtureA().getBody().getUserData();
}
if (contact.getFixtureB().getUserData() != null && contact.getFixtureB().getUserData().equals("p")) {
groundedPlatform = (Platform)contact.getFixtureB().getBody().getUserData();
}
return true;
}
return false;
}
}
return false;
}
@Override
public boolean keyDown (int keycode) {
if (keycode == Keys.W) jump = true;
return false;
}
@Override
public boolean keyUp (int keycode) {
if (keycode == Keys.W) jump = false;
return false;
}
Vector2 last = null;
Vector3 point = new Vector3();
@Override
public boolean touchDown (int x, int y, int pointerId, int button) {
cam.unproject(point.set(x, y, 0));
if (button == Input.Buttons.LEFT) {
if (last == null) {
last = new Vector2(point.x, point.y);
} else {
createEdge(BodyType.StaticBody, last.x, last.y, point.x, point.y, 0);
last.set(point.x, point.y);
}
} else {
last = null;
}
return false;
}
abstract class Platform {
abstract void update (float deltatime);
}
class CirclePlatform extends Platform {
Body platform;
public CirclePlatform (int x, int y, float radius, float da) {
platform = createCircle(BodyType.KinematicBody, radius, 1);
platform.setTransform(x, y, 0);
platform.getFixtureList().get(0).setUserData("p");
platform.setAngularVelocity(da);
platform.setUserData(this);
}
@Override
void update (float deltatime) {
}
}
class MovingPlatform extends Platform {
Body platform;
Vector2 pos = new Vector2();
Vector2 dir = new Vector2();
float dist = 0;
float maxDist = 0;
public MovingPlatform (float x, float y, float width, float height, float dx, float dy, float da, float maxDist) {
platform = createBox(BodyType.KinematicBody, width, height, 1);
pos.x = x;
pos.y = y;
dir.x = dx;
dir.y = dy;
this.maxDist = maxDist;
platform.setTransform(pos, 0);
platform.getFixtureList().get(0).setUserData("p");
platform.setAngularVelocity(da);
platform.setUserData(this);
}
public void update (float deltaTime) {
dist += dir.len() * deltaTime;
if (dist > maxDist) {
dir.scl(-1);
dist = 0;
}
platform.setLinearVelocity(dir);
}
}
}