类似于Java,UScript同样有属于一个自己的VM(VirtualMachine,虚拟机),下面就看看需要注意吸收理解的几点:


Unreal虚拟机

Unreal虚拟机由以下几部分组成: 服务器、客户端、渲染引擎及引擎支持代码。

Unreal控制着所有的玩家和物体间的游戏性和交互。在单玩家游戏中,Unreal客户端和Unreal服务器在同一台机器上运行;在网络游戏中,有一个机器用于专用服务器;所有连接到这个机器上的玩家是客户端。

所有的游戏播放都发生在一个"关卡"中,它是一个包含着几何体和actors的独立环境。尽管UnrealServer可以同时运行多个关卡,但每个关卡独立运作并且彼此屏蔽: 物体(actors)不能在不同关卡间穿行,而且一个关卡中的物体不能和另一个关卡中的物体进行通信。

地图中的每个actor可以由玩家控制(在网络游戏中可以有很多玩家)或者由脚本控制。当actor在脚本的控制下时,那么该脚本完全地定义了该actor如何移动及如何与其它actors进行交互。

对于世界中所有这些到处跑动的actors、执行的脚本及发生的事件,你也许会问它是如何能理解UnrealScript的执行流程哪。答案如下所示:

为了管理时间,Unreal将游戏运行的每秒钟分隔为"Ticks"。一个Tick是关卡中所有actors更新所使用的最小时间单位。一个tick一般是一秒钟的1/100到 1/10。tick时间仅受到CPU功率的限制,机器速度越快,tick持续时间越短。

在UnrealScript中的某些命令的执行只需要使用零tick的时间(也就是:它们的执行没有占有任何游戏时间),也有些命令需要占用很多ticks。需要占用游戏时间的函数称为"latent functions(潜伏的函数)"。一些latent functions函数的例子包括 Sleep , FinishAnimMoveTo 。UnrealScript中的Latent functions仅可以从在一个状态的代码中进行调用(所以也称作"state code(状态代码)"),而不能从一个函数的代码中(包括在一个状态中定义的函数)进行调用。

当一个actor在执行一个latent函数,那个actor的状态执行不会继续直到latent函数执行完毕。然而,其它的actor或者VM可能会调用该actor内部的函数。最终的结果是所有的UnrealScript的函数可以在任何时间被调用,甚至在latent 函数没有执行完毕的情况下。

按照传统的编程术语来说,UnrealScript就像在关卡中的每个actor 有它们自己的执行"thread(线程)"一样工作。在内部,Unreal不使用Windows线程,因为那将是非常低效的(Windows 95和Windows NT不能高效地处理同时发生的成千上万的线程)。然而,UnrealScript 模拟线程。虽然这个事实对于UnrealScript代码是透明的,但当您书写和UnrealScript交互的C++代码时则变得非常显然的。

所有的UnrealScripts将彼此独立地执行。如果有100个怪物正在关卡中走动,那么所有的这100个怪物的脚本在每个"Tick"中都正在同时地且独立地执行着。


UnrealScript的实现

要想获得更多的关于UnrealScript在底层是如何实现的信息 – 从编译过程执行到最终的字节代码的呈现 – 请查看UnrealScript实现页面。


UnrealScript的二进制兼容问题

UnrealScript的设计可以在不破坏二进制兼容性的情况下使包文件中的类随着时间不断地扩展。 这里所说的二进制数据兼容性是指“所依赖的二进制文件可以无误地进行加载并连接”;而您所修改的代码是否向您设计的那样工作是一个单独的问题。 需要特别说明的是,可以安全地进行修改的种类如下所示:


  • 在包中的.uc脚本文件可以在不破坏二进制兼容性的情况下重新编译。
  • 增加新的类到包中。
  • 增加新的函数到类中。
  • 增加新的状态到类中。
  • 增加新的变量到类中。
  • 从类中删除私有变量。

其它的改变一般都是不安全的,包括(但不限于):


  • 添加新的成员到struct中。
  • 从一个包中移除一个类。
  • 改变任何变量、函数参数或者返回值的类型。
  • 改变函数中参数的个数。


技术注意事项

垃圾回收

