MeshEditor(五) 網格頂點動畫(變形動畫)
原始碼已上傳至github,並持續更新,連結請看底部。(本帖跟隨github持續更新)
網格頂點動畫(變形動畫)是針對於物體的形狀可以隨意變換並記錄為關鍵幀的動畫,雖然模型的頂點資料還是應該交給GPU繪製才是正道,CPU重新整理模型頂點始終是個吃力不討好的事(不過我好像至始至終就是在幹吃力不討好的事來著),所以變形動畫還是別用到過於複雜的模型之上,畢竟到頭來吃力的只會是你的CPU,不過一些簡單的模型倒不用擔心,像什麼旗幟飄揚什麼的,不用開啟3DMAX(前提是得會用這東西K動畫),不用侷限於Unity的animator系統(畢竟給你一個做得像旗幟的cube,你能用animator調出一個飄動的動畫?),只需簡單的幾步拖拽便可以K出一個動畫,並且可以將動畫資訊儲存為本地檔案,實現多專案間複用,同時,頂點數相同的模型也可以複用動畫。
變形動畫完全不同於Unity Animator系統的機制,事實上跟它半毛錢關係都沒有,所以這兩種動畫在同一物體上是可以共同存在的,事實上,眾所周知,Animator的關鍵幀只會記錄物體的transform元件的position、rotation以及scale的數值變化(當然其他元件的部分屬性它也是可以記錄的,比如Image的Color),其餘的很多屬性改變都不會被它視為有另一關鍵幀產生,而變形動畫只會記錄模型的頂點資料作為關鍵幀,完全不會改動transform元件的屬性,所以這兩種動畫完全可以共存。
好了,進入正題,我以給一個cube調節一個變形動畫為例子講解一下整個流程及實現的思路。
第一步:
為cube新增我們的變形動畫編輯器元件(MeshAnimation)
新增動畫幀:以Scene場景中當前物體的狀態資訊儲存為一個新的關鍵幀,這裡的程式碼主要是記錄每個頂點的位置
/// <summary> /// 新增動畫幀 /// </summary> public void AddFrame() { Vector3[] vertices = new Vector3[_Vertices.Length]; for (int i = 0; i < _Vertices.Length; i++) { vertices[i] = _Vertices[i].transform.position; } _VerticesAnimationArray.Add(vertices); }
我們最好先在cube的初始狀態就新增一個動畫幀,以便於播放動畫時它會從初始狀態開始
第二步:
現在我們多新增幾個關鍵幀,目前每幀的狀態都是保持在初始形態
第三步:
我們的第一幀就讓他保持初始狀態,現在選中第二幀,同時在場景中調節cube的形態,當你覺得滿意的時候,點選apply應用就可以將物體的狀態應用到當前的第二幀資料,當然如果這一關鍵幀不想要了,點選delete刪除即可
/// <summary>
/// 應用動畫幀
/// </summary>
public void ApplyFrame()
{
//如果當前動畫幀資料存在,則應用當前物體的各頂點資料至當前動畫幀
if (_NowSelectFrame >= 0 && _NowSelectFrame < _VerticesAnimationArray.Count)
{
for (int i = 0; i < _Vertices.Length; i++)
{
_VerticesAnimationArray[_NowSelectFrame][i] = _Vertices[i].transform.position;
}
}
}
/// <summary>
/// 刪除動畫幀
/// </summary>
public void DeleteFrame()
{
//如果當前動畫幀資料存在,則刪除當前動畫幀資料
if (_NowSelectFrame >= 0 && _NowSelectFrame < _VerticesAnimationArray.Count)
{
_VerticesAnimationArray.RemoveAt(_NowSelectFrame);
_NowSelectFrame = -1;
}
}
我們將cube調節成這個樣子,然後點選apply應用關鍵幀
第四步:
選中第三個關鍵幀,再調到自己滿意的形態,並再點選apply應用
第五步:
選中第四個關鍵幀,這裡我們要讓他有個緩衝的效果,也就是說跟第三幀的差距小一點
然後我們的第四幀就調成了這個慫樣~
第六步:
第五幀我們就要讓他發射出去(前幾幀是收縮,蓄勢,然後第五幀猛地彈出~~有沒有一種發射炮彈的感覺~~),當然如果你想複製某一幀的話,只需選中這一幀,點選新增關鍵幀,最後面就會多出來與此幀相同的一幀,然後在此基礎上調節下一幀更方便
第七步:
之後就是給他K幾個反彈回來的緩衝關鍵幀,注意這裡選中任意一幀場景中的cube就會變化到那一幀的形態(這種方式是仿Animator的),隨意修改之後點選應用可以儲存,不點選應用預設改動無效,所以修改之後,如果覺得滿意,一定要點選apply應用,否則待你切換到其他幀時,這一幀改動的資料就將丟失
/// <summary>
/// 選定指定幀
/// </summary>
public void SelectFrame(int frameIndex)
{
//如果當前動畫幀資料存在,則選定當前動畫幀,所有頂點應用當前動畫幀資料
if (frameIndex >= 0 && frameIndex < _VerticesAnimationArray.Count)
{
_NowSelectFrame = frameIndex;
for (int i = 0; i < _Vertices.Length; i++)
{
_Vertices[i].transform.position = _VerticesAnimationArray[frameIndex][i];
}
}
}
第八步:
完成之後點選預覽按鈕就可以馬上在Scene介面看到cube的動畫效果,這裡沒截圖,後面用動畫播放器播放的時候再截圖
因為指令碼就算添加了編輯器執行的標識,它的update函式依然不會逐幀執行,而是在場景物體發生變化的時候才執行,所以這裡的動畫預覽函式不能放在update裡,那麼只有將之加入到Unity編輯器逐幀重新整理週期了
/// <summary>
/// 預覽動畫
/// </summary>
public void PlayAnimation()
{
//沒有動畫可以預覽
if (_VerticesAnimationArray.Count <= 0)
{
return;
}
//預覽從第一幀開始(頂點動畫陣列下標0)
_AnimationIndex = 0;
//重置記錄動畫播放上一序列的變數
_AnimationLastIndex = -1;
//重建新的動畫片段
_AnimationFragment = new Vector3[_Vertices.Length];
//重置動畫播放控制器
_AnimationPlayControl = 0;
//動畫進入到第一幀
for (int i = 0; i < _Vertices.Length; i++)
{
_Vertices[i].transform.position = _VerticesAnimationArray[0][i];
}
_IsPlay = true;
//將重新整理動畫函式註冊到Unity編輯器幀執行模組
EditorApplication.update += PlayingAnimation;
}
動畫重新整理函式採用將每個關鍵幀切分為動畫片段的方式,將片段迴圈累加給cube的網格頂點
/// <summary>
/// 動畫預覽中
/// </summary>
void PlayingAnimation()
{
if (_IsPlay)
{
//動畫播放至最後一幀,動畫播放完畢
if (_AnimationIndex + 1 >= _VerticesAnimationArray.Count)
{
//動畫播放完畢
_IsPlay = false;
//清除重新整理動畫函式的註冊
EditorApplication.update -= PlayingAnimation;
//動畫迴歸到第一幀
for (int i = 0; i < _Vertices.Length; i++)
{
_Vertices[i].transform.position = _VerticesAnimationArray[0][i];
}
return;
}
//當前動畫播放序列不等於上一幀序列,則進入下一幀
if (_AnimationIndex != _AnimationLastIndex)
{
_AnimationLastIndex = _AnimationIndex;
//分割動畫片段
for (int i = 0; i < _AnimationFragment.Length; i++)
{
_AnimationFragment[i] = (_VerticesAnimationArray[_AnimationIndex + 1][i] - _VerticesAnimationArray[_AnimationIndex][i])/ _AnimationPlaySpeed;
}
}
//動畫進行中
for (int i = 0; i < _Vertices.Length; i++)
{
_Vertices[i].transform.position += _AnimationFragment[i];
}
//動畫控制器計數
_AnimationPlayControl += 1;
//動畫控制器記錄的一個動畫幀播放完畢
if (_AnimationPlayControl >= _AnimationPlaySpeed)
{
_AnimationPlayControl = 0;
_AnimationIndex += 1;
}
RefishMesh();
}
}
第九步:
這裡是重點了,記得點選匯出動畫,如果你直接點選編輯完成或是突然有了什麼好想法跑去VS裡隨意改了下指令碼導致Unity編輯器重新編譯的話,很遺憾你的動畫資料都會丟失,記得匯出完畢了之後再點選編輯完成
使用scriptableobject序列化動畫資料至asset檔案中,這裡的坑是真坑,路徑必須還得是Asset開頭,字尾必須還得是asset,剛開始坑了我不少無辜的時間
/// <summary>
/// 匯出動畫
/// </summary>
public void ExportAnimation()
{
//動畫幀數小於等於1不允許匯出
if (_VerticesAnimationArray.Count <= 1)
return;
//建立動畫資料檔案
MeshAnimationAsset meshAnimationAsset = ScriptableObject.CreateInstance<MeshAnimationAsset>();
//記錄動畫頂點數
meshAnimationAsset._VertexNumber = _RecordAllVerticesList.Count;
//記錄動畫幀數
meshAnimationAsset._FrameNumber = _VerticesAnimationArray.Count;
//記錄動畫幀資料
meshAnimationAsset._VerticesAnimationArray = new Vector3[_VerticesAnimationArray.Count * _RecordAllVerticesList.Count];
for (int n = 0; n < _VerticesAnimationArray.Count; n++)
{
for (int i = 0; i < _VerticesAnimationArray[n].Length; i++)
{
for (int j = 0; j < _AllVerticesGroupList[i].Count; j++)
{
int number = n * _RecordAllVerticesList.Count + _AllVerticesGroupList[i][j];
EditorUtility.DisplayProgressBar("匯出動畫", "正在匯出頂點資料(" + number + "/" + meshAnimationAsset._VerticesAnimationArray.Length + ")......", 1.0f / meshAnimationAsset._VerticesAnimationArray.Length * number);
meshAnimationAsset._VerticesAnimationArray[number] = transform.worldToLocalMatrix.MultiplyPoint3x4(_VerticesAnimationArray[n][i]);
}
}
}
//建立本地檔案
string path = "Assets/" + GetComponent<MeshFilter>().sharedMesh.name + "AnimationData.asset";
AssetDatabase.CreateAsset(meshAnimationAsset, path);
EditorUtility.ClearProgressBar();
}
如下就是我們匯出來的動畫資料,可以看到裡面包含了10個關鍵幀,適用於一切有24個網格頂點的模型(網格頂點是可操控頂點的3倍),當然他的原主是cube
第十步:
然後,為cube新增變形動畫播放器元件(MeshAnimationPlayer)並將我們的CubeAnimationData拖到其MeshAnimationAsset屬性上,每一個MeshAnimationPlayer對應一個AnimationData檔案,暫不支援程式碼中動態變更
MeshAnimationAsset:動畫播放器的目標asset檔案,頂點數量需與當前掛載物體一致
AnimationPlaySpeed:動畫播放速度,注意,這裡是值越小播放越快
另外兩個引數是開啟迴圈播放和啟動時即播放,我們勾選啟動播放,然後執行程式,下面是動態效果圖
其他效果:
一個看起來有點醜又有點僵硬的機甲變形(用最新的骨架調節方式,雖然這樣還是顯得一團糟)
原形:
編輯狀態:
變形動畫:
MeshAnimationPlayer的播放有外部可控開關
/// <summary>
/// 播放動畫
/// </summary>
public void Play()
{
//從第一幀開始播放(頂點動畫陣列下標0)
_AnimationIndex = 0;
//重置記錄動畫播放上一序列的變數
_AnimationLastIndex = -1;
//重置動畫播放控制器
_AnimationPlayControl = 0;
//動畫跳轉到第一幀
SelectFrame(_AnimationIndex);
_IsPlaying = true;
}
/// <summary>
/// 停止播放
/// </summary>
public void Stop()
{
_IsPlaying = false;
//動畫迴歸到第一幀
SelectFrame(0);
}
以及要獲取當前動畫是否播放中,可以直接讀取_IsPlaying屬性。
-----by MeshEditor