在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);
}
沒有留言:
張貼留言