Jump to content
模组网
icedream

【Creation Kit 学习指南 34】多线程说明

Recommended Posts

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()事件,所以游戏创建一个"线程",你可以将其想象为脚本中命令的复制品。按照序列等到那个线程时,它将立即运行。

gallery_1_72_9400.jpg

也许在下一个框架中,游戏将会处理线程。现在我们假设onActivate()事件的内容如下所示:

; Note - this is a non-functional snippet
EVENT onActivate()
	wait(5.0)
	player.additem(gold001, 10)
endEVENT
玩家会触发一个开关,这个开关会在5秒之后通过Papyrus创建一个包括一些命令的线程,10个金币将会加入玩家的物品栏。这都是自动完成的。

gallery_1_72_28310.jpg

现在玩家在短时间内多次触发杠杆。这是合法输入,因此脚本可以接受这个信息。在第一个被触发的事件中,线程被创造并开始运行。在紧接而来的第二个活动中,另一个线程将会在第一个之后被短暂建立。只要玩家触发杠杆,它就会继续运行。

在线程正常情况下,脚本也许会在敏感时刻出现问题。Thread #1并不了解Thread #2,因此每一个线程都会等待5秒钟并给予玩家金币。

这会导致快得无秩序,我们必须让一切具有组织性。

gallery_1_72_20503.jpg

以上案例的脚本表现迫使我们要重写它,在第一个后面禁用大量活动。解决之道多种多样,例如我们可以借助状态解决它。

如果要创建用于反应多种情况下的事件的脚本,那么状态将是一个不错的选择。以下就是一个伪脚本案例。

; 注意 - 这是一个无函数片段
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
endif
SetStage潜伏,因此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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...