原文:
http://udn.epicgames.com/Two/UnrealScriptLanguageReference.html
将游戏逻辑放在脚本这样的沙盒(Sandbox)中,保护环境客户端调用,防止程序崩溃
虚幻脚本是:
类似java的无指针,自动垃圾回收机制
比C++更高级的时间,状态,属性和网络
引擎程序员使用C++写功能模块,并使用脚本封装成脚本可访问对象方法,脚本程序员根据游戏逻辑需求书写逻辑代码,并暴露可供调试和设置的属性,通过编辑器可以很方便的编辑和修改这些属性
需要花费时间的做事情的函数叫做延时函数,类似Sleep FinishAnim MoveTo等。延时函数只能在状态函数中调用,而不能是普通函数。普通函数只有在延时函数挂起时才能被执行
虚幻只是模拟windows的线程概念而非照抄,windows线程效率很低。
所有虚幻的对象脚本执行都是并行的,对象独立的
所有对象都继承于Object
Actor(继承于Object)定义了一些列诸如可控制的移动,交互和影响的行为
Pawn(继承于Actor) 一个拥有高阶AI和玩家控制级别的对象
Class(继承于Object) 定义了一类物体(有别于C++的class)
类定义修饰符:
Native:
表示其被C++后台支持,如果你有一个类为native Robots,虚幻将会在磁盘上需找Robots.dll实现脚本功能,前提是,C++类必须提供
IMPLEMENT_CLASS
宏定义
Abstrat:
抽象类,类似于C++的接口类,接口类本身不能被实例化,子类必须实现其方法方可实例化。在虚幻脚本中有一个Pawn类,其本身只有用类似Brute类才能被实例化
Guid(a,b,c,d):
现阶段没有被使用,在以后支持COM时将会被添加到虚幻中
Transient:
表明这个类的不必保存到磁盘,也就是说这种类并不是永久使用的,而是暂时的。类似的有Players,Windows
Config(section_name):
一些有必要保存的变量,全局变量。一个类就是一个ini文件
虚幻脚本的name类型可以是函数,状态或者类等等的预定义名称,它和字符串的区别就在于:字符串可以动态修改,而name一次确定,不再修改
变量修饰符:
Input:可以被虚幻输入系统识别的变量,类似于将键盘或者手柄的输入映射于某个变量
Native:
从C++装载或者保存的类型
虚幻只支持1维数组,多维数组请自行实现
函数修饰符
Static:
与C++不同的是:虚幻脚本静态函数只能访问静态函数,一些变量的默认值,不能调用非静态,实例化了的函数。并且不能被重载
Singular:
一种防止函数被递归调用的修饰。当你写一个Actor碰撞另外一个Actor的逻辑时,函数递归调用将会导致栈溢出。添加这个修饰符后,已经被调用的函数就不会被重复调用。
Latent:
延时函数:在状态中调用一个延时函数,在过一定的tick后,该函数才会被返回
Iterator:
定义一个函数为迭代器,用于使用foreach遍历一个actor list
Simulated:
当一个actor是模拟代理(simulated proxy)或者独立代理(
autonomous proxy
)时决定一个函数在客户端运行
Operator:
与C++类似
Event:
只是一个编译器修饰,在使用
unreal -make –h
可以将脚本事件函数与
C++
连接代码对应起来。
脚本中的
event Touch( Actor Other )
{ ... }
在被列出源码为
void Touch(class AActor* Other)
{
FName N("Touch",FNAME_Intrinsic);
struct {class AActor* Other; } Parms;
Parms.Other=Other;
ProcessEvent(N,&Parms);
}
状态
虚幻脚本是唯一一个于语言层支持状态的语言
使用状态的好处:
可以很方便的书写基于状态的函数(逻辑),一些函数和逻辑就可以被不同的状态共享,而其执行结果只是决定于actor实际在做什么。
状态中可以使用延时函数,这是一种特殊类型的函数,C++和java都没有实现。例如:你可以实现以下逻辑:打开一个门,等待2秒后播放音效然后再打开另外一道门,释放一些怪,让它们打玩家,你可以以非常普通常见,线性的逻辑来书写这些代码。而具体的细节将有虚幻底层来实现
3种主要的延时函数
Sleep:
暂停脚本执行一段时间,后继续执行
FinishAnim
等待当前播放的动画完毕后继续执行。使用这个函数可以很方便的书写动画驱动的逻辑,AI逻辑就是严格的的动画驱动(有别于时间驱动),平滑的动画控制是一个AI系统的关键
FinishInterpolation
等待当前茶致电动作完成后继续
状态的流程控制:
使用Goto语句在不同的代码段间切换
auto state MyState
{
Begin:
Log( "MyState has just begun!" );
Sleep( 2.0 );
Log( "MyState has finished sleeping" );
goto Begin;
}
使用GotoState转入执行某个状态
实例
// This is the automatic state to execute.
auto state Idle
{
// When touched by another actor…
function Touch( actor Other )
{
log( "I was touched, so I’m going to Attacking" );
GotoState( ‘Attacking’ );
Log( "I have gone to the Attacking state" );
}
Begin:
log( "I am idle…" );
sleep( 10 );
goto ‘Begin’;
}
// Attacking state.
state Attacking
{
Begin:
Log( "I am executing the attacking state code" );
//...
}
运行结果
I am idle...
I am idle...
I am idle...
I was touched, so I’m going to Attacking
I have gone to the Attacking state
I am executing the attacking state code
状态继承和可视规则
// 父类
class MyParentClass expands Actor;
// 非状态函数
function MyInstanceFunction()
{
log( "Executing MyInstanceFunction" );
}
// A 状态
state MyState
{
// A 状态函数
function MyStateFunction()
{
Log( "Executing MyStateFunction" );
}
// Begin标签
Begin:
Log("Beginning MyState");
}
// 子类实现
class MyChildClass expands MyParentClass;
// 重载非状态函数
function MyInstanceFunction()
{
Log( "Executing MyInstanceFunction in child class" );
}
// 重定义状态,并且重载函数
state MyState
{
// 被重载的函数
function MyStateFunction()
{
Log( "Executing MyStateFunction" );
}
// 标签被重载
Begin:
Log( "Beginning MyState in MyChildClass" );
}
规则:
1. 如果对象是一个状态,无论被定义过多少次,最后重载的那个类将会被执行
2. 否则,按照重载次数,并且是非状态版本的函数被执行
状态可以同样使用expand来继承状态
// Base Attacking state.
state Attacking
{
}
// 上段攻击
state MeleeAttacking expands Attacking
{
}
//远距离攻击.
state RangeAttacking expands Attacking
{
}
使用ignores指定忽略的函数
state Retreating
{
// 忽略以下函数的调用
ignores Touch, UnTouch, MyFunction;
}
默认属性:
默认属性可以在物体在编辑器或者创建的时候给出一些列的预设值。这是一种很好的可重入性表现和代码规范的标准
技术细节:
虚幻的垃圾回收采用与JAVA 虚拟机类似的树跟随方式(tree-following),回收器使用Uobject的序列化功能递归的获取到哪些物体引用了其他的对象
虚幻脚本是基于bytecode的,编译时,脚本将类似于java一样被编译为虚拟机代码。这使得其可以做到方便的跨平台
虚幻脚本是2次pass,第一遍pass确定变量,状态和函数定义,第二遍编译为p-code。这种机制使得复杂的循环依靠的逻辑继承结构可以完整的编译并且链接
可恢复的玩家状态:虚幻可以在游戏进行的任何时刻保存所有actor的状态和运行情况到磁盘,并在必要时恢复。因为其状态和函数执行只是运行于低端的虚拟机,运行结果只是依赖于栈数据,所以,保存栈数据是非常关键的
虚幻文件包:这是一种包含索引,序列化dump和对象细节的二进制格式包。其很类似于DLL,可以拥有一个引用其他包的列表。这样可以很方便的将预定义包放在Internet提供下载,已经下载的包将不会在下载以减少下载次数(manifest机制)
开发策略
C++脚本大约每秒能执行5千万条指令,虚幻脚本能执行250万条指令,相差20倍,整个游戏中,脚本执行需要花费CPU 5~10%的时间。使用虚幻脚本做一些你实际感兴趣的时间,或者需要自定义的部分。而不是将一些基础移动的代码重新书写一遍,物理系统可以为你做一切。
大量使用延时函数,如果依然按照时间驱动的传统方式书写这类逻辑将是低效的
代码书写中减少无限递归的出现:在Move命令执行中,actor将发生碰撞,然后调用Bump函数,如果在Bump中依然有Move指令将形成无限递归。无限递归和死循环是虚幻脚本不能很好解决的地方(所有语言都是-_-! Tim太谦虚)
创建和销毁actor将是服务器端一个非常耗费资源的操作,特别是在网络游戏中,销毁和销毁中需要发包,占用一定的网络带宽。
尽量使用面向对象特性。在写新功能时尽可能的重载已有函数和状态让代码清爽,方便修改,便于和其他人的成果集成。避免使用传统的C技巧,类似于使用switch判断actor的状态并执行不同的操作。这样的操作阻止你加入新的类和修改一些东西。