没有线程的脚本和电影


12

我一直在努力在游戏引擎中实现脚本。我只有几个要求:应该是直观的,我不想编写自定义语言,解析器和解释器,并且我不想使用线程。(我确定有一个更简单的解决方案;我不需要多个游戏逻辑线程的麻烦。)这是一个示例示例,使用Python(又称伪代码)编写:

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    alice.walk_to(bob)
    if bob.can_see(alice):
        bob.say("Hello again!")
    else:
        alice.say("Excuse me, Bob?")

那段史诗般的讲故事带来了实现问题。我不能立即评估整个方法,因为这walk_to需要花费时间。如果立即返回,爱丽丝将开始走到鲍勃,然后(在同一帧中)打个招呼(或打招呼)。但是,如果walk_to有一个阻塞呼叫在她到达Bob时返回,那么我的游戏就会卡住,因为它阻塞了使Alice走动的执行线程。

我考虑过让每个函数加入一个动作- alice.walk_to(bob)将对象推入队列,无论爱丽丝到达鲍勃,无论他身在何处,该队列都会弹出。这更巧妙地被打破了:if分支立即得到评估,因此鲍勃可能会向爱丽丝致意,即使他的背对着她。

其他引擎/人员如何在不创建线程的情况下处理脚本?我开始在非游戏开发领域中寻找想法,例如jQuery动画链。似乎应该有一些解决此类问题的好的模式。


1
+1,特别是“ Python(aka伪代码)” :)
Ricket

Python就像拥有超能力。
ojrac

Answers:


3

像Panda这样的方式是通过回调实现的。而不是阻止它会像

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    def cb():
        if bob.can_see(alice):
            bob.say("Hello again!")
        else:
            alice.say("Excuse me, Bob?")
    alice.walk_to(bob, cb)

通过完成回调,您可以根据需要深度链接此类事件。

编辑:JavaScript示例,因为它对此样式具有更好的语法:

function dramatic_scene(actors) {
    var alice = actors.alice;
    var bob = actors.bob;
    alice.walk_to(bob, function() {
        if(bob.can_see(alice)) {
            bob.say('Hello again!');
        } else {
            alice.say('Excuse me, Bob?');
        }
     });
}

它对于+1来说已经足够了,我只是认为设计内容是错误的。我愿意为此做一些额外的工作,以使脚本看起来简单明了。
ojrac 2010年

1
@ojrac:实际上,这种方法更好,因为在同一脚本中,您可以告诉多个演员同时开始行走。
Bart van Heukelom

gh,好点。
ojrac

但是,为了提高可读性,可以将回调定义嵌套在walk_to()调用内,或放在其后(两者都适用:如果语言支持),以便稍后在源代码中看到稍后被调用的代码。
Bart van Heukelom

是的,不幸的是,Python对于这种语法并不是很好。在JavaScript中看起来好多了(请参见上文,此处不能使用代码格式)。
coderanger

13

您要在此处搜索的术语是“ 协程 ”(通常语言关键字或函数名称是yield)。

协程是对子例程进行概括的程序组件,以允许多个入口点在某些位置挂起和恢复执行。

实施将首先取决于您的语言。对于游戏,您希望实现尽可能轻巧(比线程甚至纤维轻)。Wikipedia页面(链接)具有一些指向各种特定于语言的实现的链接。

我听说Lua内置了对协程的支持。GameMonkey也是如此。

UnrealScript通过所谓的“状态”和“潜在函数”来实现。

如果您使用C#,则可以查看Nick Gravelyn撰写的此博客文章

另外,“动画链”的想法虽然不是同一回事,但却是解决同一问题的可行解决方案。Nick Gravelyn也对此具有C#实现


不错,Tetrad;)
安德鲁·罗素

这真的很好,但是我不确定它能使我100%达到目标。看起来协程让您屈服于调用方法,但是我想要一种从Lua脚本屈服的方法,从堆栈一直到C#代码,而无需编写while(walk_to()!= done){yield}。
ojrac

@ojrac:我不知道Lua,但是如果您使用Nick Gravelyn的C#方法,则可以返回一个委托(或包含一个对象的对象),该委托保存有条件供脚本管理器检查(Nick的代码仅返回一个时间)。这是有条件的)。您甚至可以让潜在函数本身返回委托,因此您可以yield return walk_to();在脚本中编写:
安德鲁·罗素

C#的产出非常棒,但我正在针对简单,难以理解的脚本优化我的解决方案。我比解释yield更容易解释回调,所以我接受另一个答案。如果可以的话,我会+2。
ojrac 2010年

1
通常,您不必解释yield调用-例如,可以将其包装在“ walk_to”函数中。
Kylotan'9

3

不穿线很聪明。

大多数游戏引擎都是一系列模块化阶段,内存中的内容驱动着每个阶段。对于您的“以身作则”,通常会有一个AI阶段,其中所行走的角色处于不应该寻找杀死敌人的状态;一个动画阶段,应运行动画X;一个物理阶段(或模拟阶段),更新其实际位置,等等。

在上面的示例中,“ alice”是由生活在许多阶段中的片段组成的actor,因此,阻塞的actor.walk_to调用(或您在每帧中一次调用协程的协程)可能没有适当的上下文做出很多决定。

相反,“ start_walk_to”函数可能会执行以下操作:

def start_cutscene_walk_to(actor,target):
    actor.ai.setbrain(cutscene_brain)
    actor.physics.nocoll = 1
    actor.anims.force_anim('walk')
    # etc.

然后,您的主循环运行其AI滴答,其物理滴答,其动画滴答和其过场动画滴答,过场动画更新引擎中每个子系统的状态。过场动画系统绝对应该跟踪其每个过场动画的操作,而协程驱动的系统对于像场景这样的线性和确定性的东西可能是有意义的。

之所以采用这种模块化,是因为它可以使事物保持简单美观,对于某些系统(例如物理系统和AI),您需要同时了解所有事物的状态才能正确解决事物并使游戏保持一致状态。

希望这可以帮助!


我喜欢您想要的东西,但实际上我对actor.walk_to([另一个actor,固定位置,甚至返回位置的函数])的值感到很强烈。我的目标是提供简单易懂的工具,并处理游戏内容创建部分之外的所有复杂性。您还帮助我认识到,我真正想要的只是一种将每个脚本视为有限状态机的方法。
ojrac

很高兴我能帮上忙!我觉得我的回答有点离题了:)绝对同意actor.walk_to函数对于实现目标的价值,我很期待听到您的实现。
亚伦·布雷迪

看来我将结合使用jQuery样式的回调和链接函数。参见已接受的答案;)
ojrac 2010年
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.