2013年12月26日 星期四

編輯器的復原和重做功能

UEd裡的許多編輯器有提供常見的復原(Undo)和重做(Redo)功能,例如AnimSet Editor、Matinee、PhAT等,它們其實都是透過交易(Transaction)功能實作出來的。本篇就來介紹一下如何利用交易為自訂的編輯器寫出復原和重做功能。

首先要知道交易功能是利用UObject的序列化(Serialization)實作出來的功能,概念上就是把變更前後的物件狀態儲存起來,然後復原時恢復成變更前的狀態,重做時回到變更後的狀態。所以你想要復原的狀態必需是某個UnrealScript類別的欄位。

基本上復原和重做功能就是在變更物件時順便建立一份交易資料,然後利用它復原或重做。建立交易資料的步驟如下:

2013年11月17日 星期日

擴充分割視窗

UE3內建支援四個分割視窗,可分別對應到四個本地玩家。本篇將介紹相關的類別,以及如何擴充到更多的分割視窗。

分割視窗主要和LocalPlayer和GameViewportClient這兩個類別有關係。

LocalPlayer存放本地玩家的相關資料,包括個別玩家的視埠位置和大小。LocalPlayer物件存放在Engine的GamePlayers陣列裡,有幾個本地玩家就有幾個LocalPlayer。
  • Origin [ Vector2d ]:本地玩家視埠在畫面中的位置,以左上角為準。值域正規化在[0,1]之間。
  • Size [ Vector2d ]:本地玩家視埠在畫面中的長寬。值域正規化在[0,1]之間。
  • ControllerId [ int ] :指定這個本地玩家使用哪個遊戲控制器。

GameViewportClient負責視埠的相關功能,並且繪製出每個本地玩家的視埠畫面。 它定義了一個分割視窗的列舉型別叫做ESplitScreenType對應到一到四個分割視窗的排版,例如兩個分割視窗就可分成水平和垂直分割兩種:
水平分割
垂直分割

2013年10月17日 星期四

自訂UnrealScript型別轉換

UnrealScript跟其他程式語言一樣,有型別轉換的功能。每個型別轉換對應到一個轉換指令,可在一個二維全域陣列GConversions中查表找出對應的指令代碼,再到全域函式指標陣列GCasts中找到對應的轉換函式。

如果要自訂型別轉換,大致需要以下步驟:
  • 在列舉型別ECastToken中新增一個成員代表這個轉換指令。
  • GConversions的第一個索引是目標型別,第二個索引是來源型別。我們需要修改對應的GConversions元素成自訂的轉換指令。
  • 撰寫對應的轉換函式並且註冊到GCasts。
 

範例


本例示範如何新增一個從物件轉成名稱的型別轉換,轉換後變成該物件的名稱。首先新增CST_ObjectToName代表這個轉換:

2013年9月19日 星期四

自訂UnrealScript常數

UnrealScript跟其他程式語言一樣,有提供常數這個語言構件,本篇來介紹一下要怎麼擴充自訂的常數型別。

在UnrealScript中,常數實際上是用一個編成位元組碼(byte code)的指令代表。要自訂新的常數,大致上需要以下的步驟:
  • 找一個還未使用的位元組碼當作自訂的常數指令碼
  • 如果這個常數需要使用新的名稱,可以在UnNames.h註冊新的名字
  • 修改UByteCodeSerializer使得新增的指令可以正確地序列化
  • 撰寫對應的函式並且註冊到GNatives
  • 修改FTokenBase使解析器可以記錄新的常數型別
  • 修改FScriptCompiler讓編譯器認得新的指令

2013年8月31日 星期六

修正UnrealScript不能使用out bool array的問題

在UnrealScript中不允許使用宣告使用out bool參數的函式,例如:
function TestOutBool(out bool Bool);
編譯器會回報"Booleans may not be out parameters"。

這是因為布林變數實作上其實只是個bitfield,在編譯時同一個類別的布林屬性會湊在一起,儘量減少總共需要的大小。然而如果把上述宣告為原生函式,要產生C++標頭檔時,會遇到無法在C++中表示reference to bitfield的問題。雖然可以為這種情況作特殊處理,不過其實只要改為宣告為out byte就可以解決了。

然而宣告使用out array<bool>參數的函式會遇到編譯器回報同樣的錯誤:
function TestOutBoolArray(out array<bool> Array);

2013年7月29日 星期一

自訂UnrealScript指令

UDN花了許多篇幅解釋UnrealScript底層的運作,不過卻沒提如何自訂新的指令。本篇將透過一個簡單的範例說明如何擴充UnrealScript的指令。

UnrealScript並未提供擴充機制,讓開發者可以在自己的專案裡撰寫程式碼自訂新的指令。雖然增加擴充機制不會太難,主要是讓FScriptCompiler和UByteCodeSerializer可以subclassing,不過由於不是這次的主題,所以本例中就直接修改底層的程式碼。

要自訂新的指令,大致上需要以下的步驟:
  • 找一個還未使用的位元碼(byte code)當作自訂指令碼
  • 如果這個指令需要使用一個新的名稱,可以在UnNames.h註冊一個新的名字
  • 修改UByteCodeSerializer使得新增的指令可以正確地序列化。
  • 撰寫對應的函式並且註冊到GNatives
  • 修改FScriptCompiler讓編譯器認得新的指令

2013年6月9日 星期日

優化Kismet節點AttachToEvent

當在Kismet中設置事件節點時,會需要設定事件的發起者。對於事先放置在場景中的Actor,可以先選取Actor再建立事件,就會自動設定好發起者。然而對於動態產生的Actor,由於無法在編輯時直接參照,就會需要使用AttachToEvent節點來動態設定發起者。

