2013년 11월 13일 수요일

[Intelligence Engine] Decision Modeling and Optimization in GameDesign,Part 4: PvP 클래스 밸런싱

이 글은 현재 연재중인 ‘Decision Modeling and Optimization in Game Design' 시리즈의 4번째 글이며, 현재까지 작성된 전체 글 목록은 다음과 같다.




이 글에서 사용된 엑셀 예제화일 다운로드 받기: link



Spreadsheets vs Simulations

앞서 올렸던 3개의 글들을 통해 ‘결정 모델'과 ‘최적화'의 개념 및 MS Excel의 기능인 Solver 도구(역: 한글 버전 엑셀의 경우 ’데이터’ 리본의 ‘해 찾기’ 기능)의 활용방법 등에 관하여 소개했다. 또한 그 것들을 이용하여 *4X 전략 게임에서 최적의 ‘세율'을 찾아내는 방법, Space 시뮬레이션 게임에서 최적의 teleport 위치를 결정하는 방법, 그리고 Part 1에서 맛보기로 소개했던 SuperTank 게임에서 최적의 무기 조합 찾아내기 등의 문제에 어떻게 활용될 수 있는지 살펴 보았다.
(역: 4X - eXplore, eXpand, eXploit, and eXterminate)


이 시점에서 자연스럽게 한가지 의문이 생긴다. 어? 혹시 이거 게임 밸런싱에도 사용될 수 있는 거 아닌가? 전략 게임, RPG, MMORPG 등 다양한 게임을 만들 때마다 접하게되는 난감한 밸런싱 문제들을 해결하는 데에도 혹시 적용 가능한 거 아닐까?


대답은 ‘예쓰' 되겠다. :)
물론, 만병통치약은 아니고 수 많은 제약과 고려사항이 수반되긴 한다. 근본적으로 Excel sheet를 통해 우리가 만들고자 하는 게임을 정확하게 표현해 내는 것 자체가 사실상 곤란하기 때문에 많은 제약을 안고 시작할 수 밖에 없다. 실제로 게임을 만들면서 우리가 접하는 진짜 어려운 밸런싱 문제의 대부분은 Excel sheet에 표현할 수 있는 영역을 벗어나는 경우가 대부분이기 때문에 우리가 강력한 optimization technique을 갖고 있다고 하더라도 실제로 적용하는 것이 쉽지 않다. 적어도 Excel이라는 제약 하에서는 말이다. 실시간에 발생 가능한 다양한 동적인 상황과 유동적인 조건을 모두 고려하여 엑셀에 절차적으로 표현하고 정확히 시뮬레이션 하는 것은 사실상 불가능에 가깝다.


따라서, MMORPG게임인 WildStar 또는 Planetary Annihilation 같은 전략 게임에 등장하는 유닛 클래스들 간의 밸런싱을 위해 우리가 다루고 있는 기술들을 사용하여 정확하고 의미있는 결과를 얻고자 한다면 애초부터 시뮬레이터 자체를 게임에 포함시켜 넣는 수 밖에는 없을 것이다.


또한, 어찌어찌 하여 시뮬레이션 할 수 있는 적절한 수단을 찾았다고 하더라도 본질적으로 밸런싱에는 ‘자동화' 될 수 없는 영역이라는 것이 존재한다. 첫 번째 글의 ‘배쨈(Disclaimers)’ 섹션에서도 언급했다시피 플레이어들의 ‘느낌' 까지 자동으로 튜닝되도록 만들 수 있는 방법은 없기 때문이다.


결과적으로, 우리가 이번 글을 통해 여러분에게 제시할 수 있는 최선은 간단한 밸런싱 관련 예제를 제시하고 우리가 다루고 있는 결정모델과 최적화 기술을 동원하여 Excel을 통해 문제를 해결해 나가는 기본적인 접근방법을 소개하는 것이라고 생각한다.


우리는 이번 글에서 단순한 RPG 게임의 캐릭터 클래스간의 전투 밸런싱 문제를 제시하고, Excel의 Solver도구를 이용하여 효과적으로 문제를 해결하는 방법을 소개하려고 한다. 이번 과정을 충분히 이해하고 나면, 예제에 사용된 기본적인 framework을 응용하여 조금 더 복잡하고, 여러분이 현실적으로 접하게 되는 밸런싱 문제의 해결에 도 응용할 수 있을 것으로 믿는다.



“Balancing” is Undefined

