300x250

뱀파이어 서바이벌 같은 대규모 오브젝트들을 다루는 게임을 만들었었는데 이게임이 후반부로 갈수록 발열과 버벅거림 같은 렉 현상이 발견이 되더라고요. 처음엔 dots를 적용해야 하나 싶었는데 이 프로젝트를 유니티 2021.3.5f1로 진행을 해서

 

1.0.0 pre 버전은 유니티 2022.2.0b8 버전부터 지원을 하더라고요 어쩔 수 없이 유니티 프로파일러를 돌려서 최대한 문제점을 찾아보려고 했어요.

확인을 해보니 예상대로 대규모의 적 오브젝트들이 리소스를 가장 많이 사용하는 것 을 확인 하였고 CoroutineDelayedCalls안에 이동관련해서 리소스를 가장 많이 잡아먹고 있었기에 고민을 해봤어요.

Enemy.cs

적오브젝트 스크립트인데 각 오브젝트마다 생성이 되면 MoveRoutine 코루틴을 오브젝트가 죽을 때까지 실행하게 되는 코드로 작성이 돼있어요. 즉 Enemy객체가 200개라면 코루틴 200개가 돌고 있는 거죠.

 

그래서 실험을 한번 해보기로 했어요. 처음 아이디어는 Enemy들이 각자가 코루틴을 돌리며 이동하는 게 아니라 Enemy들을 모두 관리하는 EnemySpawner라는 객체가 있는데 이 객체가 Enemy들 안에 있는 Move 메서드를 호출하게 하는 코루틴을 한 개만 호출하게 하는 방식이에요.

 

이게 과연 더 효율적인 것일까를 검증하기 위해서 프로젝트를 하나 새로 만들어서 테스트를 해봤어요.

결론부터 말하자면 더 효율적이지 않았어요. 실패입니다.

아래내용은 실패에 관한 내용이에요.

 

간단하게 움직이는 플레이어를 만들고

 

실험을 도와줄 UnityChan들이에요.

 

플레이어를 열심히 따라와 줘야 해요.

 

먼저 500 마리 정도 만들어서 기존 이동코드를 사용해 봤어요.

 

EnemySpawner.cs

using System.Collections;
using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    public GameObject[] enemyGos;
    public GameObject rangeObject;
    public GameObject playerGo;
    private BoxCollider rangeCollider;

    public void Init(int spawnAmount)
    {
        rangeCollider = rangeObject.GetComponent<BoxCollider>();
        this.SpawnEnemys(spawnAmount);
    }

    private void SpawnEnemys(int spawnAmount)
    {
        StartCoroutine(this.SpawnEnemysImpl(spawnAmount));
    }

    private IEnumerator SpawnEnemysImpl(int spawnAmount)
    {
        for (int i = 0; i < spawnAmount; i++)
        {
            var randEnemyIndex = Random.Range(0, enemyGos.Length);
            // 생성 위치 부분에 위에서 만든 함수 Return_RandomPosition() 함수 대입
            GameObject enemyGo = Instantiate(enemyGos[randEnemyIndex], Return_RandomPosition(), Quaternion.identity, this.transform);
            Enemy enemy = enemyGo.GetComponent<Enemy>();
            enemy.Init(playerGo);
            yield return new WaitForSeconds(0.1f);
        }
    }


    private Vector3 Return_RandomPosition()
    {
        Vector3 originPosition = rangeObject.transform.position;
        
        // 콜라이더의 사이즈를 가져오는 bound.size 사용
        float range_X = rangeCollider.bounds.size.x;
        float range_Z = rangeCollider.bounds.size.z;

        range_X = Random.Range((range_X / 2) * -1, range_X / 2);
        range_Z = Random.Range((range_Z / 2) * -1, range_Z / 2);


        Vector3 RandomPostion = new Vector3(range_X, 0f, range_Z);

        Vector3 respawnPosition = originPosition + RandomPostion;
        return respawnPosition;
    }
}

 

Enemy.cs

