로그라이크 게임의 특성(Perks)을 코딩하는 방법
요약
본 글은 어휘 기반 로그라이크 게임 'Word Play'의 핵심 시스템인 '수정자(modifiers)' 구현 과정을 다룹니다. 플레이어에게 이점을 주는 수정자를 효율적으로 관리하고, 복잡한 게임 로직을 확장 가능하게 설계하는 방법을 제시합니다.
핵심 포인트
- 로그라이크 특성(perks)은 게임 경험의 깊이를 더한다.
- 수정자 시스템은 '선물', '업그레이드', '패시브' 등으로 분류된다.
- 초기 구현 방식의 한계를 극복하고 확장 가능한 시스템을 설계하는 것이 중요하다.
- Unity 환경에서 수정자 로직을 체계적으로 관리하는 방법을 배울 수 있다.
안녕하세요, Mark입니다. 저는 평소 이 채널에서 프로그래밍 관련 콘텐츠를 많이 다루지는 않습니다. 물론 프로그래밍은 어떤 게임 제작 도구 상자(toolkit)에서도 필수적인 부분이지만요. 하지만 제가 이 주제에 대해서는 전문가가 아니기 때문입니다. 제 두 비디오 게임인 Mind Over Magnet과 Word Play의 코드베이스는 기본적으로 임시방편으로 겨우 붙여 놓은 상태입니다. 하지만 Word Play에서 프로그래밍한 것 중, 제가 정말 자랑스러워하는 부분이 있습니다. 바로 이 게임의 수정자(modifiers)를 구현한 방식입니다.
배경 설명을 드리겠습니다. Word Play는 단어를 스펠링하여 점수를 얻는 어휘 기반 로그라이크 게임입니다. 그리고 플레이어는 규칙을 변경하고 더 큰 점수를 얻게 해주는 특성(perks)들을 잠금 해제할 수 있습니다. 이 특성들은 세 가지 종류가 있습니다. '선물(Gifts)'은 일반적으로 가방에 글자를 더 추가해 줍니다. '업그레이드(Upgrades)'는 타일을 변경하고 개선할 수 있게 합니다. 그리고 '수정자(modifiers)'는 특정 조건 하에서 큰 이점을 주는 패시브 특성입니다. 예를 들어, 첫 글자가 모음일 경우 점수를 1.5배로 곱하는 것 같은 것이죠. 즉, 슬레이 더 스파이어(Slay the Spire)의 유물(relics)이나 발라트로(Balatro)의 조커 카드와 비슷한 개념입니다.
처음에 이 게임을 만들 때 저는 어떻게 이러한 수정자들을 구현해야 할지 전혀 알지 못했습니다. 명확하고 쉬운 방법이 없었죠. 하지만 결국, 저는 매우 만족스러운 시스템을 고안해 냈습니다. 이 시스템은 시스템의 요구 사항에 맞고, 게임 내 모든 120개의 수정자에 쉽게 적용할 수 있으며, 많은 수정자들을 테스트하고 변경하고 디버깅하는 것을 손쉽게 만들어 줍니다. 그래서 제가 이것이 초급 또는 중급 수준의 프로그래머에게 흥미롭거나 도움이 되거나 영감을 줄 수 있기를 바랍니다.
실제 Steam에서 구매할 수 있는 다소 복잡한 시스템을 구현하는 데 어떤 과정이 필요한지 알아보기 위해서입니다. 흥미롭게 들리신다면 바로 시작해 보겠습니다. 참고로 제가 Unity를 사용했기 때문에 이 내용을 Unity 환경에서 게임을 만드는 맥락으로 이야기할 것이라는 점을 미리 말씀드립니다. 하지만 기본적인 아이디어는 거의 모든 엔진에 적용될 수 있습니다. Word Play의 수정자(modifiers)들은 단어 새로고침, 보너스 라운드, 특수 타일, 그리고 철자의 기본 규칙 등 게임의 너무나 많은 측면들을 건드립니다.
이 부분까지 오게 될 텐데, 지금은 여러분의 단어가 점수를 얻는 방식에 영향을 미치는 수정자들부터 살펴보면서 간단하게 만들어 보겠습니다. 예를 들어, 아까 보여드렸던 수정자인 '첫 글자가 모음이면, 최종 점수에 1.5를 곱한다' 같은 경우입니다. 이걸 어떻게 구현할 수 있을까요? 먼저 제출된 모든 타일의 점수를 합산하여 단어의 초기 점수를 계산하는 코드를 작성하겠습니다. 그리고... Word Play에서는 모든 수정자들이 별도의 GameObject로 존재합니다.
그리고 플레이어가 현재 소유하고 있는 수정자들의 목록이 있습니다. 그래서 플레이어가 'VowelFirst'라는 수정자를 소유했는지 확인할 수 있습니다. 그리고 첫 글자가 모음인지도요. 이 두 조건이 모두 참이라면... 단어의 점수에 1.5를 곱합니다. 이렇게 하면 작동할 것입니다. 아주 잘 작동했습니다. 사실, Word Play의 급하게 만든 게임잼 프로토타입에서도 제가 정확히 이렇게 구현했습니다. 그리고 그 역할을 해냈습니다. 하지만 이것을 실제 비디오 게임으로 만들 시간이 되자, 저는 더 나은 시스템이 필요하다는 것을 알았습니다. 왜냐하면... 몇 가지 이유가 있었기 때문입니다.
가장 큰 문제는 이겁니다. 수정자(modifier)를 추가할 때마다, 이 스크립트는 점점 길어집니다. if 문으로 이루어진 거대한 스파게티 덩어리가 되는 거죠. 그리고 그건 무언가를 찾고, 바꾸고, 버그나 문제를 고치기 더 어렵게 만듭니다. 예를 들어, 마지막 글자가 모음일 경우에만 점수를 곱하고 싶다고 가정해 봅시다. 이제 저는 이 코드 전체를 뒤져서 그것을 찾아야 합니다. 또, 수정자들이 작동하는 방식 자체에 대해 무언가를 바꾸고 싶다면—100개의 다른 if 문에서 동일한 코드를 변경해야 할 수도 있습니다.
아이고. 그리고 또 하나의 문제는 이 수정자들이 발동되는 순서가 코드에 나타나는 순서로 정의된다는 겁니다. 수정자 목록에 있는 순서가 아닙니다. 사실 이것이 Word Play에서는 큰 문제가 되지 않았습니다. 당신은 수정자의 순서를 신경 쓸 일이 거의 없으니까요. 하지만 Balatro에서는 엄청난 문제입니다. 그래서—제가 처음 영감을 받은 게임인 이 게임에서는—이것이 제가 처음에 시스템을 코딩할 때 요구 사항 중 하나였습니다.
좋아요. 그래서 제 계획은 각 수정자마다 단어 점수를 계산하는 함수를 가진 매우 짧은 자체 스크립트를 주는 것이었습니다. 저는 단어와 그 점수를 전달할 수 있습니다. 그리고 조건이 충족되는지 확인합니다. 만약 그렇다면, 단어 점수에 원하는 양만큼 곱하고 메인 점수 스크립트로 다시 전달합니다. 이제 메인 점수 스크립트는 플레이어의 수정자 목록만 필요하며, 연결된 스크립트들을 하나씩 반복하면서 onWordScore 함수를 테스트하기만 하면 됩니다. 유일한 문제는...
음, 임의의 스크립트 목록을 가지고 모든 스크립트에 대해 함수를 실행하려고 할 수는 없습니다. 이렇게 생각해 보세요. 제가 당신에게 물건 10가지 목록을 가져오라고 요청한 다음, '이것들을 먹어라'라고 말한다고 가정해 봅시다. 당신은 '잠깐만, 뭐요? 이것들 중 하나는 오토바이잖아요.'라고 할 겁니다. 그리고 저는 '먹어라'라고 말할 겁니다. 작동하지 않겠죠? 목록은 더 구체적이어야 합니다. 우선 모두 먹을 수 있어야 하고, 모두 같은 유형이나 같은 클래스에 속해야 합니다. 아! 보세요, Unity에서 작성하는 모든 스크립트는 MonoBehavior라는 동일한 클래스를 기반으로 합니다.
이를 통해 OnCollisionExit, OnMouseOver, OnDestroy와 같이 유용한 함수들을 상속받아 사용할 수 있습니다. 하지만 실제로 자신만의 클래스를 만들고, 그 클래스의 모든 기능을 상속받는 스크립트를 작성할 수도 있습니다. 에휴... 제가 보여드리는 것이 더 쉬울 겁니다. 정말 간단합니다. ModifierClass라는 새 클래스를 만들고, 이것을 추상(abstract) 클래스로 설정하기만 하면 되고, 이제 이 위에 다른 클래스들을 구축할 수 있습니다.
그리고 이 기본 클래스에서는 예를 들어 onWordScored라는 public float를 만들 수 있는데, 여기에는 단어와 현재 단어 점수가 포함됩니다. 기본적으로 우리는 마이너스를 반환할 것입니다. Word Play에서는 점수에 음수일 수 없기 때문입니다. 그래서 저는 '아무 일도 일어나지 않았으니, 다음으로 넘어가라, 아무것도 하지 마라'는 의미로 -1을 간편하게 사용할 수 있습니다. 이를 오버라이드(override) 할 수 있도록 virtual float로 변경해야 합니다. 그런 다음 mVowelFirst라는 새 클래스를 만들고 ModifierClass를 상속받도록 설정한 후, onWordScored를 사용하여 public override로 설정할 수 있습니다.
따라서 기본적으로는 이 클래스의 베이스 버전이 가지고 있는 값을 반환하게 되는데, 그 값은 마이너스 1입니다. 하지만 이것의 경우에는 단어의 첫 번째 문자(substring zero)가 A 또는 E 또는 I 또는 O 또는 U, 즉 모음인지 확인하고, 그렇다면 단어 점수에 1.5를 곱한 값을 반환하도록 합니다. 그렇지 않다면 마이너스 1을 반환합니다. 따라서 기본적으로 단어를 입력받아 첫 글자가 모음인지 확인합니다. 만약 그렇다면 단어 점수에 1.5를 곱하여 점수 확인 스크립트로 다시 전달하고, 그렇지 않으면 모음이 없다는 의미로 마이너스 1을 반환합니다.
여기 볼 것은 없습니다. 다음으로 넘어가죠. 이제 저는 실제로 스크립트 목록, 구체적으로는 ModifierClass 스크립트 목록을 만들 수 있습니다. 왜냐하면 Unity가 모든 스크립트에 반드시 OnWordScored 함수가 있을 것이라고 알기 때문입니다. 단순히 -1을 반환하는 기본 함수이거나, 더 높은 숫자를 반환할 수도 있는 오버라이드(override)일 수 있습니다. 그래서 다시 우리의 wordScore 스크립트로 돌아와서, 이제 소유한 수정자(modifier) 목록을 반복하면서 모든 수정자에 대해 OnWordScored 함수를 실행하고... 만약 이들이 -1을 반환하지 않는다면, 단어 점수를 업데이트할 수 있습니다.
그리고 어쩌면, 모르겠지만, 그 수정자가 작은 핑 애니메이션과 사운드 효과를 하도록 만들 수도 있습니다. 이제 저는 OnWordScored 오버라이드가 있는 이런 아주 작은 스크립트들을 잔뜩 만들 수 있게 되었습니다... 그리고 점수 계산기는 계산이 끝날 때까지 올바른 순서로 이들을 반복할 것입니다. 결국 이게 전부입니다! 우리는 스크립트에 거대하고 다루기 힘든 덩어리로 100개의 if 문이 있던 것에서, 스크립트가 단지 하나의 우아한 루프를 실행하는 것으로 바꿨습니다. 그리고 모든 점수 기능은 별도의 스크립트에 존재하게 되어—찾거나, 변경하거나, 물론 새로운 것을 추가하기 쉽게 만들었습니다.
이 시스템을 사용하면 수정자(modifier)를 게임의 다른 영역에서도 작동하게 만들 수 있습니다. 예를 들어, 업그레이드를 사용할 때 특정 동작을 수행하는 수정자들이 있습니다. 그래서 기본 클래스에 OnUpgradeUsed라는 불리언(bool) 값을 추가했는데, 기본값은 false입니다. 그런 다음 오버라이드(override)가 있는 새로운 스크립트를 만들어서, 예를 들어 업그레이드를 사용할 때 보너스 점수를 증가시키도록 할 수 있습니다. 그리고 마지막으로 '업그레이드 사용' 코드에서는 소유한 모든 수정자를 순회하며(loop through) 각 수정자에 대해 OnUpgradeUsed 함수를 트리거합니다.
여기서 좋은 점은 이것이 다른 수정자들과 완전히 호환된다는 것입니다. 예를 들어, '첫 글자가 모음인지 확인하는(is the first letter a vowel)' 수정자는 OnUpgradeUsed에 대한 오버라이드가 없기 때문에, 기본 클래스의 코드를 그대로 사용하게 됩니다. 이 코드는 아무것도 하지 않고 false를 반환합니다. 따라서 이를 통해 Word Play의 모든 곳에서 이러한 트리거들을 가질 수 있습니다. 예를 들어, 수정자를 판매할 때, 각 글자에 점수를 매길 때, 생명을 잃을 때, 게임 오버가 될 때, 특성(perk) 화면을 열 때, 특성을 재굴림할 때, 새로운 라운드에 도달했을 때, 타일을 새로고침할 때, 글자 주머니에 타일을 추가할 때 등등. 이 모든 것이 작은 스크립트들로 구성되어 있으며, 수정자의 기능에 필요한 몇 줄의 코드와 더불어 수정자의 아이콘, 설명, 희귀도, 특별한 숫자 등의 세부 정보를 담고 있어 Unity 인스펙터(inspector)에서 쉽게 변경할 수 있습니다. 또한 업그레이드에도 이 동일한 시스템을 사용했는데, 이는 타일을 드래그했을 때 변화시키는 노란색 특성들입니다.
각 업그레이드는 OnTileDropped 함수를 가진 기본 클래스(base class)로부터 상속받으며, 이후 각 업그레이드의 스크립트는 타일을 파괴하는지, 에메랄드 타일로 변환하는지, 타일의 점수를 증가시키는지 등 여부에 따라 해당 함수의 고유한 오버라이드(override)를 가집니다. 따라서 이것이 바로 상속(inheritance)의 힘이며, 클래스를 다루는 데 정말 유용한 방법입니다. 그리고 여러분은 게임에서 이것을 사용할 수 있는 모든 종류의 방법을 상상할 수 있습니다. 예를 들어, 모두 동일한 기본 이동 방식, 체력 시스템, 사망 함수를 공유하는 적들을 위한 기본 클래스나, 조준 및 재장전(reloading)에 대한 코드는 같지만 특수 능력에 대한 고유한 기능을 가진 총기들을 위한 기본 클래스가 있을 수 있습니다. 가능성은 무궁무진합니다. 하지만 이 시스템은 결국 제 게임에 완벽한 아키텍처를 제공했습니다. Word Play에는 150개가 넘는 특성(perks)을 추가하고, 제거하고, 디버깅하며, 균형을 맞추고, 변경할 수 있게 해주었습니다. 시청해 주셔서 정말 감사합니다. 흥미롭다고 느끼셨기를 바랍니다. 게임의 모습이 마음에 드신다면, Word Play는 현재 Steam에서 이용 가능하며 곧 iPhone과 iPad로 출시될 예정입니다.
그러니 관련 공지를 주목해 주세요. 대단히 감사드리며 다음에 뵙겠습니다. 안녕!
AI 자동 생성 콘텐츠
본 콘텐츠는 YouTube GMTK (게임 디자인)의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기