【问题标题】:Alternatives to coroutines协程的替代品
【发布时间】:2011-07-16 12:28:12
【问题描述】:

此示例已在另一个问题中用于说明如何使用协程编写视频游戏中的过场动画:

bob.walkto(jane)
bob.lookat(jane)
bob.say("How are you?")
wait(2)
jane.say("Fine")
...

在恢复协程之前,每个函数都会让位于执行动画、计时等的主引擎。协程的一种可能替代方案是事件队列而不是代码,但是必须将控制逻辑和循环作为事件来实现。是否有任何其他可用于实现此类功能的协程替代方案?我在一些文章中看到过回调,但我不确定代码的外观。

【问题讨论】:

  • 协程、FSM 和基于事件的编程的替代方案是 CSP(通信顺序进程)。在此处查看 LuaCSP 实现(在协程之上):github.com/loyso/LuaCSP 注意:人们通常缺少的是协调和沟通方面。

标签: coroutine


【解决方案1】:

协程非常适合这种情况,因为您可以毫不费力地保留所有本地状态变量。 IE。无需手动将其存储在某个上下文中。

但我不认为事件系统可以替代。除了基于协程的脚本系统之外,您可能还想拥有更多的补充。

示例(在有些连贯的 C++ 中):

您已经使用协程实现了以下方面的行为:

class EnterHouse : public NPCBehavior
{
    EnterHouse(House theHouse) { _theHouse = theHouse; }
    void Begin() { _theHouse.AddNPC(NPC()); }
    void Update()
    {
        NPC().WalkTo(_theHouse.GetDoor().Position());
        NPC().FaceObject(_theHouse.GetDoor());
        NPC().PlayAnimation(NPC().Animations().Get(eAnimKnockOnDoor));
        Sleep(1.0f);       
        NPC().OpenDoor(_theHouse.GetDoor());
    }
    void End() { /* Nothing */ }

    private House _theHouse;
}

想象一下,NPC 上的方法自己会创建 NPCBehavior 对象,将它们推送到某种行为堆栈上,并在这些行为完成时从调用中返回。

Sleep(1.0f) 调用将让给您的脚本调度程序并允许其他脚本运行。 WalkToFaceObjectPlayAnimationOpenDoor 也将调用 Sleep 来让步。要么基于已知的动画持续时间,要么定期唤醒以查看探路者和运动系统是否已完成行走或其他。

如果NPC在去门口的路上遇到他必须处理的情况怎么办?您不想在基于协程的代码中到处检查所有这些事件。让事件系统补充协程将使这一切变得容易:

垃圾桶倾倒:垃圾桶可以向附近的所有 NPC 广播事件。 NPC 对象决定在他的堆栈上推送一个新的行为对象来修复它。 WalkTo 行为在某处产生Sleep 调用,但现在FixTrashcan 行为由于事件而运行。当FixTrashcan 完成时,WalkTo 行为将从Sleep 唤醒,并且永远不会知道垃圾桶事件。但它仍会在通往门口的路上,在它下面我们仍在运行EnterHouse

发生爆炸:爆炸像垃圾桶一样广播事件,但这次 NPC 对象决定重置其运行行为并推送FleeInPanic 行为。他不会回EnterHouse

我希望你明白我所说的让事件和协程一起存在于 AI 系统中的意思。您可以使用协程来保持本地状态,同时仍然屈服于您的脚本调度程序,并且您可以使用事件来处理中断并保持集中处理它们的逻辑而不会污染您的行为。

如果您还没有看过 this article by Thomas Tong 关于如何在 C/C++ 中实现单线程协程,我强烈推荐它。

他只使用了最少量的内联汇编(一条指令)来保存堆栈指针,并且代码很容易移植到一大堆平台上。我已经在 Wintel、Xbox 360、PS3 和 Wii 上运行过它。

调度程序/脚本设置的另一个好处是,如果您需要资源来做其他事情,那么让屏幕外或远处的 AI 角色/脚本对象挨饿变得微不足道。只需将它与调度程序中的优先级系统结合起来,您就可以开始了。

【讨论】:

【解决方案2】:

你没有提到你使用的是什么语言,所以我将用中产阶级提供的面向对象的 Lua 编写这个 - https://github.com/kikito/middleclass(免责声明:我是中产阶级的创造者)

另一种选择是将过场动画拆分为“动作列表”。如果您已经有一个在对象列表上调用“更新”方法的游戏循环,这可能会更好地与您的代码融合。

像这样:

helloJane = CutScene:new(
  WalkAction:new(bob, jane),
  LookAction:new(bob, jane),
  SayAction:new(bob, "How are you?"),
  WaitAction:new(2),
  SayAction:new(jane, "Fine")
)

Actions 将有一个 status 属性和三个可能的值:'new''running''finished'。所有的“动作类”都是Action 的子类,它将定义startstop 方法,并且默认将状态初始化为'new'。还有一个默认的update 方法会引发错误

Action = class('Action')

function Action:initialize() self.status = 'new' end

function Action:stop() self.status = 'finished' end

function Action:start() self.status = 'running' end

function Action:update(dt)
  error('You must re-define update on the subclasses of Action')
end

Action 的子类可以改进这些方法,并实现update。例如,这里是WaitAction

WaitAction = class('WaitAction', Action) -- subclass of Action

function WaitAction:start()
  Action.start(self) -- invoke the superclass implementation of start
  self.startTime = os.getTime() -- or whatever you use to get the time
end

function WaitAction:update(dt)
  if os.getTime() - self.startTime >= 2 then
    self:stop() -- use the superclass implementation of stop
  end
end

唯一缺少的实现部分是 CutScene。一个 CutScene 主要有三样东西: * 要执行的操作列表 * 对当前动作的引用,或动作列表中该动作的索引 * 更新方法如下:

function CutScene:update(dt)
  local currentAction = self:getCurrentAction()
  if currentAction then
    currentAction:update(dt)
    if currentAction.status == 'finished' then
      self:moveToNextAction()
      -- more refinements can be added here, for example detecting the end of actions
    end
  end
end

使用这种结构,您唯一需要的就是游戏循环在每次循环迭代时调用helloJane:update(dt)。并且您消除了对协程的需求。

【讨论】:

    【解决方案3】:

    回调(C# 风格的伪代码):

    bob.walkto(jane, () => {
        bob.lookat(jane), () => {
            bob.say.....
        })
    })
    

    绝对不是最方便的方式。

    另一种方法是 Futures(也称为承诺):

    futureChain = bob.walkto(jane)
      .whenDone(bob.lookAt(jane))
      .whenDone(...)
      .after(2 seconds, jane.Say("fine"));
    
    futureChain.run();
    

    E 是一种值得关注的有趣语言 - 它内置了对期货的支持,语法比上面的更好。

    【讨论】:

    • 感谢回调示例。除了不方便之外,循环还会导致堆栈溢出。
    猜你喜欢
    • 2011-01-13
    • 1970-01-01
    • 2021-04-08
    • 2011-08-28
    • 2017-08-03
    • 1970-01-01
    • 2016-03-30
    • 2018-11-15
    • 1970-01-01
    相关资源
    最近更新 更多