我最近尝试进入游戏编程。我对Java很有经验,但对游戏编程却没有。我阅读了http://www.koonsolo.com/news/dewitters-gameloop/并使用以下代码实现了此处提出的游戏循环:

private static int UPDATES_PER_SECOND = 25;
private static int UPDATE_INTERVAL = 1000 / UPDATES_PER_SECOND * 1000000;
private static int MAX_FRAMESKIP = 5;

public void run() {
    while (true) {
        int skippedFrames = 0;
        while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) {
            this.updateGame();
            this.nextUpdate += UPDATE_INTERVAL;
            skippedFrames++;
        }

        long currentNanoTime = System.nanoTime();
        double interpolation = (currentNanoTime + UPDATE_INTERVAL - this.nextUpdate) / UPDATE_INTERVAL;
        this.repaintGame(interpolation);
    }
}

这个循环看起来很有希望且容易,但是现在我实际上正在尝试做一些事情,因此我不确定。
如果我没有完全误解,那么updateGame()会处理诸如计算位置,移动敌人,计算碰撞等等……的工作。由于没有将插值传递给updateGame(),这是否意味着我们假设每秒精确且稳定地调用updateGame()UPDATES_PER_SECOND?这是否意味着我们所有的计算都基于该假设?如果由于某种原因而导致对updateGame()的调用被延迟,这是否会给我们带来很多麻烦?
例如,如果我的角色 Sprite 应该向右走,并根据其在每个updateGame()上的速度移动它-如果要延迟该方法,那将意味着我们的计算已结束并且角色会滞后吗?

在网站上,说明了以下插值示例:



我知道这只是一个例子,但这是否会使车速取决于UPDATES_PER_SECOND呢?那么更多的UPS将意味着更快的汽车吗?这是不对的...?

任何帮助,教程,任何赞赏。

更新/解决方案

经过所有的帮助(谢谢!),这就是我当前正在使用的-到目前为止效果很好(用于移动角色 Sprite ),但是让我们等待真正复杂的游戏决定这一点。尽管如此,我还是想分享一下。
我的游戏循环现在看起来像这样:
private static int UPDATES_PER_SECOND = 25;
private static int UPDATE_INTERVAL = 1000 / UPDATES_PER_SECOND * 1000000;
private static int MAX_FRAMESKIP = 5;

private long nextUpdate = System.nanoTime();

public void run() {
    while (true) {
        int skippedFrames = 0;
        while (System.nanoTime() > this.nextUpdate && skippedFrames < MAX_FRAMESKIP) {
            long delta = UPDATE_INTERVAL;
            this.currentState = this.createGameState(delta);
            this.newPredictedNextState = this.createGameState(delta + UPDATE_INTERVAL, true);

            this.nextUpdate += UPDATE_INTERVAL;
            skippedFrames++;
        }

        double interpolation = (System.nanoTime() + UPDATE_INTERVAL - this.nextUpdate) / (double) UPDATE_INTERVAL;
        this.repaintGame(interpolation);
    }
}

如您所见,进行了一些更改:
  • delta = UPDATE_INTERVAL吗?是的。到目前为止,这是实验性的,但我认为它会起作用。问题是,一旦您实际上从两个时间戳计算一个增量,就会引入浮点计算错误。那些很小,但是考虑到您的更新被称为数百万次,它们加起来。而且由于第二个while循环可确保我们 catch 错过的更新(例如,渲染花费很长时间),因此我们可以确定每秒获得25个更新。最坏的情况:我们错过的不仅仅是MAX_FRAMESKIP更新-在这种情况下,更新将丢失并且游戏会滞后。就像我说的那样,仍然是实验性的。我可能会再次将其更改为实际的增量。
  • 预言下一个游戏状态?是的。 GameState是保存所有相关游戏信息的对象,渲染器将该对象传递给游戏以将其渲染到屏幕上。在我的情况下,我决定给渲染器两个状态:通常会传递给他的状态,其中包含当前游戏状态,以及一个预测的 future 状态,即UPDATE_INTERVAL。这样,渲染器可以使用插值轻松地在两者之间进行插值。计算 future 的游戏状态实际上非常容易-由于您的更新方法(createGameState())始终采用增量值,只需通过UPDATE_INTERVAL增加增量-这样就可以预测 future 的状态。当然,将来的状态假定用户输入等保持不变。如果不是这样,则下一次游戏状态更新将负责这些更改。
  • 其余部分几乎保持不变,并取自deWiTTERS游戏循环。如果硬件真的很慢,MAX_FRAMESKIP几乎是一种故障保护,以确保我们不时渲染某些内容。但是,如果这种情况发生了,我想我们无论如何都会有极端的滞后。插值与以前相同-但现在渲染器可以简单地在两个游戏状态之间插值,除了插值数字,它不需要任何逻辑。那很好!
    也许为了澄清,举个例子。这是我计算字符位置的方法(略有简化):
    public GameState createGameState(long delta, boolean ignoreNewInput) {
        //Handle User Input and stuff if ignoreNewInput=false
    
        GameState newState = this.currentState.copy();
        Sprite charSprite = newState.getCharacterSprite();
        charSprite.moveByX(charSprite.getMaxSpeed() * delta * charSprite.getMoveDirection().getX());
        //getMoveDirection().getX() is 1.0 when the right arrow key is pressed, otherwise 0.0
    }
    

    ...然后,在窗口的绘制方法中...
    public void paint(Graphics g) {
        super.paint(g);
    
        Graphics2D g2d = (Graphics2D) g;
    
        Sprite currentCharSprite = currentGameState.getCharacterSprite();
        Sprite nextCharSprite = predictedNextState.getCharacterSprite();
        Position currentPos = currentCharSprite.getPosition();
        Position nextPos = nextCharSprite.getPosition();
        //Interpolate position
        double x = currentPos.getX() + (nextPos.getX() - currentPos.getX()) * this.currentInterpolation;
        double y = currentPos.getY() + (nextPos.getY() - currentPos.getY()) * this.currentInterpolation;
        Position interpolatedCharPos = new Position(x, y);
        g2d.drawImage(currentCharSprite.getImage(), (int) interpolatedCharPos.getX(), (int) interpolatedCharPos.getY(), null);
    }
    

    最佳答案

    不要以更新间隔为常数的假设为基础来制定游戏逻辑。包括一个游戏时钟,该时钟可精确测量两次更新之间耗时。然后,您可以将所有计算都基于延迟,而不必担心实际更新速率。

    在这种情况下,汽车速度将以单位/秒为单位,而增量将是自上次更新以来的总秒数:

    car.position += car.velocity * delta;
    

    将游戏逻辑的更新与绘制框架分开是一种常见的做法。就像您说的那样,通过不时地跳过渲染帧,可以保持稳定的更新速率。

    但是,保持更新间隔不变并不重要。真正重要的是每个时间单位具有最少数量的更新。想象一下,一辆汽车真的很快驶向障碍物。如果更新频率太低,则两次更新之间的行进距离可能会大于障碍物的总大小。车辆将向右移动。

    10-01 06:24