2013年6月27日 星期四

[Unity] 使用 Mecanim 需要注意的事情

此處紀錄我在使用Mecanim過程中所發現需要注意的一些地方,有些可能不正確或解說錯誤,僅供參考!

  • 骨架檔的root的x,y需要對正至原點
骨架檔的root的x,y需要對正至原點,否則動作blending時會有位移的誤差。
  • (Humanoid Rig)如果想要動作在blending前後不會移動到collider,除了在Import Settings裡把動作位移鎖住之外,要注意若是進行blending的時間區段中有較大的位移會容易造成collider的移動,因為blending時會吃掉一部分的位移量導致發生誤差。
  • Any State可以立即執行動作
想要立刻執行的動作(例如死亡)可以利用Any State,Any State代表任何時機點且在任何State之上,所以只要觸發條件成立就會立即執行,但其conditions必須是一次性的命令發送。詳細內容:[Unity] Mecanim 強制進入某個State的方法
Any State的觸發時機是任何時候,只要觸發條件成立就會立刻執行
  • 骨架由無實體物件組成時,需要給骨架的root綁定一個mesh
如果骨架是由bone, dummy等無實體的物件組成,且採取骨架、動態分離的檔案規則時,骨架的root需要綁定一個mesh,且動態檔內也必須要留著這個mesh,如此骨架檔才能成功套用到動態。
  • Humanoid Rig會消耗較多的效能
角色的Rig選擇Humanoid會比起Generic消耗更多的效能,而Legacy則是依據開發者所撰寫的controller,當角色越簡單時,使用Legacy會比較省效能。詳細內容:Mecanim Performance and Optimization
  • 當不使用IK功能時,Rig選擇Generic可以避免一些問題
角色需求較單純時使用Generic可以避免一些因為Humanoid的IK所造成的表現性上的誤差(如transition時的位移誤差)。當Rig選擇Generic時可看到Clip設定中的Root Transform Position(XZ)的Based Upon可選項目除了Original(依照動作檔中原始所key的位置)外就是Root Node Position,選擇Root Node Position時會以骨架的root對正(0,0),而Humanoid則是Center of Mass,以骨架體積中心點對正(0,0),因此當角色的腳挪動時僅管hip沒有移動仍然會使角色的位置偏移。
  • Humanoid Rig最好只使用在人類角色上
Humanoid是針對人類型骨架角色設計的,例如因Foot IK是針對單關節(人類的膝蓋)所設計,所以當腿部結構有雙關節時(如羊人的腳),就會發生一些不正常的現象(如滑步明顯、腳步抖動...)。
  • Transition的conditions為bool時,無法調整blending timing
當條件為bool時,代表該transition可以在任何時間發生,所以就只能調整duration而無法調整timing,如果去調整它會發現重新點選該transition時,會被自動歸回中央點。
當conditions為bool時是無法設定transition timing的
  • 承上,transition的conditions可以不只一個
譬如已有一個bool的條件再加上一個exit time條件,可以使transition依照bool判斷觸發,但transition發生的timing會是固定的,也就是你在transition的timeline中所調整的timing。 參考文章:


bool搭配exit time可以指定transition的timing

延伸閱讀:

[Unity] 清除Console的Log - Clear Console

參考文章:

如果直接
using System

會造成UnityEnging.Object與object的衝突

using sys = System;

static void clearConsoleLog ()
{
 Assembly assembly = Assembly.GetAssembly(typeof(SceneView));
    sys.Type type = assembly.GetType("UnityEditorInternal.LogEntries");
    MethodInfo method = type.GetMethod ("Clear");
    method.Invoke (new object (), null);
}

2013年6月21日 星期五

