导言 编辑

文档目标 编辑

这是一份讲述UnrealScript语言的技术性文档。这不是一个教程,也没有提供有用的UnrealScript代码的详细例子。读者可以参考虚幻引擎,它包了数万行的UnrealScript源代码,它解决了诸如人工智能,运动,inventory,和触发器等问题。一个好的入手点是看"Actor", "Object", "Controller", "Pawn", and "Weapon" 的脚本。

本文假设读者具备C/C++ 或Java的编程经验,熟悉面向对象编程,玩过虚幻游戏,用过虚幻关卡编辑器(UnrealEd)。

对于OOP(面向对象编程)新手,强烈建议去书店买本Java的书。Java和UnrealScript非常相似,它清晰、简洁、优秀。

UnrealScript的设计目标 编辑

由于游戏编程的自然需要和细微差别UnrealScript被创建出来,为开发团队和第三方虚幻开发商提供了一个强大的内置编程语言。

UnrealScript的主要设计目标是:

  • 支持传统编程语言没有解决的时间,状态,属性的网络化。这大大简化了UnrealScript的代码。编写基于人工智能和游戏逻辑的程序,主要运用事件交互技术,这些事件采取一定的游戏时间完成,并且事件非常依赖对象状态,这让网络化问题极度复杂化。在c/c++中书写、理解、维护和调试这样的代码是非常困难的。UnrealScript天生支持时间、状态、网络复制大大简化了游戏编程。
  • 提供Java编程风格,简单、基于对象的、编译时错误检查的特性。就象Java为Web程序员带来了清晰、简洁的编程平台一样,UnrealScript为3D游戏程序员提供了一个同样清晰、简洁、可靠的编程语言。从Java继承的主要编程概念如下:
    • 具有自动垃圾收集的无指针环境;
    • 一个简单的类单一继承机制;
    • 强壮的编译时类型检查;
    • 安全客户端执行的“沙盒”;
    • 熟悉的c/c++/java代码外观和感受;
  • 提供游戏对象丰富的高层互动,而不是底层的位和像素。在设计权衡时,由于UnrealScript工作在对象互动层,而不是底层的位和像素,因此在UnrealScript中,牺牲了性能选择了简单性和功能。而在底层和性能的关键代码是用c/c++写的,在这些地方增加性能获得的好处超过了增加复杂性得到的坏处,选择了性能牺牲了简单性和功能。

在UnrealScript的早期发展期间,我们探索过几个主要的不同的编程模式。首先,我们研究把Sun和微软的Java虚拟机的Windows版本,作为虚幻脚本语言的基础。证明了在虚幻中Java由于缺乏必要的语言特性(如运算符重载),而拥有令人沮丧的限制,同时在巨型图形对象的情况中,虚拟机任务切换和Java垃圾收集器效率过低,慢得无法接受,Java没有提供不在c/c++中编程的优点。其次,我们尝试使用VB的一个早期修改版作为UnrealScript的实现,效果良好,但可惜的是它不符合c/c++程序员的习惯。最终,基于对速度和熟悉感的期望,我们决定UnrealScript采用c++/java的一个修改版来实现,同时融入游戏特有的概念到语言的定义中。

虚幻引擎3的新内容 编辑

熟悉UnrealScript的读者们,可以在这里找到自虚幻引擎2后的重大改变。

  • 复制-复制语法在UE3中有以下变化:
    • 复制块现在只用于变量
    • 复制函数现在由函数说明符定义(Server, Client, Reliable)
  • 堆叠状态-你现在可以把状态从堆栈中推进和弹出
  • UnrealScript预处理-支持宏和条件编译
  • 调试函数-添加了新的调试函数
  • 默认属性-defaultproperties有所改变和增强
  • 默认结构-现在结构也有默认属性了
    • 不允许再给配置或局部变量设置缺省值
    • Defaultproperties在运行变为只读,不允许做class'MyClass'.default.variable = 1 操作
  • 动态数组-动态数组新增了一个find()方法,用于按索引查询元素
  • 动态数组迭代器-现在能用foreach操作动态数组的方法
  • 函数委托-现在UE3允许委托作为函数的参数
  • 接口-添加对接口的支持
  • 访问其他类的常量:class'SomeClass'.const.SOMECONST
  • 支持多定时器
  • 函数默认参数值-现在能指定函数的可选参数了,给可选参数设定默认值即可
  • 支持工具提示(Tooltip)-当您的鼠标悬浮在属性上时,如果那个属性的UnrealScript上面存在/** 工具提示文本 */的注释声明,那么编辑器属性窗口将会显示一个包含该注释信息的工具提示。
  • 支持元数据-通过与元数据相关联的各种属性扩充游戏和编辑器的功能

