이번 프로젝트에서 사용할 패턴을 결정하였다.
사실 구조를 친구랑 같이 생각해보고 같이 짜다보니 이러한 패턴이 만들어졌는데
이 패턴이 객체지향 프로그래밍에서 가장 많이 사용되는 패턴라고 한다.
템플릿 메소드 패턴의 장점은 다음과 같습니다.
코드 재사용: 공통된 알고리즘의 구조를 상위 클래스에 정의하므로, 여러 클래스에서 해당 알고리즘을 재사용할 수 있습니다.
유연성: 알고리즘의 일부분을 하위 클래스에서 구현하므로, 상위 클래스의 알고리즘 구조를 변경하지 않고도 하위 클래스에서 알고리즘의 일부분을 수정할 수 있습니다.
확장성: 상위 클래스의 알고리즘 구조를 변경하거나 확장할 수 있습니다.
새로운 알고리즘을 추가할 때, 상위 클래스를 변경하거나 확장하여 새로운 하위 클래스를 만들면 됩니다.
코드 중복 최소화: 공통된 알고리즘의 구조를 상위 클래스에 정의하므로, 중복된 코드를 최소화할 수 있습니다.
구현 감춤: 상위 클래스에서 알고리즘의 구조를 정의하고, 구체적인 구현은 하위 클래스에 숨겨져 있으므로, 알고리즘의 구현 세부사항을 캡슐화할 수 있습니다.
템플릿 메소드 패턴은 상위 클래스와 하위 클래스 간의 강한 의존성을 유지하면서도 코드의 재사용성과 확장성을 높이는 데 도움이 됩니다.
제어 흐름의 제한: 템플릿 메소드 패턴은 알고리즘의 구조를 상위 클래스에서 정의하기 때문에 하위 클래스에서 알고리즘의 흐름을 변경하기 어렵습니다.
클래스 간의 결합도 증가: 상위 클래스와 하위 클래스 간의 강한 결합도를 유발할 수 있습니다.
상위 클래스에서 구체적인 메소드를 호출하기 때문에 클래스 간의 의존성이 높아질 수 있습니다.
단일 상속 제한: 템플릿 메소드 패턴은 상속을 기반으로 동작하기 때문에 단일 상속만을 지원합니다.
이미 다른 클래스를 상속받고 있는 경우에는 템플릿 메소드 패턴을 사용하기 어려울 수 있습니다.
클래스의 확장성 감소: 상위 클래스의 변경이 하위 클래스에 영향을 미칠 수 있습니다.
새로운 알고리즘을 추가하거나 기존의 알고리즘을 수정할 때 상위 클래스를 수정해야 하므로, 확장성이 감소할 수 있습니다.
복잡성 증가: 상위 클래스와 하위 클래스 간의 상호작용이 복잡해질 수 있습니다.
여러 개의 추상 메소드를 구현해야 하거나, 템플릿 메소드를 호출하는 하위 클래스가 많아질수록 코드의 복잡성이 증가할 수 있습니다.
예시 )
public abstract class Unit
{
public void Attack()
{
// 기본적인 공격 동작
Debug.Log("유닛이 공격합니다.");
}
public abstract void Move(); // 하위 클래스에서 구현해야 함
}
public class Marine : Unit
{
public override void Move()
{
// 마린만의 이동 동작
Debug.Log("마린이 이동합니다.");
}
}
public class Zealot : Unit
{
public override void Move()
{
// 질럿만의 이동 동작
Debug.Log("질럿이 이동합니다.");
}
}
GPT 답변
: 템플릿 메소드 패턴은 객체지향 디자인 패턴 중 하나로, 상위 클래스에서 알고리즘의 구조를 정의하고, 하위 클래스에서 알고리즘의 일부를 구체화하여 사용하는 패턴입니다.
템플릿 메소드 패턴은 다음과 같은 구조를 가집니다.
Abstract Class (추상 클래스):
알고리즘의 구조를 정의하는 클래스입니다.
템플릿 메소드를 포함하고 있습니다.
템플릿 메소드는 알고리즘의 구조를 정의하고, 여러 단계 중 일부는 추상 메소드로 정의합니다.
Concrete Classes (구체 클래스):
추상 클래스를 상속받아 구체적인 알고리즘을 구현하는 클래스입니다.
추상 메소드를 구현하여 알고리즘의 일부를 구체화합니다.
템플릿 메소드 패턴의 특징은 다음과 같습니다.
알고리즘의 구조는 상위 클래스에 정의되어 있으며, 일부 단계는 하위 클래스에서 구현됩니다.
상위 클래스에서는 템플릿 메소드를 통해 알고리즘의 구조를 정의하고, 하위 클래스에서는 이를 구현합니다.
상위 클래스에서 정의된 템플릿 메소드는 하위 클래스에서 오버라이드될 수 있습니다.
그리고 오늘은 오브젝트 폴링을 제작 하였다.
우리는 스테이지가 5개 라운드가 총 50라운드기 때문에
50라운드 동안 사용할 몹들을 전부 폴링 해놓으면 보스 포함해서 총 1005 마리 이다.
이것을 전부다 폴링하여 사용하려하니 아무리 생각해도 시작시 1000 마리나 소환하는것은 좋지않아보이기 때문에
1라운드 20마리 사용하고 setAcive(false) 한 다음
다음 라운드 시작시 20마리를 true로 바꿔 재사용 한 다음 필요한만큼 다시 추가를 하는것이 좋아보인다
이렇게 하면 매 라운드마다 20마리씩만 추가해서 사용하는것이 훨씬 좋다
이것은 선생님께서 조언 해주셔서 그대로 구현하였다.
WaveManager.cs
using System.Collections;
using UnityEngine;
public class WaveManager : MonoBehaviour
{
[System.Serializable]
public class MonsterData
{
public GameObject prefab;
public int count;
}
[SerializeField] private MonsterData[] monsters;
[SerializeField] private float summonInterval = 0.1f;
[SerializeField] private Transform[] go;
[SerializeField] private GameObject[] monsterPool;
private int currentMonsterIndex;
private static int monsterIndexTemp;
private Coroutine spawnCoroutine;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
StartSpawnCoroutine(0, 20);
}
else if (Input.GetMouseButtonDown(1))
{
DeactivateAllMonsters();
}
else if (Input.GetKeyDown(KeyCode.Space))
{
ClearMonsterPool();
}
else if (Input.GetKeyDown(KeyCode.V))
{
StartSpawnCoroutine(2, 20);
}
}
private void InitializeMonsterPool(int monsterIndex, int count)
{
if (monsterIndexTemp != monsterIndex)
{
ClearMonsterPool();
}
monsterIndexTemp = monsterIndex;
Debug.Log(monsterIndexTemp);
// 이미 폴링된 몬스터의 수를 가져옵니다.
int pooledMonsterCount = (monsterPool != null) ? monsterPool.Length : 0;
// 새로운 몬스터 풀 배열을 생성합니다.
GameObject[] newMonsterPool = new GameObject[count + pooledMonsterCount];
// 기존에 폴링된 몬스터를 새로운 배열로 복사하고 비활성화합니다.
for (int i = 0; i < pooledMonsterCount; i++)
{
newMonsterPool[i] = monsterPool[i];
newMonsterPool[i].SetActive(false);
}
// 새로운 몬스터만 폴링합니다.
for (int j = pooledMonsterCount; j < newMonsterPool.Length; j++)
{
GameObject monster = Instantiate(monsters[monsterIndex].prefab, Vector3.zero, Quaternion.identity, transform);
monster.SetActive(false);
newMonsterPool[j] = monster;
}
// 새로운 몬스터 풀 배열로 교체합니다.
monsterPool = newMonsterPool;
currentMonsterIndex = 0;
}
private IEnumerator ActivateAllMonstersSequentially(int count)
{
for (int i = 0; i < count; i++)
{
if (!monsterPool[i].activeSelf)
{
monsterPool[i].SetActive(true);
Debug.Log(monsterPool[i].name + "를 활성화했습니다.");
yield return new WaitForSeconds(summonInterval);
}
}
}
private IEnumerator SpawnMonsters(int monsterIndex, int count)
{
InitializeMonsterPool(monsterIndex, count);
yield return StartCoroutine(ActivateAllMonstersSequentially(monsterPool.Length));
}
private void StartSpawnCoroutine(int monsterIndex, int count)
{
if (spawnCoroutine != null)
{
StopCoroutine(spawnCoroutine);
}
spawnCoroutine = StartCoroutine(SpawnMonsters(monsterIndex, count));
}
private void ClearMonsterPool()
{
if (monsterPool != null)
{
for (int i = 0; i < monsterPool.Length; i++)
{
Destroy(monsterPool[i]);
}
monsterPool = null;
}
}
private void DeactivateAllMonsters()
{
if (monsterPool != null)
{
for (int i = 0; i < monsterPool.Length; i++)
{
monsterPool[i].SetActive(false);
}
}
}
}
여기서 몹을 스폰하는 조건만 붙여주면 끝이난다.