2011-05-30 162 views
4

我正在使用Lua进行NPC行为的C++游戏引擎。我在设计中遇到了一些问题。使用Lua在C++游戏引擎中定义NPC行为

对于需要多个帧执行的任何事情,我想使用一个进程链接列表(这是C++类)。所以这个:

goto(point_a) 
say("Oh dear, this lawn looks really scruffy!") 
mowLawn() 

将创建一个GotoProcess对象,这将有一个指向SayProcess对象,这将有一个指向MowLawnProcess对象。这些对象将在NPC产生时立即创建,无需进一步编写脚本。 这些对象中的第一个将更新每一帧。完成后,它将被删除,下一个将用于更新。 我通过一个ParallelProcess扩展了这个模型,它将包含多个同时更新的进程。

我发现了一些严重的问题。看看这个例子:我想要一个角色走到point_a,然后去疯狂攻击任何靠近的人。该脚本将如下所示:

goto(point_a) 
while true do 
    character = getNearestCharacterId() 
    attack(character) 
end 

这对我的设计根本不起作用。首先,当角色甚至没有开始走到point_a时,字符变量将被设置在开始处。然后,由于while循环,脚本会继续添加AttackProcesses。

我可以实现循环的WhileProcess并逐行评估脚本。我怀疑这会提高代码的可读性。

我有没有想过解决这个问题的另一种常见方法?

+0

听起来像你的第二个例子可能会受益于使用有限状态机 – Necrolis 2011-05-30 11:28:19

+0

我的计划是有NPC的对象表示,但我没有多想,因为上面提到的问题似乎更加“低级别“ 对我来说。 – Daerst 2011-05-30 11:45:25

+0

使用协程的情况如何?这将是我的第一个方法。 – 2011-05-30 12:03:27

回答

5

我认为你给的方法失去了很多使用脚本语言的优点。它将与条件以及循环一起中断。

随着协同程序你真正需要做的是:

npc_behaviour = coroutine.create(
    function() 
     goto(point_a) 
     coroutine.yield() 
     say("Oh dear, this lawn looks really scruffy!") 
     coroutine.yield() 
     mowLawn() 
     coroutine.yield() 
    end 
) 

跳转,说和mowLawn立即返回,但开始在C++的行动。一旦C++完成这些操作调用coroutine.resume时(npc_behaviour)

为避免你可以隐藏他们的goto等功能,内部收益率,还是做我想做的是有一个WAITFOR功能,如:

function waitFor(id) 
    while activeEvents[id] ~= nil do 
     coroutine.yield() 
    end 
end 

activeEvents只是一个跟踪当前正在进行的所有事情的Lua表 - 因此,goto会在启动时向表添加ID,并在完成时将其删除,然后每次操作完成时,所有协程都会被激活以检查他们正在等待的动作是否完成。

+0

是的,先生!这听起来很像我需要的。我要做一个测试实现,看看我能否实现它。谢谢! – Daerst 2011-05-31 10:58:11

+0

好吧,我得到它的工作。在Lua中,我只定义了一个名为OnIdle()的函数。然后,我使用C++中的lua_newthread创建协程,并使用lua_resume启动它。 waitFor()函数也在C++中实现,并返回lua_yield以暂停协程。我存储要等待的进程的ID。每一帧我都会更新该过程,并在完成后再次调用lua_resume继续执行脚本。完美,谢谢! – Daerst 2011-05-31 21:02:25

0

我不知道你设计背后的原因,可能有更简单/更习惯的方式。

但是,会写一个自定义的“循环”过程,以某种方式采取一个函数,因为它的参数做的伎俩?

goto(point_a) 
your_loop(function() 
    character = getNearestCharacterId() 
    attack(character) 
end) 

因为lua有倒闭(见here in the manual),该功能可以连接到您的“LoopProcess”,而你拨打每一帧此相同的功能。你可能必须实现你的LoopProcess,以便它永远不会从进程列表中删除...

如果你希望你的循环能够停止,那会更复杂一点;你将不得不传递另一个包含测试逻辑的函数(并且,LoopProcess必须每帧都调用它)。

希望我理解你的问题......

+0

是的,这基本上是我“实施WhileProcess”的意思。我认为它会奏效,但是怀疑它是否是最好的方法。你能想到任何更简单/更习惯的方法来做到这一点吗?随意忽略我完全想出的设计:) – Daerst 2011-05-30 11:47:40

+0

那么,我有另一个问题:我假设你这样做,以便你可以为你的实体定义一个“计划”,跨越多个框架,对吧?问题在于,您可以在一个框架中执行的操作的粒度是多少?我认为你将不得不使用定制函数来构建计划;计划的某些部分将是'原子'行动(可以在一个框架内完成的事情)。你可以添加语法糖(这样'goto(point_a)'实际上意味着(“在计划中增加一个步骤,试图将代理推向point_a每帧一点)” – phtrivier 2011-05-30 14:21:28

+0

但是,对于更高阶的构造,如循环等等......你将无法依赖lua基础语法,恐怕(除非lua有一些我不知道的宏观魔法......) – phtrivier 2011-05-30 14:22:48

1

你看着Finite State Machines?如果我是你,我不会使用链接列表,而是使用堆栈。我认为最终的结果是一样的。

stack:push(action:new(goto, character, point_a)) 
stack:push(action:new(say, character, "Oh dear, this lawn was stomped by a mammoth!")) 
stack:push(action:new(mowLawn, character)) 

执行的操作顺序会看到这样的:

while stack.count > 0 do -- do all actions in the stack 
    action = stack:peek() -- gets the action on top of the stack 
    while action.over ~= true do -- continue action until it is done 
     action:execute() -- execute is what the action actually does 
    end 
    stack:pop() -- action over, remove it and proceed to next one 
end 

goto等功能应该是这样的:

function goto(action, character, point) 
    -- INSTANT MOVE YEAH 
    character.x = point.x 
    character.y = point.y 
    action.over = true -- set the overlying action to be over 
end 

function attack(action, character, target) 
    -- INSTANT DEATH WOOHOO 
    target.hp = 0 
    action.over = true -- attack is a punctual action 
end 

function berserk(action, character) 
    attack(action, character, getNearestCharacterId()) -- Call the underlying attack 
    action.over = false -- but don't set action as done ! 
end 

在进攻一所以每当你stack:push(action:new(berserk, character))它将循环每次都有不同的目标。

我还让你在对象lua here中执行堆栈和操作。没有尝试过。可能会像地狱一样被窃听。祝你好运!

+0

感谢您的详细解答!我希望我可以避免在Lua中的每帧循环(就像你的'action:execute()'),并且只用C++来完成,但我认为这是一个可以尝试的方法。 – Daerst 2011-05-30 12:12:32