編輯:關於Android編程
這次加入的功能比之前幾次的實用性明顯要高些,像什麼破碎啊,融化啊,其實細想一下會發現......沒什麼卵用,模型的頂點數據還是應該交給GPU繪制才是正道,CPU刷新模型頂點始終是個吃力不討好的事(不過我好像至始至終就是在干吃力不討好的事來著),所以今天要講的網格頂點動畫也還是別用到過於復雜的模型之上,畢竟到頭來吃力的只會是你的項目,不過一些簡單的模型倒不用擔心,像什麼旗幟飄揚什麼的,不用打開3DMAX(前提是得會用這東西K動畫),不用局限於Unity的animator系統(畢竟給你一個做得像旗幟的cube,你能用animator調一個飄動的動畫?),只需簡單的幾步拖拽便可以K出一個動畫,並且可以將動畫信息保存為本地文件,實現多項目間復用,同時,頂點數相同的模型也可以復用動畫(這感覺也沒什麼卵用)。
今天所提的網格頂點動畫完全不同於Unity Animator系統的機制,事實上跟它半毛錢關系都沒有,所以這兩種動畫在同一物體上是可以共同存在的,事實上,眾所周知,Animator的關鍵幀只會記錄物體的transform組件的position、rotation以及scale的數值變化,其余的任何屬性改變都不會被它視為有另一關鍵幀產生,而網格頂點動畫只會記錄模型的頂點數據作為關鍵幀,完全不會改動transform組件的屬性,所以這兩種動畫完全可以共存。
好了,進入正題,我以給一個cube調一個動畫為例子講解一下整個流程及實現的思路。
第一步、為cube添加我們的網格頂點動畫編輯器組件(MeshAnimation)
添加動畫幀:以Scene場景中當前物體的狀態信息保存為一個新的關鍵幀,這裡的代碼主要是記錄每個頂點的位置
////// 添加動畫幀 /// 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刪除即可
////// 應用動畫幀 /// public void ApplyFrame() { //如果當前動畫幀數據存在,則應用當前物體的各頂點數據至當前動畫幀 if (_NowSelectFrame >= 0 && _NowSelectFrame < _VerticesAnimationArray.Count) { for (int i = 0; i < _Vertices.Length; i++) { _VerticesAnimationArray[_NowSelectFrame][i] = _Vertices[i].transform.position; } } }
////// 刪除動畫幀 /// public void DeleteFrame() { //如果當前動畫幀數據存在,則刪除當前動畫幀數據 if (_NowSelectFrame >= 0 && _NowSelectFrame < _VerticesAnimationArray.Count) { _VerticesAnimationArray.RemoveAt(_NowSelectFrame); _NowSelectFrame = -1; } }
我們將cube調節成這個樣子,然後點擊apply應用關鍵幀
第四步、選中第三個關鍵幀,再調到自己滿意的形態,並再點擊apply應用
第五步、選中第四個關鍵幀,這裡我們要讓他有個緩沖的效果,也就是說跟第三幀的差距小一點
然後我們的第四幀就調成了這個慫樣~
第六步、第五幀我們就要讓他發射出去(前幾幀是收縮,蓄勢,然後第五幀猛地彈出~~有沒有一種發射炮彈的感覺~~),當然如果你想復制某一幀的話,只需選中這一幀,點擊添加關鍵幀,最後面就會多出來與此幀相同的一幀,然後在此基礎上調節下一幀更方便
第七步、之後就是給他K幾個反彈回來的緩沖關鍵幀,注意這裡選中任意一幀場景中的cube就會變化到那一幀的形態(這種方式是仿Animator的),隨意修改之後點擊應用可以保存,不點擊應用默認改動無效
////// 選定指定幀 /// 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編輯器逐幀刷新周期了
////// 預覽動畫 /// 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的網格頂點
////// 動畫預覽中 /// 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,剛開始坑了我不少無辜的時間
////// 導出動畫 /// public void ExportAnimation() { //動畫幀數小於等於1不允許導出 if (_VerticesAnimationArray.Count <= 1) return; //創建動畫數據文件 MeshAnimationAsset meshAnimationAsset = ScriptableObject.CreateInstance(); //記錄動畫頂點數 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 ().sharedMesh.name + "AnimationData.asset"; AssetDatabase.CreateAsset(meshAnimationAsset, path); EditorUtility.ClearProgressBar(); }
如下就是我們導出來的動畫數據,可以看到裡面包含了10個關鍵幀,適用於一切有24個網格頂點的模型(網格頂點是可操控頂點的3倍),當然他的原主是cube
第九步、然後,為cube添加網格頂點動畫播放器組件(MeshAnimationPlayer)並為其添加我們的CubeAnimationData,每一個MeshAnimationPlayer對應一個AnimationData文件,暫不支持代碼中動態變更
MeshAnimationAsset:動畫播放器的目標asset文件,頂點數量需與當前掛載物體一致
AnimationPlaySpeed:動畫播放速度,注意,這裡是值越小播放越快
另外兩個參數是開啟循環播放和啟動時即播放,我們勾選啟動播放,然後運行程序,下面是動態效果圖
我們可以看到動畫有點丑~~當然這只是隨便調調,畢竟我不是美術
MeshAnimationPlayer的播放有外部可控開關
////// 播放動畫 /// public void Play() { //從第一幀開始播放(頂點動畫數組下標0) _AnimationIndex = 0; //重置記錄動畫播放上一序列的變量 _AnimationLastIndex = -1; //重置動畫播放控制器 _AnimationPlayControl = 0; //動畫跳轉到第一幀 SelectFrame(_AnimationIndex); _IsPlaying = true; } ////// 停止播放 /// public void Stop() { _IsPlaying = false; //動畫回歸到第一幀 SelectFrame(0); }
以及要獲取當前動畫是否播放中,可以直接讀取_IsPlaying屬性。
玩微信的人都知道,微信無法直接使用QQ號登錄,想要使用QQ號登錄微信就需要先注冊,那麼我們如何使用qq號注冊微信帳號呢?本文就為大家詳細介紹使用申請微信賬號
在Android實現沒有標題欄的方法有兩種:在代碼中添加requestWindowFeature(Window.FEATURE_NO_TITLE); 在清單文件Andro
+ code); if (code == 200) { InputStream is = conn.getInputStream();
本章內容第1節 線性布局第2節 相對布局第3節 幀布局第4節 表格布局第5節 網格布局 線性布局線性布局使用標簽進行配置,對應代碼中的類是android.wid