Post | Sanctuary
top of page
  • Join our Discord!
  • Join our Kickstarter!

10,000개의 선택지에서 목표를 골라내는 법


반가워요, 여러분. 제 이름은 Badump, 그리고 이건 제 첫 개발자 로그랍니다. 저는 길찾기, 넷코드, 유닛 동작, 그리고 지금은 조준 코딩을 담당하고 있어요. 대부분 높은 사양을 요구하는 코드들이죠. 조만간 이것들과 관련된 개발자 로그를 받아보실 수 있을 겁니다. 최근에는 생츄어리에서 사용될 조준 시스템을 작업하고 있기도 합니다. 공간 카테고리 쿼리같은 것들을 담당하는 시스템이기도 하죠. (n 범위 안에 있는 x 계열의 모든 유닛들을 선택하는 것 같은 요소들 말입니다).

우선, 가능한 유즈 케이스, 그리고 이것이 최적화에 미치는 영향이 무엇일까요?

한번 매우 사양을 많이 잡아먹는 상태의 유즈 케이스를 가정해봅시다. 양쪽에 5000대씩, 총 10000개의 포병 유닛이 있는 상황이고, 두 부대 모두 서로가 사거리에 들어온 상황입니다.

예시를 들기 위해 Timevir가 그린 단순한 그림입니다. 놀랍게도 복붙을 10000번 반복하지는 않았더라고요.


대강 비교만 해 봐도, 5000^2개, 또는 2500만개의 계산이 동시에 이루어지고 있는 상황이죠. 으악. (심지어 이건 게임이 구현하려고 하는 최대 규모도 아닙니다. 그 상황에선 30000 vs 30000, 그러니까 9억개 정도의 계산이 이루어질 겁니다.)

한 가지 방법은 계산을 시간에 따라 분산시키는 것이지만, 이런 방식은 더 많은 유닛이 있는 상황에서는 문제를 일으킬 것이고, 그건 우리가 원하는 바가 아닙니다.

그리고, 당신의 레이더가 전력 고갈로 꺼졌다 켜졌다 하는 상황을 상상해보세요. 이제 모든 유닛들이 목표물을 재조준해야 하는 상황인데 그 과정에서 문제가 발생하면 유닛들이 사격하지 못해 잃는 DPS가 기하급수적으로 증가하겠죠.

그래서, 저희가 구현하고자 한 것은 이 계산을 지연시키지 않고 즉각적으로 수행할 수 있는 무언가였습니다. 또다른 특징들도 필요했고요. 예를 들자면, 이미 죽기 직전인 유닛들을 조준하지 않는다던가(오버킬 방지), 각도를 고려해서 가까운 거리에 있는 유닛을 조준한다던가 하는 것 말입니다. 바로 앞에 적이 있다고 전함 포탑이 180도 회전하게 할 수는 없으니까요.

제가 만들어낸 해결책은 대부분의 이 유닛 사거리들이 서로 겹친다는 점을 염두에 둔 것이었습니다. 즉, 이것들을 하나의 큰 그룹으로 묶어서, 각각의 유닛을 위해 계산하기보다는 전체 그룹을 위해 계산하는 방식 말입니다. 이 방식이 가장 잘 작동하는 상황에서는, 2500만번의 계산을 5000번으로, 9억번의 계산을 30000번으로 감소시킬 수 있죠. 그럼에도, 언제나 최적의 상황이 나올 수는 없기에 상황에 따라 100배에서 1000배 사이까지 연산 속도의 차이가 생기고, 유닛이 많아질수록 최저점과 최고점 사이의 간극도 벌어집니다. 빅 O 공식에 익숙한 사람들을 위해 보여드리자면: O(n^2) vs O(n log n).



퍼즐의 다음 조각은 가까운 유닛을 조준할 때 각도도 고려하는 것이었습니다. 그러니까, 공격을 위해 선회하는 데 너무 많은 시간을 잡아먹지 않는 유닛들을 우선적으로 조준하게 만들도록 하는 것이었죠. 그걸 정공법으로 해결하기엔 돈이 너무 많이 들었고, 그래서 우리는 약간의 편법을 사용했습니다. 우리는 모든 유닛들을 유닛 그룹의 대략적인 중심부를 둘러싼 고리로 분산시켰고, 각도 증분 수치로 이것을 치환했습니다. 10도가 36개의 고리가 되도록 하는 식이죠. 게다가 유닛들에는 오버킬 요소가 있고 우리는 유닛들이 그들의 피해보다 더 적은 체력을 가지고 있는 유닛들에게 조준하는 것을 되도록이면 피하게 만들었습니다.

자, 이제 해야 하는 일들을 알았으니, 그게 어떻게 이루어지는지 알아볼 차례겠죠? 아래에 코드 기반 테스트 중 하나를 그대로 공개해 어떻게 조준이 이루어지는지 알아보도록 하겠습니다:


이 코드는 우리의 모더들이 조준 시스탬이나, 그와 유사한 API 를 루아에 적용하는 데 쓰일 겁니다(당장은 다루게 될 주제가 아니니, 추후 소식을 기대헤주세요).

