I was making a 2d top-down game, but when I tested it on two devices (Samsung M31 and Samsung S25) there was a problem with player speed - it was faster on S25 and slower on M31. I tried using some fixed steps and delta things, but nothing worked.
Thanks for any help!
Here's some code:
render version 1)
@Override
public void render(float delta) {
world.step(1f/60f, 6, 2);
updateGameLogic();
renderGame();
}
render version 2:
static final float STEP_TIME = 1f/300f;
float accumulator = 0;
...
@Override
public void render(float delta) {
accumulator += Math.min(delta, 0.25f);
while (accumulator >= STEP_TIME) {
accumulator -= STEP_TIME;
updateGameLogic();
world.step(STEP_TIME, 6, 2);
}
renderGame();
}
updateGameLogic()
private void updateGameLogic() {
if (!actMenu && !actVending)
position.lerp(new Vector2(player.getX(), player.getY()), 0.1f); // 0.1f - camera smoothing coefficient
camera.position.set(position, 0);
camera.update();
up = Gdx.input.isKeyPressed(Input.Keys.W) || Gdx.input.isKeyPressed(Input.Keys.UP);
down = Gdx.input.isKeyPressed(Input.Keys.S) || Gdx.input.isKeyPressed(Input.Keys.DOWN);
left = Gdx.input.isKeyPressed(Input.Keys.A) || Gdx.input.isKeyPressed(Input.Keys.LEFT);
right = Gdx.input.isKeyPressed(Input.Keys.D) || Gdx.input.isKeyPressed(Input.Keys.RIGHT);
attack = Gdx.input.isKeyPressed(Input.Keys.SPACE) || Gdx.input.isKeyPressed(Input.Keys.ENTER);
menu = Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE) || Gdx.input.isKeyJustPressed(Input.Keys.BACK);
menuX = position.x - SCR_WIDTH / 4;
menuY = position.y - SCR_HEIGHT / 3;
vendingX = position.x - SCR_WIDTH / 4;
vendingY = position.y - SCR_HEIGHT / 4;
// touch handler
touchHandler();
// events
if (deathTime == 0 && !actMenu && !actVending) {
player.changePhase();
changePhaseGhosts();
changePhaseZombies();
changePhaseWardens();
changePhaseAnimatedObstacles();
player.step(actJoystick);
player.updateProjectiles();
updateProjectilesWardens();
// player.updateMeleeRegion();
// meleeRegionUpdate();
updateGhosts();
updateZombies();
updateWardens();
updateDoors();
updateCoins();
updateChests();
updateLevel();
updateVending();
updateButtons();
}
}
renderGame():
private void renderGame() {
// draw
ScreenUtils.clear(0, 0, 0, 0);
// debugRenderer.render(world, camera.combined);
batch.setProjectionMatrix(camera.combined);
rayHandler.setCombinedMatrix(camera.combined, camera.position.x, camera.position.y, camera.viewportWidth, camera.viewportHeight);
camera.update();
batch.begin();
...
batch.end();
playerLight.attachToBody(player.getBody());
rayHandler.updateAndRender();
destroyScheduledBodies();
touchHandler():
private void touchHandler() {
actJoystick = false;
actAttack = false;
float playerSpeed = player.getSpeed();
float playerDiagSpeed = playerSpeed / (float) Math.sqrt(2);
ArrayList<Vector3> touches = new ArrayList<>();
for (int i = 0; i < 3; i++) {
touches.add(new Vector3());
}
for (int i = 0; i < 3; i++) {
if (Gdx.input.isTouched(i)) {
touches.get(i).set(Gdx.input.getX(i), Gdx.input.getY(i), 0);
camera.unproject(touches.get(i));
if (touches.get(i).x < player.getX()) {
actJoystick = true;
joystick.updateKnob(touches.get(i));
if (player.isShopping()) {
player.setShopping(false);
}
} else if (touches.get(i).x > player.getX()) {
if (btnAttack.hit(touches.get(i).x, touches.get(i).y)) {
actAttack = true;
}
}
}
}
if (elevatorUseTime != 0 || actMenu || actVending) {
actJoystick = false;
actAttack = false;
}
if (actJoystick) {
Vector2 directionVector = joystick.getDirectionVector();
player.getBody().setLinearVelocity(
directionVector.x * player.getSpeed(),
directionVector.y * player.getSpeed()
);
if (Math.abs(directionVector.x) > Math.abs(directionVector.y)) {
if (directionVector.x > 0) player.setDirection('r');
else player.setDirection('l');
} else {
if (directionVector.y > 0) player.setDirection('u');
else player.setDirection('d');
}
} else {
joystick.resetKnob();
player.getBody().setLinearVelocity(0, 0);
}
if (actAttack || attack) player.attack();
if (menu) {
actMenu = !actMenu;
uiInput.setButtons(hudButtons);
if (actMenu) uiInput.setButtons(menuButtons);
}
if (up) {
player.setDirection('u');
player.getBody().setLinearVelocity(
player.getBody().getLinearVelocity().x,
playerSpeed
);
} else if (down) {
player.setDirection('d');
player.getBody().setLinearVelocity(
player.getBody().getLinearVelocity().x,
-playerSpeed
);
} else if (right) {
player.setDirection('r');
player.getBody().setLinearVelocity(
playerSpeed,
player.getBody().getLinearVelocity().y
);
} else if (left) {
player.setDirection('l');
player.getBody().setLinearVelocity(
-playerSpeed,
player.getBody().getLinearVelocity().y
);
}
if (up && right) {
player.setDirection('r');
player.getBody().setLinearVelocity(
playerDiagSpeed,
playerDiagSpeed
);
} else if (up && left) {
player.setDirection('l');
player.getBody().setLinearVelocity(
-playerDiagSpeed,
playerDiagSpeed
);
} else if (down && right) {
player.setDirection('r');
player.getBody().setLinearVelocity(
playerDiagSpeed,
-playerDiagSpeed
);
} else if (down && left) {
player.setDirection('l');
player.getBody().setLinearVelocity(
-playerDiagSpeed,
-playerDiagSpeed
);
}
}
Entity:
public class Entity {
private Body body;
private float width, height;
private float speed = 50f;
protected int health, maxHealth;
protected char direction = 'd';
private int phase, nPhases;
protected long timeLastPhase, timePhaseInterval, timeBasePhaseInterval;
private boolean isAlive;
protected boolean isBattle;
public Entity(World world, float width, float height, float x, float y, int maxHealth, int nPhases, long timeBasePhaseInterval) {
phase = 0;
this.nPhases = nPhases;
this.timeBasePhaseInterval = timeBasePhaseInterval; // except running animation!
this.timePhaseInterval = timeBasePhaseInterval;
this.width = width;
this.height = height;
this.health = this.maxHealth = maxHealth;
this.isAlive = true;
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(x, y);
bodyDef.fixedRotation = true;
body = world.createBody(bodyDef);
PolygonShape shape = new PolygonShape();
shape.setAsBox(this.width / 2, this.height / 2);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.4f;
body.createFixture(fixtureDef);
shape.dispose();
}
protected void changePhase(){
if (isBattle) timePhaseInterval = timeBasePhaseInterval-250;
else timePhaseInterval = timeBasePhaseInterval;
if(TimeUtils.millis() > timeLastPhase+timePhaseInterval) {
if (++phase == nPhases) phase = 0;
timeLastPhase = TimeUtils.millis();
}
}
public void hit(int damage) {
health-=damage;
if (health<=0) {
isAlive = false;
health = 0;
}
}
```
camera.update()- one inupdateGameLogic()on a fixed time step, and one inrenderGame()on a variable time step. Is this intentional? \$\endgroup\$