程序结构示例 编辑

这一个典型、简单的UnrealScript类的例子,它演示了UnrealScript的语法特点。请注意,此代码可能和当前的虚幻源代码不一致,因为这个文档并不参与代码同步。

//=====================================================================
// TriggerLight.
// A lightsource which can be triggered on or off.
//=====================================================================

class TriggerLight extends Light;

//---------------------------------------------------------------------
// Variables.

var() float ChangeTime; // Time light takes to change from on to off.
var() bool bInitiallyOn; // Whether it's initially on.
var() bool bDelayFullOn; // Delay then go full-on.

var ELightType InitialType; // Initial type of light.
var float InitialBrightness; // Initial brightness.
var float Alpha, Direction;
var actor Trigger;
 
//---------------------------------------------------------------------
// Engine functions.
 
// Called at start of gameplay.
function BeginPlay()
{
   // Remember initial light type and set new one.
   Disable( 'Tick' );
   InitialType = LightType;
   InitialBrightness = LightBrightness;
   if( bInitiallyOn )
   {
      Alpha = 1.0;
      Direction = 1.0;
   }
   else
   {
      LightType = LT_None;
      Alpha = 0.0;
      Direction = -1.0;
   }
}
 
// Called whenever time passes.
function Tick( float DeltaTime )
{
   LightType = InitialType;
   Alpha += Direction * DeltaTime / ChangeTime;
   if( Alpha > 1.0 )
   {
      Alpha = 1.0;
      Disable( 'Tick' );
      if( Trigger != None )
         Trigger.ResetTrigger();
   }
   else if( Alpha < 0.0 )
   {
      Alpha = 0.0;
      Disable( 'Tick' );
      LightType = LT_None;
      if( Trigger != None )
         Trigger.ResetTrigger();
   }
   if( !bDelayFullOn )
      LightBrightness = Alpha * InitialBrightness;
   else if( (Direction>0 &amp;amp;amp;&amp;amp;amp; Alpha!=1) || Alpha==0 )
      LightBrightness = 0;
   else
      LightBrightness = InitialBrightness;
}
 
//---------------------------------------------------------------------
// Public states.
 
// Trigger turns the light on.
state() TriggerTurnsOn
{
   function Trigger( actor Other, pawn EventInstigator )
   {
      Trigger = None;
      Direction = 1.0;
      Enable( 'Tick' );
   }
}
 
// Trigger turns the light off.
state() TriggerTurnsOff

{
   function Trigger( actor Other, pawn EventInstigator )
   {
      Trigger = None;
      Direction = -1.0;
      Enable( 'Tick' );
   }
}
 
// Trigger toggles the light.
state() TriggerToggle
{
   function Trigger( actor Other, pawn EventInstigator )
   {
      log("Toggle");
      Trigger = Other;
      Direction *= -1;
      Enable( 'Tick' );
   }
}
 
// Trigger controls the light.
state() TriggerControl
{
   function Trigger( actor Other, pawn EventInstigator )
   {
      Trigger = Other;
      if( bInitiallyOn ) Direction = -1.0;
      else Direction = 1.0;
      Enable( 'Tick' );
   }
   function UnTrigger( actor Other, pawn EventInstigator )
   {
      Trigger = Other;
      if( bInitiallyOn ) Direction = 1.0;
      else Direction = -1.0;
      Enable( 'Tick' );
   }
}

