Unity3D 2Dアニメーションのレンダリングを時々高速化しますか? かんたん

この記事では、エイリアン大虐殺ゲームを作成する際に、モンスターのレンダリングがどのように加速されたかについてお話したいと思います。 このソリューションは、スプライトアニメーションを使用するプロジェクトに適しています。



モバイルゲームの開発の結果、ステージ上で多数のアニメーションオブジェクトを再生することがかなりボトルネックになることが判明しました。 その結果、次の要件が形成されました。





すぐに使えるソリューション



もちろん、最初の解決策は簡単でした。UnityEngineにすでに組み込まれているAnimatorコンポーネントの助けを借りてすべてを実行することです。 その結果を見てみましょう。



元のアニメーションのアトラスとして、それぞれ64x64ピクセルの24フレームのスプライトアニメーションを持つ邪悪なモンスターを使用します。







Unity3Dでは、テクスチャのタイプをスプライトに設定し、SpriteEditorでは24個に分割しました。 アニメーションを作成し、すべてを空のオブジェクトにドロップします。 ここで、さまざまなオブジェクトのさまざまなアニメーションの進行状況に関する条件があったことを思い出してください。 質問なし! 1分間の作業とスクリプトの準備ができました。



AnimationOffset.cs
using UnityEngine; namespace Kalita { [RequireComponent(typeof(Animator))] public class AnimationOffset : MonoBehaviour { public int Offset; public bool IsRandomOffset; private void Start() { var animator = GetComponent<Animator>(); var runtimeController = animator.runtimeAnimatorController; var clip = runtimeController.animationClips[0]; if (IsRandomOffset) Offset = Random.Range(0, (int) (clip.length*clip.frameRate)); var time = (Offset*clip.length/clip.frameRate); animator.Update(time); } } }
      
      







ここですべてをまとめて、Unity3Dが「すぐに使える」ソリューションを提供します。







今後は、「すぐに使用できる」ソリューションのパフォーマンスは非常に高く、柔軟性が高いと言えます。 アニメーターは、Unity3Dで働くすべての人を設定することに長い間慣れています。 しかし、アプリケーションでより高いパフォーマンスが必要な場合はどうでしょうか?



DIYソリューション



一般的な概念から始めましょう:





レンダリングシェーダーから始めましょう。



