3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UnityAdvent Calendar 2024

Day 1

[Unity] InstantiateAsync を使用する

Last updated at Posted at 2024-11-30

はじめに

InstantiateAsyncを使用して、大量のGameObjectをInstantiateする際のスパイクを緩和してみました。

サンプルコード

3DObject>Cubeにアタッチしてください
image.png

サンプルコードを開く
InstantiateAsyncCopy.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace AsyncDemo
{
    public class InstantiateAsyncCopy : MonoBehaviour
    {
        static public int maxCount;

        [SerializeField, Range(0, 5)] int m_maxDepth = 3;
        [SerializeField] bool m_useAsync = true;
        [SerializeField] bool m_immediately = false;
        [SerializeField] TMPro.TMP_Text m_text;
        [SerializeField] Material[] m_materials;
        int m_myDepth = 0;

        //int myDepth { get { return m_myDepth; } set { m_myDepth = value; } }
        Vector3[] m_positions;
        GameObject m_baseObj;

        // Start is called once before the first execution of Update after the MonoBehaviour is created
        async void Start()
        {
            if(m_myDepth == 0)
            {
                m_baseObj = Instantiate(gameObject);
                m_baseObj.SetActive(false);
                List<Vector3> posList = new List<Vector3>();
                posList.Add(new Vector3(1, 0, 0));
                posList.Add(new Vector3(-1, 0, 0));
                posList.Add(new Vector3(0, 1, 0));
                posList.Add(new Vector3(0, -1, 0));
                posList.Add(new Vector3(0, 0, 1));
                posList.Add(new Vector3(0, 0, -1));
                m_positions = posList.ToArray();

                maxCount = 0;
            }

            maxCount++;
            int sum = (int)((Mathf.Pow(6, m_maxDepth + 1) - 1) / 5);
            string resultTxt = $"Async: {m_useAsync},   Count: {maxCount}/{sum}";

            if (m_text != null)
            {
                m_text.text = resultTxt;
            }
            else
            {
                Debug.Log(resultTxt);
            }
            if (m_myDepth < m_maxDepth)
            {
                await Awaitable.WaitForSecondsAsync(2.0f);
                createCube(m_myDepth + 1);
            }
        }

        // Update is called once per frame
        void Update()
        {
            transform.Rotate(new Vector3(1f,1.3f,1.7f) * (m_myDepth+1) * Time.deltaTime);
        }

        public void Prepare(int _idx, int _depth, GameObject baseObj, Vector3[] _positions)
        {
            m_myDepth = _depth;
            m_baseObj = baseObj;
            m_positions = _positions;
            if(m_materials.Length > 0)
            {
                gameObject.GetComponent<MeshRenderer>().material = m_materials[_depth % m_materials.Length];
            }
            transform.localScale = Vector3.zero;
            transform.localPosition = m_positions[_idx] * 3f;
            gameObject.SetActive(true);
            StartCoroutine(appearCo(_idx));
        }

        IEnumerator appearCo(int _idx)
        {
            float timer = m_immediately ? 1 : 0;
            while (timer < 1)
            {
                timer += Time.deltaTime;
                transform.localScale = Vector3.one * timer * 0.5f;
                yield return null;
            }
            transform.localScale = Vector3.one * 0.5f;
        }

        async void createCube(int _depth)
        {
            Quaternion rot = transform.rotation * Quaternion.Euler(11,21,31);
            Vector3[] posArr = new Vector3[m_positions.Length];
            Quaternion[] rotArr = new Quaternion[m_positions.Length];
            for (int i = 0; i < m_positions.Length; i++)
            {
                posArr[i] = Vector3.zero;
                rotArr[i] = rot;
            }
            GameObject[] objArr;
            if (m_useAsync)
            {
                objArr = await InstantiateAsync(m_baseObj, m_positions.Length, transform, posArr, rotArr);
            }
            else
            {
                List<GameObject> objList = new List<GameObject>();
                for (int i = 0; i < m_positions.Length; i++)
                {
                    objList.Add(Instantiate(m_baseObj, posArr[i], rotArr[i], transform));
                }
                objArr = objList.ToArray();
            }
            for (int i = 0; i < objArr.Length; i++)
            {
                InstantiateAsyncCopy cubeScr = objArr[i].GetComponent<InstantiateAsyncCopy>();
                cubeScr?.Prepare(i, _depth, m_baseObj, m_positions);
            }
        }
    }
}

