2011-03-16 56 views
8

這個例子中的另一個問題是用來說明如何協同程序可在視頻遊戲中使用腳本過場動畫:替代協程

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

每個功能收率到主發動機,其確實動畫,定時等在恢復協程之前。協程的一個可能的替代方案是事件隊列而不是代碼,但是然後必須實現控制邏輯和循環作爲事件。有沒有其他的可以用來實現這種功能的協程的替代品?我看過一些文章中提到的回調,但我不確定代碼的外觀。

+0

協程,FSM和基於事件的編程的替代方法是CSP(通信順序進程)。查看LuaCSP實現(在協程上): https://github.com/loyso/LuaCSP注意:人們通常缺少的是協調和溝通方面。 – 2013-03-12 10:10:01

回答

3

協程很適合這樣做,因爲您可以毫不費力地保留所有本地狀態變量。即而無需手動將其存儲在某處的上下文中。

但我沒有看到一個事件系統作爲替代。除了基於協同程序的腳本系統之外,更多的是作爲您最有可能想要的補充。

實施例(在有些相干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)通話將屈服於你的腳本調度,並允許其他腳本運行。 WalkTo,FaceObject,PlayAnimationOpenDoor也將調用Sleep產生。要麼基於已知的動畫持續時間,要定期喚醒以查看探路者和運動系統是否已經走路或者其他什麼。

如果NPC遇到的情況,他將不得不應對的方式向大門,會發生什麼?您不希望在基於協同程序的代碼中檢查所有這些事件。擁有一個事件系統補充協程會使這一點變得簡單:

垃圾桶傾覆:垃圾桶可以向所有附近的NPC播放一個事件。 NPC對象決定在他的堆棧上推送一個新的行爲對象來修復它。 WalkTo行爲在某處產生Sleep調用,但現在FixTrashcan行爲由於該事件而正在運行。當FixTrashcan完成WalkTo行爲將從Sleep醒來,並永遠不知道有關垃圾箱事件。但它仍將繼續前進,在它下面我們仍然在運行EnterHouse

爆炸發生:爆炸像垃圾桶一樣廣播事件,但此時NPC對象決定重置它的運行行爲並推動FleeInPanic行爲。他不會回到EnterHouse

我希望你明白我的有事件和協程共同生活在一個AI系統的意思。您可以使用協同程序保持本地狀態,同時仍讓步到您的腳本調度程序,並且您可以使用事件來處理中斷並保持邏輯以集中處理它們而不會污染您的行爲。

如果您還沒有看到如何實現C/C單線程協同程序this article by Thomas Tong ++我可以強烈推薦它。

他僅使用內聯組件(單指令)的最小的位保存在堆棧指針,該代碼是容易移植到一大堆平臺。我已經運行在Wintel,Xbox 360,PS3和Wii上。

有關調度程序/腳本設置的另一個好處是,它變得微不足道,如果你需要的資源用於別的東西餓死離屏或遙遠的AI角色/腳本對象。只需將它與調度程序中的優先級系統連接起來即可,而且您可以輕鬆前往。

+0

有趣的垃圾桶和爆炸的例子!我沒看過這篇文章,但還有另一篇文章[使用狀態機實現協程,並使用一些預處理器技巧隱藏細節](http://www.chiark.greenend.org.uk/~sgtatham/coroutines .html)(無內聯彙編)。 – absence 2011-03-18 10:06:56

2

回調(C#風格的僞代碼):

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

絕對不是最方便的方式。

一種不同的方法是期貨(又稱承諾):

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

futureChain.run(); 

一個有趣的語言看就是E - 它已經內置了對期貨的支持,比上面一個更好的語法。

+0

感謝您的回調示例。除了不方便之外,循環會導致堆棧溢出。 – absence 2011-03-18 10:02:51

3

你沒有提到你正在使用哪種語言,所以我將要在Lua與中產階級提供面向對象的寫這篇 - https://github.com/kikito/middleclass(免責聲明:我是中產階級的創造者)

另一種選擇會將你的過場動畫分成「動作列表」。如果你已經有一個遊戲循環在對象列表上調用'update'方法,這可能會更好地融合你的代碼。

像這樣:

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

行動將有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 

子類可以提高在這些方法,並實現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 

唯一缺少的實現部分是過場動畫。過場動畫將主要有三個方面: *動作列表執行 * A參考當前動作,或動作列表 對行動的指數*如下所示的更新方法:

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)。而且你消除了對協程的需求。