Unreal中的所有objects和actors都是用一个类似于Java VM的树形结构遍历的垃圾回收器进行垃圾回收。Unreal垃圾回收器使用UObject类的序列化函数来递归地确定每个活动的对象在引用哪些其它的对象。因此,不必显示地删除对象,因为当它们不被引用时,垃圾回收器最终会对它们进行回收。尽管这个方法具有隐藏删除未引用的对象的负面影响,但是它的效率远远地高于把那种很少发生删除的情况在内的引用算在内。请参照垃圾回收页面获得更多信息。


UnrealScript是基于字节码的

UnrealScript代码编译成为一系列类似于p-code(移植码)或Java字节代码的字节码。这使UnrealScript具有平台独立性,这可以使Unreal的客户端和服务器端组件直接地移植到其它的平台上,也就是Macintosh或Unix,并且所有的版本通过执行相同的脚本都可以很容易地进行交互操作。

Unreal作为虚拟机

虚幻引擎可以被看成一个3D游戏方面的虚拟机,就像Java语言和它的内置 Java类层次结构为网页开发脚本定义了一个虚拟机一样。Unreal虚拟机天生具有可移植性(由于在不同的模块中分离出了所有的平台依赖的代码)和可扩展性(由于可扩展的类的层次结构)。然而,目前我们没有想过要把Unreal VM文档化到那种足以让其它人可以用来创建独立的但兼容的实现的程度。

UnrealScript 编译器执行三遍

和C++不同, UnrealScript编译要进行独立的三遍。第一遍,对变量、struct、枚举型、常量、状态及函数声明进行分析和记忆;构建了每个类的概要。 在第二遍,将脚本代码编译为字节代码。这使得在这两遍中对带有循环依赖的复杂的脚本层次进行完全地编译和连接,没有独立的连接阶段。 第三个阶段使用在.uc文件的 defaultproperties 语句块指定的值来分析并导入类的默认属性.

持久的 actor 状态

在Unreal中值得重点注意的是,因为用户可以在任何时间内保存游戏,所有actors的状态,包括它们的脚本执行状态仅能在当所有的actors都处在UnrealScript栈的最低的层次时才能进行保存。这个持久的要求是latent函数仅能从状态代码中进行调用的背后的原因: 状态代码在栈中尽可能低的层次上执行,因此可以非常容易被序列化。函数代码可以存在于任何栈级别,并在可以使(比如)C++ 的native函数在栈中处于它的下面,这显然不能保存到硬盘并在稍后进行恢复。

Unrealfiles(Unreal文件)是Unreal的native二进制文件

Unrealfiles包括一个索引、特定Unreal包内objects的序列化存储。Unrealfiles类似于DLL文件,因为它们可以包含任何到其它Unrealfiles存储的其它objects的引用。这个方法使在因特网上通过预先确定的包来发布Unreal内容成为可能,从而节约了下载时间(通过永远只能对一个特定的包进行一次下载进行限制)。

为什么UnrealScript不支持静态变量

C++支持静态变量的很好的原因是忠实于低层次语言的根源,Java支持静态变量的原因似乎并没有彻底地想好,在UnrealScript中不支持静态变量是由于它们的作用范围在序列化、继承及多个关卡方面的含糊性: 静态变量是否应该具有全局性,意味着所有的静态变量的值在所有活动的Unreal关卡中是一样的? 它们是否应该在每一个包中? 它们是否应该在每个关卡中? 如果这样,那么如何对它们进行序列化 – 使用在它的.u文件中的类还是使用它.unr文件中的关卡? 它们在每个基类中都是唯一的或者子类是否有它们自己的静态变量的值? 在UnrealScript中,我们通过不定义静态变量作为一个语言功能来回避了这个问题,把它留给了程序员来管理,程序员可以通过创建包含它们的类并在一个真实的对象中暴露它们来管理这些类似于静态变量和全局变量的变量。如果您想使用一些基于每个关卡访问的变量,那么您可以创建一个包含这些变量的新类,并确保它们随同关卡进行序列化。这样就不会有歧义性。要想查看用于这种用途的类的示例,请参照LevelInfo和GameInfo。