대체 “밸런싱”을 뭐라고 정의해야 할까? 모든 상황을 포괄하면서도 간단하게 한마디로 “밸런싱"이란 녀석을  정의하기는 쉽지 않을 듯 하다. 실제로 다루어지고 있는 문제의 ‘맥락'에 따라서 의미하는 바는 매번 달라질 수 있기 때문이다. 예를 들어, RPG나 Team 기반의 액션 게임에서라면 서로 다른 캐릭터 클래스들이 각각의 차별화된 스탯을 갖고 있지만 전체적으로 봤을 때는 동등한 효용성을 갖도록 하는 것을 말하는 것일 수도 있고, 전략 게임에서라면 특정 종족이 특별히 유리하거나 불리하게 되지 않도록 만드는 것을 말하는 것일 수도 있으며, 어떤 게임 내에서 특정 유닛이나 자원이 그 가격에 대해 상응하는 효용성을 갖도록, 즉 유저들이 납득할 수 있는 수준의 ROI를 지니도록 만드는 것일 수도 있다.


“밸런싱"의 가장 적절한 정의는 다루고 있는 해당 게임의 기획적 목표에 따라 달라지게 될 것이지만, 그러한 기획적 목표라고 하는 것은 무엇이라도 될 수 있기 때문에 특정 게임 또는 장르를 벗어나 일반적으로 “밸런싱이란 xxx 이다" 라고 일반화 하여 정의할 방법은 딱히 없는 듯 하다.


어떤 유저들의 경우 전투 밸런싱이란 말을 ‘데미지'와 동일시 하는 경향이 있는데, 특히 MMORPG 장르에서 특정 클래스의 DPS(Damage Per Second)가 타 클래스들에 비해 특별히 높거나 낮은 경우 흔히 불만과 함께 밸런싱 문제를 호소하는 경우가 그러할 것이다.


당근, 캐릭터 클래스라고 하는 것은 DPS하나로 밸런싱될 수 없으며, DPS만으로 잘 밸런스 되었는지 여부를 판단할 수도 없다. 사실 특정 클래스가 다른 클래스에 비해 조낸 높은 DPS를 갖는다고 해도 아~무 문제 될 것이 없기 때문이다. 왜냐면(정상적으로 밸런싱 되어 있다면) 다른 부분에서 그에 상응하는 페널티를 가져야 정상일 것이고, 결국 전체적으로 봤을 때 각 클래스는 게임상에서 동일한 ‘효익'을 갖게 되기 때문이다. 뭐 예를 들자면, DPS는 졸 ~ 높지만 피통이 그에 상응하여 대단히 적다거나, 한방의 딜은 좋지만 쿨다운이 길어 전체적인 플레이 시간을 놓고 보면 쨉 졸라 날리는 놈과 실질적 효익 측면에서는 결과적으로 동일한 가치를 갖게 될 것이기 때문이다.




The Tiny MMO

음… 이제부터 우리는 “Tiny MMO”라는 지극히 단순화된 MMORPG를 하나 만들어 보려고 한다. 4개의 캐릭터 클래스를 가지고 있으며, 각각의 클래스들은 서로 다른 특징을 갖고 있지만 PvP 전투를 했을 때, 절대적으로 강한 클래스 또는 약한 클래스가 존재해서는 안되며, 모든 클래스의 캐릭터는 각자 동등한 전투능력을 갖도록 밸런싱 할 것이다.


“Tiny MMO”는 실시간 게임이며, 각 플레이어의 액션은 3초간 진행된다고 가정하자. 이 경우 우리는 문제를 단순화하기 위해 각 “턴"에 3초가 소요되는 턴제 게임으로 간주하는 것이 가능할 것이다. 이렇게 함으로써 각 액션을 엑셀 테이블 상에 단계적으로 표현하는 것이 가능해지며, 결과적으로 우리가 문제를 파악하고 해결하는 것이 한결 수월해진다.


이 게임에서 유저는 다음과 같은 4개의 서로다른 캐릭터 클래스 중 하나를 선택할 수 있다.


  • Warrior : 데미지 딜러다.
  • Mage : 원거리 캐릭이며 가장 긴 사정거리를 갖는다.
  • Healer : 매 턴마다 자신의 HP를 자동 회복한다.
  • Barbarian : 몸빵맨이다.


현 상태에서 우리가 각각의 클래스에 대해 정의할 수 있는 특징은 대략 이정도 뿐이기 때문에, 이제 각 클래스별로 4개의 파라미터 - HP, Damage, Healing, Attack Range - 들이 적절한 값을 갖도록 세팅할 차례이다. 다시 말하지만, 우리의 목표는 각 클래스들별 특징이 명확하게(즉, 각 파라미터별 수치 차이가 클래스별로 확실하여 각 클래스의 개성을 유저가 확실히 인지할 수 있도록) 유지되면서 특정 클래스가 특별히 유리하다거나 불리함 없이 정확히 “밸런스"된 상태를 만들어내는 것이다. (역: 전사와 바바리안의 공격력을 각각 10과 11로 해놓고 서로 '다른' 클래스의 캐릭터라고 부를 수는 없지 않겠는가? 즉, 인지 가능한 수준으로 충분히 큰 차이를 만들어 내야 한다는 얘기)


