UDN
Search public documentation:

UnrealScriptReferenceCH
English Translation
日本語訳
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 主页 > 虚幻脚本 >UnrealScript语言参考指南

UnrealScript 语言参考指南


概述


本技术文档介绍了UnrealScript语言。该文档不是个指南,也不没有提供有用的UnrealScript代码的详细示例。对于想获得UnrealScript示例的读者,请参阅引擎的源码,那里提供了成千上万行的可以正常运行的UnrealScirpt代码,这些代码解决了很多问题,比如AI、运动、武器库及触发器。进行入门学习的一个很好的方法是查看"Actor"、"Object"、"Controller"、"Pawn"和"Weapon" 脚本。

本文假设读者具有 C/C++或 Java方面的知识、熟悉面向对象编程、玩过Unreal制作的游戏、并使用过UnrealEd编辑环境。

为了让您进行进一步的阅读,UnrealWiki也在其的UnrealScript概述页面提供了关于UnrealScript语言的大量信息。

对于面向对象编程的初学者来说,强烈推荐您到Amazon或书店买一些关于Java编程的入门书籍。 Java和UnrealScript很相似,并且由于它的干净和简单的方法使它成了一种值得学习的极好的语言。

UnrealScript的设计目标


UnrealScript的设计目的是为开发团队以及第三方Unreal开发人员提供了一个强大的内置编程语言,它满足了游戏编程的要求与细节。

UnrealScript的主要设计目标有:

  • 支持主要的概念:时间、状态、属性及网络,这些概念是传统编程语言没有强调的。这将大大地简化UnrealScript代码。基于C/C++的AI和游戏逻辑编程的主要复杂性是处理要花费一定量的游戏时间来完成的事件以及处理依赖对象状态的各个方面的事件。 在C/C++中,这将会导致冗长的混乱的代码,使代码难于书写、理解、维护和调试的。UnrealScript包含了对时间、状态和网络复制的内部支持,这大大地简化了游戏编程。
  • 提供一种像Java类型编程语言一样简单的、面向对象的并在编译时进行错误检查的语言。就像Java为Web开发人员提供了一个干净的开发平台,UnrealScript为3D游戏提供了一个同样干净的、简单的、强大的编程语言。UnrealScript从Java语言中衍生的主要编程观念有:
    • 没有指针并自动进行垃圾回收的环境;
    • 一个简单的单继承类图;
    • 编译时进行强类型检查;
    • 安全的客户端执行的"sandbox(沙箱限制)";
    • 像C/C++/Java代码一样熟悉的外观和感觉。
  • UnrealScript为了在游戏对象和交互方面而不是位和像素方面提供丰富的高层次的编程语言。UnrealScript在设计上必须有一些妥协,我们为了获得开发的简单性和强大性,从而牺牲了执行速度。毕竟, Unreal的底层的、对性能起重要作用的代码是使用C/C++书写的,在那里所获得的性能提高的价值超出了所增加的复杂性。UnrealScript是在那个层次的基础上进行运作的,在物体和交互性的层次上而不是位和像素的底层上。

在UnrealScript开发的早期,我们探索了几个主要的不同的编程语言范例,然后又放弃了,最终开发出了目前的UnrealScript。首先,我们研究使用Sun 和 Microsoft为Windows设计的Java VM(虚拟机) 作为Unreal脚本语言的基础。但最终证明在Unreal的情境中Java并没有提供比C/C++更大的编程好处,反而由于缺乏所需要的语言功能(比如 运算符重载)而增加了一些令人沮丧的限制,另外也证明由于VM过多的任务转换开销和Java垃圾回收器在处理大的对象图形时的低效率导致运行速度特别的慢。其次,我们设计了基于Visual Basic变种的UnrealScript初期实现,它可以工作的很好,但是对于已经习惯于C/C++的程序员来说,它不是那么友好。最终基于我们想将游戏的特定概念映射到语言定义本身的愿望以及获得速度和熟悉度的需要,我们决定将UnrealScript设置为基于C++/Java变种的语言。结果证明这是一个好主意,因为它在很多方面大大地简化了Unreal代码。

语言特点


语言功能

对于已经熟悉UnrealScript的人来说,这里是自虚幻引擎2后在UnrealScript中所发生的主要改变的简短概述。

  • 动态数组 - 动态数组现在有一个新功能 find() ,用于查找一个元素的索引。
  • 动态数组迭代器 - Foreach操作符现在可以应用于动态数组。
  • 从其它的类中访问常量: class'SomeClass'.const.SOMECONST
  • 支持工具提示- 如果在UnrealScript中属性声明的上面有一个 /** tooltip text */ 形式的注释,则在编辑器的属性窗口中,当您将鼠标放到该属性上时可以显示工具提示文本。
  • 默认属性 - defaultproperties块的处理已经发生了很大的 改变/改进。
    • Struct 默认值 - structs现在也可以具有默认值。
    • 不再允许给配置或者本地化变量设定默认值。
    • 在运行时Defaultproperties (默认属性)是随机的,它不再允许执行class'MyClass'.default.variable = 1的操作。
  • 支持元数据 - 通过将属性和各种类型的元数据相关联,扩展了in-game(游戏中)和in-editor(编辑器中)的功能。
  • 调试功能 - 添加了新的调试相关的功能。
  • 支持多个计时器
  • 默认函数参数值 - 现在可以指定可选的函数参数的默认值。
  • 状态栈 - 现在您可以让状态进栈及出栈。
  • UnrealScript预处理器 - 支持宏和条件编译。
  • 接口 - 添加了对接口的支持。
  • Delegate 函数参数 - UE3现在允许传递 delegates作为函数的参数。
  • Replication(复制)-在UE3中replication(复制)语句已经改变:
    • 现在复制(replication)语句块只适用于变量。
    • 函数复制(replication)现在通过以下方法定义:function specifiers (Server, Client, Reliable )

资源

UnrealScript工具和使用


脚本分析

Script Profiler(脚本分析器)能够帮助您了解脚本执行的哪些部分占有的时间最多。

脚本调试器

请查看Unreal的调试工具页面获得更多信息。

高级技术主题


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。