UDN
Search public documentation:
UnrealScriptFoundationsCH
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
日本語訳
한국어
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
UnrealScript基本概念
概述
什么是UnrealScript?
.uc
文件扩展名的简单文本,它包含了一个单独的UnrealScript类的定义。请参照 UnrealScript类页面获得关于这个类是什么、引擎如何使用这些类、以及如何定义新类的更多信息。
从本质上讲,可以使用任何文本编辑器程序创建UnrealScripts,但是某些文本编辑器没有针对UnrealScirpt提供可以更加方便地使用UnrealScript的高亮显示功能及其他特定功能。某些集成开发环境 - nFringe和WOTGreal - 也提供了使用UnrealScript开发Unreal项目的完整集成解决方案。
脚本命名和位置
Development\Src
目录中的各种文件夹内。这个目录默认包含了很多文件夹 - Core、Engine、UDKBase、UnrealEd等 - 每个文件夹代表不同的UnrealScript工程或包。在每个包文件夹中都有一个 Classes
文件夹,它包含了属于该包的所有UnrealScripts 。
当您想为您的游戏添加引擎所使用的自定义UnrealScirpts时,您必须在 Development\Src
目录中创建一个或多个新的包文件及后续的 Classes
文件夹。这些文件夹是您放置您的UnrealScripts的地方。
请参照自定义UnrealScirpt项目获得关于设置您自己的由引擎使用的自定义UnrealScript包的更多信息。
UnrealScript剖析
/********************* * Class Declaration * *********************/ class MyActor extends Actor; /************************************ * Instance Variables/Structs/Enums * ************************************/ enum MyEnum { ME_None. ME_Some, ME_All } struct MyStruct { var int IntVal; var float FloatVal; } var int IntVar; var float FloatVar; var bool BoolVar; var Actor ActorVar; var MyEnum EnumVar; var MyStruct StructVar; /********************** * Functions & States * **********************/ function MyFunction() { local int TempInt; if(ActorVar != none && BoolVar) { TempInt = IntVar; } } state NewState { function MyFunction() { local float TempFloat; if(ActorVar != none && BoolVar) { TempFloat = FloatVar; } } } /********************** * Default Properties * **********************/ defaultproperties { IntVar=5 FloatVar=10.0 BoolVar=true }
- 类声明
- 每个UnrealScript都以类声明开始。这也就是命名和该脚本相关的类、确定该类所继承的其他类,并可以通过类修饰符来控制类的其他几个方面,这些修饰符选项添加在声明的结尾处。除了您想在开头处添加描述类的注释、指定版权等外,这些应该是脚本中最先出现的内容。
- 实例 变量/结构体/枚举值
- 类声明的后面是实例变量声明。这指定了类所包含的属性。
- 默认属性
-
defaultproperties
是脚本中最后出现的内容。这是为类中声明的实例变量指定默认值的地方。
脚本和类 对 Objects和Actors
脚本间的通信
Pawn
。这个变量的类型是 Pawn
( 因此它的名称也是 Pawn
)。这个变量的声明如下所示:
var Pawn Pawn;
none
。实际上,您必须将一个值(您想引用的实例)赋予那个变量。不幸的是,这里没有固定的规则可以遵循。有很多获得到世界中一个对象实例的引用的方法,您如何操作完全取决于您的特定情况及那两个对象之间的关系。一些示例:
- 辅助函数- 在某些情况下,某些类中提供了一些辅助函数,它们可以返回到常用对象的引用。比如,
GFxMoviePlayer
类具有GetPC()
函数,它返回到具有那个视频的PlayerController
的引用。 - 事件- 一个actor在另一个actor中触发一个事件,比如
Touch
事件。很多情况下,引擎会自动地将触发事件的actor的引用传递给那个事件。这允许您在事件中使用那个引用或者将其保存到一个变量中以便稍后使用。 - 生成 -
Spawn()
函数返回到它创建的actor的引用。当一个actor生成另一个actor并需要立即或稍后同该生成的actor进行通信时,您可以使用Spawn()
函数返回的引用或者将该引用保存到一个变量中。 - 第三方 - 很多时候,您需要引用的对象正在被您所引用的另一个类所引用。这意味着您可以您可以借用那个引用为己用。作为示例,一般需要及使用这个处理的是当前游戏类型或者
GameInfo
类的实例。唯一一个具有到游戏类型直接引用的类是WorldInfo
,它通过它的Game
进行引用,但是您通常需要在其他位置访问该引用。幸运的是, 每个Actor都具有到当前WorldInfo
实例的引用,通过WorldInfo
变量实现。这意味着您可以使用您的WorldInfo
引用来获得到 GameInfo 实例的引用。这种方法通常被初学者所忽略。要经常检查您已经引用的对象,因为它们可能具有您正在查找的引用。 - 迭代- 使用迭代器函数类似于执行一种搜索。它们允许您指定一些标准并以对象引用的形式返回一个结果列表。您可以在迭代器中直接对这些对象引用进行操作或者将它们保存到变量中稍后使用。
- 编辑器 - 在某些情况下,您可以让关卡设计人员在编辑器中设置一个到actor的引用。这所需要做的就是创建一个可编辑变量来存放该引用。
Health
变量,您可以这么做:
Pawn.Health
StartFire()
函数来实现:
Pawn.StartFire(0);
GFxMoviePlayer
类中的 GetPC()
函数。该函数返回了具有Scaleform视频的 PlayerController 。现在,假设我们想访问当前玩家的生命值,该值存放在由PlayerController控制的Pawn所存放。和上面一样,我们可以使用controller中的 Pawn
变量,但是这次我们必须先引用controller,因为我们位于 GFxMoviePlayer
类中。但我们不需要将该引用保存到一个变量中。我们可以直接结合 GetPC()
函数调用使用点操作符。
GetPC().Pawn.Health
GetPC().Pawn.StartFire(0);
GetPC()
是到 PlayerController
实例的引用,因为该实例使它返回的值,正如您在 GFxMoviePlayer
类的函数声明中所看到的一样:
/**
* Helper function to get the owning player controller for this movie
*
* @return The PlayerController corresponding to the LocalPlayerOwnerIndex that owns this movie
*/
event PlayerController GetPC()
{
local LocalPlayer LocalPlayerOwner;
LocalPlayerOwner = GetLP();
if (LocalPlayerOwner == none)
{
return none;
}
return LocalPlayerOwner.Actor;
}
如何使用现有脚本?
Development\Src
目录下的几个不同的包中。理解这些类是什么及如何在游戏中使用它们是非常重要的。现有的类主要提供了基本的通用的功能。从本质上讲,它们是引擎本身的一部分而不是“游戏”的一部分,尽管有些时候这很难分辨。大部分主要的系统及构成它们的类都在UDN上的各种“技术指南”中进行了解释。要想获得关于所有这些可用类的完整理解,您可能需要通过跟踪实际的脚本本身并分析代码、阅读注释等来进行研究。关于这方面的一个非常有用的工具是 UnCodeX 。它根据脚本创建了Java帮助文档风格的文档,很容易导航阅读。
首先要理解的是在正常情况下您不能修改现有的脚本。修改native类或native包中的类且不重新编译引擎将会导致严重的后果,一般会导致游戏运行时引擎崩溃。
UnrealScript是个面向对象的语言,意味着每个类都以UnrealScript 用法继承或 扩展 另一个类,从而创建了一种父类-子类关系。子类继承其父类中存在的所有变量、函数、状态等。所以,您应该继承现有脚本,使用它们作为您自己的自定义类的起点,而不是修改现有脚本。这样做的好处是您不会受到父类传递给子类的内容的限制,因为您可以添加您需要的任何新变量和函数;您也不会受到父类所传递的函数的实现方式的限制,因为UnrealScript允许您重载任何继承的函数,使它执行您需要的任何特定动作。
某些情况下,仅继承类而不能修改类看上去是种限制,因为通常都会想到在现有基类上添加一个变量,以便该变量可以在所有其子类中可用。但通常都有一种不需要修改现有类而可以设计出您的系统的可替换方法。
基本规则: 继承现有类并重载函数,永远不要修改现有类。
继承哪些类
对象层次
- Object
- Unreal中所有对象的父类。在
Object
类中的所有函数可以在任何地方访问,因为任何东西都继承于Object
。==Object== 是一个抽象基类,它没有做任何有用事情。所有的功能都是由子类提供,比如Texture
(一个贴图)、TextBuffer
(文字块)和Class
(它描述了其它对象的类)。 - Actor (extends Object)
- Unreal中所有独立游戏对象的父类。Actor包含了一个物体所需要的用于四处运动、和其它物体进行交互、影响环境及做其它与游戏相关的有用的事情的所有功能。
- Pawn (extends Actor)
- Unreal中所有的可以使用高层次AI和玩家控制的生物和玩家的父类。
- Class (extends Object)
- 一种特殊的对象类别,描述了一类对象。刚看到时可能是令人疑惑的: 一个类是一个对象,而其一个类描述某些对象。但是这概念是合理的,并且在很多情况下您将会用到Class的对象。例如,当你在UnrealScript产生一个新的actor,你可能会通过Class对象来指定actor的类。
UnrealScript编程策略
- UnrealScript和C/C++ 相比是一个较慢的语言 。 一个典型的C++程序的运行速度会比UnrealScript快20倍。我们自己书写脚本的编程思想是: 书写的脚本在大部分时候是不运行的。换句话说,仅使用UnrealScript来处理你想进行自定义的感兴趣的事件,而不是反复执行的任务,比如基本的运动,这些由Unreal的物理代码为您处理。比如,当您书写一个射弹脚本,你一般书写HitWall()、Bounce()和Touch()函数来描述当主要的事件发生时需要做什么处理即可。因此95%的时间中,您的射弹脚本是不会被执行的,它仅仅是等待物理代码来通知它一个事件。这是非常的高效的。在一般的关卡中,尽管UnrealScript要比C++慢很多,但UnrealScript的执行时间平均只占CPU执行时间的5-10%。
- 尽可能地使用latent 函数(比如FinishAnim和Sleep) 。通过基于这些latent函数控制你的脚本的执行流程,您正在创建动画驱动或时间驱动的代码,这在UnrealScript中是非常高效的,
- 当您在测试脚本时,请关注您的Unreal 日志 。在运行时通常会在日志文件中生成一些有用的警告来通知您正在发生的非致命问题。
- 提防会引起无限递归的代码 。比如,"Move"命令移动actor,当您在碰撞某物时调用您的Bump()函数。因此,如果您在一个Bump函数中使用一个Move命令,那么请小心,您有进入无限递归的危险。注意: 无限递归和无限循环是UnrealScript不能很好处理的两个错误情形。
- 在服务器端产生和销毁actors 是相当昂贵的操作,在网络游戏中则更加的昂贵,因为产生和销毁actors 会占用网络带宽 。请您合理的使用它们,把actors当成“性能消耗非常大”的对象对待。比如,不要尝试通过产生100个独立的actors来创建一个粒子系统并使用物理代码让它们在不同的轨道进行发射。那将会导致运行非常非常的慢。
- 尽可能地使用UnrealScript 的面向对象兼容性能 。通过重载现有的函数和状态来创建新的功能,会使干净的代码更容易进行修改及更容易和其他人的代码进行集成。避免使用传统的C技术,比如基于一个actor或state的类来使用一个switch()语句,因为这样的代码在您增加一个新类及修改一些东西时更容易被破坏。
- UnrealScript的.u包文件是严格地按照.ini文件的EditPackages列表进行编译的 ,所以每个包仅能引用本包中及在它前面编译的包中的对象,而不能引用在它后面编译的包中的对象。如果您发现需要在包之间进行循环引用,有两个解决方案:
- 把类分成需要在第一个.u包中进行编译的一组基类包和一组需要在第二个.u包中进行编译的子类包,要保证基类永远不会引用子类。不管怎样,这都是很好的编程实践,一般都会有效。
注意: 如果给定类C
需要一个到滞后连编包中的一个类或对象O
的引用,那么您可以把那个类分解为两部分: 在第一个包中定义一个抽象基类C
,它定义了一个变量MyO
(但在它的默认属性中不要给MyO
设定默认值);在第二个包中定义一个子类D
,它为MyO
指定了适当的默认值,而且仅能在第二个包中来进行默认值的指定操作。 - 如果两个.u包由于相互引用而彻底的纠缠在一起,那么可以把它们融合到一个包中。这是合理的,因为包是作为代码模块化的一个单元来设计的,并且把这些不能分开的一组类分别放到多个包中不能带来任何实质性的好处(比如节约内存)。
- 把类分成需要在第一个.u包中进行编译的一组基类包和一组需要在第二个.u包中进行编译的子类包,要保证基类永远不会引用子类。不管怎样,这都是很好的编程实践,一般都会有效。