使用方法

InstantiateAsync

GameObject[] objArr = await InstantiateAsync();objArrにInstantiateしたgameObjectが返ってくるので、追加で設定したい場合はここで行います。

重たいPrefabをInstantiateAsyncする

image.png
合計216個のGameObjectからなる重たいPrefabを200個InstantiateAsyncしてみたところ、スパイクが発生しました。
重たいPrefabをInstantiateAsyncした場合は効果が薄いようです。

await InstantiateAsync(m_prefab, m_numInstantiatePerLoop, transform, vector3s, rotArray);

ちなみに1個だけInstantiateAsyncした場合ではスパイクは発生しません。
そこで、同数のprefabをループの中で分割してInstantiateAsyncしてみると、複数のワーカースレッドで並列処理されるようになり、スパイクが軽減しました。
Youtube-UnityJapan

for (int i = 0; i < m_numLoopInstantiate; i++)
{
  await InstantiateAsync(m_prefab, m_numInstantiatePerLoop, transform, vector3s, rotArray);
}
サンプルコードを開く
InstantiateAsyncSimple.cs
using System.Collections.Generic;
using UnityEngine;

namespace AsyncDemo
{
    [HelpURL("https://docs.unity3d.com/ScriptReference/Object.InstantiateAsync.html")]
    public class InstantiateAsyncSimple : MonoBehaviour
    {
        [SerializeField] GameObject m_prefab = null;
        [SerializeField, Range(1, 500)] int m_numInstantiatePerLoop = 5;
        [SerializeField, Range(1, 100)] int m_numLoopInstantiate = 5;
        [SerializeField, Range(0, 10)] float m_randRadius = 3f;
        [SerializeField] bool m_useAsync = true;
        [SerializeField] TMPro.TMP_Text m_text;
        int m_numInstantiate;
        List<GameObject> m_gameObjList;

        // Start is called once before the first execution of Update after the MonoBehaviour is created
        async void Start()
        {
            List<Vector3> posList = new List<Vector3>();
            List<Quaternion> rotList = new List<Quaternion>();
            m_gameObjList = new List<GameObject>();
            m_numInstantiate = m_numInstantiate * m_numInstantiatePerLoop;

            //https://www.youtube.com/shorts/Q9K-3zkEijQ
            AsyncInstantiateOperation.SetIntegrationTimeMS(10);

            for (int i = 0; i < m_numLoopInstantiate; i++)
            {
                for (int j = 0; j < m_numInstantiatePerLoop; j++)
                {
                    Vector3 pos = Random.insideUnitSphere * m_randRadius;
                    posList.Add(pos);
                }
                Quaternion rot = Random.rotation;
                rotList.Add(rot);
            }

            await Awaitable.WaitForSecondsAsync(3.0f);
            if (m_useAsync)
            {
                Quaternion[] rotArray = rotList.ToArray();
                for (int i = 0; i < m_numLoopInstantiate; i++)
                {
                    Vector3[] vector3s = posList.GetRange(i * m_numInstantiatePerLoop, m_numInstantiatePerLoop).ToArray();
                    GameObject[] goArr = await InstantiateAsync(m_prefab, m_numInstantiatePerLoop, transform, vector3s, rotArray);
                    m_gameObjList.AddRange(goArr);
                }
            }
            else
            {
                for(int i = 0; i < m_numLoopInstantiate; i++)
                {
                    for (int j = 0; j < m_numInstantiatePerLoop; j++)
                    {
                        int idx = i * m_numInstantiatePerLoop + j;
                        GameObject go = Instantiate(m_prefab, posList[idx], rotList[idx% m_numLoopInstantiate], transform);
                        m_gameObjList.Add(go);
                    }
                }
            }
        }

        // Update is called once per frame
        void Update()
        {
            Vector3 rotVec = new Vector3(1, 2, 3) * Time.deltaTime;
            transform.Rotate(rotVec);
            if (m_text != null)
            {
                m_text.text = $"Async: {m_useAsync} / {m_gameObjList.Count}";
            }
        }
    }
}

既知の問題点

本記事作成時点で最新のバージョン6000.0.25f1では
InstantiateAsync中にエディターを停止するとエラーが表示され、再度実行するとクラッシュするようです(報告済:IN-88119)。
追記:6000.0.30f1で修正されているのを確認

以上

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?