using System.Collections;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    private float moveSpeed = 3f;
    private GameObject playerGo;
    public void Init(GameObject playerGo)
    {
        this.playerGo = playerGo;
        this.Move();
    }
    private void Move()
    {
        StartCoroutine(this.MoveRoutine());
    }

    private IEnumerator MoveRoutine()
    {
        while (true)
        {
            this.transform.LookAt(this.playerGo.transform.position);

            transform.Translate(Vector3.forward * Time.deltaTime * this.moveSpeed);

            yield return null;
        }
    }
}

 

 

 

이렇게 했을 때 프로파일러를 확인해 보면

 

500개에 Calls가 있고 1.17 Time ms값을 갖고 있어요.

코루틴이 500개가 돌고 있다는 거죠.

 

이걸 이제 EnemySpawner에서 전부 관리하게 해 볼게요.

 

EnemySpawner.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    public GameObject[] enemyGos;
    public GameObject rangeObject;
    public GameObject playerGo;
    private BoxCollider rangeCollider;

    private List<Enemy> enemyList;

    public void Init(int spawnAmount)
    {
        rangeCollider = rangeObject.GetComponent<BoxCollider>();
        this.enemyList = new List<Enemy>();

        StartCoroutine(this.SpawnEnemys(spawnAmount));
        StartCoroutine(this.MoveEnemys());
    }

    private IEnumerator SpawnEnemys(int spawnAmount)
    {
        for (int i = 0; i < spawnAmount; i++)
        {
            var randEnemyIndex = Random.Range(0, enemyGos.Length);
            // 생성 위치 부분에 위에서 만든 함수 Return_RandomPosition() 함수 대입
            GameObject enemyGo = Instantiate(enemyGos[randEnemyIndex], Return_RandomPosition(), Quaternion.identity, this.transform);
            Enemy enemy = enemyGo.GetComponent<Enemy>();
            enemy.Init(playerGo);

            this.enemyList.Add(enemy);
            yield return new WaitForSeconds(0.1f);
        }
        Debug.Log("Spawn Complete");
    }


    private Vector3 Return_RandomPosition()
    {
        Vector3 originPosition = rangeObject.transform.position;
        
        // 콜라이더의 사이즈를 가져오는 bound.size 사용
        float range_X = rangeCollider.bounds.size.x;
        float range_Z = rangeCollider.bounds.size.z;

        range_X = Random.Range((range_X / 2) * -1, range_X / 2);
        range_Z = Random.Range((range_Z / 2) * -1, range_Z / 2);


        Vector3 RandomPostion = new Vector3(range_X, 0f, range_Z);

        Vector3 respawnPosition = originPosition + RandomPostion;
        return respawnPosition;
    }

    private IEnumerator MoveEnemys()
    {
        while(true)
        {
            foreach (var enemy in enemyList)
            {
                enemy.Move();
            }
            yield return null;
        }
    }
}

 

Enemy.cs

using System.Collections;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    private float moveSpeed = 3f;
    private GameObject playerGo;
    public void Init(GameObject playerGo)
    {
        this.playerGo = playerGo;
        this.Move();
    }
    public void Move()
    {
        this.transform.LookAt(this.playerGo.transform.position);

        transform.Translate(Vector3.forward * Time.deltaTime * this.moveSpeed);

        //StartCoroutine(this.MoveRoutine());
    }

    private IEnumerator MoveRoutine()
    {
        while (true)
        {
            this.transform.LookAt(this.playerGo.transform.position);

            transform.Translate(Vector3.forward * Time.deltaTime * this.moveSpeed);

            yield return null;
        }
    }
}

 

 

개인적으로 놀랐던 거는 500개의 코루틴이 돌아가는 거랑 똑같은 움직임으로 보였어요 전 이거까지만 확인했을 땐 최적화과 됐겠다 싶어서 프로파일러를 확인해 봤어요.

아~ 전혀 나아지지 않은 모습 실패예요. 

Calls는 1개로 줄어들었지만 TIme.ms가 전혀 줄어들지 않았어요.

 

아 될 거 같았는데 이게 안되네요.

300x250