icedream 210 Report post Posted March 12, 2015 Papyrus是一个'多线程脚本语言。本质上,这意味着游戏可以同时运行多个脚本。这使得Creation引擎能更为有效地控制脚本进程。Papyrus编写者必须大致精通它们的作用。 本章主要面向新手讲解线程。 线程 基本要点如下: 只有一种东西可以在一个脚本中做任何事。 如果多个线程试图同时控制脚本,那么“队列”将会控制它们的先后次序。 如果脚本调用外部脚本的函数,或者调用一个“潜在的”函数(在返回前运行的函数),那么当其它线程停止工作时它将开始下一个。然后一个新线程要占用脚本,当先前线程停止后又想重新继续原先进程时,它必须等待直到新线程完成脚本(或者它调用一个潜在函数或其它脚本的函数) 例如: ObjectA在脚本中使用函数:DoSomething() ObjectB和ObjectC同时调用ObjectA.DoSomething() ObjectB完成运行ObjectA的DoSomething()后ObjectC才运行它。 但是如果ObjectA脚本中的DoSomething()如下所示: Function DoSomething() SomeThing1() ;another function in ObjectA ObjectD.DoSomethingElse() ;a function in a script on ObjectD Something2() ;Another function in ObjectA EndFunction然后第一个ObjectB调用ObjectA.DoSomething(),当ObjectA调用ObjectB的Something1()时,ObjectC会按顺序等待,而非运行ObjectA的DoSomething()。但是一旦因为ObjectD.DoSomethingElse()在不同脚本/目标中调用函数而导致ObjectB的线程调用它,那么ObjectA将会示意正在等待调用DoSomething()的ObjectC 现在ObjectC的线程正在运行SomeThing1(),ObjectB的线程完成ObjectD中的DoSomethingElse,但是在它运行Something2()之前它必须按顺序等待直到ObjectC完成Something1()并移出ObjectD的DoSomethingElse()。一旦ObjectC的线程冲向ObjectD的DoSomethingElse(),ObjectA示意从ObjectD的DoSomethingElse()返回的ObjectB的线程,ObjectA将会被允许继续Something2()等等。 多线程: 一个基本案例 试想一下,玩家激活一个操作杆,游戏将给Papyrus一个激活事件的通知。因为我们的脚本包括一个onActivate()事件,所以游戏创建一个"线程",你可以将其想象为脚本中命令的复制品。按照序列等到那个线程时,它将立即运行。 也许在下一个框架中,游戏将会处理线程。现在我们假设onActivate()事件的内容如下所示: ; Note - this is a non-functional snippet EVENT onActivate() wait(5.0) player.additem(gold001, 10) endEVENT玩家会触发一个开关,这个开关会在5秒之后通过Papyrus创建一个包括一些命令的线程,10个金币将会加入玩家的物品栏。这都是自动完成的。 现在玩家在短时间内多次触发杠杆。这是合法输入,因此脚本可以接受这个信息。在第一个被触发的事件中,线程被创造并开始运行。在紧接而来的第二个活动中,另一个线程将会在第一个之后被短暂建立。只要玩家触发杠杆,它就会继续运行。 在线程正常情况下,脚本也许会在敏感时刻出现问题。Thread #1并不了解Thread #2,因此每一个线程都会等待5秒钟并给予玩家金币。 这会导致快得无秩序,我们必须让一切具有组织性。 以上案例的脚本表现迫使我们要重写它,在第一个后面禁用大量活动。解决之道多种多样,例如我们可以借助状态解决它。 如果要创建用于反应多种情况下的事件的脚本,那么状态将是一个不错的选择。以下就是一个伪脚本案例。 ; 注意 - 这是一个无函数片段 STATE readyState EVENT onActivate() gotoState("emptySTATE") wait(5.0) player.additem(gold001, 10) ; 想要延迟5秒钟触发活动?请在此添加gotoState("readyState") endEVENT endSTATE STATE emptySTATE ; 此状态不包括任何事件 ; 注意 - 其它任何想关联此引用的脚本将会一直运行它们的活动事件 endSTATE 注意 "GoToState()"在先前的案例中,一定要注意goToState()的调用。因为additem()是一个外部脚本(和下方案例中的setValue()类似),如果其它线程想要在它预计所在的状态之前运行你的脚本,那么它将对此线程创造一个机会。因此,首先要跳转到你期望的状态。你的线程将会完成调用的gotoState()下方的命令,以后的线程将会把脚本按状态对待 - 例如以下案例中的"XYZ"。 换句话说,不要这么做: gigawatts.setValue(1.21) GoToState("XYZ")而应这么做: GoToState("XYZ") gigawatts.setValue(1.21) 其它案例在一封寄往Jeff的电子邮件中有这么一个案例:在“安全线程”一行中,如果多次从外部脚本中调用IncrementMyProperty ...假设这些函数和属性都在相同脚本中。 Function IncrementMyProperty myProperty += 1 endFunction在100%的“安全线程”中,myProperty将会正确地增长。只要在你知情的情况下,此刻它将会解决2个函数的调用。 Function IncrementMyProperty myProperty += 1 if myProperty == 5 ;做某事 endif endFunction[code] 在执行“做某事”时,myProperty将保证达到5。如果“做某事”潜伏,或调用一个在脚本或其父类外部的函数时,myProperty也许会改变。 [code]Function IncrementMyProperty myProperty += 1 if myProperty == 5 myQuest.setStage(10) endif endFunction ;Elsewhere In MyQuest’s stage 10 fragment: If myProperty == 5 ;do something endifSetStage潜伏,因此myPropery也许会改变,即使你使用“SetStage(10)”并使脚本继承一个与之关联的任务。在片段案例中,myProperty完全有可能因为片段的运行而不等于5。记住:你调用的是setStage而非一个函数。片段执行“以后的次数”(将会在大多数事件中发生)。在调用一个函数比调用一个片段更为有效的情况下,这种情况很容易发生。 请注意: 如果myProperty不是你的本地脚本,那么“myProperty += 1”也许不会增加。这是因为它分解为“myProperty.set(myProperty.get() + 1)”,以及myProperty的值也许会在读取调用中有所改变。由于它不是在你自己的脚本中。[ 编辑器提示这是因为你必须在其它脚本中写入一个IncrementMyProperty()函数-- JPD ] Share this post Link to post Share on other sites