示例代码关键元素分析:

  • 类声明。每个类"extends"(继承自)一个父类。每个类属于一个"package"(包)。包是一个,把多个相关对象组织在一起的对象集合。所有函数和变量属于一个类,并且只能通过属于这个类的元件(actor)进行访问。没有所谓的全局函数和全局变量。详见
  • 变量声明。UnrealScript支持非常多的变量类型,包括c/java的基本类型、对象引用、结构和数组。此外,变量还可声明为让设计人员无需进行任何编程工作,就能在UnrealEd(虚幻关卡编辑器)访问的可编辑属性。这些属性使用var()语法进行声明,而不是用var。详见
  • 函数。函数有一个可选的参数列表和返回值。函数可以有局部变量。一些函数由虚幻引擎本身调用(比如BeginPlay)。一些函数由其他地方的脚本代码调用(比如Trigger)。详见
  • 代码。支持c和java的标准关键字,象for、while、break、switch、if等等。花括号和分号的用法和c、c++和java中的一样。
  • 引用元件(actor)和对象。在脚本中,你能看到一些函数,被另外一个对象通过对象引用调用的情况。详见
  • “state“(状态)关键字。这个脚本定义了一些“state“,它由函数、变量和代码构成,这些成分只在元件(actor)进入相应的状态时才被执行。详见
  • 请注意,在UnrealScript中所有关键字、变量名、函数和对象名是不区分大小写的。在UnrealScript、Demo、demON和demon是相同的东西。

虚幻虚拟机 编辑

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

虚幻服务器控制玩家和元件之间的所有互动。在单人游戏中,客户端和服务端运行在同一台机器上;网络游戏的服务器运行于一台专用机器中;所有玩家使用客户端链连接到这台机器上。

所有玩家都在关卡中进行游戏,关卡是个包含几何体和元件的独立环境。虚幻服务器有同时运行多个关卡的能力。每个关卡独立运作互相隔离:元件不能在关卡之间往来,并且一个关卡中的元件不能和另外一个关卡中的元件进行通信。

地图中的每一个元件可以被玩家(网络游戏中可以有很多玩家)和脚本控制。脚本可以定义元件如何移动以及如何和其他元件交互。游戏世界中的元件有序的运作,脚本的执行,事件的发生,等等的一切是如何在UnrealScript中实现的。你可以在下面找到答案:

在时间处理方面,虚幻将每秒的游戏逻辑划分为tick做单位的时间片。一个tick是活动对象(actor)被 update的最小时间间隔。在大部分情形下,一个tick 为百分一或者十分一秒tick的划分细度是依cpu的表现性能的。cpu越强大,计算能力越强,tick就可以划分得越细。

UnrealScript中的一些命令零tick执行(也就是说,执行它们,游戏时间没有任何流逝),而另一些则需要花费许多tick。需要花费游戏时间的函数称为“潜伏函数”。Sleep,FinishAnim,和MoveTo都是潜函数的例子。潜伏函数只能在状态代码(state code)中调用,不能从函数代码中调用(包括state函数定义)。

在活动对象的潜伏函数执行完成前,它的state不会继续执行。而它的非潜伏函数仍然可以被其他活动对象和VM调用。这样的结果是,可以在任何时间调用UnrealScript函数,即使潜伏函数还未完成。

从传统编程角度来看UnrealScript的行为,似乎关卡中的每个活动对象都执行在自己的线程中。实际上,在UnrealScript内部并没有使用Windows线程,因为那样效率过低(Window95和WindowNT无法高效的处理数以千计的线程并发)。而是采用UnrealScript模拟线程。这对UnrealScrit来说是透明得,当你用c++书写和UnrealScript进行交互的代码时显得非常明显。

所有得UnrealScript脚本是独立执行得。如果在一个关卡中有100个怪物在周围走动,所有这些怪物脚本对每一个tick来说是同时独立得执行得。

