在UnrealScript中,常數實際上是用一個編成位元組碼(byte code)的指令代表。要自訂新的常數,大致上需要以下的步驟:
- 找一個還未使用的位元組碼當作自訂的常數指令碼
- 如果這個常數需要使用新的名稱,可以在UnNames.h註冊新的名字
- 修改UByteCodeSerializer使得新增的指令可以正確地序列化
- 撰寫對應的函式並且註冊到GNatives
- 修改FTokenBase使解析器可以記錄新的常數型別
- 修改FScriptCompiler讓編譯器認得新的指令
範例
基礎型別的常數都已經有對應的指令了,本例中將使用Vector2D為例,新增一個新的vec2D指令用來產生Vector2D的對應常數。語法如下:
static function Vector2D vec2D(float x, float y);
enum EExprToken { ... EX_DynArrayAddUniqueItem= 0x2B, ... };AddUniqueItem這個名稱稍後解析時會用到,先在UnNames.h裡註冊一下:
REGISTER_NAME( 1251, Vector2D ) REGISTER_NAME( 602, Vec2D )接著修改序列化的部分,由SERIALIZEEXPR_INC這個巨集負責處理,會被UByteCodeSerializer::SerializeExpr()和UStruct::SerializeExpr()呼叫。
#ifdef SERIALIZEEXPR_INC ... case EX_Vector2DConst: { XFER(FLOAT); XFER(FLOAT); break; }Vector2D常數的位元組碼序列其實就是EX_Vector2DConst後面跟著兩個float值而已。
然後撰寫對應函式UObject::execVector2DConst並且註冊:
void UObject::execVector2DConst( FFrame& Stack, RESULT_DECL ) { ((FVector2D*)Result)->X = Stack.ReadFloat(); ((FVector2D*)Result)->Y = Stack.ReadFloat(); } IMPLEMENT_FUNCTION( UObject, EX_Vector2DConst, execVector2DConst );UnrealScript在編譯時會先解析程式碼,轉換成token方便編輯。Token會記錄各式各樣的相關資訊,包括常數在內。所以需要一併修改token,讓它可以儲存Vector2D的資訊:
class FTokenBase { ... UBOOL IsVector2D() const { return Type==CPT_Struct && Struct->GetFName() == NAME_Vector2D; } ... FString GetValue() { if ( TokenType == TOKEN_Const ) { if (IsVector()) { FVector& Vect = *(FVector*)VectorBytes; return FString::Printf(TEXT("FVector(%f,%f,%f)"),Vect.X, Vect.Y, Vect.Z); } else if (IsVector2D()) { FVector2D& Vect = *(FVector2D*)VectorBytes; return FString::Printf(TEXT("FVector(%f,%f)"),Vect.X, Vect.Y); } else if (IsRotator()) ... } } ... UBOOL GetConstVector2D( FVector2D& v ) const { if( TokenType==TOKEN_Const && IsVector2D() ) { v = *(FVector2D *)VectorBytes; return 1; } return 0; } void SetConstVector2D( FVector2D &InVector ) { (FPropertyBase&)*this = FPropertyBase(CPT_Struct); static UScriptStruct* VectorStruct = FindObjectChecked<UScriptStruct>(UObject::StaticClass(), TEXT("Vector2D")); Struct = VectorStruct; *(FVector2D *)VectorBytes = InVector; TokenType = TOKEN_Const; } };接著修改解析語法的程式碼產生對應Vector2D的token。
UBOOL FScriptCompiler::GetToken( FToken& Token, const FPropertyBase* Hint/*=NULL*/, UBOOL NoConsts/*=FALSE*/ ) { ... else if( Token.TokenName==NAME_Vec2D && MatchSymbol(TEXT("(")) ) { // This is a vector constant. FVector2D V = FVector2D( 0.f, 0.f ); if(!GetConstFloat(V.X)) { ScriptErrorf(SCEL_Unknown/*SCEL_Expression*/, TEXT("Missing X component of Vector2D" )); } if( !MatchSymbol(TEXT(",")) ) { ScriptErrorf(SCEL_Unknown/*SCEL_Expression*/, TEXT("Missing ',' in Vector2D" )); } if(!GetConstFloat(V.Y)) { ScriptErrorf(SCEL_Unknown/*SCEL_Expression*/, TEXT("Missing Y component of Vector2D" )); } if(!MatchSymbol(TEXT(")"))) { ScriptErrorf(SCEL_Unknown/*SCEL_Expression*/, TEXT("Missing ')' in Vector2D" )); } Token.SetConstVector2D(V); return TRUE; } ... }最後修改編譯器輸出Vector2D常數:
void FScriptCompiler::EmitConstant( FToken& Token ) { ... case CPT_Struct: { if( Token.IsVector() ) { FVector V; Token.GetConstVector(V); Writer << EX_VectorConst; Writer << V; } else if( Token.IsVector2D() ) { FVector V; Token.GetConstVector2D(V); Writer << EX_Vector2DConst; Writer << V; } ... }於是就可以在UnrealScript中使用vec2D產生Vector2D常數:
exec function TestVec2D() { local Vector2D v; v = vec2D(1,2) + vec2D(3,4); `log(v.X@v.Y); }
沒有留言:
張貼留言