In the How To 7 tutorial, we will implement the jump and slide movement. If you have missed it, go check the How To 6 tutorial, where we implemented the left and right movement. It is also essential to check it because you can download the images of the buttons from there.
How to make your character jump and slide using box2D and libGDX
Go to our " HeroActor " class and create the functions for the jump and the slide;
package com.getest.game.actors;
import com.badlogic.gdx.math.Vector2
;import com.badlogic.gdx.physics.box2d.Body
;import com.badlogic.gdx.utils.Timer;
import com.getest.game.box2d.HeroUserData;
public class HeroActor extends GameActors {
private Vector2 velocityEachTime;
private Vector2 movingLinearImpulse;
private float velocityX;
private float finalVel;
private float heroPositionY, heroPositionX;
private boolean jumping = false;
private boolean dodging = false;
private boolean land = true;
private Vector2 heroPosition;
private Timer timer1 = new Timer();
public HeroActor(Body body) {
super(body);
}
//Rest of the code
public void jump() {
if(!jumping && !dodging) {
body.applyLinearImpulse(getUserData().getJumpingImpulse(), body.getWorldCenter(), true);
jumping = true;
land = false;
}
}
public void landed() {
jumping = false;
land = true;
}
public void dodge() {
if(land && !dodging) {
heroPosition = body.getPosition();
heroPositionY = heroPosition.y - 0.4F;
heroPositionX = heroPosition.x;
heroPosition = new Vector2(heroPositionX, heroPositionY);
body.setTransform(heroPosition, -1.5707964F); // -90 degrees in radians
dodging = true;
Timer.Task task = new Timer.Task() {
public void run() {
heroPosition = body.getPosition();
heroPositionY = heroPosition.y + 0.4F;
heroPositionX = heroPosition.x;
heroPosition = new Vector2(heroPositionX, heroPositionY);
body.setTransform(heroPosition, 0.0F);
dodging = false;
}
};
timer1.scheduleTask(task, getUserData().getSlideDuration());
}
}
}
For the jump, we apply a linear impulse to our hero on the y - axis and let the gravity do the rest. We want to know when we land, so we just create a land function that properly handles our boolean variables. We also don't want to double jump (the jumping boolean variable prevents us from double jumping).
For the slide, we rotate our hero and after a certain amount of time we bring him to his original position. We don't want to slide when we are jumping, thus we slide only when the land boolean variable is true.
" How can we know if we have landed though? "
If you are asking the above question in your mind then you are totally right. We have to create a function that differentiates the collision between bodies. For example we want to know when our hero collides with the ground and when he collides with a wall. Inside the " misc " package, create a java class with the name " BodyMisc " and paste this;
package com.getest.game.misc;
import com.badlogic.gdx.physics.box2d.Body;
import com.getest.game.box2d.UserData;
import com.getest.game.enums.UserDataType;
public class BodyMisc {
public BodyMisc(){
}
public static boolean bodyIsHero(Body body) {
UserData userData = (UserData)body.getUserData();
return userData != null && userData.getUserDataType() == UserDataType.HERO;
}
public static boolean bodyIsGround(Body body) {
UserData userData = (UserData)body.getUserData();
return userData != null && userData.getUserDataType() == UserDataType.GROUND;
}
}
Giving a random body to these functions, will return us whether it belongs to a hero or not. Same goes for the ground.
And now classical procedure. We must create the jump and slide buttons, but in order to do so, we need 2 more .json files with names " jumpButton.json " and " slideButton.json " with these lines of code respectively;
{ "com.badlogic.gdx.scenes.scene2d.ui.ImageButton$ImageButtonStyle": { "default": { "up": "jumpNorm", "down": "jumpPushed", "unpressedOffsetY":-10, } } }
and
{
"com.badlogic.gdx.scenes.scene2d.ui.ImageButton$ImageButtonStyle": {
"default": {
"up": "slideNorm",
"down": "slidePushed",
"unpressedOffsetY":-10,
}
}
}
The " GameStage " class now becomes;
package com.getest.game.stages;
import ...
public class GameStage extends Stage implements ContactListener{
private float accumulator, TIME_STEP;
private Box2DDebugRenderer renderer;
private WorldMisc wrl;
private World world;
private GroundActor ground;
private LeftWallActor leftWall;
private RightWallActor rightWall;
private HeroActor hero;
private Boolean right = false, left = false;
private Skin leftButtonSkin, rightButtonSkin, jumpButtonSkin, slideButtonSkin;
private ImageButton leftButton, rightButton, jumpButton, slideButton;
public GameStage(){
super(new ExtendViewport(16f, 9f, new AndroidCamera(16f, 9f)));
accumulator = 0.0F;
TIME_STEP = 1/300F; // Recomended by libgdx
setupWorld();
}
private void setupWorld() {
wrl = new WorldMisc();
world = wrl.createWorld();
renderer = new Box2DDebugRenderer();
Gdx.input.setInputProcessor(this);
world.setContactListener(this);
setupGround();
setupLeftWall();
setupRightWall();
setupHero();
setupButtons();
}
//Rest of the code
private void setupButtons() {
leftButtonSkin = new Skin(Gdx.files.internal("skins/leftButton.json"), new TextureAtlas(Gdx.files.internal("skins/inGameButtons.atlas")));
rightButtonSkin = new Skin(Gdx.files.internal("skins/rightButton.json"), new TextureAtlas(Gdx.files.internal("skins/inGameButtons.atlas")));
jumpButtonSkin = new Skin(Gdx.files.internal("skins/jumpButton.json"), new TextureAtlas(Gdx.files.internal("skins/inGameButtons.atlas")));
slideButtonSkin = new Skin(Gdx.files.internal("skins/slideButton.json"), new TextureAtlas(Gdx.files.internal("skins/inGameButtons.atlas")));
leftButton = new ImageButton(leftButtonSkin);
rightButton = new ImageButton(rightButtonSkin);
jumpButton = new ImageButton(jumpButtonSkin);
slideButton = new ImageButton(slideButtonSkin);
leftButton.setSize(2.35F, 5F);
rightButton.setSize(2.35F, 5F);
jumpButton.setSize(2.35F, 5F);
slideButton.setSize(2.35F, 5F);
leftButton.setPosition(1.25F, -0.5F);
rightButton.setPosition(4.0F, -0.5F);
jumpButton.setPosition(12.75F, -0.5F);
slideButton.setPosition(10F, -0.5F);
addActor(leftButton);
addActor(rightButton);
addActor(jumpButton);
addActor(slideButton);
leftButton.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
left = true;
}
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
left = false;
}
});
rightButton.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
right = true;
}
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
right = false;
}
});
jumpButton.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
hero.jump();
}
});
slideButton.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
hero.dodge();
}
});
}
//Rest of the code
}
Nothing special until now. But you might have noticed that our class now also implements
" ContactListener ". Insert all the new functions. Basically, we now know when a collision inflicts and with the help of our " BodyMisc " class we are able to know which bodies collided.
package com.getest.game.stages;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.getest.game.actors.GroundActor;
import com.getest.game.actors.HeroActor;
import com.getest.game.actors.LeftWallActor;
import com.getest.game.actors.RightWallActor;
import com.getest.game.camera.AndroidCamera;
import com.getest.game.misc.WorldMisc;
import com.getest.game.misc.BodyMisc;
public class GameStage extends Stage implements ContactListener{
private float accumulator, TIME_STEP;
private Box2DDebugRenderer renderer;
private WorldMisc wrl;
private World world;
private GroundActor ground;
private LeftWallActor leftWall;
private RightWallActor rightWall;
private HeroActor hero;
private Boolean right = false, left = false;
private Skin leftButtonSkin, rightButtonSkin, jumpButtonSkin, slideButtonSkin;
private ImageButton leftButton, rightButton, jumpButton, slideButton;
public GameStage(){
super(new ExtendViewport(16f, 9f, new AndroidCamera(16f, 9f)));
accumulator = 0.0F;
TIME_STEP = 1/300F; // Recomended by libgdx
setupWorld();
}
//Rest of the code
@Override
public void beginContact(Contact contact) {
Body a = contact.getFixtureA().getBody();
Body b = contact.getFixtureB().getBody();
if((BodyMisc.bodyIsHero(a) && (BodyMisc.bodyIsGround(b) )) || ((BodyMisc.bodyIsGround(a) && (BodyMisc.bodyIsHero(b))))) {
hero.landed();
}
}
@Override
public void endContact(Contact contact) {
}
@Override
public void preSolve(Contact contact, Manifold oldManifold) {
}
@Override
public void postSolve(Contact contact, ContactImpulse impulse) {
}
}
With the help of these 4 functions you can manipulate a collision in a very efficient way. For now, we only need the beginContact which is called when two fixtures begin to touch. You can see the final version of the " GameStage " class below.
package com.getest.game.stages;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.getest.game.actors.GroundActor;
import com.getest.game.actors.HeroActor;
import com.getest.game.actors.LeftWallActor;
import com.getest.game.actors.RightWallActor;
import com.getest.game.camera.AndroidCamera;
import com.getest.game.misc.WorldMisc;
import com.getest.game.misc.BodyMisc;
public class GameStage extends Stage implements ContactListener{
private float accumulator, TIME_STEP;
private Box2DDebugRenderer renderer;
private WorldMisc wrl;
private World world;
private GroundActor ground;
private LeftWallActor leftWall;
private RightWallActor rightWall;
private HeroActor hero;
private Boolean right = false, left = false;
private Skin leftButtonSkin, rightButtonSkin, jumpButtonSkin, slideButtonSkin;
private ImageButton leftButton, rightButton, jumpButton, slideButton;
public GameStage(){
super(new ExtendViewport(16f, 9f, new AndroidCamera(16f, 9f)));
accumulator = 0.0F;
TIME_STEP = 1/300F; // Recomended by libgdx
setupWorld();
}
private void setupWorld() {
wrl = new WorldMisc();
world = wrl.createWorld();
renderer = new Box2DDebugRenderer();
Gdx.input.setInputProcessor(this);
world.setContactListener(this);
setupGround();
setupLeftWall();
setupRightWall();
setupHero();
setupButtons();
}
private void setupGround(){
ground = new GroundActor(wrl.createGround(world));
addActor(ground);
}
private void setupLeftWall(){
leftWall = new LeftWallActor(wrl.createLeftWall(world));
addActor(leftWall);
}
private void setupRightWall(){
rightWall = new RightWallActor(wrl.createRightWall(world));
addActor(rightWall);
}
private void setupHero() {
hero = new HeroActor(wrl.createHero(world));
addActor(hero);
}
private void setupButtons() {
leftButtonSkin = new Skin(Gdx.files.internal("skins/leftButton.json"), new TextureAtlas(Gdx.files.internal("skins/inGameButtons.atlas")));
rightButtonSkin = new Skin(Gdx.files.internal("skins/rightButton.json"), new TextureAtlas(Gdx.files.internal("skins/inGameButtons.atlas")));
jumpButtonSkin = new Skin(Gdx.files.internal("skins/jumpButton.json"), new TextureAtlas(Gdx.files.internal("skins/inGameButtons.atlas")));
slideButtonSkin = new Skin(Gdx.files.internal("skins/slideButton.json"), new TextureAtlas(Gdx.files.internal("skins/inGameButtons.atlas")));
leftButton = new ImageButton(leftButtonSkin);
rightButton = new ImageButton(rightButtonSkin);
jumpButton = new ImageButton(jumpButtonSkin);
slideButton = new ImageButton(slideButtonSkin);
leftButton.setSize(2.35F, 5F);
rightButton.setSize(2.35F, 5F);
jumpButton.setSize(2.35F, 5F);
slideButton.setSize(2.35F, 5F);
leftButton.setPosition(1.25F, -0.5F);
rightButton.setPosition(4.0F, -0.5F);
jumpButton.setPosition(12.75F, -0.5F);
slideButton.setPosition(10F, -0.5F);
addActor(leftButton);
addActor(rightButton);
addActor(jumpButton);
addActor(slideButton);
leftButton.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
left = true;
}
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
left = false;
}
});
rightButton.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
right = true;
}
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
right = false;
}
});
jumpButton.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
hero.jump();
}
});
slideButton.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
hero.dodge();
}
});
}
@Override
public void act(float delta) {
super.act(delta);
accumulator += delta;
while (accumulator >= delta) {
world.step(TIME_STEP, 8, 4);
accumulator -= TIME_STEP;
}
if (left) {
hero.moveLeft();
} else if (right) {
hero.moveRight();
} else {
hero.moveStop();
}
}
@Override
public void draw() {
super.draw();
renderer.render(world, getViewport().getCamera().combined);
}
@Override
public void beginContact(Contact contact) {
Body a = contact.getFixtureA().getBody();
Body b = contact.getFixtureB().getBody();
if((BodyMisc.bodyIsHero(a) && (BodyMisc.bodyIsGround(b) )) || ((BodyMisc.bodyIsGround(a) && (BodyMisc.bodyIsHero(b))))) {
hero.landed();
}
}
@Override
public void endContact(Contact contact) {
}
@Override
public void preSolve(Contact contact, Manifold oldManifold) {
}
@Override
public void postSolve(Contact contact, ContactImpulse impulse) {
}
}
Ruuuuun the game, move, jump, slide, do whatever you want!
Feel free to ask any questions in the comments below.
Comments
Post a Comment