[Unity] 以ARGB4444搭配Dithering進行貼圖減量

  之前一直受ARGB Texture所困擾,在Mobile平台沒有合適的壓縮格式能在品質與容量之間取得較好的平衡,下面列出幾個格式的缺點
  • RGBA PVRTC 4 bits - iOS推薦的壓縮格式,但是4bits對於圖像品質的破壞太大
  • ARGB 32 bit - 雖能保有圖片品質,但所消耗的容量實在太龐大
  • ARGB 16 bit - 雖能降低貼圖容量,但是顏色有明顯的帶狀瑕疵(color banding)
  Android平台由於各家GPU所支援的壓縮格式各有不同,因此ARGB 16 bit也就是降低texture容量最適合的選項,但16 bit所造成的帶狀瑕疵十分明顯,若真的要使用也不太可能,正好前一陣子看到Unity IN上有Cytus開發者之一的syyang所發表的文章



  • 使用 RGBA4444 搭配 Dithering 減少記憶體用量 by syyang @ UNITYIN

  •   這篇文章正好解決了這個問題,利用Dithering在16bit color depth獲得較佳的圖像呈現,消除帶狀瑕疵。進行Dither處理之後,圖片的顏色資訊會被重新處理,檢視圖片可發現變的有顆粒狀感(抖色),當圖片在眼中的呈現越小時,就會發現看起來跟truecolor的圖片品質頗接近。由於是對顏色資訊做了重新的演算,所以並不受到Unity的轉檔所影響,只要在Import Settings中設定Format為16 bit的壓縮,即可省下容量又能保有還能接受的圖片品質。

      此外需要注意的是經過dither處理的texture若又被Unity重新壓縮到不同尺寸的圖片時,會加重顆粒感使圖片品質降低(因為顏色訊息被重新處理過了),因此輸出給Unity的texture asset必須要輸出成遊戲主要使用的texture size。此外,若處理過的貼圖是用於3D模型的話,經過UV的拉扯也會使圖片失真更加明顯。目前Unity本身並不支援dithering,在網上也還沒看到有人做出支援dither處理的script/plug-in,因此得要自行預先處理這些textures。

    經過UV拉扯、尺寸放大後失真變的相當明顯
    但dithering還是有其諸多限制,而最原始的作法當然就是使用RGB444的安全色系來繪製texture,及在各個channel中只使用:0, 15, 31, 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239, 255。但除了顏色數便少之外,還得少用漸層表現,要犧牲的也是相當多,也許能在不影響美術風格的前提下選擇性使用。

      在以Gimp進行dithering處理時,須先將貼圖縮成最終使用的尺寸,在縮小圖片時三種內插法看起來效果幾乎相同,然後再執行Dither to ARGB,此時會多出一個圖檔視窗就是dithering後的圖檔,進行匯出之後即可。匯出PNG的設定所有選項皆可取消,但當texture需要再alpha邊緣作細緻的過度時,透明像素的色彩是必須要被儲存的。Compression Level 0跟9在色系較單純的圖片上是幾乎沒有差別。


    Gimp在安裝script之後可進行dithering處理
    Gimp匯出PNG時的設定

    參考資料


    延伸閱讀

    2013年6月6日 星期四

    [Unity] Dynamic Camera FOV

    遭遇情境

      今天想達成一個攝影機的效果,就是當有特定的物體越來越接近畫面邊緣時,就要動態的增減Camera的FOV,讓該特定物體不要跑出畫面。

    解法

      大致上很簡單,只有兩個步驟:

    1. 求出物件距離畫面邊緣的距離
    2. 利用Mathf.Lerp達成相對應的FOV調整

      先利用Camera.WorldToViewportPoint( Vector3 )求出物件在畫面上的位置,返回的是normalized value(0至1之間),如此就能得知物件相對於畫面邊緣的距離,0代表是在畫面左方邊緣,1則是在畫面右方邊緣。

      再來則是利用Mathf.Lerp來調整FOV,Lerp實際上的運作是
    public static float Lerp(float start, float end, float value)
    {
        return ((1.0f - value) * start) + (value * end);
    }
    
    在其3個arguments中,value是一種類似權重的概念,當他為0時返回的值會是start,為1時返回的即是end,為中間的數值時就是返回按照比例計算後的結果。

      因此我只要把WorldToViewportPoint求得的值,以想要的數值範圍作normalize後,讓距離變成0至1之間的數值,傳入Lerp之中就可達成動態FOV的效果。範例如下:

    // amount : Value between 0 and 1 indicating the weight of value2.
    // 為距離螢幕邊緣比重的值
    Camera.fieldOfView = Mathf.Lerp( float CameraMinFOV, float CameraMaxFOV , float amount );
    


    參考資料


    延伸閱讀

    2013年6月5日 星期三

    [Unity] Mecanim Animator .controller 文件結構紀錄

    此文章紀錄Mecanim Animator Controller檔案的內容結構,當角色數量龐大時,眾多的Animator Controller會造成極大的管理上的困擾,因此若能直接解析內部的訊息的話,即可利用Script做外部的直接修改,做出更方便的管理工具。

    而在初次看過其內容後,獲得了一個重點即是,各地方的命名十分重要,Controller內部的目標來源都是以fileID的形式運作,但同時有些東西也可以在Unity中做命名,雖然會需要多一個命名規則的訂制與管理的工,但是直接利用Name做修改會比用fileID方便許多,譬如說Transition的命名。

    參考文件
    在開始看文件內容之前,先說明fileID的運作方式,fileID實際上是由兩組數字組成,首先是該物件的Class ID Number,然後是一組五位數物件在所在檔案內的序號(偶數),舉個例子,現在有一個帶有3個Animation Clip的FBX檔案,則這3個Clip的fileID就是:( 7400000, 7400001, 7400002 ),74為Animation Clip的ID Number。

    以下開始文件內容:
    在每個區段(term,代表一個object)的一開始,都會先有一串的數據資訊
    • --- !u!91 &9100000
      !u!後代表的是物件類型
      &(ampersand)後的數字是該區段資訊獨有的fileID
    AnimatorController此部份包含了Animator的基本屬性,如:
    • m_Name
      Animator的名稱
    • m_AnimatorEvents
      Flags
    • m_Layers
      Layers
    在來是各個Transition的參數(State與State間的Transition),
    • m_Name
      Transition的命名,預設狀況下是無命名的。
      如果想要手動修改.controller文件的話,對所有的Transition做命名會方便非常多,可快速的知道各個Transition資訊的位置。
    • m_SrcState / m_DstState
      來源跟目的State,填入的資訊為fileID。
    • m_TransitionDuration
      Transition的時間長度,應該是以SrcState的Motion的時間長度為基準的趴數。
      例如來源動作的長度為2秒,而m_TransitionDuration的值為0.5時就是使用1杪做過渡。
    • m_TransitionOffset
      DstState相對於TransitionDuration起始點的offset。
      當DstState的起始點與TransitionDuration的起始點相同時,則m_TransitionOffset的值為0。
      當DstState的結束點與TransitionDuration的起始點相同時,則m_TransitionOffset的值為1。
    • m_Conditions
      進行Transition的條件,以下的參數皆為Condition的條件式
      • m_ConditionMode
        條件判斷的模式
        • 1,Bool的True
        • 2,Bool的False
        • 3,Float/Int的Greater
        • 4,Float/Int的Less
        • 5,ExitTime
        • 6,Int的Equals
        • 7,Int的NotEqual
      •  m_ConditionEvent
        要參照的Flag,m_ConditionMode為ExitTime時此欄位為空
      • m_EventTreshold
        條件式的參考值,Bool時不管是True或False值都填0
      • m_ExitTime
        ExitTime的float值
    再來是State的資訊

    最後是StateMachine的資訊


    [Unity] Mecanim 強制進入某個State的方法(ex立刻執行死亡動作)

    How to change to a specific state immediately.

    參考資料
      在Mecanim Animator Controller中有個叫做Any State的State,是一個一直處在作用中的State,只能連接給其他的State,其他的State是不能連接給他的。

    Animator - Any State

      假使想要有一個在任何State規則之上的動作流程時(即條件成立時能立即進入該流程),譬如說人物的死亡動作,當程式下達死亡指令時,你會想要死亡動作是立刻表演,不希望這個流程會被其他的State卡住。
      這時候Any State就能達到這個效果,在這裡以一個範例作解說:在Animator Parameters中新增一個IsDead的Bool,並新增一個接收Any State的死亡動作State,且Transition Conditions為IsDead == true,如此當死亡時程式下達指令給Animator(animator.SetBool("dead",true)),就會讓dead這個State立刻執行。
      但是因為Any State是一直在作用狀態中的,所以並不能一直讓觸發條件成立,必須要是一次性的命令,否則就會發生一直反覆進入dead State的狀況,舉例來說:IsDead被設為true且Animator已經進入dead State的時候,就必須要把IsDead設回false。

    防止重複觸發Any State的範例Code如下:
    static int deadState = Animator.StringToHash("Base Layer.dead");
    if ( animator.GetCurrentAnimatorStateInfo(0).nameHash == deadState )
    {
        animator.SetBool("IsDead", false);
    }
    
    補充說明,animator.GetCurrentAnimatorStateInfo(int IDX)所返回的是Hash,且Hash並不能轉回String,因此得要先把你所知道的State名稱轉成Hash(.StringToHash())再做比對。
      在這樣的需求之下代表State的命名必須是有規則的、固定的,為了方便作業流程,動作片段的取名與State相同也可以省去某些操作上的步驟(直接將動作片段拉入Animator生成state時會自動以動作片段名稱為state命名)。
      另外直接從Any State做一個Transition到預設動作上,也能用來跳出任何的State。

    Unity 4.3 更新

    在Unity 4.3中新增了一個新的Parameter類型:Trigger,其作用是達成一次性的判斷條件,非常實用。其功能就是當Animator.SetTrigger("ParameterName")命令下達之後,該parameter會被設為True,並在transition完成時會被設為False,以往都需要自己撰寫設回False的程序,現在利用Trigger這個Parameter就會方便很多。
    另外在4.3中Mecanim有大幅的優化,譬如Mecanim已幾乎適用所有的component、property...,可以利用Mecanim控制你的相機FOV、控制你的Script的參數...,再配上新支援的Event(以往ImportedAnimation不支援Event)以及優化過的Animation Window(Dope Sheet + Curves),Mecanim的實用程度已大幅提高。


    參考文章
    延伸