2013年9月19日 星期四

自訂UnrealScript常數

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

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

範例


基礎型別的常數都已經有對應的指令了,本例中將使用Vector2D為例,新增一個新的vec2D指令用來產生Vector2D的對應常數。語法如下:
static function Vector2D vec2D(float x, float y);
首先需要找到一個未使用的位元碼,然後在EExprToken宣告:
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);
}

沒有留言:

張貼留言