다른 말로, 아래 테이블의 내용을 최적화 해야 한다. (역: 우리의 goal을 만족하는 최적해를 찾아내서 아래 노란색의 각 셀에 채워 넣어야 한다. 그치만 global optimum을 찾는다는 보장은 없고 다만 "쓸만한" 정도일 거다)






뭐 일단은 모든 클래스에 동일하게 임의의 값들을 채워 넣었다. 각 값들이 갖는 의미를 설명하자면 다음과 같다. HP는 50, 공격시 매 턴당 10의 데미지를 입히며, 턴당 HP회복량은 0, 그리고 40m의 공격 거리를 갖는다는 의미 되겠다. 또한, 모든 캐릭은 5 m/turn 의 동일한 이동속도를 갖는다고 가정하겠다. 4종의 클래스 모두 고정된 이동속도를 갖기때문에 위 테이블에는 포함시키지 않았다.


‘Damage’ 관점에서 볼 때 지극히 단순화된 예제라는 것을 필자도 알고 있다. 여기서 사용되는 damage는 Damage-Per-Turn 이라고 하는 평균 데미지가 지속적으로 가해지는 (어찌보면 이상적인) 상황을 가정하고 있는 관계로 Mana를 이용한 조낸 강력한 스킬 공격이라거나 그 밖에 캐릭터의 공격 능력에 급격한 변화를 줄 수 있는 어떠한 상황도 쌩까고 있다. 뿐만 아니라 단순화를 위해 실제 게임에서 등장하게 되는 다양한 데미지 유형(역: 물리공격, 마법공격, 속성공격 등)들은 쿨하게 쌩까고 딱 한가지의 데미지 타입만 사용하고 있으며, 데미지량의 랜덤 요소도 배제 시켰는데, 랜덤에 의한 데미지 편차가 전투 결과에 매번 유의미한 차이를 만들어 낼정도로 크게 작용하지 않는 형태의 단순한 게임을 가정했기 때문이다.


물론, 앞서도 말했지만 우리가 아무리 다양한 요소를 고려한다고 하더라도 Excel에서 이루어지는 밸런싱이 완벽할 수는 없다. 결국 최종 밸런싱은 수 많은 플레이 테스트의 결과로 사람에 의해 다듬어질 수밖에 없기 때문이다. 그럼에도 불구하고 이러한 시도가 의미를 갖는 이유는, 실제 플레이 테스트에 앞서 단지 1~2시간만을 투자하여 충분히 탄탄한 밸런스 가이드라인을 확보한 상태에서 플레이 테스트를 진행하게 된다면 궁극적으로 지향하는 밸런싱 목표에 도달하기위해 필요한 플레이 테스트 및 수치조정 시간과 비용을 엄청나게 절약할 수 있을 것이기 때문이다.



The Victory Table

우리는 모든 캐릭터 클래스 각각에 대해 나머지 클래스와 1:1 전투를 붙였을 때 특정 클래스에게 강하고 약함의 치우침이 없도록 밸런싱 해야 한다. 우리가 밸런스 해야 하는 캐릭터 클래스의 갯수는 4개인 관계로 실제로 조합 가능한 1:1 전투 쌍은 다음과 같이 총 6가지 되겠다. (역: 당근 자기 자신과의 전투는 의미 없으니 배제)


  • Warrior vs Mage
  • Warrior vs Healer
  • Warrior vs Barbarian
  • Mage vs Healer
  • Mage vs Barbarian
  • Healer vs Barbarian


아 ㅆㅂ 보기만 해도 어찌해야 할 지 졸 어려워 보인다. 단지 4가지 클래스만 있는 대단히 간단한 게임을 가정했음에도 클래스간 6 가지의 전투 조합이 존재하며, 그림으로 나타내면 다음과 같다.





딱 보면 알겠지만, A와 C간의 밸런스를 맞추기 위해 아주 살짝 A의 능력치를 임의 수정할 경우, 우리 의도와는 상관 없이 즉시 A-D, A-B 사이의 밸런스에도 함께 영향을 미치게 된다. 여기에 새로운 캐릭터 클래스를 추가할 때마다 상호 연관 조합의 수는 exponential 증가하게 된다. 결국 전체를 통합하여 고려된 밸런싱 framework이 아니라 일일이 수작업으로 개별 값을 조금씩 고쳐가며 전체 밸런스를 잡아나가겠다는 접근은 사실 그 결과는 안봐도 비디오인 재앙과 같은 상황 되겠다. (역: 뭐 결국 대충 적당히 하고 덮거나, 어찌 어찌 잘 되겠지 하고 자포자기 하거나, 출시 후 수시로 무한 밸런스 패치 해가며 서비스를 걸레로 만들거나, 서비스 이후 버려지는 캐릭 클래스 나와도 속수무책 하거나 뭐 그런거겠지 >_<)


