2012年11月27日 星期二

自訂資源編輯器

自訂新的資源時,通常也需要在UEd裡撰寫相應的編輯器。就好比AnimSet有AnimSetViewer,AnimTree有AnimTreeEditor。UEd的GUI是用wxWidgets寫的,這是一個第三方跨平台的GUI函式庫。

為了結合UE系統以及寫碼方便,UEd繼承wxWidgets的類別進行擴充。原本wxWidgets的類別前綴小寫的wx,UEd繼承後變成首字大寫的Wx,所以可以依此區分是否為原wxWidget內建的型別。

以下列出幾個撰寫編輯器主視窗時常用的類別。當然編輯器不是只有主視窗而已,只是篇幅已經太長,其他像顯示3D物件的部份就另外再寫一篇好了。

WxTrackableFrame


撰寫編輯器會使用到很多類別,主視窗類別需要繼承WxTrackableFrame,這樣按Ctrl+Tab時才會出現在視窗列表裡。因為UEd會使用靜態函式WxTrackableWindow::GetTrackableWindows()取得目前開啓的編輯器,要繼承WxTrackableFrame才能正常支援此機制。

WxPropertyWindowHost


通常編輯器會使用屬性視窗顯示物件屬性,就是在關卡編輯器中點選任一物件後按F4會出現的子視窗。它的類別叫WxPropertyWindowHost,一般編輯器會建立一個WxPropertyWindowHost物件,然後呼叫成員函式SetObject()或SetObjectArray()指定要顯示的UObject。

FNotifyHook


WxPropertyWindowHost視窗在建立時可以指定一個FNotifyHook物件,當使用者修改屬性時會去通知指定的FNotifyHook,所以主視窗類別通常也會繼承FNotifyHook來取得屬性變更通知。雖然UObject也有PostEditChangeProperty()虛擬函式可以覆載,不過要在覆載函式內存取編輯器的話還是用FNotifyHook比較方便。

FDockingParent


有一個以上子視窗的編輯器通常會需要dock功能,讓主視窗類別繼承FDockingParent就可以很方便的支援dock功能。它甚至還會在主選單上自動列出子視窗,可用選單方便地開關子視窗。

以下列出重要的FDockingParent函式:
  • GetDockingParentName():用來指定排版設定檔的名稱。子類別必須覆載此純虛擬函式。
  • GetDockingParentVersion():用來指定排版設定檔的版號,當增減子視窗時需要遞增版號。子類別必須覆載此純虛擬函式。
  • SaveDockingLayout():儲存排版設定檔,可以在專案的config目錄裡找到這個檔案。
  • LoadDockingLayout():載入排版設定檔。
  • AddDockingWindow(ClientWindow, DockHost, ...):加入要dock的子視窗。DockHost參數可指定dock的方向,如果要讓某個子視窗常駐,可以設為FDockingParent::DH_None。

FSerializableObject


由於UObject的垃圾回收機制是用序列化確認是否要回收,編輯器產生的物件若沒有被其他UObject參照就會被回收掉。所以主視窗類別通常也會繼承FSerializableObject,並且在Serialize()裡序列化需要保留的UObject以免被回收。

當地化


UEd有專用的當地化組態檔,檔名叫UnrealEd,放在Localization目錄各語言版本的子目錄裡。例如國際語言版的路徑是Localization\INT\UnrealEd.int。自訂編輯器如果有需要增加當地化字串,可以在專案目錄下對支援的語言目錄加入對應的UnrealEd檔,然後新增對應的字串設定。

範例


以下程式碼展示一個典型的編輯器主視窗:
class WxMyAssetEditor :
    public WxTrackableFrame,
    public FDockingParent,
    public FNotifyHook,
    public FSerializableObject
{
public:

    WxMyAssetEditor( wxWindow* Parent, wxWindowID ID, UMyAsset* MyAsset );
    ~WxMyAssetEditor();

    // FDockingParent interfaces
    virtual const TCHAR* GetDockingParentName() const ( return TEXT("MyAssetEditor"); )
    virtual INT GetDockingParentVersion() const ( return 0; )

    // FNotifyHook interface
    virtual void NotifyPostChange( void* Src, UProperty* PropertyChanged );

    // FSerializableObject interface
    virtual void Serialize(FArchive& Ar);   

    ...

private:

    DECLARE_EVENT_TABLE()

    UMyAsset* m_Asset;
    WxPropertyWindowHost* m_PropertyWindowHost;

    ...
};
主視窗類別的建構子/解構子:
WxMyAssetEditor::WxMyAssetEditor( wxWindow* Parent, wxWindowID ID, UMyAsset* MyAsset ) :
    WxTrackableFrame( Parent, ID, TEXT(""), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_sTYLE | wxFRAME_FLOAT_ON_PARENT | wxFRAME_NO_TASKBAR ),
    FDockingParent(this),
    m_Asset(MyAsset),
    m_PropertyWindowHost(NULL)
{
    SetTitle( *FString::Printf(LocalizeSecure(LocalizeUnreaEd("MyAssetEditor_F"), *m_MyAsset->GetPathName())) );

    SetSize(800, 600);
    FWindowUtil::LoadPosSize( TEXT("MyAssetEditor"), this, 256, 256, 800, 600);
    
    m_PropertyWindowHost = new WxPropertyWindowHost;
    m_PropertyWindowHost->Create( this, this );
    m_PropertyWindowHost->SetObject( m_Asset, EPropertyWindowFlags::ExpandCategories );    
    
    AddDockingWindow( m_PropertyWindowHost, FDockingParent::DH_Bottom, *FString::Printf(LocalizeSecure(LocalizeUnreaEd("PropertiesCaption_F"), *m_MyAsset->GetPathName())), *LocalizeUnreadEd("Properties") );
    
    LoadDockingLayout();
    
    ...
}

WxMyAssetEditor::~WxMyAssetEditor()
{
    SaveDockingLayout();
    
    FWindowUtil::SavePosSize( TEXT("MyAssetEditor"), this );
    
    ...
}
要讓內容瀏覽器使用自訂編輯器開啟自訂資源,需要撰寫一個自訂的GenericBrowserType。UDN上已有詳細解說,就不在此贅述。大致如下列程式碼:
UBOOL UGenericBrowserType_MyAsset::ShowObjectEditor(UObject* InObject)
{
    WxMyAssetEditor* Editor = new WxMyAssetEditor( GApp->EditorFrame, -1, CastChecked<UMyAsset>(InObject), FALSE );
    Editor->Show(TRUE);
    return TRUE;
}

沒有留言:

張貼留言