這個節點會在每次觸發時產生事件複本掛到發起者的事件列表下,因為它可以將一個事件掛上多個發起者,如果不複製事件節點,多個發起者將共用同一個事件實例導致per instance data出錯。然而如果不曉得這個特性,將可能導致不必用的複製動作。實際上任何可能對同一個Actor重覆觸發的AttachToEvent都會導致重覆複製的事件實例,它們在條件成立時也會造成多重觸發,這通常不會是想要的結果。

接下來示範一下要怎麼修改AttachToEvent來避免重覆複製。我並不想去動引擎的原始碼,所以另外自訂一個事件叫BindEvent:

2013年5月26日 星期日

原生屬性定義語法

當你需要在UnrealScript類別中定義非UnrealScript型別的欄位時,就會需要使用原生屬性語法,當然你只能在原生類別中定義原生屬性。在此所謂的原生即指C++,也就是說可以使用C++型別來定義屬性。當然因為畢竟UnrealScript還是認不得這些型別,所以不能在UnrealScript中去呼叫這些原生屬性的C++函式,通常是透過原生函式來操作。

原生屬性定義大致上是利用大括號指定C++型別:
var native pointer property_name{ pointee_type };
先來看最簡單的例子:
var native pointer          pv;
上面pv的C++型別其實就是void*。
也可以在屬性名稱右邊加個大括號指定C++型別。例如:
var native pointer          pfile{FILE};
上面pfile的C++型別就會是FILE*。
另外pointer也可和array一起使用:

2013年4月22日 星期一

自訂按鍵綁定格式

UE的按鍵綁定功能可以在ini檔中指定按鍵會觸發的命令,開發者可以在UnrealScript中撰寫exec function,再設定好按鍵綁定即可在遊戲中使用,相當方便。按鍵綁定主要分成按鈕和類比軸兩種,各自有不同的設定格式,詳情可以參考UDN。如果想要自訂新的格式,可以改寫PlayerInput的Exec。

 範例


以下程式碼示範如何自訂一個可以取得按住時間的按鍵綁定格式:

2013年3月27日 星期三

自訂八分樹

在計算機繪圖學中,八分樹是一個常用來加速查詢場景中物件的一種資料結構。UE本身就擁有數個用途不同的八分樹,除了PrimitiveComponent用的八分樹,其他大都利用TOctree類別模板來定義:
template<typename ElementType, typename OctreeSemantics>
class TOctree;
ElementType可以視為跟STL容器的value type是一樣的東西,至於OctreeSemantics則是用來設定八分樹的參數,或是提供八分樹類別操作ElementType物件的方法。其實就是所謂的traits。

OctreeSemantics需要提供的成員如下:
  • MaxElementsPerLeaf:每個葉節點的最大能容納的元素。當新增元素會超過此上限時,會分割葉節點。
  • MaxNodeDepth:最大樹深。
  • GetBoundingBox:傳回元素的邊界盒。
  • SetElementId:TOctree會給予元素一個ID。移除元素時會用到這個ID。

2013年2月28日 星期四

自訂運算子符號

先前有提過如何自訂運算子,如果想進一步自訂UnrealScript沒有支援的運算子符號時,那該怎麼做呢?雖然UnrealScript沒有提供自訂運算子符號的功能,不過其實只要稍微修改一下解析運算子符號的程式碼就可以了。例如如果想要增加一個目前UnrealScript沒有支援的運算子&=:
   
native static final operator int &= ( out int A, int B );
只要修改一下FScriptCompiler的GetToken()函式,增加辨識&=符號即可。

2013年2月25日 星期一

Unreal的通用combo box對話窗

當撰寫編輯器,需要讓使用者先選擇一個選項再進行後續步驟時,可以使用UEd提供WxDlgGenericComboEntry類別,叫出一個combo box對話窗。例如AnimSetViewer選取LOD和bone時,都是利用WxDlgGenericComboEntry來達成。

TArray<FString> Options;
// Add option strings
Options.AddItem( TEXT("Option1") );
Options.AddItem( TEXT("Option2") );
Options.AddItem( TEXT("Option3") );

WxDlgGenericComboEntry Dialog;

if( Dialog.ShowModal( TEXT("MyTitle"), TEXT("MyCaption"), Options, 0, TRUE ) == wxID_OK )
{
    ...
}

2013年1月30日 星期三

自訂屬性控制項

UEd的屬性視窗會根據屬性的型別使用不同的控制項,也提供開發者自訂屬性控制項的擴充功能。屬性控制項的基礎類別是WxItemPropertyControl,自訂屬性控制項必須直接或間接繼承此類別。因為UEd會利用wxWidget的RTTI機制建立控制項,開發者需要使用wxWidget提供的類別宣告和定義巨集註冊自訂類別。還要在DefaultEditor.ini檔設定屬性和自訂控制項的綁定,UEd才會使用自訂控制項取代預設的控制項。

2013年1月9日 星期三

自訂UnrealScript休眠函式

UnrealScript的休眠函式(latent function)提供開發者一個簡便的寫法等待某個條件完成。可以在Actor.uc裡找到其中兩個內建的休眠函式:
// Latent functions.
native(256) final latent function Sleep( float Seconds );
native(261) final latent function FinishAnim( AnimNodeSequence SeqNode, optional bool bFinishOnBlendOut );
休眠函式只能在類別的狀態(state)中呼叫,它會暫停目前狀態內的程式執行,直到指定的條件達成。下例中的GotoState()會等Sleep(1)完成後才執行:
class MyPawn extends GamePawn;

state Idle
{
    event BeginState(Name PreviousStateName)
    {
        Sleep(1);
        GotoState('');
    }
}