跳转到内容

UnrealScript

维基教科书,自由的教学读本

导言

[编辑]

文档目标

[编辑]

这是一份讲述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编程策略

[编辑]

参考资料

[编辑]