다음의 게시글의 코드를 함께 참고하였습니다.
게임 안에서 가까이 있는 물체들로부터만 소리가 들린다거나, 서로 붙어 있는 유저들끼리만 채팅이 가능하다던지, 주변의 객체들하고만 상호작용하기 위해서 주변에 어떤 객체들이 있는지를 알아야 할 필요가 있다.
이걸 매 프레임마다 확인한다면?
매우 큰 수준의 성능 병목이 일어날 수 밖에 없는 것이다.
검사 방법도 마찬가지다.
모든 오브젝트들의 서로 간의 거리를 일일히 계산한다면?
GameObject FindClosestEnemySlow(GameObject soldier)
{
GameObject closestEnemy = null;
float bestDistSqr = Mathf.Infinity;
//Loop through all enemies
for (int i = 0; i < enemySoldiers.Count; i++)
{
//The distance sqr between the soldier and this enemy
float distSqr = (soldier.transform.position - enemySoldiers[i].transform.position).sqrMagnitude;
//If this distance is better than the previous best distance, then we have found an enemy that's closer
if (distSqr < bestDistSqr)
{
bestDistSqr = distSqr;
closestEnemy = enemySoldiers[i];
}
}
return closestEnemy;
}
벌써 O(n^2)의 확인을 매번마다 해줘야 한다는 답답한 상황이 나온다.
그래서 공간 분할 패턴에서는 이러한 객체들의 위치에 따라 객체들을 정렬하여 보관해놓는다.
정확히 어떻게 하는 것인지 알아보자.
공간 분할 패턴
공간 분할 패턴에서는 공간 자료구조라는 것을 따로 두고, 객체들의 위치에 따라 자료구조를 업데이트 해준다.
보통은 2차원의 공간을 그리드로 나누어 각각의 셀에 어떤 오브젝트가 있는지를 관리하게 된다.
void Start()
{
//Create a new grid
grid = new Grid((int)mapWidth, cellSize);
//Add random enemies and friendly and store them in a list
for (int i = 0; i < numberOfSoldiers; i++)
{
//Give the enemy a random position
Vector3 randomPos = new Vector3(Random.Range(0f, mapWidth), 0.5f, Random.Range(0f, mapWidth));
//Create a new enemy
GameObject newEnemy = Instantiate(enemyObj, randomPos, Quaternion.identity) as GameObject;
//Add the enemy to a list
enemySoldiers.Add(new Enemy(newEnemy, mapWidth, grid));
//Parent it
newEnemy.transform.parent = enemyParent;
//Give the friendly a random position
randomPos = new Vector3(Random.Range(0f, mapWidth), 0.5f, Random.Range(0f, mapWidth));
//Create a new friendly
GameObject newFriendly = Instantiate(friendlyObj, randomPos, Quaternion.identity) as GameObject;
//Add the friendly to a list
friendlySoldiers.Add(new Friendly(newFriendly, mapWidth));
//Parent it
newFriendly.transform.parent = friendlyParent;
}
}
void Update()
{
//Move the enemies
for (int i = 0; i < enemySoldiers.Count; i++)
{
enemySoldiers[i].Move();
}
//Reset material of the closest enemies
for (int i = 0; i < closestEnemies.Count; i++)
{
closestEnemies[i].soldierMeshRenderer.material = enemyMaterial;
}
//Reset the list with closest enemies
closestEnemies.Clear();
//For each friendly, find the closest enemy and change its color and chase it
for (int i = 0; i < friendlySoldiers.Count; i++)
{
//Soldier closestEnemy = FindClosestEnemySlow(friendlySoldiers[i]);
//The fast version with spatial partition
Soldier closestEnemy = grid.FindClosestEnemy(friendlySoldiers[i]);
//If we found an enemy
if (closestEnemy != null)
{
//Change material
closestEnemy.soldierMeshRenderer.material = closestEnemyMaterial;
closestEnemies.Add(closestEnemy);
//Move the friendly in the direction of the enemy
friendlySoldiers[i].Move(closestEnemy);
}
}
}
주의점
공간 분할을 해서 내가 과연 성능 상의 이득을 얻을 수 있는 지를 잘 고민해봐야 한다.
기본적으로는 O(n^2)보다 나은 수준의 공간 탐색을 구현하겠다는 것인데, n의 크기가 충분히 작다면 그냥 그대로 쓰는 것이 더 나을 수도 있다는 것이다.
또한 객체가 소속된 공간이 변경될 때마다 이 위치 변경을 처리하기가 더 어려워지기 때문에 코드가 더 복잡해진다.
또한 자료구조를 통해 객체들의 소속 위치를 저장하기 때문에 추가적인 메모리가 필요하다.
CPU 성능 최적화보다 메모리 최적화가 더 필요한 상황이라면? 안쓰는게 더 나을수도 있다는 것이다.
공간 분할을 위해 사용할 수 있는 자료구조
공간을 분할하고, 그것을 자료구조에 저장할 때, 사용할 수 있는 것들은 다음과 같다.
- 격자
- 쿼드트리
- 이진 공간 분할 (BSP)
- k-d 트리
- 경계 볼륨 계층 구조 (BVH)
보통 1차원 자료구조를 다차원으로 확장한 것에 지나지 않기 때문에, 한 번 간단하게 살펴보고 내 상황에 맞는 자료구조를 선택해보자.
Uploaded by N2T
'개발 > 게임 디자인 패턴' 카테고리의 다른 글
게임 디자인 패턴 17. MVC 패턴 (MVC Pattern) (0) | 2023.11.20 |
---|---|
게임 디자인 패턴 16. 이벤트 버스 패턴 (Event Bus Pattern) (0) | 2023.11.14 |
게임 디자인 패턴 14. 객체 풀 패턴 (Object Pool Pattern) (0) | 2023.11.09 |
게임 디자인 패턴 13. 더티 플래그 패턴 (Dirty Flag Pattern) (0) | 2023.11.08 |
게임 디자인 패턴 12. 서비스 중개자 패턴 (Service Mediator Pattern) (0) | 2023.11.07 |