はじめに
InstantiateAsync
を使用して、大量のGameObjectをInstantiateする際のスパイクを緩和してみました。
サンプルコード
サンプルコードを開く
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);
}
}
}
}
使用方法
GameObject[] objArr = await InstantiateAsync();
のobjArr
にInstantiateしたgameObjectが返ってくるので、追加で設定したい場合はここで行います。
重たいPrefabをInstantiateAsyncする
合計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で修正されているのを確認
以上