[ Object Pool ]
Create a fountain of stuff with physics.
Make objects poolable.
Add functionality to prefabs.
Generate pools on demand.
Survive recompiles and scene transitions.
[ 1 Spawning Lots of Stuff ]
Object Pool
빈번하게 생성하고 삭제해야 하는 Object들을 재사용할 수 있도록 하는 것
생성과 삭제를 반복하지 않고, 미리 Object를 만들고 활성화, 비활성화를 반복하도록 하는 것
기존 Tutorial(Frames Per Second)에서 사용했던 FPS Panel을 재사용
Spawning Things 만들기 ( 분수에서 생성되서 나오는 물리적인 속성을 가진 물체 만들기)
Menu : GameObject >> 3D >> Cube
Menu : GameObject >> 3D >> Sphere
Project Window : Create >> C#
Name : Stuff
[RequireComponent(typeof(Rigidbody))]
public class Stuff : MonoBehaviour {
Rigidbody body;
void Awake () {
body = GetComponent<Rigidbody>();
}
}
Stuff 드래그 >> (to) Cube , (to)Sphere
Cube와 Sphere를 Prefab으로 만들기
Spawner 만들기
Menu : GameObject >> Create Empty
Name : Spawner
Project Window : Create >> C#
Name : StuffSpawner
StuffSpawner 드래그 >> (to) Spawner
public class StuffSpawner : MonoBehaviour {
public float timeBetweenSpawns;
public Stuff[] stuffPrefabs;
float timeSinceLastSpawn;
void FixedUpdate () {
timeSinceLastSpawn += Time.deltaTime;
if (timeSinceLastSpawn >= timeBetweenSpawns) {
timeSinceLastSpawn -= timeBetweenSpawns;
SpawnStuff();
}
}
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
Stuff spawn = Instantiate<Stuff>(prefab);
spawn.transform.localPosition = transform.position;
}
}
timeBetweenSpawn 에 설정한 시간 간격마다 랜덤으로 stuffPrefab 배열 중에 하나가 생성
Spawner에서 분출되는 Stuff들의 생성 속도를 설정할 수 있도록 하기
[RequireComponent(typeof(Rigidbody))]
public class Stuff : MonoBehaviour {
public Rigidbody Body { get; private set; }
void Awake () {
Body = GetComponent<Rigidbody>();
}
}
Property를 이용해서 Body로 접근이 가능할 수 있도록 하고 있음
public float velocity;
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
Stuff spawn = Instantiate<Stuff>(prefab);
spawn.transform.localPosition = transform.position;
spawn.Body.velocity = transform.up * velocity;
}
GetComponent<Rigidbody>()로 접근하지않고, 편하게 Body로 접근
Time Between Spawns : 0.1 , Velocity : 15
Spwaner를 여러 개 생성(링 모양)해서 분수같이 표현하도록 하기
Menu : GameObject >> Create Empty
Name : SpawnerRing
Project Window : Create >> C#
Name : StuffSpawnerRing
StuffSpawnerRing(Script) 드래그 >> (to) StuffSpawnerRing(GameObject)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StuffSpawnerRing : MonoBehaviour {
public int numberOfSpawners;
public float radius, tiltAngle;
public StuffSpawner spawnerPrefab;
void Awake () {
for (int i = 0; i < numberOfSpawners; i++) {
CreateSpawner(i);
}
}
void CreateSpawner (int index) {
Transform rotater = new GameObject("Rotater").transform;
rotater.SetParent(transform, false);
rotater.localRotation =
Quaternion.Euler(0f, index * 360f / numberOfSpawners, 0f);
StuffSpawner spawner = Instantiate<StuffSpawner>(spawnerPrefab);
spawner.transform.SetParent(rotater, false);
spawner.transform.localPosition = new Vector3(0f, 0f, radius);
spawner.transform.localRotation = Quaternion.Euler(tiltAngle, 0f, 0f);
}
}
Spawner를 생성하기 전에 부모 객체(rotater)를 만들어서 (시계 프로젝트에서 했던 것처럼 )부모 객체를 회전시켜 배치하고 있음. 즉, 원점(rotater 위치)을 중심으로 Spawner를 회전하고 있음
Number Of Spawners : 20, Radius : 25, Tilt Angle : -20, Spawner Prefab : Spawner(프리팹)
Kill Zone 만들기
일정 위치에 있으면 Stuff들을 사라지게 하기 위해(현재는 사라지지 않고 계속 만들어짐)
Menu : GameObject >> Create Empty
Name : KillZone
Position (0, -60, 0)
Add Component >> Box Collider
Is Trigger : Check
size (1000, 100, 1000)
[ Tag ] 만들기
Kill Zone(GameObject)에 Kill Zone이라는 Tag 붙이기
유니티에서 제공하는 충돌 체크 시스템
Stuff Script 추가 소스
[RequireComponent(typeof(Rigidbody))]
public class Stuff : MonoBehaviour {
public Rigidbody Body { get; private set; }
void Awake () {
Body = GetComponent<Rigidbody>();
}
void OnTriggerEnter (Collider enteredCollider) {
if (enteredCollider.CompareTag("Kill Zone")) {
Destroy(gameObject);
}
}
}
Kill Zone이라는 "Tag"를 가진 오브젝트의 Collider를 만나게 되면 자신을 파괴함
[ 2 Adding Variation ]
너무 규칙적인 분수 효과를 랜덤하게 진행될 수 있도록 하자
랜덤 Structure 만들기
많은 곳에서 사용될 예정이니, 쓰기 편한 Structure로 만들기
using UnityEngine;
[System.Serializable]
public struct FloatRange {
public float min, max;
public float RandomInRange {
get {
return Random.Range(min, max);
}
}
}
물방울이 분출되는 시간을 랜덤하게 만들자
public FloatRange timeBetweenSpawns;
public Stuff[] stuffPrefabs;
float timeSinceLastSpawn;
float currentSpawnDelay;
void FixedUpdate () {
timeSinceLastSpawn += Time.deltaTime;
if (timeSinceLastSpawn >= currentSpawnDelay) {
timeSinceLastSpawn -= currentSpawnDelay;
currentSpawnDelay = timeBetweenSpawns.RandomInRange;
SpawnStuff();
}
}
분출되는 물방울 크기를 랜덤하게 하기
public FloatRange timeBetweenSpawns, scale;
public Stuff[] stuffPrefabs;
float timeSinceLastSpawn;
public float velocity;
float currentSpawnDelay;
void FixedUpdate () {
timeSinceLastSpawn += Time.deltaTime;
if (timeSinceLastSpawn >= currentSpawnDelay) {
timeSinceLastSpawn -= currentSpawnDelay;
currentSpawnDelay = timeBetweenSpawns.RandomInRange;
SpawnStuff();
}
}
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
Stuff spawn = Instantiate<Stuff>(prefab);
spawn.transform.localPosition = transform.position;
spawn.transform.localScale = Vector3.one * scale.RandomInRange;
spawn.transform.localRotation = Random.rotation;
spawn.Body.velocity = transform.up * velocity;
}
분출되는 물방울의 속도 랜덤하게 하기
public class StuffSpawner : MonoBehaviour {
public FloatRange timeBetweenSpawns, scale, randomVelocity;
public Stuff[] stuffPrefabs;
float timeSinceLastSpawn;
public float velocity;
float currentSpawnDelay;
void FixedUpdate () {
timeSinceLastSpawn += Time.deltaTime;
if (timeSinceLastSpawn >= currentSpawnDelay) {
timeSinceLastSpawn -= currentSpawnDelay;
currentSpawnDelay = timeBetweenSpawns.RandomInRange;
SpawnStuff();
}
}
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
Stuff spawn = Instantiate<Stuff>(prefab);
spawn.transform.localScale = Vector3.one * scale.RandomInRange;
spawn.transform.localRotation = Random.rotation;
spawn.Body.velocity = transform.up * velocity +
Random.onUnitSphere * randomVelocity.RandomInRange;
}
}
물방울이 임의의 각속도를 가질 수 있도록 추가
Stuff를 중심으로 크기 1인 임의의 방향[onUnitSphere]으로 영향을 주고 있음
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
Stuff spawn = Instantiate<Stuff>(prefab);
spawn.transform.localPosition = transform.position;
spawn.transform.localScale = Vector3.one * scale.RandomInRange;
spawn.transform.localRotation = Random.rotation;
spawn.Body.velocity = transform.up * velocity +
Random.onUnitSphere * randomVelocity.RandomInRange;
spawn.Body.angularVelocity =
Random.onUnitSphere * angularVelocity.RandomInRange;
}
결과 그림
Material 삽입하기
Material 만들기
Project Window : Create >> Material
Name : Blue
Blue Inspector : Albedo를 파란색으로 설정
public Material stuffMaterial;
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
Stuff spawn = Instantiate<Stuff>(prefab);
spawn.transform.localPosition = transform.position;
spawn.transform.localScale = Vector3.one * scale.RandomInRange;
spawn.transform.localRotation = Random.rotation;
spawn.Body.velocity = transform.up * velocity +
Random.onUnitSphere * randomVelocity.RandomInRange;
spawn.Body.angularVelocity =
Random.onUnitSphere * angularVelocity.RandomInRange;
spawn.GetComponent<MeshRenderer>().material = stuffMaterial;
}
다양한 Material 사용하기
위와 같은 방법으로 Purple, Yellow, Red Material 생성
public Material[] stuffMaterials;
void CreateSpawner (int index) {
// 부모 객체 rotator를 만들고 그것을 회전한 다음
Transform rotater = new GameObject("Rotater").transform;
rotater.SetParent(transform, false);
rotater.localRotation =
Quaternion.Euler(0f, index * 360f / numberOfSpawners, 0f);
StuffSpawner spawner = Instantiate<StuffSpawner>(spawnerPrefab);
spawner.transform.SetParent(rotater, false);
spawner.transform.localPosition = new Vector3(0f, 0f, radius);
spawner.transform.localRotation = Quaternion.Euler(tiltAngle, 0f, 0f);
spawner.stuffMaterial = stuffMaterials[index % stuffMaterials.Length];
}
분수 분출기 마다 설정된 Material이 사용될 수 있도록 하기 위해 StuffSpawnerRing 스크립트를 고친다.
분수 중앙에 Obstacle 넣어 보기
Sphere : scale : 15
[ 2.1 Making Heavier Stuff ]
Menu : Window >> Profiler
매 프레임마다 발생하는 memory allocation 확인
좀 더 복잡한 Stuff를 만들어서 테스트해 보기 (cross prefab)
Menu : GameObject >> 3D Object >> Cube
Cube Inspector : Transform >> Scale (0.5, 0.5, 2.5)
2개의 Cube를 더 만들고 Scale을 각각 (2.5, 0.5, 0.5), (0.5, 2.5, 0.5)로 해서 아래와 같은 모양으로 만듬
Menu : GameObject >> Create Empty
Name : Cross
위에서 만든 3개의 Cube를 Cross의 하위 자식으로 만듬
Cross Inspector >> Add Component >> Rigidbody
Mass : 3
Cross에 Stuff(스크립트)를 Component로 삽입하기
Stuff 드래그 >> (to) Cross
Cross를 프리팹으로 만들기
Cross 드래그 >> (to) Project Window
Spawner Inspector >> Stuff Spawner >> Stuff Prefbs에 Cross 추가해서 넣기 (Prefab 적용을 해줘야 함)
여러개의 게임오브젝트를 가진 Cross의 material 처리하기
[RequireComponent(typeof(Rigidbody))]
public class Stuff : MonoBehaviour {
public Rigidbody Body { get; private set; }
MeshRenderer[] meshRenderers;
public void SetMaterial (Material m) {
// 인자로 넘어오는 메티리얼(m)으로 모든 material을 대치함
// 당연히 하나의 메티리얼을 사용하고 있으면, 하나만 사용하게 됨
for (int i = 0; i < meshRenderers.Length; i++) {
meshRenderers[i].material = m;
}
}
void Awake () {
Body = GetComponent<Rigidbody>();
// GetComponentInChildren : 모든 자식객체들이 가고 있는 <component>를 얻어올 수 있도록 하는 함수
// 즉, 자식들의 MeshRenderer를 얻어 옴
meshRenderers = GetComponentsInChildren<MeshRenderer>();
}
void OnTriggerEnter (Collider enteredCollider) {
if (enteredCollider.CompareTag("Kill Zone")) {
Destroy(gameObject);
}
}
}
StuffSpawner
public Material stuffMaterial;
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
Stuff spawn = Instantiate<Stuff>(prefab);
spawn.transform.localPosition = transform.position;
spawn.transform.localScale = Vector3.one * scale.RandomInRange;
spawn.transform.localRotation = Random.rotation;
spawn.Body.velocity = transform.up * velocity +
Random.onUnitSphere * randomVelocity.RandomInRange;
spawn.Body.angularVelocity =
Random.onUnitSphere * angularVelocity.RandomInRange;
//spawn.GetComponent<MeshRenderer>().material = stuffMaterial;
spawn.SetMaterial(stuffMaterial);
}
caltrop 추가하기
Leg 1 만들기
Menu : GameObject >> 3D Object >> Capsules
Capsule Inspector : Transform >> Position (0, 0.5, 0)
Menu : GameObject >> Create Empty
Name : Leg 1
Capsule 드래그 >> (to) Leg 1
Leg 2 만들기
Leg 1 카피 (Ctrl + D)
Name : Leg 2
Leg 2 Inspector : Transform >> Rotation ( 70.52878, 180, 180)
Leg 3, 4 만들기
Leg 2 카피 (Ctrl + D) 2번
Name : Leg 3 , Leg 4
Leg 3, 4 Inspector : Transform >> Rotation ( 70.52878, 300, 180) , Rotation ( 70.52878,60, 180)
Caltrop prefab 만들기
Menu : GameObject >> Create Empty
Name : Caltrop
위에서 만든 4개의 Leg를 Caltrop의 하위 자식으로 만듬
Caltrop Inspector >> Add Component >> Rigidbody
Mass : 4
Caltrop에 Stuff(스크립트)를 Component로 삽입하기
Stuff 드래그 >> (to) Caltrop
Caltrop 드래그 >> (to) Project Window
Spawner Inspector >> Stuff Spawner >> Stuff Prefbs에 Caltrop 추가해서 넣기 (Prefab 적용을 해줘야 함)
[ 2.1 Pooling Objects ]
강제적으로 실행환경을 힘들게 해서 프레임 수를 떨어트려 보자.
FindObjectsOfType<Stuff>();
Stuff( Component)를 가지고 있는 모든 게임 오브젝트를 찾는 함수로 실행 실행 속도를 현저하게 떨어짐
Profiler를 통해 이를 확인
이것을 해결하고자 하는데는 Object Pool이 필요한 것은 아니지만, 스크립트를 작성하는데 있어서 사용 목적 및 활용 가치성을 고려하지 않으면 실행 속도를 떨어트리는 문제가 발생할 수 있음
Instantiate 함수
Instantiate 함수의 경우 동적으로 메모리를 할당하게 되는데, 지금까지의 만든 예제처럼 반복적으로 객체를 생성(Instantiate)하고 삭제(Destroy)를 하는 경우 Mobile 시연환경에서는 심각한 lag을 발생시킬 수 있음
동적으로 할당된 메모리는 우리가 제어할 수 없는 순간에 GC(Gabage Collection)를 발생시켜 한 순간 속도를 저하 시키는 현상을 발생 시킴
Object Pool
반복적으로 객체를 생성하고 삭제해야 하는 상황에서 미리 객체들을 만들어 놓고, 사용될 때는 활성화(Active)될 수 있도록 하고, 사용되지 않을 때는(파괴) 삭제하지 않고 비활성화 상태로 놔둘 수 있도록 함
즉, 한번 생성된 객체를 재사용함으로써 추가적인 동적 메모리 할당이 생겨나지 않도록 하여 최대한 GC의 활동을 저지함
PooledObject 만들기
Object Pool에서 들어갈 자료형(클래스) 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PooledObject : MonoBehaviour {
public ObjectPool Pool { get; set; }
// Use this for initialization
public void ReturnToPool() {
if (Pool)
Pool.AddObject (this);
else {
Destroy (gameObject);
}
}
}
Stuff에서 상속받기
[RequireComponent(typeof(Rigidbody))]
public class Stuff : PooledObject {
.................
void OnTriggerEnter (Collider enteredCollider) {
if (enteredCollider.CompareTag("Kill Zone")) {
//Destroy(gameObject);
ReturnToPool ();
}
}
}
Stuff가 PooledObject를 상속 받았으며, 이전에는 파괴(Destroy)되었던 것을 Object Pool에 들어갈 수 있도록 하고 있음
Object Pool 만들기
Menu : GameObject >> Create Empty
Name : TestObjectPool
C# 스크립트 생성
Name : ObjectPool
using UnityEngine;
using System.Collections.Generic;
public class ObjectPool : MonoBehaviour {
public PooledObject prefab; // prefab 변수는 사실 여기서는 사용 의미가 없음
public PooledObject GetObject () {
PooledObject obj = Instantiate<PooledObject>(prefab);
obj.transform.SetParent(transform, false);
obj.Pool = this;
return obj;
}
public void AddObject (PooledObject o) {
Object.Destroy(o.gameObject) ;
}
}
GetObject() : 원래는 ObjectPool에서 만들어진 GameObject를 얻어오는 함수이지만, 일단 여기서는 큰 구조를 잡을 수 있도록 새롭게 객체를 생성할 수 있도록 함
AddObject() : 원래는 다쓰고난 GameObject는 ObjectPool에 넘겨주는 함수 이지만, 일단 여기서는 파괴할 수 있도록 함
ObjectPool(스크립트)드래그 >> (to) TestObjectPool
StuffSpawner 변경
public ObjectPool objPool;
void Start() {
objPool = GameObject.Find ("TestObjectPool").GetComponent<ObjectPool> ();
}
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
//Stuff spawn = Instantiate<Stuff>(prefab);
objPool.prefab = prefab;
Stuff spawn = (Stuff) objPool.GetObject();
spawn.transform.localPosition = transform.position;
spawn.transform.localScale = Vector3.one * scale.RandomInRange;
spawn.transform.localRotation = Random.rotation;
spawn.Body.velocity = transform.up * velocity +
Random.onUnitSphere * randomVelocity.RandomInRange;
spawn.Body.angularVelocity =
Random.onUnitSphere * angularVelocity.RandomInRange;
//spawn.GetComponent<MeshRenderer>().material = stuffMaterial;
spawn.SetMaterial (stuffMaterial);
}
StuffSpawner가 prefab으로 되어 있어 objPool에 레퍼런스를 넣을 수가 없기 때문에 Start() 함수에서 ObjectPool을 찾을 수 있도록 하고 있음
실행 테스트
ObjectPool 구현
List를 스택 자료 구조로 사용하고 있음
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool : MonoBehaviour {
List<PooledObject> availableObjects = new List<PooledObject>();
// 새로운 Stuff를 만들어야 하는 상황에서는 prefab으로 연결된 Stuff를 만들도록 하고 있음
// 향후 Stuff의 모양별로 ObjectPool을 만들 예정이라 필요한 변수
public PooledObject prefab;
public PooledObject GetObject() {
PooledObject obj;
int lastAvailableIndex = availableObjects.Count - 1;
if (lastAvailableIndex >= 0) { // 리스트 내의 맨 마지막 Stuff를 재활용할 수 있도록 해준다.
obj = availableObjects [lastAvailableIndex];
availableObjects.RemoveAt (lastAvailableIndex);
obj.gameObject.SetActive (true);
Debug.Log ("availableObjects : Pool Out" + availableObjects.Count);
} else { // 현재 리스트에 재활용할 수 있는 Stuff이 없는 경우 Stuff를 새로 만든다.
obj = Instantiate<PooledObject> (prefab);
obj.transform.SetParent (transform, false);
obj.Pool = this;
}
return obj;
}
public void AddObject(PooledObject obj) {
obj.gameObject.SetActive (false);
availableObjects.Add(obj); // 더이상 사용하지 않는 Stuff는 리스트에 넣을 수 있도록 함
Debug.Log ("availableObjects : Pool in" + availableObjects.Count);
}
}
실행 테스트
TestObjectPool에 연결된 Stuff가 일정 이상의 개수 이후에는 만들어지지 않음 (public 변수를 만들어서 확인해 보기 바람)
각 Stuff마다 Object Pool 만들기
기존의 TestObjectPool은 삭제
TestObjectPool을 사용하기 위해서는 각 Stuff을 위한 Object Pool을 씬 내에 하나씩 만들어야 하지만, 이들을 가변적으로 만들어 내게 하기 위해서는 삭제하여야 함(prefab으로 미리 만들어 놓는 것도 하나의 방법이지만, 여기서는 만들려고 하는 stuff의 prefab의 종류에 따라서 동적으로 Object Pool을 만들고자 하고 있으며, 그런 부분에서 prefab으로 만들어서 사용할 수 없음)
SpawnSuff 고치기
// 삭제
//void Start() {
// objPool = GameObject.Find ("TestObjectPool").GetComponent<ObjectPool> ();
//}
void SpawnStuff () {
Stuff prefab = stuffPrefabs[Random.Range(0, stuffPrefabs.Length)];
//objPool.prefab = prefab;
//Stuff spawn = (Stuff) objPool.GetObject();
Stuff spawn = prefab.GetPooledInstance<Stuff>();
GetPoolInstance함수를 통해 랜덤으로 선택된 prefab(Stuff의 한종류) 형태로 되어 있는 Pool에서 Stuff을 얻어 올 수 있도록 함
PooledObject에서 GetPoolInstance() 구현하기
public T GetPooledInstance<T> () where T : PooledObject { ... }
[ Generic Programming ]
제네릭 프로그래밍(Generic programming)은 데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있는 기술에 중점을 두어 재사용성을 높일 수 있는 프로그래밍 방식
[ where ]
제네릭 선언에 정의된 형식 매개 변수의 인수로 사용할 수 있는 형식에 대한 제약 조건을 지정하는 데 사용
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PooledObject : MonoBehaviour {
public ObjectPool Pool { get; set; }
[System.NonSerialized]
ObjectPool poolInstanceForPrefab;
// poolInstanceForPrefab이 있으면(즉 Stuff의 해당 Pool이 있을 경우에는)
// 그 Pool에서 Stuff를 얻어오고, 없으면 이 Stuff 형태의 Pool을 만들 수 있도록 한다.
// ObjectPool.GetPool은 static 함수
public T GetPooledInstance<T> () where T : PooledObject {
if (!poolInstanceForPrefab) {
poolInstanceForPrefab = ObjectPool.GetPool(this);
}
return (T)poolInstanceForPrefab.GetObject();
}
public void ReturnToPool() {
if (Pool)
Pool.AddObject (this);
else {
Destroy (gameObject);
}
}
}
ObjectPool 완성
GetPool 함수는 static으로 만들어야 객체가 생성되지 않은 상황에서도 호출해서 사용할 수 있음
NonSerialized 이유
poolInstanceForPrefab를 단지 참조만을 위해서 사용하는 것이기 때문에, Inspector창에서 보여줄 필요가 없음(prefab의 일부로도 만들 수 없음)
Serialized를 사용하게되면,
(perfab으로 만들고자 할 때) 유니티는 Object Pool의 영역을 prefab의 일부로 만들고자 할 것이지만 이를 실패하게 됨
만약 변수가 public으로 되어 있다면 inspector창에서 미스매칭이 이루어지는 현상이 발생함
public class ObjectPool : MonoBehaviour {
List<PooledObject> availableObjects = new List<PooledObject>();
public static ObjectPool GetPool (PooledObject prefab) {
GameObject obj;
ObjectPool pool;
// Editor가 실행중이면, 이미 만들어져 있는지 확인하도록 한다.
// Rebuild 상황에서 두번 만들어질 수 있다고 하는데, 필요없는 것 같아 보임
if (Application.isEditor) {
obj = GameObject.Find(prefab.name + " Pool");
if (obj) {
pool = obj.GetComponent<ObjectPool>();
if (pool) {
return pool;
}
}
}
// 인자로 넘어온 prefab의 Object Pool을 만듬
obj = new GameObject(prefab.name + " Pool");
pool = obj.AddComponent<ObjectPool>();
pool.prefab = prefab;
return pool;
}
public PooledObject prefab;
..........................
[ 2.1 Pooling Across Scenes ]
씬을 전환할 UI 만들기
Menu : GameObject >> UI >> Event System ( Event System 만들어져 있으면 만들 필요 없음)
씬 전환을 위한 버튼 만들기
Canvas(GameObject) 오른쪽 클릭 >> UI >> Button
Name : Switch Scene Button
씬 전환코드 만들기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneSwitcher : MonoBehaviour {
public void SwitchScene () {
int nextLevel = (Application.loadedLevel + 1) % Application.levelCount;
//Application.LoadLevel(nextLevel);
SceneManager.LoadScene (nextLevel);
}
}
Applciation.LoadLevel()이 5.4 버전 이후부터는 SceneManager.LoadScene을 권장하고 있음
향후 지원 안하는 함수가 될 예정임
Application.loadLevel 및 Application.levelCount는 나중에 대치해야 함(편의상 집어 넣음)
SceneSwitcher 드래그 >> (to) Canavas : UI : Switch Scene Button
Button 클릭 처리 ( Button이 클릭 되었을 때, SceneSwitcher 클래스의 SwitchScene이 호출될 수 있도록 함
Button Inspector : OnClick
Switch Scene Button 드래그 >> (to) ...
SceneSwitcher.SwitchScene() 연결
새로운 씬 만들기
기존 저장된 씬을 Save as를 통해 새롭게 씬을 만듬
새로운 씬은 중앙에 있는 Sphere를 Cube로 바꿀 수 있도록 함
두 개의 씬을 Build할 수 있도록 환경 설정
File >> Build Settings ...
실시간으로 라이트 맵을 굽게 되는 것을 미리 구운 상태의 라이트 맵 생성하기
Window >> Lighting >> Debug Settings : Scene( or Global Maps) 페이지 : Auto Generate : Uncheck
Scene이나 Global maps에 있는 Auto Generate를 Uncheck
그리고 Generate Lighting 클릭 ( 두개의 씬 모두 수행하기 )
씬 전환할 때, Object Pool이 사라지지 않게 하기
DontDestroyOnLoad(obj);
씬의 전환이 이루어져도 obj는 사라지지 않게 하는 함수
public class ObjectPool : MonoBehaviour {
List<PooledObject> availableObjects = new List<PooledObject>();
public static ObjectPool GetPool (PooledObject prefab) {
GameObject obj;
ObjectPool pool;
if (Application.isEditor) {
obj = GameObject.Find(prefab.name + " Pool");
if (obj) {
pool = obj.GetComponent<ObjectPool>();
if (pool) {
return pool;
}
}
}
obj = new GameObject(prefab.name + " Pool");
DontDestroyOnLoad(obj);
pool = obj.AddComponent<ObjectPool>();
pool.prefab = prefab;
return pool;
}
....
실행을 해보면 기존에는 씬을 바꿀 때마다 새롭게 Stuff을 생성해서 분수를 만들었으나, Object Pool이 사라지지 않은 관계로 여기에 연결되어 있는 Stuff도 온전히 남아 있으며, 씬을 전환해도 분수의 모양은 그대로 남아 있음
씬이 전환될 때, 모든 Stuff을 Object Pool로 들어가게 만들기
기존에는 OnLevelWasLoaded() 함수를 사용했으나, 조만간 지원되지 않을 함수로 SceneManager 클래스를 이용해서 사용해야 함
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
[RequireComponent(typeof(Rigidbody))]
public class Stuff : PooledObject {
. . . . .
void Awake () {
Body = GetComponent<Rigidbody>();
// GetComponentInChildren : 모든 자식객체들이 가고 있는 <component>를 얻어올 수 있도록 하는 함수
// 즉, 자식들의 MeshRenderer를 얻어 옴
meshRenderers = GetComponentsInChildren<MeshRenderer>();
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
}
void OnDisable() {
//SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
}
void SceneManager_sceneLoaded (UnityEngine.SceneManagement.Scene arg0, LoadSceneMode arg1)
{
ReturnToPool ();
}
. . . . .
}
Delegate로 되어 있는 SceneManger.sceneLoaded
SceneManager_sceneLoaded() 함수를 SceneManager.sceneLoaded 에 등록 시킴으로써, 씬이 전환될 때마다 이 이벤트를 받을 수 있게 됨