1
\$\begingroup\$

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;
        }
    }
```
\$\endgroup\$
2
  • \$\begingroup\$ I see two different calls to camera.update() - one in updateGameLogic() on a fixed time step, and one in renderGame() on a variable time step. Is this intentional? \$\endgroup\$ Commented Oct 7 at 4:43
  • \$\begingroup\$ I use something similar to your version 2 with a STEP_TIME = 5 and it seems to work fine. Maybe your STEP_TIME is too low so you are always running logic and render once per frame. Have you count the number of iteration in your while loop for each render pass on your two systems? \$\endgroup\$ Commented Oct 7 at 15:11

1 Answer 1

2
\$\begingroup\$

The solution was pretty obvious. I had two world.step() in different methods instead of just one.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.