- 프레임 속도를 유지하려면, 자주 생성되는 요소를 일부 메모리에 예약하는게 좋다.
- 최근 죽인 적을 메모리에서 업애는 대신 다시 사용할 수 있도록 오브젝트 풀에 추가.
- 엔티티의 새로운 인스턴스를 로드하는 초기의 촉기화 비용이 들지 않는다.
- Unity에는 오브젝트 풀링이 API에 구현되어 있다.
ObjectPool : 객체의 풀을 관리한다. 즉, 객체를 생성하고, 관리하고, 파기하는 책임
ReusablePool : 실제로 재사용될 객체이다. ObjectPool이 여기 객체들을 관리한다.
Client : 클라이언트는 필요할 때 ObjectPool에서 ReusablePool 객체를 요청하고, 사용 후에는 다시 풀에 반환한다.
예측할 수 있는 메모리 사용
성능의 향상
이미 C#이 메모리 최적화가 뛰어나서 굳이 필요없다는 이야기가 있다.
객체가 예측이 불가능하게 된다. (예측 불가능한 객체 상태)
public class ClientObjectPool : MonoBehaviour
{
private DroneObjectPool _pool;
void Start()
{
_pool = gameObject.AddComponent<DroneObjectPool>();
}
void OnGUI()
{
if (GUILayout.Button("Spawn Drones"))
_pool.Spawn();
}
}
public class Drone : MonoBehaviour
{
public IObjectPool<Drone> Pool { get; set; }
public float _currentHealth;
[SerializeField]
private float maxHealth = 100.0f;
[SerializeField]
private float timeToSelfDestruct = 3.0f;
void Start()
{
_currentHealth = maxHealth;
}
void OnEnable()
{
AttackPlayer();
StartCoroutine(SelfDestruct());
}
private void OnDisable()
{
ResetDrone();
}
IEnumerator SelfDestruct() {
yield return new WaitForSeconds(timeToSelfDestruct);
TakeDamage(maxHealth);
}
private void ReturnToPool() {
Pool.Release(this);
}
private void ResetDrone() {
_currentHealth = maxHealth;
}
public void AttackPlayer() {
Debug.Log("Attack player!");
}
public void TakeDamage(float amount) {
_currentHealth -= amount;
if (_currentHealth <= 0.0f)
ReturnToPool();
}
}
public class DroneObjectPool : MonoBehaviour
{
public int maxPoolSize = 10;
public int stackDefaultCapacity = 10;
// 필요할 떄 오브젝트 풀을 생성함
public IObjectPool<Drone> Pool
{
get
{
if (_pool == null)
_pool =
new ObjectPool<Drone>(
CreatedPooledItem,
OnTakeFromPool,
OnReturnedToPool,
OnDestroyPoolObject,
true,
stackDefaultCapacity,
maxPoolSize);
return _pool;
}
}
private IObjectPool<Drone> _pool;
private Drone CreatedPooledItem()
{
var go =
GameObject.CreatePrimitive(PrimitiveType.Cube);
Drone drone = go.AddComponent<Drone>();
go.name = "Drone";
drone.Pool = Pool;
return drone;
}
// 드론이 풀로 돌아올 때 호출됨
private void OnReturnedToPool(Drone drone)
{
drone.gameObject.SetActive(false);
}
// 드론이 풀에서 꺼내질 때 호출됨
private void OnTakeFromPool(Drone drone)
{
drone.gameObject.SetActive(true);
}
// 드론 오브젝트를 파괴할 때
private void OnDestroyPoolObject(Drone drone)
{
Destroy(drone.gameObject);
}
// 무작위 수의 드론을 생성하고 위치를 설정할 때
public void Spawn()
{
var amount = Random.Range(1, 10);
for (int i = 0; i < amount; ++i) {
var drone = Pool.Get();
drone.transform.position =
Random.insideUnitSphere * 10;
}
}
}
- 드론의 다양한 동작을 구현하는 상황
- 런타임에 특정 동작을 객체에 바로 할당할 수 있다.
Context : 자신의 작업을 수행하는데 필요한 전략을 선택하는 클래스
Strategy : 전략 인터페이스를 구현한 클래스들로, 특정 행동을 제공한다.
Client : 클라이언트에서 Context 클래스를 생성
캡슐화가 잘될 수 있다.
런타임에 객체가 사용하는 알고리즘을 교환할 수 있다.
public class Drone : MonoBehaviour {
public void ApplyStrategy(IBehaviour strategy) {
strategy.Maneuver(this);
}
}
public class ClientStrategy : MonoBehaviour {
private GameObject _drone;
private List<IBehaviour>
_components = new List<IBehaviour>();
private void SpawnDrone() {
_drone =
GameObject.CreatePrimitive(PrimitiveType.Cube);
_drone.AddComponent<Drone>();
_drone.transform.position =
Random.insideUnitSphere * 10;
ApplyRandomStrategies();
}
private void ApplyRandomStrategies() {
_components.Add(
_drone.AddComponent<Weaving>());
_components.Add(
_drone.AddComponent<Bopping>());
_components.Add(
_drone.AddComponent<Fallback>());
int index = Random.Range(0, _components.Count);
_drone.GetComponent<Drone>().
ApplyStrategy(_components[index]);
}
void OnGUI() {
if (GUILayout.Button("Spawn Drone")) {
SpawnDrone();
}
}
}
- 커맨드 패턴은 게임 내에서 발생하는 모든 행동 (이동, 점프)을 명령으로 캡슐화를 할 수 있다. 그리고 이 명령들이 모두 쉽게 기록이 된다.
- 기록을 재생하여 리플레이 시스템을 구현할 수 있다.
Clint : 커맨드 객체를 생성, 그 커맨드가 어떤 Receiver와 연결될지를 결정한다.
Invoker (호출자) : 커맨드를 받아서 실행한다.
Command (커맨드) : 실행될 모든 명령에 대한 인터페이스
Receiver (수신자) : 실제로 작업을 수행할 객체.
분리 : 실행하는 객체와 호출하는 개체가 분리된다.
명령하는 것을 큐에 넣어서 리플레이, 매크로, 명령 큐 등을 구현할 수 있다.
public abstract class Command
{
public abstract void Execute();
}
class Invoker : MonoBehaviour
{
private bool _isRecording;
private bool _isReplaying;
private float _replayTime;
private float _recordingTime;
private SortedList<float, Command> _recordedCommands =
new SortedList<float, Command>();
public void ExecuteCommand(Command command)
{
command.Execute();
if (_isRecording)
_recordedCommands.Add(_recordingTime, command);
Debug.Log("Recorded Time: " + _recordingTime);
Debug.Log("Recorded Command: " + command);
}
public void Record()
{
_recordingTime = 0.0f;
_isRecording = true;
}
public void Replay()
{
_replayTime = 0.0f;
_isReplaying = true;
if (_recordedCommands.Count <= 0)
Debug.LogError("No commands to replay!");
_recordedCommands.Reverse();
}
void FixedUpdate()
{
if (_isRecording)
_recordingTime += Time.fixedDeltaTime;
if (_isReplaying)
{
_replayTime += Time.fixedDeltaTime;
if (_recordedCommands.Any())
{
if (Mathf.Approximately(
_replayTime, _recordedCommands.Keys[0])) {
Debug.Log("Replay Time: " + _replayTime);
Debug.Log("Replay Command: " +
_recordedCommands.Values[0]);
_recordedCommands.Values[0].Execute();
_recordedCommands.RemoveAt(0);
}
}
else
{
_isReplaying = false;
}
}
}
}
public class TurnLeft : Command
{
private CharacterController _controller;
public TurnLeft(CharacterController controller)
{
_controller = controller;
}
public override void Execute()
{
_controller.Turn(CharacterController.Direction.Left);
}
}
public class TurnRight : Command
{
private CharacterController _controller;
public TurnRight(CharacterController controller)
{
_controller = controller;
}
public override void Execute()
{
_controller.Turn(CharacterController.Direction.Right);
}
}