Fixed time step stutter even with interpolation?

Viewed 595

First let me describe what I mean by stutter. When the player moves it looks as if it moves forward a little then back to where it should be and keeps doing it. I am making a small game for learning purposes in lwjgl3 and I am using JOML as my math library. I implemented a fixed time step loop (FPS = 60 and UPS = 30) and I use interpolation to try and smooth my player movement. It works nicely sometimes (not as smooth as I want it though) but other times its just as stuttery as without it. Any ideas on how to fix this? Am I doing the interpolation correctly?

Game Loop:

@Override
public void run() {
    window.init("Game", 1280, 720);
    GL.createCapabilities();

    gameApp.init();

    timer.init();

    float delta;
    float accumulator = 0f;
    float interval = 1f / Settings.TARGET_UPS;
    float alpha;

    while (running) {
        delta = timer.getDelta();
        accumulator += delta;

        gameApp.input();

        while (accumulator >= interval) {
            gameApp.update();
            timer.updateUPS();
            accumulator -= interval;
        }

        alpha = accumulator / interval;

        gameApp.render(alpha);
        timer.updateFPS();
        timer.update();
        window.update();

        if (Settings.SHOW_PERFORMANCE) {
            System.out.println("FPS: " + timer.getFPS() + " UPS: " + timer.getUPS());
        }

        if (window.windowShouldClose()) {
            running = false;
        }
    }

    gameApp.cleanUp();
    window.cleanUp();
}

SpriteRenderer:

public class SpriteRenderer {

    public StaticShader staticShader;

    public SpriteRenderer(StaticShader staticShader, Matrix4f projectionMatrix) {
        this.staticShader = staticShader;
        staticShader.start();
        staticShader.loadProjectionMatrix(projectionMatrix);
        staticShader.stop();
    }

    public void render(Map<TexturedMesh, List<Entity>> entities, float alpha) {
        for (TexturedMesh mesh : entities.keySet()) {
            prepareTexturedMesh(mesh);
            List<Entity> batch = entities.get(mesh);
            for (Entity entity : batch) {

                Vector2f spritePos = entity.getSprite().getTransform().getPosition();
                Vector2f playerPos = entity.getTransform().getPosition();
                spritePos.x = playerPos.x * alpha + spritePos.x * (1.0f - alpha);
                spritePos.y = playerPos.y * alpha + spritePos.y * (1.0f - alpha);

                prepareInstance(entity.getSprite());
                GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, entity.getSprite().getTexturedMesh().getMesh().getVertexCount());
            }
            unbindTexturedMesh();
        }
    }

    private void unbindTexturedMesh() {
        GL20.glDisableVertexAttribArray(0);
        GL20.glDisableVertexAttribArray(1);
        GL30.glBindVertexArray(0);
    }

    private void prepareInstance(Sprite sprite) {
        Transform spriteTransform = sprite.getTransform();
        Matrix4f modelMatrix = Maths.createModelMatrix(spriteTransform.getPosition(), spriteTransform.getScale(), spriteTransform.getRotation());
        staticShader.loadModelMatrix(modelMatrix);
    }

    private void prepareTexturedMesh(TexturedMesh texturedMesh) {
        Mesh mesh = texturedMesh.getMesh();
        mesh.getVao().bind();
        GL20.glEnableVertexAttribArray(0);
        GL20.glEnableVertexAttribArray(1);

        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        texturedMesh.getTexture().bind();
    }
}

EntityPlayer:

public class EntityPlayer extends Entity {

    private float xspeed = 0;
    private float yspeed = 0;

    private final float SPEED = 0.04f;

    public EntityPlayer(Sprite sprite, Vector2f position, Vector2f scale, float rotation) {
        super(sprite, position, scale, rotation);
        this.getSprite().getTransform().setPosition(position);
        this.getSprite().getTransform().setScale(scale);
        this.getSprite().getTransform().setRotation(rotation);
    }

    @Override
    public void update() {
        this.getTransform().setPosition(new Vector2f(this.getTransform().getPosition().x += xspeed, this.getTransform().getPosition().y += yspeed));
    }

    public void input() {
        if (KeyboardHandler.isKeyDown(GLFW.GLFW_KEY_RIGHT)) {
            xspeed = SPEED;
        } else if (KeyboardHandler.isKeyDown(GLFW.GLFW_KEY_LEFT)) {
            xspeed = -SPEED;
        } else {
            xspeed = 0;
        }

        if (KeyboardHandler.isKeyDown(GLFW.GLFW_KEY_UP)) {
            yspeed = SPEED;
        } else if (KeyboardHandler.isKeyDown(GLFW.GLFW_KEY_DOWN)) {
            yspeed = -SPEED;
        } else {
            yspeed = 0;
        }
    }
}

Timer:

    public class Timer {

    private double lastLoopTime;
    private float timeCount;
    private int fps;
    private int fpsCount;
    private int ups;
    private int upsCount;

    public void init() {
        lastLoopTime = getTime();
    }

    public double getTime() {
        return GLFW.glfwGetTime();
    }

    public float getDelta() {
        double time = getTime();
        float delta = (float) (time - lastLoopTime);
        lastLoopTime = time;
        timeCount += delta;
        return delta;
    }

    public void updateFPS() {
        fpsCount++;
    }

    public void updateUPS() {
        upsCount++;
    }

    // Update the FPS and UPS if a whole second has passed
    public void update() {
        if (timeCount > 1f) {
            fps = fpsCount;
            fpsCount = 0;

            ups = upsCount;
            upsCount = 0;

            timeCount -= 1f;
        }
    }

    public int getFPS() {
        return fps > 0 ? fps : fpsCount;
    }

    public int getUPS() {
        return ups > 0 ? ups : upsCount;
    }

    public double getLastLoopTime() {
        return lastLoopTime;
    }
}
1 Answers
Related