31. 유니티 2D 종스크롤 슈팅 - 오브젝트 폴링
오브젝트 폴링이란
Instatiate 나 Destroy 함수는 생성 삭제하면서 조각난 메모리가 쌓임
가비지컬렉트를 제 때 하지 않으면 게임이 점점 느려짐
* 가비지컬렉트 : 쌓인 조각난 메모리를 비우는 기술
* 오브젝트 풀링 : 미리 생성된 풀에서 활성화, 비활성화를 통해 사용
풀 생성
먼저 오브젝트를 관리할 오브젝트 매니저와 스크립트 생성
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectManager : MonoBehaviour
{
//프리팹 변수 생성 후 할당
public GameObject enemySPrefab;
public GameObject enemyMPrefab;
public GameObject enemyLPrefab;
public GameObject itemCoinPrefab;
public GameObject itemPowerPrefab;
public GameObject itemBombPrefab;
public GameObject bulletPlayerAPrefab;
public GameObject bulletPlayerBPrefab;
public GameObject bulletEnemyAPrefab;
public GameObject bulletEnemyBPrefab;
//public을 사용하면 인스펙터 창에 너무 많은 인포 나옴
GameObject[] enemyS;
GameObject[] enemyM;
GameObject[] enemyL;
GameObject[] itemCoin;
GameObject[] itemPower;
GameObject[] itemBomb;
GameObject[] bulletPlayerA;
GameObject[] bulletPlayerB;
GameObject[] bulletEnemyA;
GameObject[] bulletEnemyB;
void Awake()
{
//한번에 등장할 오브젝트 갯수 고려해 배열갯수 할당
enemyS = new GameObject[20];
enemyM = new GameObject[20];
enemyL = new GameObject[20];
itemCoin = new GameObject[20];
itemPower = new GameObject[20];
itemBomb = new GameObject[20];
bulletPlayerA = new GameObject[50];
bulletPlayerB = new GameObject[50];
bulletEnemyA = new GameObject[50];
bulletEnemyB = new GameObject[50];
Generate();
}
void Generate()
{
//프리팹 만들고 배열에 저장
//생성 후엔 비활성화
//1. Enemy
for (int i = 0; i < enemyS.Length; i++)
{
enemyS[i] = Instantiate(enemySPrefab);
enemyS[i].SetActive(false);
}
for (int i = 0; i < enemyM.Length; i++)
{
enemyM[i] = Instantiate(enemyMPrefab);
enemyM[i].SetActive(false);
}
for (int i = 0; i < enemyL.Length; i++)
{
enemyL[i] = Instantiate(enemyLPrefab);
enemyL[i].SetActive(false);
}
//2. Item
for (int i = 0; i < itemCoin.Length; i++)
{
itemCoin[i] = Instantiate(itemCoinPrefab);
itemCoin[i].SetActive(false);
}
for (int i = 0; i < itemPower.Length; i++)
{
itemPower[i] = Instantiate(itemPowerPrefab);
itemPower[i].SetActive(false);
}
for (int i = 0; i < itemBomb.Length; i++)
{
itemBomb[i] = Instantiate(itemBombPrefab);
itemBomb[i].SetActive(false);
}
//3. Bullet
for (int i = 0; i < bulletPlayerA.Length; i++)
{
bulletPlayerA[i] = Instantiate(bulletPlayerAPrefab);
bulletPlayerA[i].SetActive(false);
}
for (int i = 0; i < bulletPlayerB.Length; i++)
{
bulletPlayerB[i] = Instantiate(bulletPlayerBPrefab);
bulletPlayerB[i].SetActive(false);
}
for (int i = 0; i < bulletEnemyA.Length; i++)
{
bulletEnemyA[i] = Instantiate(bulletEnemyAPrefab);
bulletEnemyA[i].SetActive(false);
}
for (int i = 0; i < bulletEnemyB.Length; i++)
{
bulletEnemyB[i] = Instantiate(bulletEnemyBPrefab);
bulletEnemyB[i].SetActive(false);
}
}
}
* 첫 로딩 시간 = 장면 배치 + 오브젝트 풀 생성
프리팹들 할당
//오브젝트 생성 함수
public GameObject MakeObject(string type)
{
switch(type)
{
case "EnemyS":
targetPool = enemyS;
break;
case "EnemyM":
targetPool = enemyM;
break;
case "EnemyL":
targetPool = enemyL;
break;
case "ItemCoin":
targetPool = itemCoin;
break;
case "ItemPower":
targetPool = itemPower;
break;
case "ItemBomb":
targetPool = itemBomb;
break;
case "BulletPlayerA":
targetPool = bulletPlayerA;
break;
case "BulletPlayerB":
targetPool = bulletPlayerB;
break;
case "BulletEnemyA":
targetPool = bulletEnemyA;
break;
case "BulletEnemyB":
targetPool = bulletEnemyB;
break;
}
for (int i = 0; i < targetPool.Length; i++)
{
if (!targetPool[i].activeSelf)
{
targetPool[i].SetActive(true);
return targetPool[i];
}
}
return null;
}
*activeSelf 함수 : true면 오브젝트 활성화, false면 오브젝트 비활성
오브젝트 생성 역할을 수행하는 public 함수 따로 생성
void SpawnEnemy()
{
//랜덤 변수를 통해 소환할 적과 적 위치 조정
int randomEnemy = Random.Range(0, enemyObjects.Length);
Debug.Log(enemyObjects.Length);
int randomPoint = Random.Range(0, spawnPoints.Length);
Debug.Log(spawnPoints.Length);
//적 리지드바디 가져옴
GameObject enemy = Instantiate(enemyObjects[randomEnemy], spawnPoints[randomPoint].position, spawnPoints[randomPoint].rotation);
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
Enemy enemyScript = enemy.GetComponent<Enemy>();
//적 생성 후 플레이어 변수 넘겨주기
enemyScript.player = player;
//왼쪽에서 스폰
if (spawnPoints[randomPoint].gameObject.name.Contains("Left"))
{
enemy.transform.Rotate(Vector3.forward* 90);
rigid.velocity = new Vector2(enemyScript.speed * 1, -1);
}
//오른쪽에서 스폰
else if (spawnPoints[randomPoint].gameObject.name.Contains("Right"))
{
enemy.transform.Rotate(Vector3.back * 90);
rigid.velocity = new Vector2(enemyScript.speed * (-1), -1);
}
else
{
rigid.velocity = new Vector2(0, enemyScript.speed * (-1));
}
}
위 스크립트에서 적을 생성하는 부분
GameObject enemy = Instantiate(enemyObjects[randomEnemy], spawnPoints[randomPoint].position, spawnPoints[randomPoint].rotation);
이 Instantiate 부분을 변경
GameObject enemy = objectManager.MakeObject(enemyObjects[randomEnemy]);
enemy.transform.position = spawnPoints[randomPoint].position;
MakeObject 함수를 통해서 적을 생성하고 위치를 따로 정해주도록 변경함
MissingReferenceException 에러가 나는데 그 이유는 오브젝트 풀링으로 비활성화하는 식으로 오브젝트를 관리하기 때문에 Destroy를 하면 안되기 때문
지금까지 사용한 Destroy 로직 모두 SetActive로 변경해줌
Instantiate 부분은 모두 오브젝트 풀링으로 교체
프리팹은 변수를 게임 매니저에서 넘겨주는 느낌으로 넘김
체력이 0에서 Active되고 방향이 이상하게 나오고 아이템이 중간에 멈춰있는 등의 오류 발생
public void onHit(int dmg)
{
//예외처리
if (health <= 0)
return;
health -= dmg;
//평소 스프라이트는 0, 피격시 스프라이트는 1
spriteRenderer.sprite = sprites[1];
//데미지 들어오고 일정 시간 후 원래대로 스프라이트 돌아오게 함
Invoke("ReturnSprite", 0.1f);
if (health <= 0) {
Player playerLogic = player.GetComponent<Player>();
playerLogic.score += enemyScore;
//아이템 랜덤드랍
int ran = Random.Range(0, 10);
if (ran < 4)
Debug.Log("아이템 없음");
else if (ran < 6)
{
GameObject itemCoin = objectManager.MakeObject("ItemCoin");
itemCoin.transform.position = transform.position;
Rigidbody2D rigid = itemCoin.GetComponent<Rigidbody2D>();
rigid.velocity = Vector3.down * 2.0f;
}
else if (ran < 8)
{
GameObject itemPower = objectManager.MakeObject("ItemPower");
itemPower.transform.position = transform.position;
Rigidbody2D rigid = itemPower.GetComponent<Rigidbody2D>();
rigid.velocity = Vector3.down * 2.0f;
}
else if (ran < 10)
{
GameObject itemBomb = objectManager.MakeObject("ItemBomb");
itemBomb.transform.position = transform.position;
Rigidbody2D rigid = itemBomb.GetComponent<Rigidbody2D>();
rigid.velocity = Vector3.down * 2.0f;
}
gameObject.SetActive(false);
/* 적의 회전, 체력 초기화*/
transform.rotation = Quaternion.identity;
health = maxHealth;
/**/
}
}
* Quaternion.identity : 기본 회전값 = 0
적의 체력 값을 최대체력을 기록해둔 maxHealth 변수를 이용해 초기화하고
Quaternion.identity를 통해 기본 회전값으로 돌려놓음
아이템의 속도처리를 아이템 스크립트 내에서가 아닌 Onhit함수 내에서 처리함
void OnEnable()
{
health = maxHealth;
}
* OnEnable() : 컴포넌트가 활성화될 때 호출되는 생명주기 함수
오브젝트가 Active될때마다 public 함수의 변수들이 초기화됨
위와 같이 OnEnable함수로 Active할 때마다 체력을 초기화해도 됨
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Item : MonoBehaviour
{
public string type;
Rigidbody2D rigid;
void Awake()
{
rigid = GetComponent<Rigidbody2D>();
}
void OnEnable()
{
rigid.velocity = Vector2.down * 2.0f;
}
}
아이템 스크립트도 위와 같이 변경
Find 교체
GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
for (int i = 0; i < enemies.Length; i++)
{
Enemy enemyLogic = enemies[i].GetComponent<Enemy>();
enemyLogic.onHit(30);
}
//적 총알 모두 처리
GameObject[] bullets = GameObject.FindGameObjectsWithTag("EnemyBullet");
for (int i = 0; i < bullets.Length; i++)
{
bullets[i].SetActive(false);
}
Boom 함수에서 위와 같이 FindGameObjectWithTag로 오브젝트를 찾는것은 컴퓨터에 많은 부하가 가해지므로 교체
public GameObject[] GetPool(string type)
{
switch (type)
{
case "EnemyS":
targetPool = enemyS;
break;
case "EnemyM":
targetPool = enemyM;
break;
case "EnemyL":
targetPool = enemyL;
break;
case "ItemCoin":
targetPool = itemCoin;
break;
case "ItemPower":
targetPool = itemPower;
break;
case "ItemBomb":
targetPool = itemBomb;
break;
case "BulletPlayerA":
targetPool = bulletPlayerA;
break;
case "BulletPlayerB":
targetPool = bulletPlayerB;
break;
case "BulletEnemyA":
targetPool = bulletEnemyA;
break;
case "BulletEnemyB":
targetPool = bulletEnemyB;
break;
}
return targetPool;
}
오브젝트 매니저에 지정한 오브젝트 풀을 가져오는 GetPool 함수 추가
GameObject[] enemiesS = objectManager.GetPool("EnemyS");
GameObject[] enemiesM = objectManager.GetPool("EnemyM");
GameObject[] enemiesL = objectManager.GetPool("EnemyL");
for (int i = 0; i < enemiesS.Length; i++)
{
if (enemiesS[i].activeSelf)
{
Enemy enemyLogic = enemiesS[i].GetComponent<Enemy>();
enemyLogic.onHit(30);
}
}
for (int i = 0; i < enemiesM.Length; i++)
{
if (enemiesM[i].activeSelf)
{
Enemy enemyLogic = enemiesM[i].GetComponent<Enemy>();
enemyLogic.onHit(30);
}
}
for (int i = 0; i < enemiesL.Length; i++)
{
if (enemiesL[i].activeSelf)
{
Enemy enemyLogic = enemiesL[i].GetComponent<Enemy>();
enemyLogic.onHit(30);
}
}
//적 총알 모두 처리
GameObject[] bulletsA = objectManager.GetPool("BulletEnemyA");
GameObject[] bulletsB = objectManager.GetPool("BulletEnemyB");
for (int i = 0; i < bulletsA.Length; i++)
{
if (bulletsA[i].activeSelf)
bulletsA[i].SetActive(false);
}
for (int i = 0; i < bulletsB.Length; i++)
{
if (bulletsB[i].activeSelf)
bulletsB[i].SetActive(false);
}
위와 같이 코드 변경함으로써 메모리 부담 줄여줌