即使插值,固定时间步长卡顿?

时间:2017-07-27 05:52:29

标签: java opengl lwjgl

首先让我描述口吃的意思。当玩家移动时,它看起来好像向前移动一点,然后回到它应该的位置并继续这样做。我正在为lwjgl3中的学习目的制作一个小游戏,我使用JOML作为我的数学库。我实现了一个固定的时间步长循环(FPS = 60和UPS = 30),我使用插值来尝试平滑我的玩家运动。它有时效果很好(虽然不像我想要的那样平滑),但有时候它就像没有它一样的结果。有想法该怎么解决这个吗?我正确地进行插值吗?

游戏循环:

@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;
        }
    }
}

计时器:

    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 个答案:

答案 0 :(得分:1)

您的&#34;固定时间步骤&#34;并不像你想象的那么顺利。
此代码:

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

可以以10000000Hz或0.1Hz运行,具体取决于gameApp.update()执行的时间。

修改:您无法确保timer.getDelta()每次调用时的值大致相同。同样适用于accumulator,这也取决于上次-=interval来电后的剩余价值,但每次都以不同的delta开头。
操作系统可以花费更多的时间来完成自己的过程,从而延迟你的操作。有时你的基于度量的时间步长可能会正常运行,而下一秒它会暂停几毫秒,足以搞乱这些措施。
另外,请注意向GPU发送命令并不能保证它们能够立即得到处理;也许它们会累积起来,然后连续运行。

如果您希望每M毫秒执行一些代码(例如,60 FPS为16.6ms),则使用计时器 scheduleAtFixedRate()。见this

您必须处理的下一个问题是渲染必须在比固定步骤更短的时间内完成,否则会出现一些延迟。为实现这一目标,只需向GPU发送大部分数据(顶点,纹理等)。并且对于每个帧渲染,仅发送更新的数据(摄像机位置,或仅发送几个对象)。