우선, 목표물은 고정된 규모로 인스턴스화되고, 게임 이전에 플레이어의 숫자를 알 수 있어야 합니다. 애석하게도 지금은 플레이어당 50MB정도가 생성되고, 이게 가장 빠른 방법이긴 하지만, 동시에 32명의 플레이어가 있는 경우에는 영 좋지 않은 일들이 발생할 수도 있겠죠. 저는 나중에 공개할 수 있도록 더 효율적인 방법을 지금도 작업하고 있습니다.

그 다음에 조준 우선 대상이 설정되는데, 유닛이 조준하고자 하는 것을 요약한 것이라고 할 수 있습니다. 지금은 테스트지만, 0이 지상 유닛이고 1이 공중 유닛을 의미한다고 생각해보세요. 우리의 유닛은 공중 유닛을 공격할 수 없고요.

당장은 단순히 조준 대상이 조준 유닛 목록에 있는 모든 카테고리에 해당되는 것인지 일치시키기만 하면 되는 겁니다. (이제 이 코드는 버스트 가능하니, 목록의 활용에 너무 신경쓰지는 마세요. 그냥 편의성 API일 뿐이니까요).

다른 유닛들은 모두 타입 0이라고 가정합시다. 지상 유닛이죠. 모든 게 잘 돌아가고 있습니다.서로 다른 플레이어들은 여러 팀이 존재할 가능성 때문에 서로 다른 그룹으로 배정받게 됩니다. 2팀이 고정으로 존재하는 경우에는 플레이어 id 대신 팀 id를 사용하는 것도 가능하고요. 그래서 지금은 32명의 플레이어의 2팀일 경우에는 잘 돌아가지만, 32명의 플레이어의 난투전일 경우에는... 영 좋지 않은 결과가 발생합니다. 그 뒤에 모든 조준 유닛들을 더한 뒤, 약간의 마법을 부려 그 유닛들을 하나의 그룹으로 묶습니다.

다음으로, 목표 대상인 유닛들을 추가합니다. 당장은 유닛 체력을 비롯해 몇몇 요소가 없는 상태지만, 추후 개발을 거치며 추가될 예정입니다.

목표물들 역시 그룹에 추가되고, 적합한 고리 분절에 분배됩니다 (포탑 회전을 줄이기 위해서)


그 뒤가 마지막 부분입니다. 목표물 자체를 유닛들에게 분배해, 각각의 그룹이 평행하게 처리되도록 하는 것이죠. (사격 유닛들은 각각 독립적이기 때문에, 각자 별도의 목표물을 가지고 있으니까요). 이 과정은 단순히 각각의 유닛이 가장 가까운 고리 분절로부터 방사형으로 반복되도록 하는 식입니다. 만약 0번째 분절(회전 각도가 0도)에서 시작한다면, 0.1, -1.2, -2.3...과 같은 식으로 뻗어나가며 목표물을 탐색하도록 하는 식입니다.


현재 코드는 20x20 = 400개의 유닛들이 9100개 정도 되는 유닛들을 조준하는 테스트 사례를 위한 것으로, 대략 800µs 안에 끝납니다(1000µs = 1ms). 포병대가 1티어 유닛들의 무리를 조준하는, 제가 게임에서 가장 좋아하는 시나리오이자, 제가 이 코드를 작성하는 데 그토록 많은 시간을 쏟아부은 이유죠. 대략 4백만번의 비교가 이루어지는 시나리오입니다.


T아직 최적화 작업에는 우리가 개선해야만 하는 몇 개의 큰 결점이 남아있습니다. 고리 시스템은 죽기 직전의 유닛들을 나타낼 수 없고, 나중에 조준하는 유닛들은 앞서 오버킬당한 유닛들을 피해 조준을 해야 하죠. 그 방식을 변경할 수 있다면 시간이 훨씬 덜 소모될 겁니다.

그리고 지금 당장 이 코드는 오직 한 개의 스레드에만 사용되고 있습니다. 몇몇 부분에서는 완전히 스레드 안전 이 보장되지 않은 상태니까요..

이 두 문제를 수정하는 것은 조준 시스템의 최적화가 더 빨리 이루어지도록 할 겁니다.(이미 600µs 수준에 다다르긴 했네요). 비록 평소에는 같은 프레임에서 이렇게 많은 유닛들이 동시에 조준을 시작할 일이 없다는 것을 기억해야겠지만요. 이건 우리의 시뮬레이션 규모에서 나올 수 있는 최악의 시나리오니까요.


현재 가장 큰 문제는 효율적으로 메모리를 사용할 수 있으면서도 스레딩이 안전하고 음, 물론 빨라야 하기까지 하는 리스트를 개발하는 것입니다. 애석하게도, 유니티의 nativeList 는 확장을 하면 스레드 안전이 보장되지 않습니다.

추신: 제가 이 포스트를 처음 작성했을 때, 디버그 모드를 꺼놓는 것을 깜빡했어요! 이제 40µs 까지 끌어올릴 수 있게 되었습니다 :)

추추신: 테스트 결과가 프로파일링을 하기에는 너무 빨랐기 때문에, 테스트의 규모를 적어도 10ms가 걸릴 때까지는 증가시킬 수밖에 없었습니다. 이 점에 대해서는 다음 글에서 설명드리고자 합니다.

다음 소식도 기대해주세요!

13 views0 comments
bottom of page