After creating animations for our enemies, it is time to do exactly the same for our hero. We will use 32 frames for the walking animation and for the idle, the jump and the slide states we will be using 3 frames (1 frame for each one of these movements). We will choose those frames from the initial 32 frames of our walking animation.
Animation with LibGDX
Remember the pink evil guy from Black Dodger? Well, he decided that he didn't want to be a bad guy anymore and that he wanted his own leading role. Download the packed frames.
Hero frames |
You might see less than 32 frames, but that is just because we are going to use some frames more than one time. Name it " hero.png " and also create a " hero.atlas " file with the below lines of code and place both files in the " inGame " folder that we have previously created in our assets folder.
hero.png size: 2048,256 format: RGBA8888 filter: Linear,Linear repeat: none 10 rotate: false xy: 860, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 11 rotate: false xy: 938, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 12 rotate: false xy: 1016, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 13 rotate: false xy: 1094, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 18 rotate: false xy: 1172, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 2 rotate: false xy: 1172, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 19 rotate: false xy: 1250, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 3 rotate: false xy: 1250, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 22 rotate: false xy: 2, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 6 rotate: false xy: 2, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 23 rotate: false xy: 80, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 24 rotate: false xy: 158, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 25 rotate: false xy: 236, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 26 rotate: false xy: 314, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 27 rotate: false xy: 392, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 28 rotate: false xy: 470, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 29 rotate: false xy: 548, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 30 rotate: false xy: 704, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 14 rotate: false xy: 704, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 31 rotate: false xy: 782, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 15 rotate: false xy: 782, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 32 rotate: false xy: 626, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 1 rotate: false xy: 626, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 16 rotate: false xy: 626, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 17 rotate: false xy: 626, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 4 rotate: false xy: 1328, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 20 rotate: false xy: 1328, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 5 rotate: false xy: 1406, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 21 rotate: false xy: 1406, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 7 rotate: false xy: 1484, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 8 rotate: false xy: 1562, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1 9 rotate: false xy: 1640, 2 size: 76, 180 orig: 76, 180 offset: 0, 0 index: -1
Now, go to our " HeroActor " class and modify it to be like this;
package com.getest.game.actors;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
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 boolean runningRight = false, runningLeft = false;
private boolean right = true, left = false;
private Vector2 heroPosition;
private Timer timer1 = new Timer();
private float stateTimeRunningRight, stateTimeRunningLeft;
private String SLIDE, JUMP, IDLE;
private String [] RUNNING;
private TextureAtlas heroMovement;
private TextureRegion slideFrame, jumpFrame, idleFrame;
private Animation<TextureRegion> runningAnimation;
public HeroActor(Body body) {
super(body);
RUNNING = new String[] {"1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"};
SLIDE = "8";
JUMP = "8";
IDLE = "1";
heroMovement = new TextureAtlas("inGame/hero.atlas");
TextureRegion[] runningFrames = new TextureRegion[RUNNING.length];
for (int i = 0; i < RUNNING.length ; i++){
String pathRunning = RUNNING[i];
runningFrames[i] = heroMovement.findRegion(pathRunning);
}
runningAnimation = new Animation<TextureRegion>(0.03125f,runningFrames);
slideFrame = heroMovement.findRegion(SLIDE);
idleFrame = heroMovement.findRegion(IDLE);
jumpFrame = heroMovement.findRegion(JUMP);
stateTimeRunningRight = 0f;
stateTimeRunningLeft = 0f;
}
public HeroUserData getUserData() {
return (HeroUserData) userData;
}
public void moveRight() {
runningRight = true;
right = true;
velocityEachTime = body.getLinearVelocity();
velocityX = velocityEachTime.x;
finalVel = getUserData().getSpeedImpulse() - velocityX;
movingLinearImpulse = new Vector2(finalVel, 0.0F);
body.applyLinearImpulse(movingLinearImpulse, body.getWorldCenter(), true);
}
public void moveLeft() {
runningLeft = true;
right = false;
velocityEachTime = body.getLinearVelocity();
velocityX = velocityEachTime.x;
finalVel = -getUserData().getSpeedImpulse() - velocityX;
movingLinearImpulse = new Vector2(finalVel, 0.0F);
body.applyLinearImpulse(movingLinearImpulse, body.getWorldCenter(), true);
}
public void moveStop() {
runningRight = false;
runningLeft = false;
velocityEachTime = body.getLinearVelocity();
velocityX = velocityEachTime.x;
finalVel = 0.0F - velocityX;
movingLinearImpulse = new Vector2(finalVel, 0.0F);
body.applyLinearImpulse(movingLinearImpulse, body.getWorldCenter(), true);
}
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());
}
}
public void act(float delta) {
super.act(delta);
}
@Override
public void draw(Batch batch, float parentAlpha) {
super.draw(batch, parentAlpha);
if (runningRight && !jumping && !dodging){
batch.draw(runningAnimation.getKeyFrame(stateTimeRunningRight,true),screenRectangle.x-screenRectangle.width/2F +1.2F,screenRectangle.y - screenRectangle.height/2+0.75F, -1.1F*0.675F,1.1F*1.6F);
stateTimeRunningRight += Gdx.graphics.getDeltaTime(); }
else if(runningLeft && !jumping && !dodging){
batch.draw(runningAnimation.getKeyFrame(stateTimeRunningLeft,true),screenRectangle.x-screenRectangle.width/2F +0.4F,screenRectangle.y - screenRectangle.height/2+0.75F,1.1F*0.675F,1.1F*1.6F);
stateTimeRunningLeft += Gdx.graphics.getDeltaTime();
}
else if (right && !runningRight && !jumping && !dodging){
stateTimeRunningRight = 0;
stateTimeRunningLeft = 0;
batch.draw(idleFrame,screenRectangle.x-screenRectangle.width/2F +1.2F,screenRectangle.y - screenRectangle.height/2+0.75F, -1.1F*0.675F,1.1F*1.6F);
}
else if (!right && !runningLeft && !jumping && !dodging){
stateTimeRunningRight = 0;
stateTimeRunningLeft = 0;
batch.draw(idleFrame,screenRectangle.x-screenRectangle.width/2F +0.4F,screenRectangle.y - screenRectangle.height/2+0.75F,1.1F*0.675F,1.1F*1.6F);
}
else if(right && jumping){
stateTimeRunningRight = 0;
stateTimeRunningLeft = 0;
batch.draw(jumpFrame,screenRectangle.x-screenRectangle.width/2F +1.2F,screenRectangle.y - screenRectangle.height/2+0.75F, -1.1F*0.675F,1.1F*1.6F);
}
else if(!right && jumping){
stateTimeRunningRight = 0;
stateTimeRunningLeft = 0;
batch.draw(jumpFrame,screenRectangle.x-screenRectangle.width/2F +0.4F,screenRectangle.y - screenRectangle.height/2+0.75F,1.1F*0.675F,1.1F*1.6F);
}
else if(right && dodging){
stateTimeRunningRight = 0;
stateTimeRunningLeft = 0;
batch.draw(slideFrame,screenRectangle.x-screenRectangle.width/2F +0.55F,screenRectangle.y - screenRectangle.height/2+1.4F,0.675F/2,1.6F/2, -1.1F*0.675F,1.1F*1.6F,1, 1, 90);
}
else if(!right && dodging){
stateTimeRunningRight = 0;
stateTimeRunningLeft = 0;
batch.draw(slideFrame,screenRectangle.x-screenRectangle.width/2F +0.4F,screenRectangle.y - screenRectangle.height/2+0.75F,0.675F/2,1.6F/2,1.1F*0.675F,1.1F*1.6F,1, 1, -90);
}
}
}
This might look confusing at first, so lets break it down in smaller pieces.
" What have we done extra? "
First of all we have created the animation, following exactly the same method as in the tutorial in which we created the animation for the enemies.
Then, in the previous code, we added 4 more boolean variables, the " runningRight ",
" runningLeft ", " left " and " right " variables, because we wanted a way to define in which direction the hero is looking and what movement he is actually doing.
Finally, we added the act method and we overrode the draw method to actually draw the animation. For the idle state we used only the first frame of the walking animation and for the jumping and the sliding movement we used the 8th frame of the walking animation (because the hero has his legs open it looks more like jumping).
Because in the original animation the hero looks at the left side, we just mirrored it for all the movements in which the hero looks at the right side - meaning we just used a negative width - and for the slide, we also rotated the frame. You might see crazy numbers for the width and the height, but those are just calibrations i made in order for the hero to fit in the rectangle. We couldn't just use the rectangles width and height, because the shape of our hero differs from that, so we had to calibrate it, based on the original width and height of the frame. Another way of course is to create your hero based on the bodies width and height or the exact opposite, but for some reason i didn't do that. Feel free to take a look here, if you want any further explanation about the draw's arguments.
One more think i have to clarify is the usage of the state time variables. Those variables actually define in which frame our animation is. For example at time 0 sec, we are in the first frame and at time 0.03125 sec, we are in the second frame, thus, is a must to make those variables equal to zero when the type of movement changes. for example from walking to jumping. Otherwise, if we start moving again, then the animation will start from where it previously stopped.
Run the game!
What else is left? Well, we have to fix our time step for the simulation to be even better, we must manage our assets in a more efficient way using libGDX's asset manager and we could also use the free physics body editor tool to create more accurate bodies for our enemies, in order for the collision to be more accurate. Finally we will add ads to our game.
That's it for today's tutorial, feel free to ask any questions in the comments below;
The hero image and atlas are mis-matched sizes, like happened with the buttons. They are the correct aspect ratio, so just a simple image resize fixes it.
ReplyDeleteThanks for the contribution once again! How is your game running up until now?
DeleteEverything is working according to your plan, except a few things I changed like making slide/dodge aware of the direction the hero is facing and letting him continue to slide in his direction of movement. I installed and played a little Black Dodger. It played some ads, so I hope you get at least some small payment for that. I'm working on Asset Management now.
DeleteGreat! That's the point -giving your own touch to it-. Hope you liked it.
Delete