2012年11月30日 星期五

自訂預覽視窗

先前提過如何撰寫資源編輯器的主視窗,接著本篇來介紹如何撰寫預覽視窗。許多資源編輯器都有預覽視窗,例如AnimSetViewer和AnimTreeEditor。UEd提供FEditorLevelViewportClient類別方便開發者撰寫預覽畫面,以及FPreviewScene類別方便管理預覽場景,接下來說明一下幾個相關的類別。

FEditorLevelViewportClient


在一般的資源編輯器中,預覽畫面大多是繼承FEditorLevelViewportClient類別加以改寫,它代表用來看預覽畫面的一個視埠,可說是撰寫預覽視窗時的主要框架。其實它不限用於預覽,Matinee編輯器的軌道視窗也是用它寫的。FEditorLevelViewportClient類別並不包括視窗的部分,所以需要把它放進一個視窗。它以虛擬函式的方式提供輸入、繪製、更新的擴充界面。以下列出幾個主要的成員:
  • ViewportType [ELevelViewportType]:可設為XY正交、XZ正交、YZ正交或透視。當只要2D繪圖不需要3D時,可設為LVT_None。
  • SetRealtime(bNewValue, ...):設定是否即時更新。
  • GetScene():可覆載此函式指定使用的場景,場景內的物件也會被繪出。預設使用GWorld的場景。
  • InputKey( Viewport, ... ):可覆載此函式取得輸入事件。
  • InputAxis( Viewport, ... ):可覆載此函式取得類比輸入。
  • MouseMove( Viewport, X, Y ):可覆載此函式取得滑鼠座標變動。
  • Draw( Viewport, Canvas ):可覆載此函式繪製2D圖形。
  • Draw( SceneView, PDI ):可覆載此函式繪製3D圖形。
  • Tick( DeltaSeconds ):可覆載此函式更新物件。

FPreviewScene


預覽視埠通常會需要一個用來放置預覽物件的預覽場景,UE提供FPreviewScene類別擔當此任。它只提供加入ActorComponent的功能,不支援Actor,而加入FPreviewScene的ActorComponent自然也沒有Owner。所以有些功能(例如AnimNotify_PlayParticleEffect)會針對沒有Owner的情況特別處理來支援預覽。然而有些功能(例如AnimNotify_PlayFaceFxAnim)就沒有支援在沒有Owner的情況下預覽。
  • GetScene():傳回場景界面。一般自訂的FEditorLevelViewportClient類別會覆寫GetScene()改成傳回這個場景界面。
  • AddComponent(ActorComponent, LocalToWorldMatrix):加入元件到指定的位置,此元件會沒有Owner。
  • RemoveComponent(ActorComponent):移除場景中的元件。
  • operator << (Archive, PreviewScene):序列化場景。序列化時必須呼叫此函式,以避免預覽場景被視為未被使用的物件而被回收掉。

FEditorCommonDrawHelper


有時3D預覽視埠會需要繪製格線和座標軸讓使用者更有方向感,UEd提供FEditorCommonDrawHelper類別方便開發者撰寫這些功能。

範例


以下程式碼展示一個典型的預覽視埠:

class FMyAssetViewportClient : public FEditorLevelViewportClient
{
public:

    FMyAssetViewportClient( class WxMyAssetEditor* MyAssetEditor );
   
    void Serialize(FArchive& Archive);
   
    // FEditorLevelViewportClient interfaces
    virtual FSceneInterface* GetScene()  { return m_PreviewScene.GetScene(); }
    virtual void InputKey( FViewport* Viewport, INT ControllerID, FName Key, EInputEvent Event, FLOAT AmountDepressed = 1.f, UBOOL bGamepad = FALSE );
    virtual void InputAxis( FViewport* Viewport, INT ControllerID, FName Key, FLOAT Delta, FLOAT DeltaTime, UBOOL bGamepad = FALSE );
    virtual void MouseMove( FViewport* Viewport, INT X, INT Y );
    virtual void Draw( FViewport* Viewport, FCanvas* Canvas );
    virtual void Draw( const FSceneView* View, FPrimitiveDrawInterface* PDI );
    virtual void Tick( FLOAT DeltaSeconds );

    ...

private:

    WxMyAssetEditor* m_MyAssetEditor;
    FPrewviewScene m_PreviewScene;
    FEditorCommonDrawHelper m_DrawHelper;

    ...       
};
預覽視埠類別的實作:
FMyAssetViewportClient::FMyAssetViewportClient( WxMyAssetEditor* MyAssetEditor ) :
    m_MyAssetEditor(MyAssetEditor)
{     
    ViewportType = LVT_Perspective;
    NearPlane = 1.f;
    SetRealtime(TRUE);
   
    UActorComponent* PreviewComponent = NULL;
    ... // Create PreviewComponent
    m_PreviewScene.AddComponent( PreviewComponent, FMatrix::Identity );
   
    ...
}

void FMyAssetViewportClient::Serialize(FArchive& Archive)
{
    Archive << Input;
    Archive << PreviewScene;
}

void FMyAssetViewportClient::Draw( const FSceneView* View, FPrimitiveDrawInterface* PDI )
{
    m_DrawHelper.Draw( View, PDI );
    
    ...
}
預覽視埠需要放進一個預覽視窗裡:
class WxMyAssetPreviewWindow : public wxWindow
{
public:

    WxMyAssetPreviewWindow(wxWindow* Parent, wxWindowID ID, class WxMyAssetEditor* MyAssetEditor);
    ~WxMyAssetPreviewWindow();
    
private:

    DECLARE_EVENT_TABLE()
        
    void OnSize( wxSizeEvent& Event );
    
    FMyAssetViewportClient* m_ViewportClient;    
};
預覽視窗類別的實作:
BEGIN_EVENT_TABLE( WxMyAssetPreviewWindow, wxWindow )
    EVT_SIZE( WxMyAssetPreviewWindow::OnSize )
END_EVENT_TABLE()

WxMyAssetPreviewWindow::WxMyAssetPreviewWindow(wxWindow* Parent, wxWindowID ID, class WxMyAssetEditor* MyAssetEditor) :
    wxWindow( Parent, ID )
{
    m_ViewportClient = new FMyAssetViewportClient( MyAssetEditor );
    m_ViewportClient->Viewport = GEngine->Client->CreateWindowChildViewport( m_ViewportClient, (HWND)GetHandle() );
    m_ViewportClient->Viewport->CaptureJoystickInput(FALSE);
}

WxMyAssetPreviewWindow::~WxMyAssetPreviewWindow()
{
    GEngine->Client->CloseViewport( m_ViewportClient->Viewport );
    m_ViewportClient->Viewport = NULL;
    delete m_ViewportClient;
}

void WxMyAssetPreviewWindow::OnSize( wxSizeEvent& Event )
{
    wxRect rc = GetClientRect();
    ::MoveWindow( (HWND)m_ViewportClient->Viewport->GetWindow(), 0, 0, rc.GetWidth(), rc.GetHeight(), 1 );
}

沒有留言:

張貼留言