KalitaAtlasDrawer.shader
 Shader "Kalita/KalitaAtlasDrawer" { Properties { _MainTex ("Texture Atlas (RGBA)", 2D) = "" {} _Frame("Frame", float) = 0 _TotalFrames("Total Frames Count in Sequence", float) = 1 } SubShader { Tags { "Queue"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha Cull Off pass { CGPROGRAM #pragma vertex vert #pragma fragment frag sampler2D _MainTex; float4 _MainTex_ST; float _Frame; float _TotalFrames; struct appData { float4 vertex : POSITION; fixed4 color : COLOR; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert (appData v) { v2f o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); float frame = (_Frame + v.color.a*255) % (_TotalFrames + 1); float offset = frame / _TotalFrames; o.uv = v.uv; o.uv.x += offset; return o; } fixed4 frag (v2f i) : COLOR { fixed4 color = tex2D (_MainTex, i.uv); return color; } ENDCG } } FallBack "Diffuse" }
      
      







次に、Unity Editorからアニメーションパラメーターを簡単に構成できるコンポーネントに移りましょう。



KalitaAnimation.cs
 using UnityEngine; namespace Kalita { [ExecuteInEditMode] [RequireComponent(typeof (MeshFilter))] [RequireComponent(typeof (MeshRenderer))] public class KalitaAnimation : MonoBehaviour { public Material RendererMaterial { get { return meshRenderer.sharedMaterial; } } public Vector2 InGameSize = Vector2.one; public Vector2 Anchor = new Vector2(.5f, .5f); public int FramesCount = 1; public bool IsRandomStartAnimation; public byte StartFrame; private MeshFilter filter; private MeshRenderer meshRenderer; private void Awake() { filter = GetComponent<MeshFilter>(); meshRenderer = GetComponent<MeshRenderer>(); BuildMesh(); SetAnimationOffset(); } #if UNITY_EDITOR && !TEST_RUNNING private void Update() { if (Application.isPlaying) return; BuildMesh(); SetAnimationOffset(); var mat = meshRenderer.sharedMaterial; mat.mainTextureScale = new Vector2(1f / FramesCount, 1); } #endif private void BuildMesh() { var anchor = Anchor; anchor.Scale(InGameSize); anchor /= 2; var mesh = BuildQuad(InGameSize, anchor, new Vector2(1f / FramesCount, 1f)); filter.mesh = mesh; } private void SetAnimationOffset() { var mesh = filter.sharedMesh; mesh.name = "Plane"; var cnt = mesh.vertexCount; var clrs = mesh.colors32; if (clrs.Length != cnt) clrs = new Color32[cnt]; if (IsRandomStartAnimation && Application.isPlaying) StartFrame = (byte)Random.Range(0, 255); for (int i = 0; i < cnt; i++) clrs[i].a = StartFrame; mesh.colors32 = clrs; } public static Mesh BuildQuad(Vector2 size, Vector2 anchor, Vector2 uvStep) { var dx = size.x / 2; var dy = size.y / 2; var vertices = new[] { new Vector3(-dx + anchor.x, -dy + anchor.y, 0), new Vector3(dx + anchor.x, -dy + anchor.y, 0), new Vector3(dx + anchor.x, dy + anchor.y, 0), new Vector3(-dx + anchor.x, dy + anchor.y, 0), }; var uvs0 = new[] { uvStep, new Vector2(0, uvStep.y), new Vector2(0, 0), new Vector2(uvStep.x, 0), }; var indices = new[] { 0, 1, 2, 0, 2, 3 }; var mesh = new Mesh { vertices = vertices, uv = uvs0, triangles = indices }; mesh.Optimize(); return mesh; } } }
      
      







このスクリプトはUnity3Dエディターで機能し、シーンのパラメーターの変更をすぐに確認できるため、セットアップが簡単で便利になります。 上記のシェーダーでマテリアルを作成し、MeshRendererに割り当てることを忘れないでください。 Unity3Dエディターでは、これはすべて次のようになります。







さて、今残っている最も簡単なことは、グローバルフレームカウンターを記述することです。 ここに彼は:



KalitaAtlasAC.cs
 using UnityEngine; namespace Kalita { [ExecuteInEditMode] public class KalitaAtlasAC : MonoBehaviour { public KalitaAnimation Animation; public float FrameRate = 24; [HideInInspector] public int CurrentGlobalFrame; private float lastGlobalFrameUpdateTime; private void Awake() { if (Animation == null) Animation = GetComponentInChildren<KalitaAnimation>(); } private void Update() { if (FrameRate <= 0) return; var t = Time.time; var nextUpdateTime = lastGlobalFrameUpdateTime + 1f/FrameRate; if (t < nextUpdateTime) return; var dt = t - lastGlobalFrameUpdateTime; lastGlobalFrameUpdateTime = t; //If we run too slow, we shoud add several frames per update CurrentGlobalFrame += (int) (dt*FrameRate); CurrentGlobalFrame %= Animation.FramesCount; Animation.RendererMaterial.SetFloat("_Frame", CurrentGlobalFrame); } } }
      
      







正しく動作させるために、1つのKalitaAtlasACコンポーネントが多くのKalitaAnimationコンポーネントを制御します。 パラメーターはsharedMaterialを介して設定されるため、多くの制御オブジェクトのいずれかがKalitaAtlasACの対応するフィールド(アニメーション)にドラッグされます。



テスト中



さて、テストの時間です。 テストのために、必要な数のオブジェクトをシーン上に作成できる小さなスクリプトを作成します。



HabrSpawner.cs
 using System.Collections.Generic; using UnityEngine; namespace Kalita { public class HabrSpawner : MonoBehaviour { public List<GameObject> Objects = new List<GameObject>(); public int MobsToSpawn; private int mobOnScene; public Vector2 SpawnZone = new Vector2(10, 10); private void Start() { Screen.sleepTimeout = SleepTimeout.NeverSleep; SpawnMany(); } private void Update() { if (spawnMany) { spawnMany = false; SpawnMany(); } } [SerializeField] private bool spawnMany; private void SpawnMany() { const int layers = 5; var rectBorderSize = Vector2.one*2.4f; var mobsPerLayer = MobsToSpawn / layers; var zone = SpawnZone; for (int j = 0; j < layers; j++) { for (int i = 0; i < mobsPerLayer; i++) Spawn(zone); zone -= rectBorderSize; } } private void Spawn(Vector2 zone) { if (Objects.Count == 0) return; var i = Random.Range(0, Objects.Count); var o = Instantiate(Objects[i]); var p = GetRandomPositionOnRect(zone); Spawn(o, p); } private void Spawn(GameObject o, Vector2 pos) { mobOnScene++; o.SetActive(true); o.transform.position = pos; } private void OnGUI() { var w = 150; var h = 20; var x = 100; var y = 0; var rect = new Rect(x, y, w, h); //+One mob is source mob GUI.Label(rect, "MobsOnScene: " + (mobOnScene + 1)); } private Vector2 GetRandomPositionOnRect(Vector2 size) { var spawnRect = size; var resultPos = new Vector2(); switch (Random.Range(0, 4)) { case 0: // Top resultPos.x = Random.Range(0, spawnRect.x) - (spawnRect.x) / 2f; resultPos.y = spawnRect.y / 2; break; case 1: // Right resultPos.x = spawnRect.x / 2; resultPos.y = Random.Range(0, spawnRect.y) - (spawnRect.y) / 2; break; case 2: // Bottom resultPos.x = Random.Range(0, spawnRect.x) - (spawnRect.x) / 2; resultPos.y = -spawnRect.y / 2; break; case 3: // Left resultPos.x = -spawnRect.x / 2; resultPos.y = Random.Range(0, spawnRect.y) - (spawnRect.y) / 2; break; } return resultPos; } } }
      
      







結果を比較します。 まず、20,000個のオブジェクトを描画するタスクを使用してUnityEditorで実行します。



Dell M4800ラップトップでUnity3D Animatorを使用すると、約5 FPSが得られます。







KalitaAtlasAC + KalitaAnimationで同じタスクを実行し、20 + FPSを取得します。







実際のデバイスでテストするとどうなりますか? 作成する施設の数を2000に減らしますが、モバイルデバイスで作業します。 テスト対象として、Samsung Galaxy S3-i9300が手元にありました。 Unity3D Animatorを使用すると、約9〜10 FPSが得られます。







KalitaAtlasAC + KalitaAnimationを結果として使用する場合、35 + FPSがあります。







まとめ



スプライトアニメーションを使用する多数のアニメーション化されたオブジェクトを使用する場合、提案された手法はレンダリングのコストを最大4倍削減します。これはモバイルアプリケーションにとって非常に重要です。



ちなみに、残りのRGB頂点カラーコンポーネントは、デモプロジェクトに示すように、オーバーレイとして使用できます。



デモプロジェクトはここからダウンロードできます: bitbucket.org/Philipp0K/kalitaanimator



All Articles