UDN
Search public documentation:
DungeonDefenseDeveloperBlogKR
English Translation
日本語訳
中国翻译
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
日本語訳
中国翻译
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
던전 디펜스 개발자 블로그
문서 변경내역: Jeremy Stieglitz 작성. Jeff Wilson 수정. 홍성진 번역.블로그 1: 1 일차
안녕하세요, 인디 개발자로서는 꽤나 흥분되는 일입니다. 작은 개발 팀의 온갖 종류 혁신적인 게임에 대해 거대 시장을 열어준 디지털 배포 방식의 출현 뿐만 아니라, 미래의 미야모토 시게루(나 킴 스위프트)처럼 되기를 간절히 바라는 그 누구에게나 명백히 게임 업계를 선도하고 있는 에픽이 그들의 플랫폼을 개방해 준 덕이기도 합니다. 오랜 기간 언리얼로 개발해 온 사람으로써 이 선두마차에 올라탔으니, 앞으로 수 개월간 UDK에 간단한 미니-게임 데모 시리즈를 올릴 계획입니다. 제 목표는 UDK 안에서 다양한 형태의 게임플레이를 구현하기 위한 방법 예제를 오픈-소스, 오픈-콘텐츠로 제공하여 커뮤니티를 활성화시키는 것입니다. 언리얼 토너먼트와 같은 풀-코스 요리보다는, 언리얼 초보 개발자가 더욱 쉽게 소화시킬 수 있는 비교적 간단한 코드와 콘텐츠가 될 것입니다. 사설이 길었으니, 자 밥 먹으러 가 봅시다! (음, 진짜 배가 고프긴 하군요.) 어떤 종류의 게임으로 시작할 것인가를 고려할 때를 살펴보니, 몇몇 분들은 언리얼이 다양한 종류의 3인칭 게임에 적합한가 걱정하시던데, 방법만 알면 아주 쉬운 일입니다. 그리고 방금 PixelJunk Monsters 를 한 판 하고 왔더니, 어째 "타워 디펜스"(Tower Defense) 분위기에 젖었습니다. 건물같은 것을 전략적으로 세워 떼로 몰려드는 적을 무찌르는 방식, 분명 재밌습니다. 그렇기에 처음 파 보려는 미니 게임은 "던전 디펜스"(Dungeon Defense)라 부를 것이며, 약간 (거의 3/4 수준의) 내려보기형 액션/타워-디펜스 하이브리드 게임이 될 것입니다. "소서러의 제자"인 작은 메이지(Mage)로써, 스승님이 자리를 비운 사이 보금자리를 지켜 내는 역할을 수행하게 됩니다. 다양한 "소환" 마법으로 보금자리 여기저기에 마법 방벽을 세우고, 방어가 밀릴 때는 마법 지팡이(Staff)로 적을 날려버려야 합니다. 액션 택틱과 자원-관리 전략이 혼합된 멋진 하이브리드가 될 것이며, 언리얼로 이런 정도 구현이야 단숨에 가능할 것입니다. 그래서 주말 새에 애셋과 콘트롤 스카마를 계획하는 디자인 작업을 한 후, 오늘 공식적으로 프로그래밍을 시작했습니다. 먼저 GameInfo, Pawn/Player, Player Controller, Camera 클래스 구현부터 시작했습니다. 그 각자의 역할을 설명해 드리겠습니다: GameInfo 클래스에는 게임의 전체적인 규칙이 담겨 있으며, 지금까지 저의 경우 자체 커스텀 PlayerPawn 및 PlayerController 클래스 내에 스폰하기 위한 디폴트 값을 덮어썼습니다 (1). 월드의 물리적인 캐릭터는 물론 PlayerPawn 이며, 그 PlayerPawn 상의 게임플레이 액션 속으로 사용자 입력이 전송되는 곳은 Controller 클래스 내입니다. 카메라 클래스는, 카메라의 위치를 폰 위로 옮겨 UT에서처럼 눈에서 바로 보게 하지는 않았으며, 또한 플레이어가 바라보는 방향으로 약간 동적으로 오프셋시켜주기 위해 UpdateViewTarget 함수를 변경하였습니다. 약간씩 타겟 방향으로 회전하도록 하기 위해서입니다 (2). 에픽의 내장 RInterpTo 및 VInterpTo 함수를 활용하여 Rotator 및 Vector 보간을 각각 처리하였는데, 언제나처럼 간단한 일이었습니다. 이를 통해 카메라를 플레이어 현재 위치 (및 방향)에서 약간 띄울 수 있어, 그 위치에 딱 고정시키는 것보다 조금 부드러운 느낌을 냅니다. PlayerController 에서 PlayerMove 함수를 (캐릭터의 방향을 2D 평면상에 제한시키기 위해) Rotation Yaw 만 바꾸고, Pitch 는 놔두도록 변경했습니다. 처음에는 그냥 마우스 델타를 사용하여 마우스를 스크롤하는 매 프레임마다 약간씩 Yaw 를 바꾸도록 했었으나, 그리 자연스럽지 못했습니다. PC에서는 정확도가 너무 떨어지는 것이었습니다. 가리키는 방향을 캐릭터가 직접 향하도록 하는 것이 맞겠지요? 그래서 현재 마우스 위치의 "캔버스 반투영"(canvas deprojection) 결과를 구하는 고유 코드 약간을 작성하고나서, 월드 상의 어디를 사용자가 가리키고 있는지를 알아내기 위해 월드에 대해 그 3D 벡터 광선투사(raycast)를 하고, 캐릭터가 그 지점을 향하도록 합니다. (3) 물론, 입력 방향을 카메라 회전에 의해 변형해 주기도 했는데, 그래야 입력이 직관적인 "카메라 상대적" 이동이 되기 때문입니다 (vector>>rotator 연산자에 해당하는 유용한 함수 TransformVectorByRotation 기억하십시오). 복수의 커스텀 풀-바디 애니메이션간의 블렌딩을 위해 재밌는 애니메이션 트리 작업을 약간 해 준 것 빼고, 플레이어-폰은 디폴트 폰과 다를 것이 거의 없었습니다. 그 작업 덕에 여러 애니메이션을 연속해서 재생해 봐도 튀어보이지 않았습니다 (4). 애님 트리 시스템 작업을 하다 보니, 언리얼 툴 모음집을 갖고 노는 것이 이렇게 재밌는 것이었던가 의아하기도 했습니다. 캐릭터 블렌딩을 실시간으로 확인해 가며 설정할 수 있으니 하드코딩할 필요도 없고요! 아무튼 그 작업이 두 시간 정도밖에 걸리지 않아, 작업 끝~ 을 외칠 수는 없었습니다. 다음으로 메이지의 지팡이 무기 작업에 착수했습니다. Weapon 을 서브클래싱하여 누를 때 무기를 "충전"하도록, 그리고 놓을 때 발사하도록 (지팡이에 다양한 충전 공격을 지원하도록) 변경했습니다 (5). 그리고서 Projectile 클래스도 서브클래싱하여 다양한-세기의 발사체도 지원하고, 순차적으로 비주얼 스케일도 전부 알맞게 조절했습니다. (6) 클래스를 직접 참조하는 것보다 "아키타입"(Archetype) 참조를 스폰하면 훨씬 편하다는 것도 말씀드려야 겠습니다 (7) -- "Spawn" 함수에 Archetype 을 지정하면 소위 "액터 템플릿"이 됩니다). 게임플레이에 대한 아키타입을 스폰하고나면, 게임플레이 오브젝트의 라이브러리 전체를 에디터에서 실시간으로 설정할 준비가 완료된 것입니다. 값을 조정하려 할 때마다 매번 "Default Properties" 들락거리지 않고도 말이지요. 또한 값을 시각적으로 설정하고, 미디어를 맞바꾸고, 코어 클래스는 같으면서 속성만 다른 변종을 여럿 만드는 등의 작업이 훨씬 쉬워집니다. 아키타입의 능력에 대해서는 나중에 자세히 다루도록 하겠으나, 반복작업에 큰 도움이 된다는 것으로 충분합니다. 그리고 물론, "-wxwindows" 인수를 붙여 실행시킬 수 있는 "리모트 콘트롤"(Remote Control) 역시 실시간 반복처리에 정말 유용한 툴입니다. 다른 글에서 리모트 콘트롤의 능력에 대해 더 자세히 얘기해 보도록 하겠습니다. 다음으로 첫 적 고블린(Goblin)의 AI 콘트롤러(AI Controller) 작업에 착수했습니다. (모든 액터를 기반으로 타게팅 가중치를 반환하는 "인터페이스"(Interface)를 구현하여) 대상을 고르는 "상태 로직"을 약간 작성했습니다 (8). 그리고서 길찾기(pathfinding) 수행 시점, 대상으로 직접 이동하는 시점, 길찾기/이동을 멈추고 공격을 시작하는 시점을 결정합니다 (9). AI 스크립트에 대해서는 나중에 자세히 다루겠습니다. 그리고 고블린 적에 구현한 재치있는 MeleeAttack 상태는, 현재와 이전 "손 소켓" 위치 사이의 매 프레임마다 추적(박스 스윕)을 끄고/켜고자 애니메이션 통지를 사용합니다 (10). 이를 통해 하드코딩된 피해값이 아닌, 애니메이션과 그 타이밍에 따라 고블린이 휘두르는 영역에 실제적인 피해를 입힐 수 있는 것입니다. 또한 현재 휘두르기에 맞은 액터 목록을 유지하고 그것에 대해 검사하여, 고블린이 "추적된"(traced) 액터를 한 번 휘두를 때 한 번만 피해를 입히게도 했습니다 (11). 모두 완료하고 나니 이 근접 공격은 꽤나 그럴듯해 져서 애니메이션이 시사하는 바를 정확히 전달해 주는 것 같았습니다. 그리고는 적을 공격하는 기본 "타워 터렛"(Tower Turret)을 구현하지 않을 수 없었죠. 이러한 단순 비이동 액터의 경우 AI 콘트롤러는 신경쓸 필요가 없고, 단지 타이머(Timer)를 통해 대상을 집어 주기만 하면 됩니다 (상태 로직은 콘트롤러 뿐만 아니라 어떤 오브젝트에 사용해도 된다는 점도 기억해야죠). (12). 터렛 윗부분이 선택한 대상을 바라보도록 하기 위해, 이 터렛의 애니메이션 트리에 LookAt Bone Controller 역시 추가했습니다 (13). 애니메이션 트리 구성을 마치고 나서, 어디를 볼 것인지 코드 한 줄 추가한 것이 전부였습니다. 예! 게임플레이 모습이 정말로 잡혀가기 시작하면서, 적들의 주요 목표로서 부수려 하는 "크리스털 코어"(Crystal Core)를 계속해서 구현했습니다 (14). 대상화 가능 액터용으로 만든 '인터페이스'를 사용하여 (15) 코어에다 특히 높은 우선권을 주었으니, 적들은 플레이어나 타워보다는 코어쪽에 전념하게 됩니다. '인터페이스'를 통해 클래스가 완전히 다른 액터도 같은 방식으로 상호작용 및 검사 가능하도록 공용 메서드 세트를 공유시킬 수 있습니다. 즉 "크리스털 코어" 클래스가 "플레이어 폰"(Player Pawn)에 계층적으로 직접 관련되어 있지는 않지만, 둘 다 똑같이 공유된 인터페이스가 제공하는 타게팅-가중치 함수를 구현할 수 있습니다. 적 AI가 어떤 개체를 우선 대상으로 잡을 지 결정하기 위해 일반적으로 접근할 수 있는 것입니다. 멋지죠! 그리고 마지막으로 프로젝트의 리드 아티스트 모건 로버츠(Morgan Roberts)가 메이지의 보금자리를 꽤나 멋지게 표현해 내는 테스트 레벨을 만들었고, 제가 키즈멧 설정을 조금 해 줘서 반복적으로 스폰되며 코어를 공격하기위해 진군하는 적 무리를 만들었습니다 (16). 그렇게 거의 하루만에, 본질적으로는 플레이 가능한 프로토타입이 생긴 것입니다. 게임플레이가 벌써 너무 도전을 요하는지라, 앞으로 며칠은 균형을 맞추는 작업은 물론, 추가적인 기법도 다량 구현해야 할 것이고, 가다듬는 작업도 해야 할 것입니다. 언리얼의 뛰어난 툴 덕에, 이러한 것들의 구현 작업이 그저 즐겁기만 했습니다! 다음 글에서는 위에서 간단히 얘기한 주제 중 여러가지 것들에 대해 자세히 다뤄 보고, 여러분의 흥미를 유발하거나 요긴하게 쓸만하다 생각되는 코드나 함수성들을 약간 살펴 보기도 하겠습니다. 그리고 괜찮다 싶은 비주얼이 나오기 시작하면, 스크린샷도 보여 드리겠습니다. 지화자, 금방 여러분께 이 게임을 선사해 드릴 날을 고대하도록 하겠습니다. 이제 아까전부터 뇌리를 떠돌던 피자를 먹으러 가야 겠습니다... - 제레미(Jeremy)블로그 1: 파일 참조
이 블로그에 언급된 정보는 아래 나열된 파일에서 온 것으로, 던전 디펜스 소스 코드의 일부이기도 합니다. 쉼표로 구분된 줄 번호는 파일 속 여러 개의 개별 라인을 말합니다. 하이픈으로 분리된 줄 번호는 파일 내 줄 범위를 말합니다.- Main.uc: 618, 638
- DunDefPlayerCamera.uc: 240 - 248
- DunDefPlayerController.uc: 1561 - 1652
- DunDefPawn.uc: 222
- DunDefWeapon_MagicStaff.uc: 111
- DunDefProjectile_MagicBolt.uc: 24
- DunDefInventoryManager.uc: 13
- DunDefEnemyController.uc: 222
- DunDefEnemyController.uc: 764
- DunDefGoblinController.uc: 52
- DunDefGoblinController.uc: 32
- DunDefTower_ProjectileType.uc: 99
- DunDefTower_ProjectileType.uc: 95
- DunDefCrystalCore.uc: 19
- DunDefTargetableInterface.uc: 15
- DunDef_SeqAct_BasicActorSpawner.uc: 11
블로그 2: 3 일차
던전 디펜스 작업 2, 3일차에는 조준 기법, AI 이동 개선과 월드에 방어 "타워"(Tower)를 놓기 위한 직관적인 시스템의 바탕을 추가하는 데 초점을 맞췄습니다. 각 구현에 대해 더 자세히 말씀드려 보겠습니다! 조준의 경우, 초기 마우스-기반 스키마는 단지 플레이어 캐릭터의 Yaw 를 마우스가 가리키는 곳을 향하도록 할 뿐이었습니다. 3D 조준만 아니라면야 괜찮습니다. (대부분) 내려보기 시점에서 Pitch 에 대한 입력을 직관적으로 결정해 내는 실용적인 방법이 없으니, 캐릭터가 Yaw 로만 회전하는 것입니다. 여기에서 문제점은 적이 플레이어 위아래에 있을 때, 짜증나게도 맞출 수가 없다는 겁니다. 그래서 두 가지 별개의 픽스를 구현했는데, 하나는 마우스 기반 스키마이고 다른 하나는 게임패드 스키마로, 꽤나 잘 작동했습니다. 마우스-기반 스키마에 대해서는 먼저 캐릭터의 Pitch를 계산하여 마우스 화면 좌표의 "반투영"(unproject) 광선과 충돌하는 지점을 조준하도록 했습니다. (1) 여담으로 "반투영"이란 화면에서 쏘는 선과 같이 2D 화면 공간에서 3D 월드 공간으로 변형하는 것을 말하며, "투영"(projection)이란 월드 스페이스에서 화면 공간으로 변형하는 것을 말합니다. 언리얼에서는 둘 다 Player의 HUD를 통해 접근 가능한 해당 Canvas 함수를 통해 이뤄낼 수 있습니다. (2) 어느 경우에도, 허리를 굽혀 올려 내려볼 수 있도록 이 Pitch 값을 캐릭터의 애니메이션 트리에 있는 Bone Controller 셋업에 물려줬습니다 (3). 이런 방식이 정확도가 가장 높으면서도 자연스러운 PC 게임 느낌이 났습니다. 그런데 캐릭터 주변을 가리킬 때면 캐릭터가 항상 내려다보려는 것에 약간의 작업을 해 줘야 했습니다. 기본적으로 발을 조준한다는 건데, 실제로 그걸 조준하고 있기 때문입니다. 플레이어 주변을 가리키고 있을 때, 그리고 조준중인 것과의 높이 차가 크기 않을 경우에는 그냥 앞을 보도록 하는 조건을 추가하기로 결심했습니다 (4). 이를 통해 커서가 캐릭터 가까이 있을 때 발을 쳐다보며 달리지 않게 할 수 있었습니다. 물론 조준점이 급격히 바뀌더라도 캐릭터가 가혹하게 움직이지 않도록, Bone Controller 에 설정된 Yaw 와 Pitch 사이에 보간도 해 줬습니다. 추가적으로 폰 안에 실제 조준점을 저장하고 (5), 웨폰에서 찾아봐 그 지점으로 발사체를 명시적으로 조준하게도 했습니다. 그 작업은 단순히 그 Rotation을 Rotator(TargetPoint-ProjectileSpawnPoint)로, Velocity를 Speed*Normal(TargetPoint-ProjectileSpawnPoint)로 설정하여 주는 것입니다 (6). 이런 식으로 PC 게임에서도 괜찮은 느낌의 정밀 사격이 가능하면서도, 원하던 수준의 단순한 아케이드식 내려보기 시점을 유지할 수 있었습니다. 게임패드 콘트롤 스키마에 대해서는, 약간 다른 작업을 해 줘야 했습니다. 플레이어에게 빠르고 정밀한 포인팅 디바이스가 없기에, 사용자가 가리키는 위치를 정확히 바라보게 할 필요는 전혀 없는 것입니다. 그래도 일종의 3D 조준 형태가 필요하기는 합니다! 그래서 최대 거리 내의 최적 타겟을 (있으면) 결정하여 조준하며, 그 대상의 위치로 조준점을 설정하는 자동-조준 함수를 구현하기로 결정했습니다 (7). 자동-조준 역시도 조준-점 시스템을 사용하기에, PC의 콘트롤 스키마용으로 만든 기존 "look at" 메서드에도 들어맞으며, 조준점이 선택되는 방식 에서만 차이가 납니다. 그래서 최적의 자동-조준 대상을 선택하고자, 자동-조준 범위 내의 모든 'enemy' 유형 액터를 수집하기 위해 Player 에서 OverlappingActors 검사를 시작했습니다. 그리고서 그 결과를 반복처리하여 후보 대상 중 어느것이 가장 가까운지 AND 바라보는 방향에 근접한지를 알아봅니다. 가장 가까이 있으면서 바라보는 방향( 계산식은 "Normal(TargetLocation - EyeLocation) dot Vector(Rotation)")에 근접한 대상은, 각각에 대한 허용치와 가중치 내에 있는 경우 이상적인 대상이 됩니다. 허용치와 가중치 조정을 마치고 나니 이 자동-조준 선택 방법은 잘 작동했으며, 이제 게임패드로 자신이 바라보는 곳( 내외)에 세로 자동-조준이 되는 것입니다. 또한 캐릭터 등뼈(Spine) 본 상의 Yaw 회전을 대상 쪽으로 약간 더해주어, 자동-조준 상의 dot-product 허용치로 인해 약간 측면으로 날아가는 듯 보이지 않도록 했습니다 (8). 게임패드 콘트롤 스키마가 이제 마우스에 준하는 수준이 되었습니다! 참고로 자동-조준 대상에 그리는 데 있어 DebugSpheres 를 사용했는데, 선택 방법이 얼마나 잘 작동하는지 알아보는데 도움이 되었습니다. 사실 저는 분석에 있어 항상 DebugSpheres, DebugLines, DebugBoxes 를 사용하는데, 프로토타이핑 단계에서는 이들을 잘 사용하실 것을, 심지어 클래스에 코드를 놔두어 그리도록 하는 것도 강력 추천합니다. 그냥 커스텀 "bDebug" 불리언으로 토글시켜 꺼 두고, 나중에 문제에 봉착했을 때나 추가 조정 작업을 하고싶을 때 다시 켜면 됩니다. 월드의 3D 작업에까지 지속되는 코드에 무슨 일이 벌어지는지 시각화시켜 볼 수 있다는 것은, 게임플레이 프로그래머에겐 작지만 커다란 함수성입니다. 다음으로는 AI 길찾기 루틴을 언리얼의 장기간 웨이포인트-패쓰노드 내비게이션 시스템에서 새로운 내비게이션 메시 시스템으로 변경하기로 했습니다. 이런 @#$%! 너무 쉬운데다 닳디 닳은 제 눈에도 너무나 엄청난 결과를 내는 것이었습니다. 단지 레벨에 파일론(Pylon) 액터를 내리꽂고, 패쓰를 빌드하면, 그 뒤의 무거운 작업들은 알아서 해 주는 것입니다. 계산이 (믿기 어려울 정도로 빠르게) 끝나자 마자, 이와같이 환경이 완벽히 인식된 경로 네트워크를 얻을 수 있었습니다: 레벨 구조가 변경되어도 패쓰노드 구성을 재생성할 필요 없이, 그냥 "패쓰 리빌드" 버튼을 클릭하기만 하면 놓아둔 파일론들이 가용 패쓰 전부를 재계산하는 무거운 작업을 알아서 해 줍니다. 메시 내비게이션 시스템을 실제로 사용하기 위한 코드 측면은 어떠한가 하면, 지극히 단순합니다 (다른 메시-기반 내비게이션 기술을 경험해 보고 하는 소리입니다). 이 정도의 코드만으로도, 본질적으로 AI 콘트롤러가 내비 메시로부터 내비게이션 결과를 얻어내는 데 필요한 전부인 것입니다:function InitNavigationHandle() { if( NavigationHandleClass != None && NavigationHandle == none ) NavigationHandle = new(self) class'NavigationHandle'; } event vector GeneratePathToActor( Actor Goal, optional float WithinDistance, optional bool bAllowPartialPath ) { local vector NextDest; //반환값을 목적지 액터 위치와 동일하게 설정 //직접 도달 가능하거나 길찾기 실패시, 그냥 이것을 반환. NextDest = Goal.Location; if ( NavigationHandle == None ) InitNavigationHandle(); //액터에 직접 도달 불가능한 경우, 그에 향하는 다음 내비게이션 지점 검색 시도. //가능하면 그냥 그 위치를 반환하여 직접 이동. if(!NavActorReachable(Goal)) { class'NavMeshPath_Toward'.static.TowardGoal( NavigationHandle, Goal ); class'NavMeshGoal_At'.static.AtActor( NavigationHandle, Goal, WithinDistance, true ); if ( NavigationHandle.FindPath() ) NavigationHandle.GetNextMoveLocation(NextDest, 60); } NavigationHandle.ClearConstraints(); return NextDest; }
//WithinRange 는 TargetActor 에서 약간의 거리만 검사하며, //아니면 GeneratePath 가 가라는 곳으로 무작정 이동. while(!WithinRange(TargetActor)) { MoveTo(GeneratePathToActor(TargetActor),none,30,false); Sleep(0); }
블로그 2: 파일 참조
이 블로그에 언급된 정보는 아래 나열된 파일에서 온 것으로, 던전 디펜스 소스 코드의 일부이기도 합니다. 쉼표로 구분된 줄 번호는 파일 속 여러 개의 개별 라인을 말합니다. 하이픈으로 분리된 줄 번호는 파일 내 줄 범위를 말합니다.- DunDefPlayerController.uc: 1575
- DunDefHUD.uc: 122
- DunDefPlayer.uc: 304, 329
- DunDefPlayerController.uc: 1611
- DunDefPlayer.uc: 304
- DunDefWeapon.uc: 134, 157
- DunDefPlayer.uc: 241
- DunDefPlayer.uc: 315
- DunDefEnemyController.uc: 938
- DunDefPlayerController.uc: 577
- DunDefTowerPlacementHandler.uc: 340
- DunDefPlayerCamera.uc: 109
- DunDefPlayerCamera.uc: 163-174
- DunDefTowerPlacementHandler.uc: 89-135
- DunDefTowerPlacementHandler.uc: 304
- DunDefTowerPlacementHandler.uc: 438, 474
- DunDefTowerPlacementHandler.uc: 468
- DunDefTowerPlacementHandler.uc: 219-238, 396-404
- DunDefTowerPlacementHandler.uc: 492-516
- DunDefTowerPlacementHandler.uc: 240-249
- DunDefTowerPlacementHandler.uc: 524-525
- DunDefPlayerController.uc: 706
- DunDefTowerPlacementHandler.uc: 691
- DunDefTowerPlacementHandler.uc: 786, 795
- DunDefTowerPlacementHandler.uc: 801
- DunDefPlayerController.uc: 602
블로그 3: 7 일차
안녕하세요. 던전 디펜스 팀이 지난 블로그 밑그림을 꽤나 크게 그려놨기에, 어디서부터 쓰기 시작해야 할지 감이 잘 잡히지 않습니다! 우선 지난 글 이후 며칠간에 걸쳐 저희가 이뤄낸 작업부터 개괄적으로 살펴 보고, 그 각각에 대해 자세히 들어가 보는 것으로 하겠습니다:- 적이 떨어뜨리며, 플레이어 주변에 있을때 진공-흡입되는 리짓 바디 "마나 토큰"(Mana Tokens)을 추가했습니다. 이는 타워를 소환하고 여러 마법을 시전하는 데 사용되는 게임 내 소모성 자원입니다.
- 편집가능한 구조체 배열로 (데이터 주도 시스템) 일련의 아키타입을 통해 "마법 지팡이"(Magic Staff) 무기를 업그레이드하는 시스템을 추가했습니다.
- 분할 화면 지원 및 동적인 로컬-플레이어 참가 기능을 추가했습니다.
- (에픽의 기존 UI 애니메이션 인프라구조 위에 세워진) 에디터-주도 애니메이션 시스템 지원을 위한 커스텀 UI 클래스를 추가했습니다.
- 함수성 플레이스홀더 UI 씬을 다량 추가했습니다: 메인 메뉴, 일시정지 메뉴, 게임 오버 UI, 개별 플레이어 HUD, 공유 글로벌 게임 정보 HUD, 로딩 화면 등.
- 비동기 로딩 ("심리스 이동") 지원을 위한 게임 로직을 구성하여, 백그라운드로 레벨을 로드하면서 화면을 애니메이팅되는 이행 화면이 나타나도록 했습니다.
- 새 캐릭터 애니메이션 노드 ("이동"으로 간주될 피직스 상태(Physics States) 지정은 물론 속력 곱수 제한용 옵션을 가진 BlendBySpeed 변종) 추가 및 플레이어-캐릭터 애니메이션 트리에 상체 블렌딩 지원을 추가했습니다.
- AI 개선: AI가 대상에 일직선상에 있다 판단했을 때 길찾기를 중지하도록 하고, 주기적으로 이상적인 대상을 다시 찾아보도록 하며, 길이 "막혔"으면 다시 내비게이션 시스템 상으로 돌아가 이동을 시도하는 일종의 안전장치를 두었습니다.
- 전체 "타워 디펜스" 게임플레이 사이클을 지원하기 위한 키즈멧 액션을 다량 추가했으며, 그 중에는:
자 그럼 위의 주제들 중 일부를 자세히 살펴 봅시다. 먼저 리짓 바디 마나 토큰부터 시작하죠. 꽤나 간단히 에픽이 제공한 KActorSpawnable 클래스를 상속했는데, 이는 (컨벡스 콜리전이 설정된) 스태틱 메시 컴포넌트 기반 액터에 리짓 바디 피직스를 적용하여 이미 구성이 완료된 것을 활용했습니다. (1) 자식 클래스의 디폴트 속성에서 그냥 "bWakeOnLevelStart=true" 로 덮어쓰(어 즉시 떨어뜨리도록 하)고, 그 bBlockActors=false 로 설정하여 플레이어가 막힘없이 오브젝트를 통과해 이동할 수 있도록 했습니다. 이 '마나토큰'에는 (그 아키타입에) 자그마한 보석같은 스태틱 메시를 주고서, "DunDefEnemy" 의 Died() 함수 속에다 (아키타입으로) 이것을 여러개 스폰시키도록 했습니다.(2) 또한 스케일된 랜덤 방향 벡터 VRand() 도 적용하여 떨어지는 토큰 각각에 자극을 주어 적으로부터 흩어지도록 했습니다. 플레이어 주변 범위 내의 마나 토근을 검사하여 있으면 "모으도록" 했습니다 (말하자면 토큰을 없애고 플레이어 콘트롤러의 총 '마나' 값에 더해주는 것입니다).(3) 마지막으로 모으기 위해 꼭 닿아야 할 필요성을 없애기 위해, (모든 토큰이 아닌!) 플레이어 주변에 주기적인 OverlappingActors (겹치는 액터) 검사를 추가하여 주변 토큰을 찾아내고, 거기에 플랙을 달아 플레이어 방향으로 포스(Force)를 적용하여 빨아들이도록 합니다. 그 속도가 플레이어 방향이지 않을 때 약간의 역방향 포스를 추가하여, 본질적으로는 플레이어 쪽으로 더욱 빠르게 움직이도록 하기 위한 "이방성 마찰"을 적용하도록 했습니다.(4) 대체적으로, 토큰이 날아다니기 시작하자 만족스러운 진공 효과라 할 만 했습니다. 게임플레이 도중의 무기 업그레이드 지원을 위해, PlayerController 에서 "Summoning Tower" 상태를 확장했습니다. (기본적으로 입력을 잠그고 플레이어 캐릭터가 소환 애니메이션을 재생하도록 합니다) (5) 이 자식 상태를 "UpgradingWeapon" 이라 했으며, 함수를 둘 정도 덮어써서 다른 애니메이션과 시각 효과를 재생하도록 했습니다.(6) 이런 식으로 원래 State 함수성 전부를 그대로 활용하면서도 신경쓸 함수성만 새로 구현해 줄 수 있었습니다. 게임플레이 프로그래밍에 있어 상태 계층구조란 엄청 유용한 개념이기에, 언어적 관점에서 볼 때 언리얼스크립트 고유의 독특한 특성입니다! 이렇게 "Upgrade" 버튼을 누르면 플레이어가 고유 애니메이션을 재생하는 상태로 들어가게 만들었으니, 이제 무기에 실제로 무언가 작업을 해 줘야 하겠습니다. "Weapon Upgrade Entries"(무기 업그레이드 항목)이란 구조체 배열을 추가시켰습니다. 여기에는 각 업그레이드 레벨에 대한 다음과 같은 정보가 포함됩니다: 마나 비용, 설명, 업그레이드에 걸리는 시간, 업그레이드가 완료되면 스폰시켜 플레이어에게 쥐어줄 무기에 대한 실제 무기 아키타입 참조.(7) 제가 왜 클래스가 아닌 (값만 저장되는) 구조체를 사용했을까요? 그것은 구조체는 에디터의 속성 에디터 내에서 동적으로 만들 수 있기에 에디터 내에서 바로 'Weapon Upgrade Entries' 값을 구성할 수 있고, 전체 시스템을 데이터 주도형으로 유지시킬 수 있기 때문입니다. 다음으로 "열거형"(enum)을 추가하여 지원되는 각 업그레이드 (최대 5) 레벨에 해당하는 항목을 포함하도록 한 후, 플레이어가 업그레이드할 때마다 단순히 다음 (현재 번호 + 1) 열거형 값을 선택한 다음, 그것을 구조체 배열에서 다음 "Weapon Upgrade Entries"을 구하기 위한 인덱스로 사용했습니다. 그리고 PlayerController 안에서 업그레이드 항목 구조체에 지정된 기간동안 단순히 (업그레이드 애니메이션을 반복 재생하며) "Upgrading Weapon" 상태로 대기하다, 시간이 지나면 (예전 것을 없애고) 새 무기 아키타입을 스폰시킵니다.(8) 모두 잘 작동했으며, 모든 값이 PlayerController 아키타입의 "Weapon Upgrade Entries" 구조체 배열에 포함되어 있다는 사실 덕에, 무기 업그레이드 비용 및 시간에 관련된 것들을 미세조정하기 위한 반복처리 작업을 리모트 콘트롤을 통해 에디터에서 실시간으로 할 수 있었습니다. 엄청 효율적이겠죠? 한 명만으로도 게임이 충분히 재밌어지기 시작했는데, 네 명이 같이 하면 네 배( 정도는 ^_~) 재밌을 테니, 분할 화면도 지원해야겠다 싶었습니다! 분할화면 멀티플레이 지원 역시도, 에픽이 제공하는 강력한 프레임워크 덕을 다시 한 번 봐서 정말 간단히 해결했습니다. 아직 플레이어에 연결되지 않은 콘트롤러에 대해 "press start" 입력을 다뤄 놓고, 그렇게 새로 생긴 콘트롤러 ID를 가지고 "CreatePlayer" 함수를 호출해 주기만 하면 되는 것이었습니다. 플레이어가 없는 게임패드에 대한 "Press Start" 입력은 Input 의 서브클래스 내 InputKey 함수에서 처리했습니다. 플레이어가 게임패드의 시작을 누르면, 이 키 이름이 InputKey 함수에 전달되고, 거기서 그에 대응하는 ControllerID 를 가지고 CreatePlayer 를 호출합니다.(9) 이 새로운 Input 클래스를 InsertInteraction() 함수를 사용하여 ViewPortClient 클래스의 "Interaction" 목록에 추가시킨 것이 다였습니다.(10) 2번 플레이어가 시작을 누르고, 둘째 PlayerController 를 뽑아내어 Player-Pawn 연결시키니 뷰포트가 자동으로 그에 맞게 분할됩니다 (화면이 분할되게 하지 않으려면, ViewportClient 클래스의 UpdateActiveSplitscreenType() (11) 함수를 덮어쓰면 되며, 그런 경우 첫 플레이어의 카메라 시점이 그려지게 됩니다). 방금까지 1인용 게임이었던 것이 동적으로 여러 명의 로컬 플레이어가 즐길 수 있게 된 것입니다! 온라인 멀티플레이어의 경우 액터 리플리케이션 시스템을 사용하여 추가적인 작업을 해 줘야 하나, 에픽이 제공하는 프레임워크 덕에 그리 큰 부담은 못됩니다. 그에 대해서는 나중에 다루도록 하겠습니다. 다음으로 완전히 플레이가능한 시스템으로써 기능할 수 있도록 게임에 기본적인 기능을 하는 유저 인터페이스에 달려들기로 했습니다. 메인 메뉴에서부터 승리 화면까지, 그리고 단일 레벨 이상의 것까지 말입니다. UI 애니메이션 시스템을 살펴 봤는데, 매우 강력했지만 DefaultProperties 를 통해서만 수정 가능했습니다. 그래서 언리얼스크립트의 힘을 빌어, 이 UI 애니메이션 클래스의 값을 구조체로 감싸 확장된 UIScene 클래스 내에서 편집가능하게 만들었습니다.(12) 커스텀 UIScene 활성화 시점에서, 이 구조체 값을 동적으로-생성된 UI 애니메이션 오브젝트 속에 복사했습니다.(13) 이리하여 에픽이 만든 기존 UI 애니메이션 시스템을 그대로 사용하면서도, 에디터에서 애니메이션 값을 수정하며 실험해 볼 수 있는 이점을 얻게 되었습니다. 이 함수성을 새로 도입하고서, 어떤 플레이스홀더 UI를 다량 만들었습니다. (HUD 클래스에 의해 열리는) Player HUD UI (14) 처럼 이들 중 일부는 각 플레이어의 뷰포트에 그릴 것이지만, 나머지는 전역적인 전체화면의 것으로 한 플레이어에게 소유되지 않는 것입니다. 지속적인 게임 상태(건설 단계에 시간이 얼마나 남았는지, 전투 단계에 적이 얼마나 남았는지 등에) 직접적으로 기반하는 이러한 Global UI를 표시하기 위해 GameInfo 클래스에 약간의 함수를 작성했습니다.(15) UI의 것에 대해 (에디터 내에서 조정되는) 작고 어울리는 (플레이스홀더) Open / Close 애니메이션을 만들었습니다. 여기에 만족하고 나니, 로딩 UI도 애니메이팅되게 하여(16), 메인 메뉴(, 실제로는 메인 메뉴 UI를 여는 레벨)에서 게임플레이 레벨로 말끔히 전환되게 하기로 했습니다. 일종의 '잠시도 지루할 틈이 없게' 하기 위함인 것입니다. 이는 다른 레벨을 임시 "전환"(transition) 맵으로 사용함과 동시에 백그라운드로 레벨을 로드하는 에픽의 함수성, SeamlessTravel 을 사용하면 됩니다. 저의 경우 전환 맵은 로딩 화면 UI 씬을 여는 것으로, 백그라운드에서 목적 레벨이 완전히 로드되어 전환 맵이 닫힐 때까지 이 맵이 표시됩니다. 그저 WorldInfo.SeamlessTravel(17) 을 호출하기만 하면 INI 에 지정된 Transition Map 이 도입되고, 백그라운드로 최종 목적 레벨이 로드되는 것입니다. 단순하고 강력하죠. 물론 "레벨 스트리밍"이라는, 게임플레이 진행에 따라 레벨 일부(방에 처음 들어설 때 건물 내부) 로드 및 오래된 부분(건물 내부에 들어설 때 외부 월드) 언로드하는 등 레벨 일부를 동적으로 스트리밍시키는(흘려들이고 보내는) 것을 말합니다. 월드가 큰 게임에 특히나 유용한 별개의 프로세스로, 키즈멧과 월드 에디터 자체적으로 처리되며, 자세한 것은 다음의 에픽 문서를 참고하시기 바랍니다: Level Streaming HowTo KR 다음으로 캐릭터 이동 애니메이션 비율(rate)을 이동 속도에 맞춰 동적으로 조절해 주면 좋겠다는 생각이 들었습니다. 에픽은 이미 이런 기능을 지원하는 애니메이션 트리 노드 BlendBySpeed 를 제공하고 있으나, 여기에 약간의 함수성을 추가하고 싶었습니다: 플레이어가 특정 물리 상태(, 말하자면 땅을 걷는 상태)에 있을 때 속력 스케일만 조절하고, 비율 스케일에 최대치를 두어 플레이어가 어떤 이유로 (폭발 등을 통해 큰 동력을 얻었다든가 해서) 너무 빨리 움직여 버려 결과적으로 이동 애니메이션이 별나 보이지 않도록 하고 싶었습니다. 다행히 이 작업은 간단했는데, 에픽의 "AnimNodeScalePlayRate" 에서 새 애니메이션 노드 클래스를 상속한 다음, 거기에 Tick 함수를 추가시키고, Tick 함수에서 그 오너 스켈레탈 메시의 액터 현재 속력을 (관심있는 클램핑 및 피직스 검사를 수행하여) 알아봅니다.(18) 이러한 틱 함수를 지원하고자 TickableAnimNode 인터페이스를 만들었으며(19), 폰이 노드를 틱하는 것을 알 수 있도록 Pawn 클래스 내 OnBecomeRelevant() 함수에서 노드를 등록(시키고, OnCeaseRelevant() 에서는 등록-해제)시켰습니다. Engine 의 베이스 클래스를 자체적으로 확장하고 거기에 언리얼스크립트로 새 함수성을 추가하여 그 프레임워크 대부분의 능력을 얻어낼 수 있으며, 키즈멧 함수성 추가를 시작할 때 명백한 것이기도 합니다. 바로 이어서 그 작업을 해 주었습니다! (캐릭터의 상체에만 재생되도록 필터링된 CustomAnimation 노드도, 그 부모로 'AnimNodeBlendPerBone' 를 사용하고 'Spine' 본에서부터 위쪽으로 필터링하도록 설정하여 추가시켰습니다. 그리하여 다리는 독립적으로 움직이면서도 반응성 애니메이션을 재생할 수 있는 캐릭터가 된 것입니다.)(20) 기능성 UI가 있는 로컬 멀티플레이어와 함께 기본적인 자원 및 무기 업그레이드 시스템을 처리하고 나니, 전부 종합하여 시작부터 끝까지 플레이 가능한 "타워 디펜스" 게임 사이클을 만들고 싶어졌습니다. 제대로 하려면 약간의 레벨 스크립팅이 필요할 것입니다 (하드-코딩할 수는 있지만, 다른 게임타입이나 레벨에 확장할 수가 없게 되니 불구스럽다 하겠습니다!). 그리하여 "건설 후 전투" 사이클을 구동시키고자 키즈멧을 사용하기로 했는데, 이는 본질적으로: 플레이어에게 건설할 시간을 (UI를 통해 알려) 주고, 적 웨이브를 스폰하(고서 남은 수를 UI를 통해 알리)며, 이 사이클을 적의 수/간격과 건설 시간을 순차적으로 조절해 가며 반복시켜 게임의 난이도를 조금씩 상승시켜, 결과적으로 전멸에 이르게 되어야 겠지요? 하하! ^~^ 우선 먼저, 적 웨이브를 스폰하는 데 있어, 즉시 완료/출력되지는 않고 시간 경과에 따라 내부적으로 업데이트되다가 내부 로직을 통한 특정 시점에만 완료되는, 잠복성 키즈멧 액션을 사용하고 싶었습니다. 그래서 "Enemy Wave Spawner" 액션을 ('Update()' 함수를 구하고자 SeqAct_Latent 를 확장하여) 만들었으며(21)), 여기에는 구조체 배열이 다수 있어 각 구조체에는 액션이 시작된 이후 일정 시간이 지난 다음에 출현하는 적 웨이브가 정의됩니다.(22) 적 웨이브를 모두 물리친 다음에야 "Wave Spawner" 키즈멧 액션이 완료되고, 그 최종 출력이 활성화됩니다.(23) 특히나 재밌는 부분은 이렇습니다. 'Wave Entries' 구조체 배열을 그냥 액션의 속성 내에서 직접 수정 가능하게 만들 수도 있었지만, 이 값들은 다수의 스포너 사이로 전달해 가며 차례로 증가시키고 UI에서 ("남은 적의 수") 정보로 처리시키기도 해야 함을 알고 있었습니다.(24). 그래서 새 Kismet Variable 클래스 SeqVar_EnemyWaveEntries 를 만들어, 그 자체에 구조체를 포함시키도록 하기로 결정했습니다.(25) 이 Kismet Variable 오브젝트가 Wave Spawner 키즈멧 액션으로의 Variable Input 으로써 받는 것으로, 그 이후 자체적으로 사용하기 위해 그 구조체를 복사합니다.(26) Wave Spawner 액션 내에서 직접 수정 가능한 값으로 하기 보단, Wave Entries 구조체를 감싸기 위해 Kismet Variable 오브젝트를 사용하였기에, Wave Entries 값을 키즈멧에서 시각적으로 전달할 수 있었습니다. 이런 식으로 키즈멧에서 수동 작성한 'ScaleEnemyWave' 액션으로 'Wave Entry' 변수를 연결시킬 수 있었습니다. 'ScaleEnemyWave' 는 Wave 항목과 거기에 곱해 줄 적의 수와 시간 간격을 플로트를 입력으로 받습니다.(27) 전투 사이클 이후 'Multiply Float' 키즈멧 액션으로 이 플로트 스케일을 변경하여, 한 판마다 게임의 난이도를 순차적으로 올릴 수 있었습니다. 추후 웨이브에 랜덤 아키타입 값을 갖도록 하(여 어떤 적을 마주치게 될 지 알 수 없게 만든다)거나, 스케일에 RandomFloat 를 사용하여 스폰되는 적의 양이 항상 약간씩은 편차가 나도록 하는 등, 이 시스템에 추가 작업을 할 계획입니다. 결론은 키즈멧 덕에 '에디터에서 플레이' 반복처리로 레벨 난이도를 조절할 수 있었으며, 이정표격 웨이브마다 부가 이벤트를 추가시키는 등 (예로 5판마다 보스가 등장한다던가 하는 것도, 게임플레이 오브젝트가 아키타입이니 특히나 간단합니다) 좀 더 독특한 시퀸스 구성도 가능합니다. 앞으로는 건설-전투-웨이브 사이클을 조정하는 것도, 키즈멧을 통해서라면 재미있는 디자이너-주도형 체험이 될 것입니다. 그러면 다음 시간까지... 닥 개발!
- 괜찮은 잠복성(, 즉 "시간 경과에 따른") '웨이브 스포너'(Wave Spawner) 액션, 적 그룹을 나타내는 임의의 구조체 배열로부터 적을 시간 경과에 따라 스폰시키며, 플레이어가 특정 웨이브 완료했을 때에 해당하는 출력 링크도 포함되어 있습니다.
- 웨이브 간의 간격과 적의 수가 동적으로 조절되는 액션, 시간 경과에 따라 점차 게임의 난이도가 상승되게 합니다.
- UI를 여는 다양한 액션, 커스텀 정보를 전달해 줍니다.
- 코어가 실제로 죽은 직후 "패배 조건"(코어 파괴) 검사를 위한 이벤트, 그런 경우 일찍 컷씬을 시작시킬 수 있습니다.
블로그 3: 파일 참조
이 블로그에 언급된 정보는 아래 나열된 파일에서 온 것으로, 던전 디펜스 소스 코드의 일부이기도 합니다. 쉼표로 구분된 줄 번호는 파일 속 여러 개의 개별 라인을 말합니다. 하이픈으로 분리된 줄 번호는 파일 내 줄 범위를 말합니다.- DunDefManaToken.uc: 8
- DunDefEnemy.uc: 208
- DunDefPlayer.uc: 351
- DunDefManaToken.uc: 62
- DunDefPlayerController.uc: 812
- DunDefPlayerController.uc: 1266
- DunDefPlayerController.uc: 69
- DunDefPlayerController.uc: 1316-1320, 1280
- DunDefViewportInput.uc: 15
- DunDefViewportClient.uc: 474
- DunDefViewportClient.uc: 226
- DunDefUIScene.uc: 11
- DunDefUIScne.uc: 36
- DunDefHUD.uc: 27
- Main.uc: 223, 333, 482, 132
- Main.uc: 482
- Main.uc: 488
- DunDef_AnimNodeScaleRateBySpeed.uc: 17
- DunDefPawn.uc: 285
- DunDefPlayer.uc: 163
- DunDef_SeqAct_EnemyWaveSpawner.uc: 162
- DunDef_SeqAct_EnemyWaveSpawner.uc: 14
- DunDef_SeqAct_EnemyWaveSpawner.uc: 198, 231
- DunDef_SeqAct_OpenKillCountUI.uc: 31
- DunDef_SeqVar_EnemyWaveEntries.uc: 10
- DunDef_SeqAct_EnemyWaveSpawner.uc: 176
- DunDef_SeqAct_ScaleEnemyWave.uc: 53
블로그 4: 10 일차
안녕들 하셨습니까, 담대한 언리얼 교도들이시여! 지난 블로그 이후 며칠간 저희 소규모 팀은 연휴마저 불태우며 엄청난 진전을 이루어 냈습니다. 먼저 개괄적으로 말씀드린 다음, 각각의 주제에 대해 세밀히 들어가 보도록 하겠습니다:- 멋진 캐릭터 디자인에 맞는 첫 아트워크(스켈레탈 메시)가 생겼습니다! 치키 판타지 클리셰 만세! 이제 애니메이션 용으로 리깅만 하면 파멸의 UT 로봇을 대체할 수 있을 것이며, 게임의 자체적인 스타일 감이 살아날 것입니다. 환경에 대한 추가 작업에 더해, 기본 "Mage Staff" 무기 모델을 구했으며, 임시 비주얼 이펙트를 가지고도 잘 작동했습니다.
- 어쩔 수 없이 메인 메뉴에 재치있는 Render Target 사용처를 구현했습니다. 즉, 이제 메인 메뉴에 1-4 플레이어에 대한 각 "플레이어 캐릭터" 애니메이션 이미지를(, 결과적으로는 메인 캐릭터를 색만 바꿔) 표시하여 게임에 "접속한" 사람을 나타내 줍니다. 이 메뉴에 있을 때는 언제든 "시작" 을 누른 플레이어가 게임플레이에 이어 접속할 수 있게 되며, 이는 (미선택 상태일 때는 'idle' 로 비활성화되는) "렌더 투 텍스처" 캐릭터가 반응하여 재생되는 '활성' 애니메이션 안에 반영됩니다. 깔끔하죠!
- 메인 메뉴를 좀 더 가지고 놀다가, 커서의 위치에 파티클을 방출하는 작은 Canvas 파티클 시스템을 만들었습니다. 이 시스템은 앞으로 다른 UI 효과에도 사용할 수 있을 것입니다.
- 마티네도 직접 손을 봐서 게임플레이-인트로와 게임-오버 시네마틱을 구현했으며, Player Controllers 에다 적절한 입력-방지 상태를 구성하여 시네마틱 도중에는 이동/발사 불가능하도록 했습니다. 플레이어가 Start/Escape 키를 누를 때 시네마틱을 건너뛰는 커스텀 솔루션도 구현해 놨습니다.
- 플레이어 HUD 작업, 즉 머티리얼-기반 체력/진행상황 바 (커스텀 UI 콘트롤), 상태-반응형 마법 아이콘, 애니메이팅되는 마나 토큰 지시자 같은 것들을 전부 구현했습니다. 또한 타워/코어 위로 떠다니는 체력 바에 대한 HUD 오버레이 (동적 캔버스 드로)는 물론, 코어가 공격받을 때 그 방향을 가리키도록 웨이포인트를 회전시키는 것도 구현했습니다. 워, 이 모든 것이 2-4 분할화면에서도 올바르게 재생됩니다.
- 무기에 대한 임팩트 데칼을 에픽의 매우-강력한 "머티리얼 인스턴스 시간 가변"(Material Instance Time Varying, 참 길죠?) 시스템을 활용해서 구현했습니다.
- 원거리-공격 적을 "궁수"(Archer) 유닛으로 하기 위한 기본 함수성을 구현했습니다. 원하는 대로 AI에 약간의 (예측조준, 고의 부정확, 그리고 일종의 "훼이크"(fudge-factor) 발사체 방향 등) 조정을 가했음에도, 상태 상속을 통하니 누워 떡먹기였습니다.
블로그 4: 파일 참조
이 블로그에 언급된 정보는 아래 나열된 파일에서 온 것으로, 던전 디펜스 소스 코드의 일부이기도 합니다. 쉼표로 구분된 줄 번호는 파일 속 여러 개의 개별 라인을 말합니다. 하이픈으로 분리된 줄 번호는 파일 내 줄 범위를 말합니다.- DunDefPlayerSelectUICharacter.uc: 24
- UIImage_PlayerSelect.uc: 38, 48
- UI_CharacterSelect.uc: 97
- UI_CharacterSelect.uc: 65
- UIImage_PlayerSelect.uc: 38, 48
- DunDefViewportClient.uc: 108
- DunDefViewportClient.uc: 14
- DunDefViewportClient.uc: 122
- DunDefPlayerController.uc: 1713, 1726
- DunDefPlayerController.uc: 1832, 1809
- UIImage_HealthBar.uc: 55
- DunDefDamageableTarget.uc: 137
- UIImage_HealthBar.uc: 16
- DunDefDamageableTarget.uc: 154
- UIImage_SpellIcon.uc: 81
- UIImage_SpellIcon.uc: 90
- UIImage_SpellIcon.uc: 11-14
- DunDefCrystalCore.uc: 41-69
- DunDefProjectile.uc: 78-103
- DunDefDarkElfController.uc: 85-107, DunDefDarkElf.uc: 56
- DunDefDarkElfController.uc: 31, 71
- DunDefDarkElfController.uc: 62, 63, 71
- DunDefDarkElfController.uc: 74
블로그 5: 13 일차
모두들 안녕하세요. 참 개발에 정신없었던 양일간이었습니다! 2주간의 마일스톤 빌드를 준비하고, Frontend 툴로 쿠킹하고, 에이스 테스터 군단(물론, 저희 친구와 가족들이죠. ^_^)에 공개할 요량으로 패키징하고 했습니다. 메커니즘을 확고히 구축하고 나니, 드디어 편히 앉아 난이도 조절용 노트를 해 가며 게임플레이를 즐길 수 있었습니다. 준수하고 꽤 재밌는 미니-게임이 되었으며, 가장 흥분되는 것은 2 주 정도 후에 여러분도 플레이해 보실 수 있을 거라는 것입니다. 구현 측면에서, 진행상황의 주요 영역에 대한 개괄은 다음과 같은데, 대부분 '마일스톤' 준비에 관련된 것입니다:- 키즈멧 함수성: (언제고 부드러운 이펙트 페이드인/아웃을 내기 위해) 파라미터가 보간되는 임의의 포스트프로세스 이펙트 토글용 잠복 액션을 추가했습니다. 또한 어떤 키즈멧 플로트 값이든 게임 내 플레이어 수만큼 (플레이어의 수에 대한 곱수 배열을 가지고) 곱해주는 액션도 추가했습니다. 이는 멀티플레이어 밸런싱 작업에 중요한데, 플레이어 수가 늘어남에 따라 적 수는 늘리고 시간은 줄이기 위함으로, 꼭 고정된 선형 스케일일 필요는 없었습니다.
- 새로운 타워형 "Blockade" 를 추가했습니다. 적이 부수거나 (기초적인 동적 길찾기를 약간 사용하여) 우회하는 장애물 역할을 합니다. 타게팅 가능한 오브젝트를 동시에 공격가능한 적의 수를 지정(된 수 이상의 적은 계속 이동하여 다른 타겟을 찾아보게) 할 수 있도록 하기 위한 Attacker Registration 시스템도 추가하였습니다. 그리하여 적들이 Blockade를 맞닿뜨리면, 일부는 공격을 하고 나머지는 우회 이동합니다!
- 이미 UT에도 있는 게임 옵션 Friendly Fire(아군 공격) 검사도 추가하였으나, UT 클래스를 사용하지는 않는 상태라 직접 작성했습니다. 또한 플레이어 시작 위치 선택 로직도 약간 추가하여, 새로이 참가하는 플레이어 각각에 대해 알맞은 시작 위치를 순환시킬 수 있도록, 동시에 4명이 참가해도 고유 시작 지점을 확보할 수 있도록 했습니다.
- UI Skin을 수정하여 '멋진' 자체 버튼 및 폰트는 물론, 모든 게임 시퀸스와 메뉴에 음악과 SFX를 추가시켰습니다. SFX에 대해서는 표준 Effect Actor 에 AudioComponent 를 추가시켜, 스폰되는 Visual Effect 전부에 오디오가 지원되도록 했습니다. (이들이 전부 아키타입이라 가정한다면, 게임의 이벤트 대부분에 오디오를 추가하는 작업도 단숨에 처리되며, 나머지 약간의 사운드 이펙트는 일반적으로 애님 시퀸스 브라우저를 통해 애니메이션에 묶입니다.)
- 요리(쿠킹)해서 포장(패키징)하는 것이, 마치 케익 같았습니다.
- PostProcessEffect 에 대한 FadeUp/Down 추가시 이미 FadeUp 또는 FadeDown 배열에 있었을 때, 새로이 추가되기 전에 예전 배열에서는 제거해 줘야 합니다. 그렇지 않으면 동시에 보간이 두 번 진행되어 망치게 됩니다. 이는 키즈멧에서 FadeUp/FadeDown 입력을 빠르게 트리거할 때 발생할 수 있습니다. (Touched/Untouched 를 통해 액션에 링크된 트리거 볼륨 경계에서 게다리춤을 춘다 가정해 보십시오.) (7)
- 분할화면을 제대로 지원하려면, 액션은 인스티게이터를 받아 그 PlayerController.LocalPlayer 의 이펙트만(, 옵션으로 모두의 것도) 변경하는 기능을 지원해야 합니다. 그러나 머티리얼 이펙트는 고유 머인불변이 있어야 고유 파라미터 값을 가질 수 있으며, 이는 현재 포스트프로세스 체인 에디터 자체내에서 직접 지정할 수 없습니다. 고로 머티리얼 이펙트의 머티리얼이 고유한지 검사하는 함수를 작성하였습니다. (이는 이펙트 상의 현재 머티리얼이 머인불변이 아닌지, 아니면 그 부모가 머인불변이 아닌 머인불변인지 검사하여 이루어 집니다. 저의 경우 둘 중 하나는 고유한 것으로 만들어지지 않았음을 뜻합니다.) 고유하지 않다면 그에 대해 새 머인불변을 만들고, 그 부모를 원래 머티리얼로 설정한 다음, 그 새로 생긴 머인불변을 머티리얼 이펙트에 적용합니다.(8) 그리하여 적절한 분할화면 고유 포스트프로세스 변경을 얻어낼 수 있는 것입니다. 휘유~
블로그 5: 파일 참조
이 블로그에 언급된 정보는 아래 나열된 파일에서 온 것으로, 던전 디펜스 소스 코드의 일부이기도 합니다. 쉼표로 구분된 줄 번호는 파일 속 여러 개의 개별 라인을 말합니다. 하이픈으로 분리된 줄 번호는 파일 내 줄 범위를 말합니다.- DunDef_SeqAct_ScaleFloatForPlayerCount.uc: 10
- DunDef_SeqAct_ScaleFloatForPlayerCount.uc: 22-26
- DunDef_SeqAct_TogglePostProcessEffects: 70-79
- DunDef_SeqAct_TogglePostProcessEffects: 240
- DunDef_SeqAct_TogglePostProcessEffects: 171-233
- DunDef_SeqAct_TogglePostProcessEffects: 236, 248
- DunDef_SeqAct_TogglePostProcessEffects: 139-155
- DunDef_SeqAct_TogglePostProcessEffects: 137, 117
- DunDefTower_Blockade.uc: 21
- DunDefEnemyController.uc: 456-460
- DunDefEnemyController.uc: 492-509
- DunDefEnemyController.uc: 522-530
- DunDefEnemyController.uc: 536
- DunDefEnemyController.uc: 563
- DunDefEnemyController.uc: 538, 545
- DunDefEnemyController.uc: 549
- DunDefEnemyController.uc: 656
- DunDefEnemyController.uc: 648, 602
- DunDefTargetableInterface.uc: 26, 29
- DunDefDamageableTarget.uc: 65, 73
- DunDefDamageableTarget.uc: 61, 93
- DunDefEnemyController.uc: 259, 272
- DunDefEnemyController.uc: 217, 283
- DunDefTargetableInterface.uc: 21, DunDefPawn.uc: 67
- DunDefPawn.uc: 148
- Main.uc: 274, 286
- DunDefEmitterSpawnable.uc: 154
- DunDef_UIAction_PlaySound.uc: 13
- Main.uc: 156
블로그 6: 17 일차
친애하는 UDK 개발자 여러분, 그간 안녕하셨는지요! 던전 디펜스 개발의 마무리 단계에 접어들면서, 지난 며칠간 AI 행위를 다듬는 작업에 집중한 다음, 리플레이 값을 제공하는 메타-게임 시스템 한 쌍을 집중해 봤습니다. 부가적으로 최근 완료된 아트워크를 한 다발 통합시켰으며, 바로 이 부분이 게임플레이에 위대한 그림이 입혀져 생명을 얻게 되는, 개발 과정에서 가장 흥미로운 부분인 것 같습니다. 여기에 언리얼 기술의 멋진 렌더링도 한 몫 했지요. 어쩄든 코드 측면에서 개괄적인 것을 먼저 말씀드리고, 각 주제에 대해 상세히 들어가 보도록 하겠습니다:- 적이 (피해를 입을 때 전달된 동력으로 인해) 네비게이션 네트워크에서 밀려나는 경우 또는 어떤 이유로 타겟에 도달하지 못하는 경우에 대한 주기적인 "걸림" 검사를 추가하여 적 AI를 개선시켰습니다. 그런 경우 동적으로 내비게이션 경로에 되돌아가면서 새 타겟을 찾아봅니다. (MMO 유저에게 익숙한) "어그로" 시스템도 도입하여 적이 최근 자신에게 피해를 입힌 양에 따라 대상에 좀 더 공격성을 띄도록 하였으며, 기타 모든 타게팅 요인도 가중되도록 하였습니다. 이를 통해 적이 좀 더 살아있는 듯한 느낌은 물론 전략적인 깊이도 더할 수 있습니다. (예를 들어 약한 피해로 강력한 적의 주의를 끌어 중요 방어 지점에서 끌어낼 수도 있습니다.)
- 기본적인 글로벌 UI 통지 시스템을 추가하여, 미션 목적이나 기타 게임 정보가 분할화면 게임중일 때도 전체화면 UI에 흘려 표시될 수 있도록 하였습니다. 통지가 여럿 쌓일 수도 있으며, 시간에 따라 화면에서 페이딩되며 표시됩니다.
- 플레이어가 적을 잡고 웨이브를 완료하는 등 하면서 점수를 얻는 "점수" 시스템을 추가했습니다. 점수가 추가될 때마다 부가적인 "Award Name" 이 그에 부착되어 있어, (보너스였는지 특별 점수였는지 등) 그 점수를 얻게된 이유가 자그맣게 뜨게 됩니다.
- 점수 시스템에 최고점수 시스템도 연계하여, 상위 10등까지의 점수가 메인 메뉴와 게임오버 화면에 표시되도록 했습니다. 게임 도중 상위 10등 점수를 획득하면, 게임이 끝날 때 최고 점수 등록에 들어갈 이름을 입력받습니다. 멀티플레이어에도 적용됩니다!
- 기본 옵션 UI 를 추가하여, 옵션을 저장하고 게임플레이 도중 변경할 수도 있습니다.
- 게임플레이 미세조정 및 밸런싱 작업과, 다양한 다듬기 작업을 많이 했으며, 모두가 즐기기에 부담이 없길 바랍니다. ^_^
블로그 6: 파일 참조
이 블로그에 언급된 정보는 아래 나열된 파일에서 온 것으로, 던전 디펜스 소스 코드의 일부이기도 합니다. 쉼표로 구분된 줄 번호는 파일 속 여러 개의 개별 라인을 말합니다. 하이픈으로 분리된 줄 번호는 파일 내 줄 범위를 말합니다.- DunDefEnemyController.uc: 972
- DunDefEnemyController.uc: 715
- DunDefEnemyController.uc: 915
- DunDefEnemyController.uc: 11
- DunDefEnemyController.uc: 109
- DunDefEnemyController.uc: 201, 167
- UI_GlobalHUD.uc: 11
- UI_GlobalHUD.uc: 27
- DunDef_SeqAct_ShowNotification.uc: 41
- Main.uc: 233
- UI_PlayerHUD.uc: 51
- DunDef_SeqAct_ShowNotification.uc: 36
- DunDefPlayerController.uc: 309
- Main.uc: 345
- UILabel_ScoreIndicator.uc: 53, 112
- UILabel_ScoreIndicator.uc: 59, 115
- UILabel_ScoreIndicator.uc: 103
- UILabel_ScoreIndicator.uc: 32
- UILabel_ScoreIndicator.uc: 65, 103
- Data_HighScores.uc: 11-19
- Data_HighScores.uc: 35-64
- UI_GameOver.uc: 75, Main.uc: 186-209
- UI_AddingHighScore.uc: 18-30, Data_HighScores.uc: 35-64
- UIPanel_HighScores.uc: 16-30
- UI_Options.uc: 31-43
- UI_Options.uc: 164-165, 55
블로그 7: 23 일차
모두 안녕하세요! 지난 블로그 글 이후 엄청 바쁜 반주간인 만큼, 꽤 많은 것들이 완성되었습니다! 약간의 게임 데모 마무리 작업에 있으며, 최종 마무리 & 다듬기 적용 중이니 곧 직접 확인해 보실 수 있을 것입니다. 결과적으로는 많은 변경 사항이 밸런싱과 콘텐츠 통합 쪽으로 맞춰졌습니다. 엄청난 미디어에 힘입어 많은 부분에 활기가 도는 것을 보는 것은 항상 흥분되는 일이지요. 물론 추가한 함수성도 엄청나게 많으니, 굵직한 것부터 함께 훑어 보도록 하겠습니다:- 키즈멧을 통해 라바 구덩이를 변경했습니다. 예전에는 엄청난 피해를 주던 Physics Volume 이었는데, 지금은 그냥 약간의 피해만 입히고 플레이어를 안전한 장소로 텔레포트시키도록 하고, 불공평하긴 해도 적의 경우는 죽도록 했습니다. 이 작업에는 트리거 볼륨을 Touched 한 것이 무엇인지 클래스 타입을 검사하기 위한 새로운 키즈멧 조건이 필요했습니다.
- 멋진 카메라 추적 기능에 보간도 적용하여 추가하여 카메라가 벽을 통과하자 않도록 했으며, (마우스 콘트롤 스키마 내의) 카메라 회전 방법을 변경하여 화면 가장자리에 커서를 올리는 것도 포함하도록 했습니다. 또한 카메라 FoV 를 퓨보트 상 비율을 가지고 동적으로 스케일하여, 플레이어가 와이드스크린 해상도 / 가로 분할화면을 사용할 때도 멀리 볼 수 있도록 했습니다.
- 마우스 커서 아래 어디를 조준하는지를 나타내는 부유 파티클 효과를 추가하고, Particle Color Parameter 를 통해서 적 위에 커서를 올렸을 때 빨간색으로 바뀌도록 했습니다. 오너 Player 만 뷰에서 이 파티클 효과를 보게 되며, 'bOnlyOwnerSee' 액터/컴포넌트 옵션을 사용합니다.
- 가스가 흐트러질 때까지 적을 기침하게 만드는 "gas trap" 을 추가했습니다. 타워가 포화를 퍼붓는 동안 적들을 느리게 만들기 좋은 기술이죠.
- 타워-배치 시스템의 상태를 상속하여 타워를 판매하는 기능을 추가했습니다. 물론 소비한 것의 일정 비율만을 그 체력에 따라 돌려받게 됩니다. 경제학 101장, 소위 '감가상각' 입죠.
- 추후 참가하는 플레이어가 고유한 캐릭터 머티리얼 색을 갖도록 하여, 쉽게 구분할 수 있게 만들었습니다.
- 메인 메뉴에 옵션을 한웅큼 추가했습니다: 감마, gfx, 음악 볼륨 슬라이더, 해상도 선택기, 전체화면/포스트프로세싱 토글 등.
- 게임의 룩을 완전히 바꿨습니다. 하하! 근본적으로 지오메트리-외곽선 포스트프로세싱 머티리얼과 대비 조절을 통해 카툰풍 느낌을 강화시켰습니다. 던전 디펜스에 활기찬 분위기를 내는 뚜렷한 느낌을 주고 싶었습니다.
- 해상도 선택 & 전체화면 토글: 여기에는 지원 해상도 ("1024x768", "1280x720" 등)에 대한 문자열 데이터를 포함하는 체크박스 배열을 사용했습니다. 이 체크박스에는 ButtonClicked 델리게이트를 설정하여 다른 해상도 박스가 체크되었는지 확인하며, 그렇다면 더이상 체크하지 않(아 한 번에 하나의 해상도만 선택되도록 했)습니다. 또한 클릭할 때마다 이 값을 참으로 설정하여 정기적으로 체크박스 "체크해제" 행위를 방지하여, 켤 수만 있도록 했습니다. (16) 전체화면 토글은 단지 디폴트 켜고/끄기식 체크박스입니다. 마지막으로 플레이어가 "OK"를 클릭하면 "SetRes [resolution][fullscreen]" 콘솔 명령을 현재 선택된 해상도 체크박스 문자열 값과 전체화면 값으로 실행시킵니다. "SetRes" 콘솔 명령은 실제 해상도 변경을 처리하며, 사용자의 INI 에 최근 값을 저장해 주기도 합니다. (17)
- 포스트프로세스 토글: 포스트프로세싱 토글을 위한 체크박스도 추가하였으며, 이 값은 (더이상 전역 포스트프로세싱 환경설정 값이 없어 보이기에) ViewportClient 클래스안에 환경설정 Boolean 에다 저장하는 값입니다. ViewportClient init 함수에서, 포스트-프로세싱 불리언이 거짓이라면, "show postprocess" 콘솔 명령을 내려 포스트프로세싱을 끕니다.(18) 옵션 메뉴를 통해 포스트-프로세싱 불리언이 토글될 때마다 매번 이 작업을 수행해 주며, ViewportClient 클래스에서 SaveConfig() 을 호출하여 포스트프로세싱 불리언을 사용자의 INI에 저장하기도 합니다.(19)
- 감마 슬라이더: 옵션 메뉴에 슬라이더를 추가해, 그 최소/최대 값을 적절한 감마 값으로 설정하고, 옵션 메뉴에 있는 매 프레임마다 "Gamma [SliderValue]" 콘솔 명령을 내립니다. (그 스크립트 콜백을 만들기가 왠지 싫었거든요, 하하.)(20) 또한 현재 감마 값을 ViewportClient 클래스의 환경설정 변수로써 저장도 하였습니다. 다른 방법으로는 저장할 길이 없기 때문이며, (콘솔 명령을 사용하여) ViewportClient 초기화 내에서 이 값을 활성 Gamma 로 설정하기도 했습니다.(21)
- 음악 & SFX 슬라이더: 여기에는 게임의 모든 SoundCue 상의 'SoundClasses' 를 지정해 주고, AudioDevice 콘트롤 상의 'SoundMode' 를 각각 SoundClasses 의 Volume 으로 설정합니다. 에픽의 SoundModesAndClasses.upk 를 (그 내장 사운드클래스 전부를 사용할 수 있도록) 게임의 스타트업 패키지에 추가했고, SoundCue 클래스의 디폴트 속성을 편집하여 디폴트 SoundClass 를 "SFX" 가 되도록 했습니다. 특히나 뮤직 큐는 에디터 내 "Music" SoundClass 의 것이 되도록 설정하고, ViewportClient 의 초기화 내 AudioDevice 상의 'Default' SoundMode 를 설정합니다. Editor 에서 'Default' SoundMode 의 Effects 배열에 두 가지 Effects 를 추가했습니다. 하나는 'SFX' , 또 하나는 'Music' 사운드클래스 용입니다. 그래야 그 볼륨 수준을 개별적으로 제어할 수 있을 테니까요. 그런 다음 (구성해 둔 "SFX" 및 "Music" 사운드클래스에 상응하는 인덱스를 가지고) Current SoundMode 의 Effects 배열의 'VolumeAdjuster' 값을 변경하기 위한 Set Volume 함수를 작성했습니다. (22) 마지막으로 SFX-Volume 및 Music-Volume 환경설정 볼륨을 ViewportClient 클래스에 추가했고, Options 메뉴에 각각의 UI Slider 값에 설정해 줬습니다.(23) 옵션 메뉴에 있을 때는, 슬라이더 관련 값을 지속적으로 AudioDevice 속에 업데이트시키기 위해 SetVolume 함수를 호출합니다.(24) 그리고 별안간, 실시간으로 개별 조절가능한 SFX 및 Music 오디오 세팅이 완성되었습니다!
블로그 7: 파일 참조
이 블로그에 언급된 정보는 아래 나열된 파일에서 온 것으로, 던전 디펜스 소스 코드의 일부이기도 합니다. 쉼표로 구분된 줄 번호는 파일 속 여러 개의 개별 라인을 말합니다. 하이픈으로 분리된 줄 번호는 파일 내 줄 범위를 말합니다.- DunDef_SeqCond_IsOfClass.uc: 14
- DunDefPlayerCamera.uc: 243
- DunDefPlayerCamera.uc: 278
- DunDefPlayerCamera.uc: 288, 295
- DunDefPlayerCamera.uc: 286
- DunDefPlayerCamera.uc: 145, 205
- DunDefPlayerCamera.uc: 354
- DunDefPlayerController.uc: 1550-1557, DunDefHUD.uc: 56, 62
- DunDefPlayer.uc: 494-504
- DunDefPlayerController.uc: 226
- DunDefPlayerController.uc: 1586
- DunDefPlayerController.uc: 1594-1596, 1606-1609
- DunDefPlayer.uc: 88
- DunDefPlayerController.uc: 270
- DunDefPlayerController.uc: 224, DunDefPlayer.uc: 114
- UI_OptionsMenu.uc: 140-152
- UI_OptionsMenu.uc: 71
- DunDefViewportClient.uc: 297
- UI_OptionsMenu.uc: 68, DunDefViewportClient.uc: 359
- UI_OptionsMenu.uc: 94
- DunDefViewportClient.uc: 299, 354
- DunDefViewportClient.uc: 318
- DunDefViewportClient.uc: 51-52, UI_OptionsMenu.uc: 39-40
- UI_OptionsMenu.uc: 95
블로그 8: 26 일차
모두들 안녕하세요. (현재로서는) 블로그의 마지막 항목입니다. 정말 강도 높은 4 주간의 게임 개발이었는데, 전적으로 경이적인 느낌이기도 했습니다. 그림으로 설명드리겠습니다. 1 주차 끝무렵의 모습: 4 주차 끝무렵의 모습: 예상한 대로 언리얼은 프로세스 전반에 걸쳐 그 성능을 아름답게 뽑냈으며, 바로 그 덕에 그리 짧은 기한 내에 엄청난 작업을 해낼 수 있었습니다. 여러분 모두 던전 디펜스 데모를 즐기시고 이 글도 잃어 보시길 바랍니다. (멀티 태스커는 아니실테니 동시에는 말고요.) 저희 팀은 여러분이 저희 게임을 어떻게 생각하시는지, 드시는 의문에 답해드릴 준비가 언제든 되어 있습니다. 그러니 UDK 포럼에 참여하십시오. 언제든 거기서 도움이 되어 드리겠습니다. 한편 이 블로그 시리즈를 마무리하기 위해, 언리얼로 게임 및 프로토타입을 개발하기 위한 전체적인 접근법에 대해 약간 논의해 보는 기회를 갖고자 합니다. 여기 있는 것들은 저만의 의견으로, 어떤 상황에도 적용할 수 있다거나 항상 수긍할 만 하다거나 하지는 않을 것입니다. 그래도 여러분의 창의적인 노력에 약간의 도움이나마 되었으면 하는 바람입니다. 이것을 (하누카 시간에 딱 맞춘) "제레미의 언리얼 게임 개발 여신 8계명" 이라 부릅시다:1. 코어 메커니즘을 초기에 잡은 다음 반복, 반복, 반복 여러 프로젝트를 거치면서 습득한 하나의 사실은, 단단한 기반 위가 아니고서는 집을 지어 봐야 소용이 없다는 것입니다. 다른 말로, 최고속 아트 제작 및 레벨 개발 단계에 이르기 전에 게임플레이 방식 자체가 재미있고 즐길만 하게 만들어 보라는 뜻입니다. 이런 발언은 무뇌아처럼 들리겠지만, 실제로 풀 콘텐츠 개발 단계로 바로 뛰어드는 것이 종종 재미야 있더라도, 결국 기초적인 작업을 깜빡 하게 되고 맙니다. 비싼 애셋 제작 전에 무슨 게임을 만들려 하는지 (최소한 디자인 표현법 만이라도) 확실히 알고, 거기에 이미 그 시점에서 (버그나 시각적인 요소와는 상관없이) 즐길만 한 게임이라 일컬을 수 있다면, 큰 고생은 이미 덜은 것입니다. 추가로 게임플레이 메커니즘을 일찍 잡을 수록 반복 시간도 많아져 되풀이되는 패스를 통한 다듬기가 용이해 집니다. 반복은 개발 사이클 전반에 걸쳐 일어날 수 있으나, 사전-제작/개념 프로토타이핑 단계로 더욱 많이 압축해 낼 수록 더욱 좋은 것입니다. 물론 예전에도 언급했 던 대로, 언리얼에는 빠른 반복을 지원하기 위한 뛰어난 툴이 다수 있습니다. 몇몇 꼽아 보자면, (실시간으로 값을 바꾸기 위한) Remote Control, (값을 하드코딩시키지 않고 본질적으로 데이터 주도형으로 만드는) Archetypes, (동일 어플리케이션 인스턴스에서 현재 편집중인 레벨을 플레이해 볼 수 있는) Play In Editor 등이 있습니다. 전부 하나하나, 물론 동시에도 (즉 PIE 에서 Remote Control 열기 가능) 활용하다 보면 세 배는 빨리 완성할 수 있을 것입니다. 게임플레이가 고마워 하겠죠. 2. 게임플레이 프로토타이핑에는 Placeholder 애셋 사용 3D 아티스트에게 캐릭터 모델링을, 테크니컬 아티스트에게 리깅을, 애니메이터에게 애니메이션을 한 다발 제작시켜 놨더니 결국 어느 미디어도 실제 게임플레이에 필요한 것에 적합하지 않았던 경험이 있으십니까? 없다고요? 그럼 운이 좋은겁니다. 이런 실수를 전에 저질러 봤는데, 말할 필요도 없이: 아티스트들은 싫어하죠. 좋아할 리 있겠습니까? 프로그래머와 디자이너들은 최종 아트워크가 프로덕션 파이프라인에 들어가기 전의 확인 작업으로, 둘 다 아트가 게임플레이 용도로 어떻게 구성되어야 하는지를 정확히 알아야 하며, 해당 아티스트와의 의사소통을 명확히 해야 합니다. 여기에 가장 좋은 방법은 PLACEHOLDER 애셋인 것 같습니다. 플레이스홀더란 뭐랄까, 최종 애셋을 표현하는 데 사용할 수 있는 인간형 캐릭터나 무기의 일반화된 단순 버전입니다. 이상적으로라면 플레이스홀더 애셋은 최종 애셋과 대략적인 치수, 모양, (스켈레탈 메시의 경우) 본 구조가 같을 것이나, 메커니즘의 복잡도에 따라 꼭 그럴 필요는 없습니다. 플레이스홀더 애셋을 사용하면 중요하게도 '코어 메커니즘을 초기에 잡기'에 (규칙 #1 참고) 용이할 뿐만 아니라, 아티스트가 최종 미디어의 실제 제작 단계에 들어가기 전에 게임에 의도된 방식을 확인할 수도 있게 되므로, 플레이스홀더 구현은 가장 효율적인 통신 수단이 될 수 있을 것입니다. 게다가 아티스트들 자체적으로도 보통 최종인 것에 대한 플레이스홀더 애셋을 직접 맞바꿀 수 있습니다. 즉 아티스트가 추상 공간에서만이 아닌, 실제 게임플레이 맥락에서의 비주얼을 반복처리할 수 있다는 뜻입니다. 고맙게도 언리얼에서는 임시 애셋을 최종 애셋으로 쉽게 맞바꿀 수 있습니다. 그냥 Archetype 또는 DefaultProperties 내에 (Mesh, AnimSet 등) 약간의 참조만 바꿔주면 (절대 코드 줄에는 직접 참조하지 마십시오) 다 된 것입니다. 여기까지 엔데쓰 계율 2번 이었습니다. 3. 언리얼 식을 따르라 분명 언리얼을 가지고 게임플레이 결과를 내는 방법은 여럿 있을 수 있지만, 이상적인 방법은 훨씬 적습니다. 이러한 "언리얼 식"은 보통 언리얼스크립트 인터페이스가 제공해 주는 함수성을 완전히 활용하는 것일 수도 있고, C++ 또는 Java 같은 기본적인 프로그래밍 언어 이상의 능력이 있습니다. 아무 예나 들어 보자면... [*]뭔가 지연을 주거나 시간에 따라 발생하게 하고 싶으십니까? (Sleep 이나 MoveTo 같은) 잠복성 상태 함수성 또는 Timers 를 사용하십시오. 한 Tick 에 Time 기반 if 문장을 잔뜩 쓰지 않아도 됩니다. [*]Player 주변의 특정 Actor 를 찾아내고 싶으십니까? AllActors 에 개별적으로 World 의 모든 Actor 와의 거리를 검사하지 마시고, OverlappingActors 에다 반경을 주기만 하면 됩니다. [*]모든 플레이어 캐릭터 위에 갑옷 부착 구성을 하고 싶으십니까? 하나마다 액터를 만들지 마시고, 폰에 새 메시 컴포넌트(나 심지어 커스텀 컴포넌트 클래스)를 동적으로 생성하여 부착하면 됩니다! [*]딱 텍스처 하나만 가지고 다양한 머티리얼을 잔뜩 만들고 싶으신가요? 하나마다 고유 베이스 머티리얼을 만들지 마시고, 부모 머티리얼을 공유하는 머티리얼 인스턴스 불변을 사용한 다음 그냥 디퓨즈 텍스처 파라미터를 맞바꿔 버리면 됩니다. [*]그리고 무심고, 제발, 제발, 제발 구조체가 참조를 통해 전달될 거라 가정하지 마십시오. 함수 파라미터에 "out" 키워드를 사용하지 않고서야 디폴트는 항상 deep-copied 상태입니다. deep-copying 구조체는 불필요하게 느리거나 메모리를 잡아먹을 수 있고, 또 로직에서 구조체 변수가 고유하지 않은 참조라 가정한다면 잠재적인 버그 발생 가능성도 있습니다. 여기에 대해 자세히 설명되어 있는 UDN 문서가 있으니 참고하시기 바랍니다. 언리얼스크립트와 엔진 프레임워크는 C++ 로 직접 작업할 때보다 게임을 구현함에 있어 훨씬 탄탄한 감을 내도록 구성되어 있습니다. 언리얼로 개발해 가면서 예상했던 것보다 많은 상속 함수성을 찾게 될 것입니다. 언리얼로 뭔가를 구현하는데 애를 먹고 있다면, 엔진 내 이미 존재하는 편리한 함수성에 대해 모르기 때문일 수가 있습니다. 의심스러운 경우라면 보통 그렇습니다. 에픽의 코드를 읽어 보면, 그 샘플을 들여다 보면, 흥미로운 UDN 문서를 파 보면, 심지어 던전 디펜스 조차도 (완벽하진 않지만 ^_~) 살펴보다 보면, 초강력 프레임워크의 완벽 활용 방향을 짚어주는 사용 패턴을 쉽게 알아볼 수 있게 될 것입니다. 그러면 바로 이 곳으로... 4. 에픽의 코드베이스를 검색! 언리얼스크립트 엔진-프레임워크 코드베이스는 꽤나 방대합니다. 또한 문서화가 매우 잘 되어있으나, 어디서부터 시작해 봐야 할 지 당혹스러울 수가 있습니다. 처음부터 끝까지 주욱 읽어내려 가는 것은 그다지 추천할 만 하지는 못합니다. (최소한 Actor.uc & Object.uc 부터 익숙해 지시기를 추천하기는 합니다.) 궁극적으로 소름끼칠만큼 방대한 코드베이스를 대화형 지식 백과사전으로 바꿔주는 친구! 바로 Visual Studio (Express 는 프리 버전) 또는 기타 텍스트 편집 프로그램같은 Code IDE 검색 기능 "Find All in Files" 입니다. "Cursor", "Force" 등의 (보통 찾고자 하는) 키워드를 에픽의 코드 파일 전체 또는 일부 내에서 검색해 보면, 모든 종류의 공용 게임 요구를 처리하기 위해 에픽이 제공하는 관련 함수성에서의 개념을 제대로 참고해 볼 수 있을 것입니다. 좋은 경험 법칙이라면, "직접 만들기"로 결정하기 전에 에픽이 이미 "만들어 주지" 않았나 코드베이스를 검색해 보라는 것입니다! 내가 찾고 있던 것들을 얼마나 많이 이미 해놓았는가, 보시면 깜짝 놀라실 겁니다. 그리고 기어즈 오브 워를 플레이해 봤다면, 그다지 놀랍지도 않을 것입니다. 5. nFringe 사용. 마침표. PixelMine 친구들에게 찬사를 보내야 겠네요. nFringe 정말 죽입니다. 그 'Intellisense' 및 코드 파싱은 보통 정확하며, 그 구문 검사도 (걍 한심한 버그와는 달리) 한심한 똥꾸빵꾸 버그를 줄이는 데도 큰 도움이 됩니다. nFringe 를 사용하면 코딩 생산성이 (적어도 저의 경우에는) 크게 향상되었으며, 에픽(과 자신)의 코드베이스 탐험도 훨씬 빨리 할 수 있을 것입니다. Intellisense 및 멤버 리스트를 통해, 클래스를 이리저리 돌아다니며 변수와 함수, 상태를 빠르게 살펴볼 수 있을 것입니다. 아무리 강조해도 지나치지가 않습니다: 언리얼 프로그래밍이 처음이라면, nFringe 의 도움으로 첫 걸음마를 훨씬 빨리 뗄 수 있을 것입니다. 현재로서는 문제가 딱 하나 있습니다: 언리얼에는 강력한 디버거가 있으나, nFringe 는 PixelMine 에서 상용 nFringe 라이선스를 구매하지 않고서는 접근 권한이 없습니다. 프로 개발자들에게만 해당되는 사항이죠. PixelMine 에 청합니다. 이 엄청난 툴을 대중에 공개하시면 신으로 숭배받을 수 있을 것입니다! 뭐 그런 정도는 되겠죠. 아무튼 네, 지금 nFringe 를 (없으시면 Visual Studio Express 도) 받으시고, 코딩이 처음인 양 코딩을 시작하시기 바랍니다! 6. 사용할 수 있는 디버깅 방법은 모두 사용 nFringe 가 있든 없든, 언리얼 프레임워크를 가지고 게임플레이를 디버깅할 수 있는 방법은 여러가지가 있으니, 최대의 결과를 내기 위해서는 다양한 기술을 전부 활용해야 할 것입니다. 그 중에서 제가 가장 선호하는 것은: [*]Debug Draws (Spheres, Lines, Boxes 등): 3D 공간에서 뭐가 벌어지는지를 시각화해 보는 데 도움이 됩니다. 계산된 3D 변형의 결과를 보려 한다거나 크기 개념을 잡아보는 등에 좋습니다. [*]로그 문장: 아 로그: 스팸인가 정보인가. 특히나 nFringe 디버거가 작동하지 않는다면야! 로그를 가지고 어떤 데이터형이든 꽤 많은 양을 출력 창에 즉시 출력시킬 수 있으며 (showlog 콘솔 명령), 한 줄에 최대한의 정보를 얻을 수 있도록 "@" 심볼을 사용하여 복수의 문자열을 연쇄시킬 수 있습니다. 단지 Tick 함수에 넣고서 깜빡하지만 않도록 조심하시기 바랍니다. 로그를 스팸시키면 게임이 느려질 수 있습니다. 사실 Actor 클래스에 "bDebug" 토글이 설정된 경우에만 로그가 활성화되도록 하는 것이 최선인데, 이 토글은 필요에 따라 실행시간에도 편집가능 변수를 통해 전환시킬 수 있습니다. [*]라이팅제외 모드, 와이어프레임 모드: 뭔가 그래픽 작업을 하는 중에 레벨 라이팅이 꼬인 경우, 그냥 F2 키를 눌러 라이팅제외 모드로 변경하십시오. 또는 벽을 투시해 (치터!) 보고자 하는 경우는 F1키 와이어프레임 모드입니다. AI가 자신을 볼 수 없을 때 어떻게 행동하는지 알아보기에 매우 유용합니다. (흘끔!) [*]"Show Collision" 콘솔 명령은 월드의 모든 콜리전을 시각화시켜, 콜리전 관련 문제에 부딪힌 것 같을 때에 좋습니다. 통로를 통과할 수가 없나요? 게임 버그가 아니라, 레벨 디자이너가 심통좀 부리려고 크고 오래된 투명 블로커 메시를 입구 앞 밑쪽에다 놓았기 때문일 수도 있습니다. 'Show Collision' 으로 모두 보입니다! (좀 더 사실적으로, Pawn 의 콜리전 크기가 얼마나 되나 확인하기에 매우 좋습니다.) [*]게임 내 시간을 말 그대로 늦추고 싶을 때는 Remote Control 상의 Time Dilation 세팅을 사용하십시오. (현실에서라면 좋겠지만, 언리얼이 아직 그만큼 강력하지는 않습니다.) 이 기능은 이상한 점을 발견하고자 미시적 게임플레이 디테일에서의 애니메이션과 비주얼 이펙트가 정확히 어떻게 돌아가나 확인하고자 할 때 매우 유용한 기능입니다. 타이밍 관련 문제를 해결하기에 좋습니다. [*]Remote Control 은 Actor List 상의 "시계" 아이콘을 클릭하면 게임플레이 도중 스폰된 Dynamic Actors 전체를 표시해 주기도 합니다. 소멸되었어야 하는데도 아직 살아있는 액터가 없는지(, 예로 탄환이 충돌한 이후 어딘가에 걸려있다든지 등을) 이중 확인하기에 좋습니다. [*]모든 키즈멧 액션에는 "Output Comment To Screen" 옵션이 있는데, 켜면 게임내 콘솔 디스플레이에 주석이 표시될 것입니다. 어떤 액션이 언제 트리거되는지 알아보기에 좋습니다. 또는 프로 키즈멧 고수의 경우, ("말하기" 위해) Console Command 키즈멧 액션을 사용하여, 원하는 키즈멧 변수를 아무거나 출력해 내기 위해 Concatenate String 액션과 결합할 수도 있습니다. ^_^ 여러가지 접근법 중 이러한 접근법들을 사용하고, 언젠가 (바라건데) nFringe 디버거의 혜택을 누구나 누릴 수 있는 날이 오게 되면, 눈이 왕방울만한 버그먹는 하마가 되실 수 있을 것입니다. 괜찮겠죠? 7. 키즈멧 사용은 똑똑히, 그러나... 오 위대한 키즈멧이시여! 레벨 디자이너에게 게임플레이 구현 능력을 하사하시고, 게임 디자이너에게 번개같은 프로토타이핑 능력을 부여하시었으니. 가져가시고, 주시도다. 키즈멧은 레벨 상호작용이나 심지어 특정 레벨-향 게임플레이 메커니즘 프로토타이핑에 있어서도 대적할 상대가 없는 환상적인 툴입니다. 그러나 거기에도 한계는 있습니다: 특별히 객체지항형/상속가능하지는 않습니다. 언리얼스크립트처럼 빠르지도 않고, 그만큼의 디버깅 능력이 있는 것도 아닙니다. 고로 키즈멧을 통해 모든 것을 다 하려는 것은 대부분의 최종 버전 게임을 구성하기 위한 접근법으로서는 적합하지 않습니다. 어떻게든 기를 쓰고 그러고자 한다면, 가능한 만큼 (확실히 많은 부분이 가능하니) 키즈멧으로 게임플레이 프로토타이핑은 가능하겠지만, 최종 제품에 가서는 그 대부분을 다시 쓰게 될 확률이 높다는 점 염두에 두시기 바랍니다. 키즈멧으로 뭔가가 잘 안된다면, 제 의견으로는 언리얼스크립트로 (또는, 능력 확장을 위해 키즈멧 액션을 새로 작성)하는 것을 고려해 보시기 바랍니다. 제 일반적인 규칙은: 레벨의 영속성 디자인 상에 발생하는 무언가라면, 키즈멧으로 하십시오. 레벨 자체적인 것이 아닌 동적인 오브젝트의 행위에 관련되거나 동적으로 스폰되는 무언가라면, 언리얼스크립트로 하는 것이 낫습니다. 키즈멧을 죽도록 사랑하다 못해 지구 끝까지 들었다 놨다 했던 다년간의 경험에서 나온 견해입니다. 오해하지는 마십시오: 이론적으로는 전체 게임을 동적으로-스폰되는 프리팹을 사용하여 작성할 수 있지만, 일정 시점을 지나게 되었을 때는 분명 무언가가 있을 거라는 것입니다. 그래도 존경스런 독자 여러분 걱정일랑 마세요. 키즈멧을 향한 저의 사랑은 식지 않을테니: 던전 디펜스의 경우 전체 하이-레벨 게임플레이 로직을 키즈멧으로 돌렸으니까요. 8. 중요한 것은 재미 우리는 인디 개발자입니다. 즉 대체로 재미에 초점을 맞추며, 억대 예산은 꿈도 못꿉니다. 물론 그 억대가, 더 나은 자신의 능력이 여러분 대부분의 목표겠죠. 언리얼이 그 목표를 이루는 능력을 갖추는 데 도움이 될 것입니다. 하지만 항상 잊지 말아야 할 사실은, 게임이 얼마나 괜찮은지, 모두의 시간을 뺏을 만한 가치가 있는지 알리러 나왔을 때는 그래픽이 얼마나 이쁜지 (물론 도움이야 되겠지만) 플레이타임이 얼마나 되는지 (오블리비언!) 메인 캐릭터의 가슴에 폴리곤 수가 얼마나 되는지는 중요하지 않다는 것입니다. 중요한 것은 플레이어가 만족할 만한 방식으로 괜찮은 피드백과 보수를 통해 상호작용식 경험에 빠져들게 만드는 것입니다. 아이러니인 것은, 가끔 동료 디자이너가 매일매일 최선의 판단을 내리지는 못한다는 것입니다. 친구, 가족, 동료, 개한테도 중요 시점을 플레이하게 해 보고, 피드백에 대해 말하거나/짖어 보게 해 보시기 바랍니다. 항상 듣기 좋지만은 않겠지만, 몸에 좋은 약은 입에 쓴 법이고, 아픈 만큼 게임은 성숙해 질 것입니다. 언리얼은 두말할 것 없이 전 세계 최강의 게임 제작 기술이지만, 균형이 잘 잡힌 재밌는 게임이 될 것인지, 또다른 Monster Madness 가 될 것인지는 전부 당신 손에 달렸습니다!모두들 몸 건강히 지내십시오. 꿈★은 이루어진다! ^_^ -제레미 스티글리츠 (Jeremy Stieglitz)