導言 編輯

文檔目標 編輯

這是一份講述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編程策略 編輯

參考資料 編輯