游戏动作需要多个框架才能完成


20

我之前从未真正做过很多游戏编程,这是一个非常简单的问题。

想象一下,我正在构建一个俄罗斯方块游戏,主循环看起来像这样。

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            remove all complete rows
            move rows down so there are no gaps
            if we can spawn a new block
                spawn a new current block
            else
                game over

到目前为止,游戏中的所有事情都是立即发生的-事情立即产生,行被立即删除等。但是,如果我希望事情立即发生(动画)。

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            ?? animate complete rows disappearing (somehow, wait over multiple frames until the animation is done)
            ?? animate rows moving downwards (and again, wait over multiple frames)
            if we can spawn a new block
                spawn a new current block
            else
                game over

在我的Pong克隆中,这不是问题,因为每帧我都在移动球并检查是否有碰撞。

我该如何解决这个问题?当然,大多数游戏所涉及的某些动作所花费的时间超过一帧,而其他事情则停下来,直到动作完成为止。

Answers:


11

对此的传统解决方案是有限状态机,在一些评论中对此进行了建议。

我讨厌有限状态机。

当然,它们很简单,每种语言都得到了支持,但使用起来却非常痛苦。每次操作都需要花费大量容易出错的复制和粘贴代码,而以较小的方式调整效果可能会对代码造成巨大的影响。

如果可以使用支持它们的语言,则建议使用协程。他们让您编写如下代码:

function TetrisPieceExplosion()
  for brightness = 0, 1, 0.2 do
    SetExplosionBrightness(brightness)
    coroutine.yield()
  end

  AllowNewBlockToFall()

  SpawnABunchOfParticles()

  RemoveBlockPhysics()

  for transparency = 0, 1, 0.5 do
    SetBlockTransparency(transparency)
    coroutine.yield()
  end

  RemoveBlockGraphics()
end

显然是伪代码,但应该清楚的是,这不仅是对特殊效果的简单线性描述,而且还很容易让我们在动画结束时放下一个新块。使用状态机来完成此任务通常会很可怕。

就我所知,此功能在C,C ++,C#,Objective C或Java中不容易使用。这是我将Lua用于所有游戏逻辑的主要原因之一:)


您还可以使用其他OOP语言来实现这些目标。想象一下一个Action类和一系列要执行的动作。当一个动作完成后,将其从队列中删除并执行下一个动作等。比状态机更灵活。
bummzack 2011年

3
那行得通,但是您要考虑的是从Action派生出每个唯一的动作。它还假定您的进程很好地适合队列-如果您想要分支或具有未定义最终条件的循环,则队列解决方案会迅速崩溃。它肯定比状态机方法干净,但是我认为协程在可读性上仍然胜过它:)
ZorbaTHut 2011年

没错,但对于俄罗斯方块示例来说,它应该足够了:)
bummzack 2011年

协同例程摇滚-但是Lua作为一种语言很烂,我只是不推荐使用。
DeadMG

只要您只需要在最高级别屈服(而不是从嵌套函数调用),就可以在C#中完成相同的事情,但是,是的,Lua协程很艰难。
优厚

8

我是从Mike McShaffry的Game Coding Complete中摘录的。

他谈到了“流程管理器”,该流程归纳为需要完成的任务列表。例如,一个过程将控制动画以绘制剑(AnimProcess),打开门或在您的情况下使该行消失。

该流程将被添加到流程管理器的列表中,该列表将在每个框架中进行迭代,并在每个框架上调用Update()。实体非常相似,但要采取行动。完成后,将有一个kill标志从列表中删除。

关于它们的另一个整洁的事情是它们如何通过指向下一个进程的指针进行链接。这样,您的动画行过程实际上可能包括:

  • 该行的AnimationProcess消失了
  • 一个移动过程以去除碎片
  • 一个将分数添加到分数的ScoreProcess

(因为过程可以是一次性使用的东西,有条件地存在,或者在那里有X倍的时间)

如果您需要更多详细信息,请询问。


3

您可以使用操作的优先级队列。您推动一个动作和一个时间。在每一帧中,您都有时间,然后弹出所有具有指定时间的操作,然后执行它们。奖励:方法可以很好地并行化,您实际上可以用这种方式实现几乎所有游戏逻辑。


1

您总是需要知道上一帧与当前帧之间的时差,然后您必须做两件事。

-确定何时更新模型:在tetris中,当开始删除行时,您不再希望东西与该行发生冲突,因此您可以从应用程序的“模型”中删除该行。

-然后,您必须将处于过渡状态的对象处理到一个单独的类,该类在一段时间内解析动画/事件。在俄罗斯方块示例中,您将使行逐渐淡出(稍微改变每帧的不透明度)。不透明度为0后,将第一行顶部的所有块向下转移。

乍一看,这似乎有些复杂,但是您将掌握到这一点,只需确保在不同的类中进行大量抽象即可,这将使其变得更容易。还请确保耗时的事件(例如在俄罗斯方块中删除一行)属于“ Fire and Forget”类型,只需创建一个新对象即可处理需要自动完成的所有操作,并在完成所有操作后,从场景图中删除自身。


同样,在某些情况下,繁重的计算可能会超出一个物理时间步骤所允许的时间(例如,碰撞检测和路径规划)。在这些情况下,使用分配的时间后,您可以跳出计算并继续下一帧的计算。
Nailer

0

您需要将游戏视为“有限状态机”。游戏可以处于以下几种状态之一:在您的情况下,“期望输入”,“向下移动”,“行爆炸”。

您根据状态执行不同的操作。例如,在“下移棋子”期间,您忽略了玩家的输入,而是将棋子从其当前行到下一行进行动画处理。像这样:

if state == ACCEPTING_INPUT:
    if player presses any key:
        handle input
    row_timer = row_timer - time_since_last_frame
    if row_timer < 0:
        state = MOVING_PIECE_DOWN
elif state == MOVING_PIECE_DOWN:
    piece.y = piece.y + piece.speed*time_since_last_frame
    if piece.y >= target_piece_y:
        piece.y = target_piece_y
        state = ACCEPTING_INPUT
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.