For my A level computing project in school I have been creating a 2D Plat-former which is similar in style to the Mega-Man games. So far I have gotten some menus and player methods working like collision detection, movement, and animation. All my player animations are in a single sprite sheet that vary in size so they're not all equal (79x136, 118x128, 88x136, etc.).
This is my specific problem:
However when playing the game and watching the animations by moving and jumping, it seems like the dimensions of the animations are being stretched or compressed into a specific size, which I believe is 118x140.
I say this because one of the still animations, for when the player is standing still, is displayed correctly in its proper size, and matches this resolution, and it seems like all the other animations are trying to fit into the same dimensions.
I've had a look around the internet and this site to see if someone had a similar issue but none seem to directly relate to this problem, hence why I'm asking for a solution to this.
This is the code used in my Player.java file, where all aspects of my player are defined:
package com.ultimate.robot.entities;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
import com.badlogic.gdx.math.Vector2;
public class Player extends Sprite implements InputProcessor {
/** the movement velocity */
private Vector2 velocity = new Vector2();
private float speed = 240 * 2, gravity = 180 * 1.8f, animationTime = 0;
private boolean canJump;
private Animation still, left, right, jump, fall;
private TiledMapTileLayer collisionLayer;
private String blockedKey = "blocked";
public Player(Animation still, Animation left, Animation right,
Animation jump, Animation fall, TiledMapTileLayer
collisionLayer) {
super(still.getKeyFrame(0));
this.still = still;
this.left = left;
this.right = right;
this.jump = jump;
this.fall = fall;
this.collisionLayer = collisionLayer;
}
public void draw(Batch spriteBatch) {
update(Gdx.graphics.getDeltaTime());
super.draw(spriteBatch);
}
public void update(float delta) {
// apply gravity
velocity.y -= gravity * delta;
// clamp velocity
if(velocity.y > speed)
velocity.y = speed;
else if(velocity.y < -speed)
velocity.y = -speed;
// save old position
float oldX = getX(), oldY = getY();
boolean collisionX = false, collisionY = false;
// move on x
setX(getX() + velocity.x * delta);
if(velocity.x < 0) // going left
collisionX = collidesLeft();
else if(velocity.x > 0) // going right
collisionX = collidesRight();
// react to x collision
if(collisionX) {
setX(oldX);
velocity.x = 0;
}
// move on y
setY(getY() + velocity.y * delta * 5f);
if(velocity.y < 0) // going down
canJump = collisionY = collidesBottom();
else if(velocity.y > 0) // going up
collisionY = collidesTop();
// react to y collision
if(collisionY) {
setY(oldY);
velocity.y = 0;
}
// update animation
animationTime += delta;
setRegion(velocity.x < 0 ? left.getKeyFrame(animationTime) :
velocity.x > 0 ? right.getKeyFrame(animationTime) :
velocity.y > 0 ? jump.getKeyFrame(animationTime) :
velocity.y < 0 ? fall.getKeyFrame(animationTime) :
still.getKeyFrame(animationTime));
}
private boolean isCellBlocked(float x, float y) {
Cell cell = collisionLayer.getCell(
(int) (x / collisionLayer.getTileWidth()),
(int) (y / collisionLayer.getTileHeight()));
return cell != null && cell.getTile() != null
&& cell.getTile().getProperties().containsKey(blockedKey);
}
public boolean collidesRight() {
for(float step = 0; step < getHeight();
step += collisionLayer.getTileHeight() / 2)
if(isCellBlocked(getX() + getWidth(), getY() + step))
return true;
return false;
}
public boolean collidesLeft() {
for(float step = 0; step < getHeight();
step += collisionLayer.getTileHeight() / 2)
if(isCellBlocked(getX(), getY() + step))
return true;
return false;
}
public boolean collidesTop() {
for(float step = 0; step < getWidth();
step += collisionLayer.getTileWidth() / 2)
if(isCellBlocked(getX() + step, getY() + getHeight()))
return true;
return false;
}
public boolean collidesBottom() {
for(float step = 0; step < getWidth();
step += collisionLayer.getTileWidth() / 2)
if(isCellBlocked(getX() + step, getY()))
return true;
return false;
}
public Vector2 getVelocity() {
return velocity;
}
public void setVelocity(Vector2 velocity) {
this.velocity = velocity;
}
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
this.speed = speed;
}
public float getGravity() {
return gravity;
}
public void setGravity(float gravity) {
this.gravity = gravity;
}
public TiledMapTileLayer getCollisionLayer() {
return collisionLayer;
}
public void setCollisionLayer(TiledMapTileLayer collisionLayer) {
this.collisionLayer = collisionLayer;
}
@Override
public boolean keyDown(int keycode) {
switch(keycode) {
case Keys.SPACE:
if(canJump) {
velocity.y = speed / 3f;
canJump = false;
animationTime = 0;
}
break;
case Keys.LEFT:
velocity.x = -speed;
animationTime = 0;
break;
case Keys.RIGHT:
velocity.x = speed;
animationTime = 0;
}
return true;
}
@Override
public boolean keyUp(int keycode) {
switch(keycode) {
case Keys.LEFT:
case Keys.RIGHT:
velocity.x = 0;
animationTime = 0;
}
return true;
}
@Override
public boolean keyTyped(char character) {
return false;
}
@Override
public boolean touchDown(int screenX, int screenY,
int pointer, int button) {
return false;
}
@Override
public boolean touchUp(int screenX, int screenY,
int pointer, int button) {
return false;
}
@Override
public boolean touchDragged(int screenX, int screenY,
int pointer) {
return false;
}
@Override
public boolean mouseMoved(int screenX, int screenY) {
return false;
}
@Override
public boolean scrolled(int amount) {
return false;
}
}
this next code is used in the playersheet.sprites file, which acts as the player's texture atlas and contains the regions:
playersheet.png
format: RGBA8888
filter: Nearest,Nearest
repeat: none
playerstill
rotate: false
xy: 363, 272
size: 118, 140
orig: 59, 70
offset: 0, 0
index: -1
playerstill
rotate: false
xy: 867, 152
size: 118, 128
orig: 59, 64
offset: 0, 0
index: -1
playerrun
rotate: false
xy: 688, 276
size: 79, 136
orig: 39, 68
offset: 0, 0
index: -1
playerrun
rotate: false
xy: 481, 272
size: 92, 140
orig: 46, 70
offset: 0, 0
index: -1
playerrun
rotate: false
xy: 767, 280
size: 132, 132
orig: 66, 66
offset: 0, 0
index: -1
playerrun
rotate: false
xy: 644, 140
size: 88, 136
orig: 44, 68
offset: 0, 0
index: -1
playerrun
rotate: false
xy: 422, 132
size: 98, 140
orig: 49, 70
offset: 0, 0
index: -1
playerrun
rotate: false
xy: 732, 144
size: 135, 132
orig: 67, 66
offset: 0, 0
index: -1
playershoot
rotate: false
xy: 592, 0
size: 116, 136
orig: 58, 68
offset: 0, 0
index: -1
playerjump
rotate: false
xy: 0, 44
size: 74, 184
orig: 74, 184
offset: 0, 0
index: -1
playerjumpshoot
rotate: false
xy: 0, 228
size: 107, 184
orig: 107, 184
offset: 0, 0
index: -1
playerfall
rotate: false
xy: 186, 76
size: 108, 168
orig: 108, 168
offset: 0, 0
index: -1
playerfallshoot
rotate: false
xy: 107, 244
size: 124, 168
orig: 124, 168
offset: 0, 0
index: -1
playerrunshoot
rotate: false
xy: 294, 132
size: 128, 140
orig: 128, 140
offset: 0, 0
index: -1
playerrunshoot
rotate: false
xy: 573, 276
size: 115, 136
orig: 115, 136
offset: 0, 0
index: -1
playerrunshoot
rotate: false
xy: 294, 0
size: 150, 132
orig: 150, 132
offset: 0, 0
index: -1
playerrunshoot
rotate: false
xy: 520, 136
size: 124, 136
orig: 124, 136
offset: 0, 0
index: -1
playerrunshoot
rotate: false
xy: 231, 272
size: 132, 140
orig: 132, 140
offset: 0, 0
index: -1
playerrunshoot
rotate: false
xy: 444, 0
size: 148, 132
orig: 148, 132
offset: 0, 0
index: -1
playerblock
rotate: false
xy: 708, 8
size: 136, 132
orig: 136, 132
offset: 0, 0
index: -1
playerduck
rotate: false
xy: 899, 304
size: 96, 108
orig: 96, 108
offset: 0, 0
index: -1
playerhurt
rotate: false
xy: 844, 48
size: 136, 96
orig: 136, 96
offset: 0, 0
index: -1
playervictory
rotate: false
xy: 74, 48
size: 112, 180
orig: 112, 180
offset: 0, 0
index: -1
and finally this code is used in my LevelOne.java file, which contains all the elements for the level and creates the player:
package com.ultimate.robot.levels;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.audio.Music;
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.Sprite;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.ultimate.robot.entities.Player;
public class LevelOne implements Screen {
private TiledMap map;
private OrthogonalTiledMapRenderer renderer;
private OrthographicCamera camera;
private Music level1music;
private Sprite desert, blueSky;
private TextureAtlas playerAtlas;
private Player player;
@Override
public void show() {
map = new TmxMapLoader().load("maps/levelone.tmx");
playerAtlas = new TextureAtlas("Sprites/playersheet.sprites");
Animation still, left, right, jump, fall;
still = new Animation(1/2f, playerAtlas.findRegions("playerstill"));
left = new Animation(1 /6f, playerAtlas.findRegions("playerrun"));
right = new Animation(1 /6f, playerAtlas.findRegions("playerrun"));
jump = new Animation(1 /6f, playerAtlas.findRegions("playerjump"));
fall = new Animation(1 /6f, playerAtlas.findRegions("playerfall"));
still.setPlayMode(Animation.PlayMode.LOOP);
left.setPlayMode(Animation.PlayMode.LOOP);
right.setPlayMode(Animation.PlayMode.LOOP);
jump.setPlayMode(Animation.PlayMode.NORMAL);
fall.setPlayMode(Animation.PlayMode.NORMAL);
player = new Player(still, left, right, jump, fall,
(TiledMapTileLayer) map.getLayers().get(0));
player.setPosition(5 * player.getCollisionLayer().getTileWidth(),
4 * player.getCollisionLayer().getTileHeight());
// setting camera
camera = new OrthographicCamera();
// creating background
desert = new Sprite(new Texture(
Gdx.files.internal("Backgrounds/desert.png")));
blueSky = new Sprite(new Texture(
Gdx.files.internal("Backgrounds/bluesky.png")));
// creating music
level1music = Gdx.audio.newMusic(
Gdx.files.internal("Music/level1.ogg"));
// playing music
level1music.setLooping(true);
level1music.play();
Gdx.input.setInputProcessor(player);
renderer = new OrthogonalTiledMapRenderer(map);
}
@Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
camera.position.set(player.getX() + player.getWidth() / 2,
player.getY() + player.getHeight() / 2, 0);
camera.update();
renderer.setView(camera);
renderer.getBatch().begin();
renderer.getBatch().draw(blueSky, 0, 0);
renderer.getBatch().draw(desert, 0, 0);
player.draw(renderer.getBatch());
renderer.getBatch().end();
renderer.render();
}
@Override
public void resize(int width, int height) {
camera.viewportWidth = width;
camera.viewportHeight = height;
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public void hide() {
dispose();
}
@Override
public void dispose() {
map.dispose();
renderer.dispose();
level1music.dispose();
playerAtlas.dispose();
desert.getTexture().dispose();
blueSky.getTexture().dispose();
}
}
I hope this can help find a solution.
Don't worry, I've managed to solve it using a custom Animation class whilst following another tutorial, it seems to fix the stretched and compressed animations. It creates a Texture Region which the animations have to fit in but I've set the size to the largest animation so they all fit within the region. I had to re-write a lot of my code but I'm fine with the end result.