笔记:有两种时间概念:1、现实世界得时间;2、游戏世界中得时间;现实中得时间一般是用秒来衡量的,也不存在最小时间单位这个概念,因为物理上得时间是很难把握得,我们能感觉时间得流逝,确无法准确得测量它定义它(除非理论物理得到突破,建立大统一理论)。。。。,而游戏中得时间是由程序定义得,我们从现实中得时间简化出了游戏世界时间得概念,在虚幻中游戏时间得最小单位是tick(相当于现实得10-100毫秒),为了保持游戏对象得时间同步,UnrealScript使用了模拟线程,以保证在定义中得游戏世界中游戏对象时间得绝对性质(游戏对象得状态变化绝对独立,每个游戏对象得时间绝对同时流逝),这在现实中是不可能得。现实中不存在绝对时间得概念,即使通常差别不大。这并不是说几亿个cpu同时执行就能解决同时性的问题,物理本质上就不存在所谓的绝对同时,时间的绝对性是个幻觉。

对象层次结构 编辑

编辑

每个脚本文件对应一个类,以class声明为开头,后面是类的父类和类的相关信息。简单的例子如下:

class MyClass extends MyParentClass;

这里声明了一个名称为“MyClass”的新类,这个类继承了“MyParentClass“的所有功能。此外类驻留在名为”MyPackage“的包中。

每个类继承父类的所有变量和函数以及状态。每个类都可以申明新的变量和新的函数(或者重写已经存在的函数)以及添加新的状态(或为已有的状态添加功能)。

在UnrealScript中设计一个新类(例如一个牛头人怪物)的典型的做法是,从具备你需要的功能的已有类(例如,Pawn类,所有怪物的基类)继承。

通过这样的做法,你永远都不用重新发明轮子--可以简单的添加新功能,你可以在自定义的同时保持不需要定制的现有功能。这种做法特别适合从虚幻中继承AI,内置的AI系统提供了大量的基本功能,同时你还可以建立自己的生物。

类声明有以下可选的说明符:

Native(包名称)(本机类)

指示:“这个类使用幕后c++支持“。虚幻native类包含在一个c++写的exe中。只有native类可以申明native函数或实现native接口。native始终来自native类。与脚本变量和特定的函数协作,native类需要创建一个自动生成的C + +头文件。默认情况下,包名是脚本类所在的exe名称。例如,引擎包中的类,要求EngineClasses.h文件。

NativeReplication

表示这个类的变量复制处理在c++中实现。只能用于native类。

DependsOn(ClassName[,ClassName,....])(依赖于)

表示ClassName是在这个类前编译。ClassName必须和这个类处于同一个包或者前一个包。可以用一个DependsOn指定多个依赖类,他们用逗号隔开。也可以使用多个DependsOn指定。

Abstract

声明类是一个“基本的抽象类”。这可以防止这个类被无意的实例化。这个关键字会对其内部子类起作用,但是不会对其他脚本文件中的子类起作用。

Deprecated(过时的)

类的所有对象被加载但不保存。当关卡设计师在关卡设计器中载入包含过时活动对象被的地图时,他们会收到编辑器的警告信息。这个关键字会对其子类产生作用。

Transient(瞬态)

表示该类的对象不应该被保存在磁盘上,这和一些非持久性的本机类自然结合是有用的。比如播放器和窗口。这个关键字会对其子类起作用;子类能使用NotTransient关键字重写它。

NotTransient(非瞬态)

表示不从基类继承Transient关键字标注的瞬态特性。

Config(IniName)(配置)

表明这个类允许访问ini文件中的数据。如果在类中有可配置变量(使用config和globalconfig申明),这可以让类访问那些保存在指定的配置文件的变量。这个标志将被传播到所有子类并且不能被否定,但是子类可以通过config关键字指定不同的配置文件。通常IniName指定ini文件的名称去存储数据,但有些名字具有特殊意义:

  • Config(Engine):用于引擎配置文件,通过你的游戏名称追加"Engine.ini"。例如,ExampleGame游戏的引擎配置文件名称是"ExampleEngine.ini"。
  • Config(Editor):用于编辑器配置文件。通过你的游戏名称追加“Editor.ini"。例如,ExampleGame游戏的编辑器配置文件名称是"ExampleEditor.ini"。
  • Config(Game): 用于游戏配置文件。通过你的游戏名称追加"Game.ini"。例如,ExampleGame游戏的配置文件名称是 ExampleGame.ini。
  • Config(Input):用于输入配置文件。通过你的游戏名称追加“Input.ini“。例如,ExampleGame游戏的输入配置文件名称是ExampleInput.ini。
  • PerObjectConfig:类的每个对象的配置信息都会被保存在配置文件中。每个对象在配置文件中都有一个如下格式的配置节[ObjectName ClassName]。这个关键字会对其子类起作用。