궁극적으로, 우리가 하려는 것은 아래와 같은 일종의 “Victory table”을 얻는 것이다. 우리가 Excel sheet상에 6가지 각각의 전투 상황에 대한 시뮬레이션을 적절하게 모델링할 수 있다면, 각 전투 결과에 대해 “score”를 뽑아낼 수 있을 것이다. 당근 높은 점수가 더 좋은 것이기 때문에, “Solver”에서 활용할 최종적인 평가함수를 만들어 내는 데에 이들 6가지 전투 결과 점수를 이용할 수 있을 것이다.





위 테이블을 이해하는 데 있어서 한 가지 알아둘 점은 대각선 라인의 경우 동일한 클래스끼리의 조합이므로 동일한 밸런스가 보장되기 때문에 0으로 채워져 있으며, 나머지 0으로 채워진 곳은 좌측 하단의 삼각형 영역에서 이미 valuation되고 있으므로 중복 scoring을 피하기 위해 역시 0으로 채웠다.


자 이제 각 클래스간의 전투 시뮬레이션 모델을 만들어 보자.





The “Combat Sim”

각 클래스간 전투 조합을 만들 때 일단은 두 클래스간의 거리가 100 미터 떨어져 있다고 가정할 것이다. 각 캐릭터는 공격에 3초를 할당받기 때문에, 앞서 언급한 대로 이를 턴베이스 시뮬레이션 방식으로 표현하기 위해 각 “턴"은 3초를 의미한다. 매 “턴"마다 각 캐릭은 상대가 사정거리 내에 있을 경우 공격을 할 것이며, 그렇지 않을 경우 상대가 사정거리내에 들 때까지 거리를 좁히기 위해 이동하게 된다. (역: 자칫 'turn-based game에만 적용 가능한 것인가? 라고 착각하지 말기 바람. 엑셀 환경이란 제약으로 인해 '턴'이란 개념으로 시뮬레이션한 것일뿐, 리얼타임에도 동일하게 적용됨. 즉, turn을 second로 이해해도 아무 문제 없음)


시뮬레이션 모습은 대략 다음과 같다.





테이블을 위쪽에서부터 살펴보면, 일단 서로 전투를 하게될 2개의 클래스를 지정했다. 위의 경우 법사(class #1)와 힐러(class #2) 간의 전투 시뮬레이션 되겠다. 맨 좌측 컬럼은 매 턴마다 두 캐릭터간의 거리를 나타내고 있다.


나머지 column들의 설명은 각각 다음과 같다.


  • Max Range: 사정거리 되겠다. 앞서 살펴본 ‘decision variable’ 테이블의 값을 직접 참조하도록 되어  있다.
  • Healing: 턴당 HP회복량 되겠다. 역시나 ‘decision variable’ 테이블에 지정된 값을 참조하고 있다.
  • HP: 매 턴당 각 캐릭의 HP잔량 되겠다. 최초의 값은 다른 녀석들과 마찬가지로 ‘decision variable’ 테이블의 값을 참조하고 있지만, 턴이 거듭 됨에 따라 상대의 공격으로 인해 피가 까이게 된다. 물론 Healing 값이 0보다 클 경우 매턴 그에 상응하는 HP회복 또한 이루어진다.
  • Damage: 적이 사정거리 내에 있을 경우 상대에게 매 턴당 입히게 되는 피해량을 의미한다. 상대 캐릭이 죽었을 경우는 0으로 리셋된다.
  • Attacks?: 상대가 사정거리 내에 있는지를 나타내는 일종의 flag 값이다. 1일 경우는 상대를 공격할 수 있음을 의미하며, 0일 경우는 거리를 좁히기 위해 이동해야 하는 상황임을 나타낸다.


이제 각 캐릭은 서로 상대를 향해 이동하기 시작하며, 공격이 가능한 상황이 되면 누군가 죽을 때 까지 서로 공격을 하게 된다. 모든 캐릭은 동일하게 3초당 5미터의 이동속도(턴 베이스로 해석할 경우 5 m/turn)를 가지기 때문에 두 캐릭 모두 거리를 좁히기 위해 이동하는 경우에는 “Range” 컬럼의 값이 매 턴당 10씩 감소하게 되며, 한 캐릭만 이동이 필요한 경우는 5씩 줄어들게 된다. 이 시뮬레이션에서는 매 턴마다 2캐릭이 동시에 행동을 하고 있음을 가정하므로, 동시에 죽는 것 또한 가능하다.


다음으로, 이 시뮬레이션의 결과에 점수를 부여해야 하는데, 전투 성적이 얼마나 “좋았는지"를 나타내는 숫자값을 생성해 내는 것이다. 다른 말로, 우리가 제시한 기획 목표에 얼마나 근접했는가를 나타낼 평가함수가 필요한 것이다.  (역: 노파심에 부연 설명하자면, 높은 점수를 받는다는 것의 의미가 특정 클래스가 졸라 “강했다" 라는 것을 의미하는 것이 아니라 서로 전투를 벌인 두 클래스가 서로 얼마나 “동등한 전투 능력을 보였는가" 라는 것이다. 우리의 목적은 특정 클래스를 강력하게 만드는 것이 아니라 각각 클래스별 특성을 유지하면서도 모든 클래스가 전반적으로 동등한 전투 능력을 갖도록 만드는 것이라는 점을 상기하자)


전투가 끝났을 때 두 캐릭이 모두 동시에 죽는 것이 가장 이상적인 밸런싱이 되겠지만 혹시 한놈이 살아남더라도 최대한 HP잔량이 적어야 한다. 즉, 살아남은 캐릭의 HP잔량이 거의 0에 가까워야 한다는 얘기 되겠다. 당연히 우리가 원하는 대로 각 캐릭의 능력이 이상적으로 밸런스 되어 있다면, 전투가 끝날 무렵엔 서로 상대방의 피를 0으로 만들 수 있어야 하기 때문이다.


그렇지만, 이 것 만으로는 충분하지 않다. 왜냐하면, 이대로 scoring 해버릴 경우(역: 전투 결과를 평가할 경우) optimizer는 damage만 가능한 한도 내에서 최대치로 상승시키려 할 것이며, 그 결과 각 캐릭은 상대방을 전투 시작과 동시에 한방에 녹이는 결과를 만들어낼 것이기 때문이다 (의심이 든다면, 첨부한 Excel 화일의 Score평가함수를 수정하여, 어떤 결과가 나오는 지 직접 확인해 보기 바란다). 전투 시작 하자마자 한방에 서로를 죽여버리는 것은 분명 우리가 원했던 결과가 아니다. 우리가 원하는 것은 납득할 수 있는 시간 동안 (턴 횟수 동안) 전투가 이어질 수 있음과 동시에 두 캐릭 모두 정확히 동시에 죽거나 혹은 거의 죽기 직전의 상태에 이를 수 있어야 한다는 것이다.


다른 말로, 단지 각 클래스간에 능력치가 동일하도록 맞추는 것 뿐만 아니라 ‘게임의 흥미' 라는 측면또한 만족시킬 수 있도록 밸런스 되어야 한다는 것이며, 그러한 ‘흥미'의 일부로서 적절한 수준의 시간동안 전투가 유지될 수 있음을 보장해야 하는 것이다.


각 전투에 대한 Score를 생성하기 위해, 시뮬레이션 테이블의 우측에 몇 개의 셀을 추가했다. Duration 은 전투가 얼마나 지속되었는지를 나타내는데, 시뮬레이션 테이블에서 2 캐릭터 모두 살아있는 동안의 턴 수를 카운트 한 값이다. Total HP는 전투가 종료되었을 때 두 캐릭에게 남아있는 HP 잔량의 합이다. 당연히 비록 서로다른 클래스별 특성은 갖고 있지만 전투 능력의 측면에서 두 캐릭은 동등해야 하므로 이상적으로는 0이 되어야 할 것이다.


끝으로, 이렇게 얻어진 Duration과 Total HP값을 이용하여 최종 “Score”는 다음과 같이 계산했다.


Score = (Duration / (1 + Total HP))


분모에 “+1”이 들어갔는데, 뭐 특별히 의미가 있다기 보다는 Total HP가 0이되는 상황이 발생하기 때문에 자칫 divided-by-zero 오류가 발생하는 것을 방지하기 위한 것이다. 이러한 평가 방법을 통해 optimizer로 하여금 전투 시간은 최대로 하면서도, 최종 HP잔량을 최소로 하는 값을 찾도록 만들 수 있다.
(역: 당연하다. Duration이 커지거나 Total HP가 작아지면 Score는 커지게 된다. 즉, 최대한 전투를 길게 끌면서 전투 종료 시 둘 다 완전히 하얗게 태워버린 경우 “참 잘했어요” 도장을 찍어주는 평가함수를 만든 것이다. OK?)


첨부한 Excel화일을 열어보면 알겠지만 각 전투 시뮬레이션은 총 17개의 rows(turns)가 있는데, 이는 17 rounds 정도 전투가 지속되는 것이 적절하지 않을까? 하고 우리가 기획적 판단을 내렸던 것 뿐이다. 만약 전투를 더 길게 늘리거나 줄이고 싶다면 적당히 평가함수 수식을 조절하고 다시 optimizer를 돌려주기만 하면 된다.


최종적으로, 각각의 시뮬레이션 결과로 얻어진 6개의 “Score”값들을 취합하여 앞서 소개했던 “Victory Table”을 채워주도록 하자.


여기서 우리는 6개의 score들을 단순히 합산하고 우리의 최종 “Score”값으로 사용할 수도 있을 것이다. 그렇지만 진짜로 이렇게 할 경우 Solver 입장에서는 각 전투별 점수의 편중을 완화할 방법이 없어진다. 즉, 높은 점수를 얻었던 전투와 낮은 점수를 얻었던 전투가 그대로 유지될 것이라는 얘기이다. 이는 우리가 원하는 결과가 아니다. 우리는 모든 전투 조합에 대한 점수가 골고루 높은 득점이 되도록 만들어야 한다. 가능하다면 하향 평준화가 아니라 상향 평준화 즉, 낮은 점수를 받았던 전투가 높은 점수를 얻을 수 있도록 개선되도록 만들어야 한다는 것이다. 이를 위해 우리는 Solver로 하여금 낮은 점수를 받은 전투 조합의 성적을 높이는 것에 더 높은 가중치를 두고 동작할 수 있도록 있도록 스코어 합산 값에 가장 적은 점수를 곱할 것이다. (역: Total Score = Sum(Score 1 : Score 6) * Min(Score 1 : Score 6)  하겠다는 얘기. 이렇게 하면 가장 낮은 점수를 받았던 녀석의 스코어를 높일수록 Total Score가 상승하는 폭이 커지게 되므로 자연스럽게 상향 평준화가 이루어질 수 있다. 다른말로, solver optimizer가 더 나은 값이 있음에도 최적화를 중단하도록 하지 않고 더 좋은 값을 찾아 계속 노가다 뛰게 만들 수 있다)


Adding Constraints

미안한데, 아직도 끝난 것이 아니다. ㅎㅎㅎ 만약 현재 상태에서 decision model을 동작시킬 경우, 여전히 우리가 원하는 결과를 보여주지는 않을 것이다. 좀 더 정확히 말하면, optimizer는 모든 클래스 파라미터들(HP, Damage, Healing, Range)에 대해 클래스를 막론하고 같은 값을 세팅해 버릴 것이다. 즉, 클래스의 특징 구분이 없어져 버릴 것이다.


물론 우리가 원하는 것은 앞서 정의했던 대로 각 클래스별로 개성을 유지할 수 있도록 서로 다른 값을 갖도록 하는 것이다. 전사는 데미지에 특화되어야 하고, 법사는 장거리 공격에 특화되어야 하며, 힐러는 회복력에 최적화되어야 하고 바바리안은 몸빵에 최적화 되어야 한다. 물론, 이들 특성이 명백히 드러날 수 있도록 각 parameter의 클래스별 차이가 충분히 크게 설정될 수 있어야 한다. (역: 예를 들어 법사와 전사의 공격력 차이가 1이라면  비록 전사 공격력이 강한 것은 맞지만 사실 유저가 느끼기에 그놈이 그놈이 될 테니까 이 역시 원하는 결과는 아니라는 얘기)


이것을 보장하기 위해, 우리는 “constraint table” 이라는 녀석을 추가로 도입하였다. 이 테이블을 통해 각 클래스들이 앞서 언급한 클래스별 특징이 유지되는 것을 보장할 것이며, 조건을 만족했는지 여부를 확인할 수 있도록 0 과 1로 나타낼 것이다.




우측의 “Min difference” 테이블은 클래스 별로 각 Parameter가 다른 여타의 클래스 대비 최소한 얼마나  큰 값을 가져야 하는지 지정하는 임계값을 설정한 것이다. 다시 말하면, Warrior는 다른 여타 클래스 대비 공격력에 있어서 최소 4이상의 큰 값을 가져야 한다는 것을 의미하며, 법사의 경우는 다른 그 어떤 클래스 보다도 사정거리가 최소한 10이상 길어야 한다는 것을 의미한다.


자 이제 이러한 제약 조건까지 꼼꼼하게 마련했으니 이제 드디어 최적화를 돌려볼 시간이다!



Solving

이제 최초에 임의로 입력했던 값들을 시작값으로 하여 Excel의 내장 기능인 Solver를 통해 optimize를 시도할 준비가 되었다 (Solver에 대한 소개와 Excel에서 어떻게 활성화 하고 사용하는 지 잘 모르겠으면 Part 2 의 글을 다시 읽어보고 오시라!). 우리는 “Objective”의 값으로 6번의 전투 결과를 이용하여 계산한 최종 “Score” 값을 지정하였다. decision variables로는 앞서 만들어 두었던 노란색 “Decision Variables” 테이블의 16개 값들을 지정했다.


또한 결과 값이 음수 또는 실수가 나오거나 혹은 너무 커지거나 작아지지 않도록 다음과 같은 몇 가지 안전 장치들을 Solver의 “Subject to the Constraints” box에 설정하였다.


  • 모든 decision cell 값들은 0 이상의 정수여야 한다.
  • HP는 최소 30에서 최대 200까지 가질 수 있다.
  • Damage의 최대값은 20 이다.
  • Healing의 최대값은 15 이다.
  • Range의 최대값은 100 이다.
  • 끝으로, 각 클래스별 특징이 밋밋해지지 않도록 도입하였던 “Constraint” 테이블의 모든 값이 1이 되어야 한다. 즉, Solver는 각 캐릭터 클래스별 특징을 보장하는 최소값을 유지하면서 해를 찾을 수 있도록 지정했다.





마지막으로, “Solving Method”를 “Evolutionary”로 성정한 다음 Solver를 동작 시켜보자. Evolutionary Solver를 사용하고 있는 관계로 몇 차례 Solver를 돌려보고 난 후 Options를 통해 evolutionary optimization의 프리셋 수치들을 미세조정 하여 더 나은 결과를 얻도록 튜닝하는 것도 가능하니 이런 저런 시도를 해 보도록 하자.


자 별다른 문제 없이 진행했다면 대략 30초 정도의 시간동안 Optimizer가 우리를 대신해 노가다를 뛴 다음 다음과 같은 결과를 뱉어낼 것이다.




… 마법과도 같이,  Excel Solver가 납득할만한 초기 밸런싱 수치를 제시해 주었다.


위 결과를 보면 알 수 있겠지만, Warrior는 Damage에 가장 특화되었으며, 법사는 가장 먼 사거리를 갖게 되었고, 힐러는 회복에 특화되었으며, 바바리안은 가장 많은 피통을 갖고 있다. 이제 Excel sheet 아래쪽의 클래스별 전투 결과도 함께 확인해 보자. 각각의 전체적인 능력은 서로 동등하게 밸런스되어 있어서 동시에 죽거나 살아 남더라도 미량의 HP만 남아있는 수준일 것이다. 또한 각 전투는 납득할만한 수준의 턴수를 보장하고 있기 때문에 어떤 특정 클래스가 다른 클래스를 원샷 원킬 하는 난감한 상황 또한 발생하지 않았다.


간단하게 1~2시간 정도 작업하여 얻은 밸런싱 결과 치고는 꽤 훌륭하지 않은가?



Conclusion

이번 예제를 통해, 간단한 밸런싱 문제를 만들고 simulation 과 optimization을 통해 해결하는 과정을 살펴 보았다. 물론 실제 게임에 사용하기엔 지극히 단순화된 샘플이긴 하지만 결정 모델의 수립과 optimization 테크닉이 응용 여하에 따라서 얼마나 강력한 도구로 사용될 수 있는 지 알게 되었을 것으로 생각한다. 이제 여러분의 게임에 적절하게 응용하여 밸런싱 툴로서 사용하는 일만 남았다. 다른 것은 다 제쳐두더라도, 최소한 실제 개발 과정에서 이와 유사한 문제를 만났을 때, 예전처럼 이런 저런 값을 수기로 임의 입력하고 일일이 테스트하는 비효율적 인 접근에서 벗어나 믿을만한 문제 해결 가이드로서 활용될 수 있기를 희망하는 바이다.


이 시리즈의 글 중 다음 2번에 걸쳐서 어떤 문제해결을 위해 2개 또는 그 이상의 개체들의 조합을 할당해야 할 때 최적의 조합을 찾기 위한 assignment problem에 대해 다뤄보려 한다. 그러한 유형의 문제를 어떻게 해결해야 하며, 실제로 우리가 iOS/Android 용 전략 게임인 City Conquest 를 만들 때 tower 설계에 활용했던 접근 방법들을 설명할 것이다.


- Paul Tozour


This article was edited by Professor Christopher Thomas Ryan, Assistant Professor of Operations Management at the University of Chicago Booth School of Business.



역자사족: 글쓴 분은 '인공지능' 분야 백그라운드를 갖고 있는 듯 하다. 사실 '최적화'니 '문제 해결 공간' 이니 하는 용어뿐만 아니라 전반적인 접근 방법이 '인공지능' 분야에서 사용하는 것이기 때문이다.

사실 우리가 접하는 수 많은 '문제'들의 경우 수학적으로 '뙇' 하고 하나의 함수로 풀어낼 수 없는 경우가 조낸 많으며, 가능하다고 하더라도 앤드류 와일스 같은 엉아라면 모를까 우리 일반적인 게임 개발자들이 해결하기엔 넘사벽이라고 봐야 할 것이다. 더불어, 해가 존재하는 지, 하나가 존재하는 지 두 개가 존재하는 지 혹은 무한대로 있는 지 자체를 사전에 결정할 수 없는 경우가 대부분이기 때문에 이는 더욱 골때려진다(즉, 내가 정답을 찾은 것인지, 근사값을 찾은 것인지, 똥을 찾은 것인지 자체를 알 수 없다). 사실 우리가 살면서 접하는 대부분의 '최적화 문제'가 이에 해당할 것이다. 




<Global Optimum 과 Local Minimum : 우리가 지금 찾은 '해'가 
위에 나타난 4개의 붉은 점들 중 도대체 어떤 녀석인지 알 방법이 없다 ㅆㅂ. 찾아야 할 최적해가 단 1개인 경우도 저러할진데, 우리가 게임 만들때 접해야 하는 수 많은 문제를 수작업이나 직관에 의존하겠다는 것은 상상만으로도 아찔하다. 그림출처: 구글이미지>


문제는 Requirements를 어느정도는 만족하는 해는 찾아낼 수 있는데, 과연 이게 global optimum인가? 하는 것을 판단하기가 골치아프다는 것이며, 지역 최적화가 아닌 전역 최적값(에 최대한 근접할 것으로 예상되는 값)을 찾기 위한 노력이 결국 'Optimzation'이라고 하는 것 되겠다. 


실제로 위 예제에서 사용된 동일한 엑셀화일을 돌릴 때마다, 또는 Solver의 탐색 옵션 값들을 살짝 바꿀 때마다 전혀 다른 결과값이 나올 수 있는 이유도 이에 기인한 것이다(돌려봤으면 알겠지만 여러분 PC에서는 위 스크린샷과 같이 420의 score가 나오지 않는다는 것에 담배 한 갑 걸겠다 ㅎㅎ). 즉, 우리가 방금 찾은 결과가 전역 최적해 인지 혹은 수없이 존재하는 국소 최적해 중에 하나가 선택된 것인지 알 수가 없으며, 대충 어느정도 requirements를 만족하는 '납득할만한 수준'에서 '쇼부' 치는 것 되겠다. (아마도 위 예제에서 solver는 진화알고리즘을 이용하여 해를 탐색하는 듯 하다)

그런데, 대부분의 게임 기획자들은 이 과정 자체를 '수작업'으로 할 수 있다고 믿는것 처럼 보인다는 것이 돌아버리는 것이다. 위 예제만 보더라도 각 attribute들이 정수로 1~100사이의 값만 가진다고 할 경우, 총 100^16 가지의 조합이 나올 수 있는데(맞나? ㅎㅎ), 이걸 수작업으로 값 입력하고, 게임에 적용해서 빌드하고, 테스터들이 2~3일 테스트 하고, 피드백 받아서 다시 수정하고 등등... 단 한 번의 테스트를 위해 이 엄청난 과정을 거치는 것인데, 100^16 가지의 가능성 중에 게임 출시때까지 과연 몇 가지나 테스트 해볼 수 있겠는가? 반면 위에서 소개한 방법을 사용해서 컴퓨터에게 일을 시켜두면 하루밤 동안에도 수십만 ~ 수백만번의 베타 테스트를 컴터가 대신 해주니 얼마나 좋은가! 


더불어, '객관적인' 테스트 결과가 반영된다는 것은 덤이다! 사람이 테스트 할 경우 '쪼금 어려운거 같아요', '전사가 너무 강한 거 같아요' 등과 같이 그 정도와 사실성에 객관성을 담보할 수 없는 qualitative 피드백들에 기인하여 'Type I/II error'에 무방비로 노출된다는 위험성까지 감안하면 그 이득은 상상 초월이다. (물론, 위에서 예제로 사용한 밸런싱 문제의 경우 attributes valuation에 기반한 transitive mechanics를 활용하여 다른 접근이 가능할 수도 있지만 문맥상 논외로 하기로 하자)


결론은... 많이 공부하고, 많은 것 접하고, 다양한 시도 해보자. 수작업은 반드시 수작업이 아니면 불가한 영역에 투자할 수 있도록 남겨두고 굳이 사람 손으로 무한 헤딩하지 않아도 되는 부분까지 엄청난 비용 낭비하면서 iterate하지 말았으면 좋겠다. 결국 우리가 만들고 있는 게임의 성패에 치명적인 영향을 미칠 것이기 때문이다.


결론: 인생은 짧고 삽질은 길다.


이 글에서 사용된 엑셀 예제화일 다운로드 받기: link

원문링크: 요기!




역: 2013 / 11 / 13 Iron Smith