回合制游戏中世界状态和动画的分离


9

在回合制游戏中,您如何处理动画与世界状态的分离?我目前正在开发基于2D网格的游戏。下面的代码被简化以更好地说明。

当演员移动时,我想在生物动画并移动到新位置时暂停转弯流程。否则,屏幕可能会大大落后于世界状态,从而导致奇怪的视觉外观。我还希望动画不会阻塞游戏的流程-粒子效果可以在多个回合中展开而不会影响游戏玩法。

我要做的是介绍两种类型的动画,分别称为阻塞动画和非阻塞动画。当玩家想要移动时,执行的代码是

class Actor {
    void move(PositionInfo oldPosition, PositionInfo newPosition) {
        if(isValidMove(oldPosition, newPosition) {
             getGame().pushBlockingAnimation(new MoveAnimation(this, oldPosition, newPosition, ....));
             player.setPosition(newPosition);
        }
    }
}

然后,主更新循环执行以下操作:

class Game {
    void update(float dt) {
        updateNonBlockingAnimations(dt); //Effects like particle explosions, damage numbers, etc. Things that don't block the flow of turns.
        if(!mBlockingAnimations.empty()) {
            mBlockingAnimations.get(0).update(dt);
        } else {
             //.. handle the next turn. This is where new animations will be enqueued..//
        }
        cleanUpAnimations(); // remove finished animations
    }
}

...动画会更新Actor的屏幕位置。

我正在实现的另一个想法是有一个并发的阻塞动画,其中多个阻塞动画将被同时更新,但是直到所有动画都完成后,下一轮才会发生。

这似乎是理智的做事方式吗?有没有人有任何建议,甚至没有提到其他类似游戏如何做到这一点。

Answers:


2

如果您的系统适合您,我认为没有任何理由不这样做。

好吧,原因之一:当您不容易判断“ player.setPosition(newPosition);”的结果时,它可能会变得混乱。不再。

示例(仅作补充):想象一下,将setPosition放在某个陷阱的顶部来移动播放器。对“ player.setPosition”本身的调用将触发对..说一个陷阱侦听器代码的其他调用,该陷阱侦听器代码本身将推动一个阻止动画,从而在其自身上方显示“令人讨厌的飞溅”。

您会看到问题:随着玩家向陷阱移动,飞溅会同时显示。(这里假设您不希望这样做,但是希望陷阱动画在移动完成后立即播放;)。

因此,只要您能够记住在执行“ pushBlockingAnimation”调用之后所做的所有操作后果,一切都会很好。

您可能需要开始将游戏逻辑包装到“阻止某些内容”(类)中,以便可以在动画结束后将它们排队等待执行。或者,您将回调“ callThisAfterAnimationFinishes”传递给pushBlockingAnimation,然后将setPosition移到回调函数中。

另一种方法是脚本语言。例如,Lua具有“ coroutine.yield”,它以特殊状态返回该函数,以便稍后可以调用它以继续下一条语句。这样,您可以轻松地等待动画的结尾执行游戏逻辑,而不会弄乱代码的“正常”程序流程外观。(意味着您的代码看起来仍然有点像“ playAnimation(); setPosition();”,而不是传递回调并在多个函数上使游戏逻辑混乱)。

Unity3D(以及我所知的至少一个其他游戏系统)具有C#“ yield return”语句,可直接在C#代码中对此进行模拟。

// instead of simple call to move(), you have to "iterate" it in a foreach-like loop
IEnumerable<Updatable> move(...) {
    // playAnimation returns an object that contain an update() and finished() method.
    // the caller will not iterate over move() again until the object is "finished"
    yield return getGame().playAnimation(...)
    // the next statement will be executed *after* the animation finished
    player.setPosition(newPosition);
}

当然,在此方案中,对move()的调用现在可以完成所有工作。您可能只将这种技术应用于特定的功能,在此您确切知道谁从何处调用它们。因此,仅将此作为其他引擎的组织方式的基本思想-对于您当前的特定问题,它可能太复杂了。

我建议您坚持使用pushBlockingAnimation的想法,直到遇到现实世界的麻烦为止。然后修改它。;)


非常感谢您抽出宝贵时间写此回复,我非常感谢。
mdkess
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.