PerObjectLocalized

类得本地化数据将被定义在每对象得基础上。每个对象在已命名得本地化文件中有个如下格式得节[ObjectName ClassName]。这个关键字会对其子类起作用。

EditInlineNew

编辑器,表示这个类的对象能从虚幻便捷器的属性窗口创建(默认行为是可以通过关联的属性窗口引用已经存在的对象)。这个关键字会对其子类起作用。子类可以使用NotEditInlineNew关键字重写它。

NotEditInlineNew

编辑器,不从基类继承EditInlineNew关键字。如果没有任何父类使用EditInlineNew关键字将不起效果。

Placeable(可放置)

编辑器。表示这个类可以通过虚幻编辑器放到关卡,UI场景,或者kismet窗口(由类的类型定)。这个标志将被传播到其所有子类中;子类能通过NotPlaceable关键字重写它。

NotPlaceable

编辑器。不从基类继承NotPlaceable关键字。表示这个类不能在虚幻编辑器中放入关卡。

HideDropDown

编辑器。防止这个类在虚幻编辑器的属性窗口的组合框中显示。

HideCategories(Category[,Gategory,......])

编辑器。表示这个类的对象在虚幻编辑器的属性窗口中隐藏一个或多个类别。使用无类别声明可以隐藏变量,使用类名称声明变量的名称。

ShowCategories(Category[,Category,......])

编辑器。不从基类继承ShowCategories关键字。

AutoExpandCategories(Category[,Category,......])

编辑器。指示一个或多个类别在编辑器的属性窗口中自动展开。使用无类别声明可以自动展开变量,使用类名称声明变量的名称。

Collapsecategories

编辑器。表示类的属性不应该在虚幻编辑器的属性窗口类别中被分组。这个关键字会对其子类起作用;子类可以用DontCollapsecategories关键字重写它。

DontCollapsecategories

编辑器。表示不从基类继承Collapsecategories。

Within ClassName

高级。表示类的对象离不开ClassName的实例。为了建立这个类的对象,您必须指定作为外部对象ClassName的实例。这个关键字必须作为class申明的后的第一关键字。

Inherits(ClassName[,ClassName,......])

高级,使用多继承。只是适用于native类。不支持从多个UObject类继承。

Implements(ClassName[,ClassName,......])(实现)

高级。指示类实现的多个接口。只有nataive类可以实现native接口。

NoExport

高级。表明这个类的c++声明不应该通过脚本编译器包含在自动生成c++头文件中。类的c++声明手动定义在一个单独的头文件中。只适用于native类。

变量 编辑

变量类型 编辑

内置类型 (Built-in types) 编辑

这里有一些在 UnrealScript 中宣告变数的例子:


var int a; // 宣告一个名称为"A"的整数变数。

var byte Table[64]; // 宣告一个长度64的静态 1-byte 阵列。

var string PlayerName; // 宣告一个名称为"PlayerName"的字串变数。

var actor Other; // 实体化 Actor 类别,并命名为"Other"。

var() float MaxTargetDist; // 宣告一个名称为"MaxTargetDist"的浮点数变数,并且它的值可以从 UnrealEd 的属性窗口中修改。


变数在 UnrealScript 能出现在两种地方:实体变数,它可以在整个类别内使用,在宣告完类别或 struct 后立即出现。局部变数,出现在函数中,只有在函数执行时有效。实体变数使用关键字 var 宣告。局部变数用关键字 local 宣告,例如:


function int Foo()

{

  local int Count;
  Count = 1;
  return Count;

}


以下是 UnrealScript 中支持的内置变数类型:


byte: 1-byte 值,范围从 0 到 255。

int: 32-bit 整数值。

bool: 布林运算值: 不是 true 就是 false。

float: 32-bit 浮点数值。

string: 一个字串(see Unreal Strings)。

constant: 不能修改的变数。

enumeration: 一个能够代表多个预先设定的整数值中的其中一个值。例如:在 Actor 内定义为 enumeration 的变数 ELightType 描述了一个动态光源,它的值可以像 LT_None、LT_Pulse、LT_Strobe,等等...。

聚合类型(Aggregate data types) 编辑

虚幻类型 编辑

变量说明符(specifiers) 编辑

变量可编辑机制(Editability) 编辑

数组 编辑

结构 编辑

结构说明符(specifiers) 编辑

枚举 编辑

常量 编辑

对象和元件(actor)引用变量 编辑

类引用变量 编辑

表达式 编辑

赋值 编辑

类之间转换对象引用 编辑

函数 编辑

函数声明 编辑

函数参数说明符(specifiers) 编辑

函数重载(Function Overriding) 编辑

"函数重载"是指在子类别中写一个同名的新函数。例如:你正在为一个名叫 Demon 的新怪物写一个类别。这个 Demon 类别,是继承自 Pawn 类别。现在,当怪物重生第一次看到玩家时,会呼叫父类别 Pawn 的 SeePlayer 函数,所以这个怪物会开始攻击这个玩家。这是一个很好的概念,但是当你想在你的新的 Demon 类别中以不同的方式处理 SeePlayer 函数时,你该怎么实现?答案就是函数重载。

重载一个函数,只需要从父类别中将函数的定剪下并复制贴上到你的新类别中。例如:你可以加入SeePlayer 函数到你的新类别 Demo 中。


// New Demon class version of the Touch function.

function SeePlayer( actor SeenPlayer )

{

  log( "The demon saw a player" );
  // Add new custom functionality here...

}

函数重载是创造新 UnrealScript 类别的关键。你可以创造一个继承自现有类别的新类别。然后,你需要做的事是重载一个你需要进行不同做法的函数。这可以使你在不必写大量程式码的情况下就建造一个新的类别。

在 UnrealScript 中的某些函数以 final 关键字做定义。这个 final 关键字(在关键字 function 马上出现的修饰字)意思是"这个函数无法被子类别所重载"。这可以用在一个你知道没有人会去重载的函数中,因为它会有更快的代码执行速度。例如:假如你有一个称做 VectorSize 用来计算一个向量大小的函数。可以确定的是绝对没有任何原因来让任何人重载它,所以可以在 function 后马上加入关键字 final。另一方面,像 Touch 这类的函数是很注重情境的,所以不能宣告它为 final。

函数高级说明符(specifiers) 编辑

控制结构 编辑

循环结构 编辑

For循环 编辑

Do循环 编辑

While循环 编辑

Continue语句 编辑

Break语句 编辑

分支结构 编辑

If-Then-Else语句 编辑

Case语句 编辑

Goto语句 编辑

语言功能 编辑

内置运算符及其优先级 编辑

一般功能 编辑

创建对象 编辑

整数函数 编辑

浮点功能 编辑

字符串函数 编辑

矢量函数 编辑

定时器函数 编辑

调试函数 编辑

UnrealScript预处理 编辑

UnrealScript工具和实用程序 编辑

脚本探测 编辑

脚本调试器 编辑

语言高级特性 编辑

定时器 编辑

状态 编辑

状态概述 编辑

状态标签和潜伏函数 编辑

状态继承和作用域法则 编辑

高级状态规划 编辑

堆叠状态 编辑

复制 编辑

迭代语句(Foreach) 编辑

函数调用说明符 编辑

在可变类中访问静态函数 编辑

变量的默认值 编辑

通过类引用访问变量的默认值 编辑

使用defaultproperties块指定默认值 编辑

语法 编辑

默认结构 编辑

动态数组 编辑

长度变量 编辑

迭代动态数组 编辑

接口类 编辑

函数委托 编辑

Native类 编辑

元数据支持 编辑

元数据概述 编辑

使用多个元数据规范 编辑

可用的元数据规范 编辑

高级技术问题 编辑

UnrealScript实施 编辑

UnrealScript二进制兼容性问题 编辑

技术说明 编辑

UnrealScript编程策略 编辑

参考资料 编辑