이번에 대학을 졸업하기 전 마지막 이벤트로 동계 게임잼에 참여하였다.
기간은 17일 부터 19일까지였지만, 퇴근 후 시간에만 일부 참여라 실질적으로 할 수 있었던 시간은 17일 저녁 잠깐과, 18일 밤~19일 낮이었다. 게다가 17일은 프로젝트 세팅이 겨우여서 18일부터 정말 스퍼트를 내 만들었다.🥺 지금까지 했던 모든 개발 중에 가장 급하게 만든 것 같다. 20시간 정도 👍
기획 및 팀빌딩
이번에는 닷지형 게임을 만들고 싶어서 기획 아이디어를 내고 팀원을 모집하였다.
아트 한 분과 기획 한 분을 무사히 모시고 팀을 꾸렸을 때, 추리 게임을 기획하고 팀원을 모집 중이던 분이 우리 팀에 서브 기획으로 들어오게 되었다. 굉장히 매력있는 스토리형 추리 게임을 기획하시던 모습을 봐서 괜찮다면 우리 팀의 게임과 기획을 합쳐보지 않겠냐고 제의하였다. 닷지로 모은 모종의 재화를 이용해 스토리를 해금하는 방식으로 만들기로 하고 개발에 들어갔다.
개발
개발 시간이 비교적 적게 걸릴 것 같은 스토리 파트를 우선적으로 끝내고 닷지게임을 개발했다. 하지만 닷지가 예상보다 더 많은 시간으로 요구해서 기획을 쳐낼 수 밖에 없었고, 기능이 거의 남지 않았기에 닷지 게임을 살릴 수가 없었다. 🥺 기존에는 있었던 무기 장착과 중첩효과가 사라지고 일반 공격과 회피만 남았다. 여긴 제거된 부분이 아닌, 구현된 부분만 적어보도록 하겠다.
홈 화면에서는 스토리를 읽거나 지금까지 모은 단서들을 다시 확인할 수 있다. 단서를 모으고 대화를 읽은 후 최종적으로 추리를 완료하려면 가운데 손가락 버튼을 눌러 대화 이벤트를 진행하면 된다.
지금까지 모은 데이터는 단서 확인창에서 확인이 가능하다. 단서는 총 세 가지 타입이다. 범인 추론을 위한 단서로 사용되기도 하지만 몇몇은 아주 쓸모없는 정보를 가지고 있기도 하다.
프로그래머로 참여하였지만 오늘(19일) 아침엔 일러스트를 그리고 있었다. 몇몇 캐릭터는 과거에 그린 그림을 건져왔지만 도저히 대체가 불가한 캐릭터가 둘 있어서 아침에 급하게 새로 그렸다. 위의 캐릭터가 그 중 하나.
스토리의 기능은 간단하다. 0~2명의 캐릭터가 나와서 대화하며, 화자 캐릭터에게 하이라이트를 넣어준다. 간단한 대사 애니메이션이 뜨고, 선택지에 의해 대화 흐름이 관리되는 기능이 있다. 선택지는 플래그로 관리한다.
TALK,0,가브렐라,"네, 맞아요. 그땐 교회 쪽으로 가는 길이었어요! "
TALK,0,가브렐라,"그러다가 비닐봉지를 발견했고, 오, 불쌍한 에이다."
SELECT,0,1,계속 말을 건다.
SELECT,0,2,떠난다.
TALK,1,가브렐라,"오오, 탐정님. 제발 에이다의 억울한 죽음을 풀어주세요. "
TALK,1,가브렐라,"비록 싸늘한 아이였지만, 저는 알아요. 그녀는 무척 따스한 영혼이었어요..!!!"
TALK,1,나,"...네, 알겠습니다."
TALK,2,나,"네, 알겠습니다. 감사합니다."
SET,0,Member,
TALK,0,나,...말이 많으신 분이군. (한숨) 시신에 대한 건 형사에게 물어야겠어.
SET,0,Member,형사
TALK,0,형사,"{n}씨, 기다리고 있었습니다."
대사는 빠르고 쉬운 소통을 위해 CSV 포맷을 사용하였다. json으로 만들어서 편하게 임포트하고 싶었지만 기획분들이 시트에 익숙하시고, 나는 Parser를 만들 짬이 없었다. … 사실 변명이다. 귀찮아서 CSV로 익스포트하고 그대로 넣었다. Reader를 만들면서 차라리 Parser를 만들걸 후회했지만 이미 늦었었다.
모든 텍스트와 대부분의 레벨 디자인은 시트를 통해서 관리한다. 닷지 플레이 씬에서 사용된 테이블은 몬스터 테이블
, 웨이브 테이블
이 대표적이다.
웨이브 테이블에 지정된 웨이브 정보로 레벨에 맞는 웨이브를 호출한다. 웨이브 테이블에서는 웨이브의 모양, 생성되는 몬스터의 리스트, 반복 횟수가 있다.
웨이브의 모양은 원형과 n시(ex 3시) 방향으로 구분된다. 지정된 포지션에서 모두 생성되면 랜덤성이 적어지기 때문에 직경 1 만큼의 노이즈를 주었다.
게임을 살펴보면 닷지의 비중이 낮기 때문에 UI에 액션에 집중할 필요가 있었다. 플레이어 잔류시간이 긴 순서대로 보면 스토리 > 홈 > 게임 씬이 되는데, 스토리와 홈씬은 UI로만 구성되어 있기 때문이다.
프로젝트 구조
.
├── Assets
│ ├── Scripts
│ │ ├── Data
│ │ │ ├── StaticData
│ │ │ │ ├── Enemy
│ │ │ │ │ ├── EnemyStaticData.cs
│ │ │ │ │ └── WaveStaticData.cs
│ │ │ │ ├── ClueStaticData.cs
│ │ │ │ ├── StoryBookmark.cs
│ │ │ │ └── StoryStaticData.cs
│ │ │ └── VariableData
│ │ │ └── Player.cs
│ │ ├── Models
│ │ │ ├── Enemy
│ │ │ │ ├── Enemy.cs
│ │ │ │ └── Wave.cs
│ │ │ └── Story
│ │ │ ├── Clue.cs
│ │ │ ├── Scenario.cs
│ │ │ └── ScenarioRequires.cs
│ │ ├── DefaultSystem
│ │ │ ├── CSVReader.cs
│ │ │ ├── EffectSoundSystem.cs
│ │ │ └── SaveDataManager.cs
│ │ ├── DefaultUI
│ │ │ ├── ButtonAnimator.cs
│ │ │ ├── OptionManager.cs
│ │ │ └── SceneLoader.cs
│ │ ├── Game
│ │ │ ├── Clue
│ │ │ │ ├── ClueDropManager.cs
│ │ │ │ └── ClueObject.cs
│ │ │ ├── Enemy
│ │ │ │ ├── EnemyController.cs
│ │ │ │ ├── EnemyObjcet.cs
│ │ │ │ ├── EnemyObjectPool.cs
│ │ │ │ ├── EnemySpawner.cs
│ │ │ │ └── WaveManager.cs
│ │ │ ├── Player
│ │ │ │ ├── PlayerColliderDetector.cs
│ │ │ │ ├── PlayerConfig.cs
│ │ │ │ ├── PlayerController.cs
│ │ │ │ └── PlayerMove.cs
│ │ │ ├── UI
│ │ │ │ ├── GameOverUI.cs
│ │ │ │ ├── GameUIManager.cs
│ │ │ │ └── ResultClueSlot.cs
│ │ │ ├── Weapone
│ │ │ │ ├── BulletPool.cs
│ │ │ │ ├── WeaponeController.cs
│ │ │ │ └── WeaponeObject.cs
│ │ │ ├── CameraFollow.cs
│ │ │ ├── Enums.cs
│ │ │ └── GameManager.cs
│ │ ├── Home
│ │ │ ├── ClueSection.cs
│ │ │ ├── ClueUIManager.cs
│ │ │ ├── ClueUIObject.cs
│ │ │ ├── HomeManager.cs
│ │ │ ├── StoriesButtonsManager.cs
│ │ │ ├── StoryButton.cs
│ │ │ └── UpperUIManager.cs
│ │ ├── Story
│ │ │ ├── DialogSetter.cs
│ │ │ ├── SelectionSetter.cs
│ │ │ ├── SelectionSlot.cs
│ │ │ ├── StandingSetter.cs
│ │ │ ├── StoryManager.cs
│ │ │ └── StroyAssets.cs
│ │ └── Title
│ │ ├── NameEnterManaegr.cs
│ │ └── TitleManager.cs
│ ├── Resources
│ │ ├── KR
│ │ │ ├── Clues.csv
│ │ │ └── Scenarios
│ │ │ ├── Scenario_0.csv
│ │ │ ├── Scenario_1.csv
│ │ │ ├── Scenario_2.csv
│ │ │ ├── Scenario_3.csv
│ │ │ ├── Scenario_4.csv
│ │ │ ├── Scenario_5.csv
│ │ │ └── Scenario_6.csv
│ │ ├── Enemies.csv
│ │ ├── ScenarioRequirement.json
│ │ └── Waves.csv
│ └── Scenes
│ ├── Game.unity
│ ├── Home.unity
│ ├── Story.unity
│ └── Title.unity
└── README.md
+
UI 액션을 살리기 위해서 애니메이션과 사운드에 집중하였다. 사운드는 마지막 날 아침까지 삽입이 불가할 것 같았는데 시간이 잠깐 남아서 후딱 만들어 넣었다.
타이틀 씬에서 생성한 사운드 이펙트 매니저가 모두 관리하며, 호출은 이펙트 이름을 이용해 한다. 급해서 오타에는 대응하지 않았다. 나를 믿었다. 👍 사운드 리스트는 오브젝트 생성시에 Hash로 저장하였다.
리소스는 리스트로 관리되고 있는데 굳이 Hash를 또 만든 이유는 서치 시간 단축과 서치 편의를 위해서였지만 생각보다 리소스 양이 적어서… 서치 편의만 살아남았다.
대부분의 UI에서는 DOTween을 이용한 애니메이션을 사용하였다. 모든 패널은 On/Off시에 페이드시켰다. 그 정도만 고작했는데도 확 있어보여서 만족스러웠다. 스케일 정도는 추가할 수 있었을 것도 같은데 이렇게 하나하나 찔끔 추가했었으면 아마 기능 하나가 더 빠지지 않았었을까! 충분해 잘했어!
Option창과 Loading창은 프리팹으로 만들어 모든 씬에서 재사용했는데 덕분에 아주 쉽게 사운드와 애니메이션을 삽입할 수 있었다. 같은 이유로, 반대로, 단서 디테일 창은 프리팹화를 해두지 않아서 홈씬과 게임씬, 별도로 작업해주어야 했다.
찬물과 더운물을 왔다갔다 하는 게임잼이었다. 1시간 전에 편했으면 1시간 뒤에 고생하는 업보 체감 시간이었다.
짧은 후기
이번에는 효율성과 순간적인 생산속도보다 가독성을 우선 순위에 두고 작업하였다. 확실히 잔버그가 없고 수정이 편해서 좋았지만, 제출 시간 직전의 급한 기능 추가가 없을 수는 없었다.(우다다다🥺) 큰 구조를 생각하고 잡던 코드도 구조가 계속 변경되며 이름이 섞이고는 했다. 다음에는 구조 변경에도 신경을 기울여야겠다.
소스코드 관리에 브랜치를 이용했지만 놀랍게도 후반에 리소스 삽입하면서부터 급해져서 하나하나 머지할 정신이 없었다. 마지막에 빌드 전 머지할 때, 아직도 브랜치 이름이… 리소스 적용인 것을 보고 이마를 쳤다.
20시간의 작업물에 프로젝트 세팅, 서드 파티 임포트를 제외하면 커밋이 5개 뿐이다. 한 브랜치에 많으면 잡다한 기능까지 30개의 커밋이 들어있다. 관리가…된 걸까 싶다. 나중엔 여유를 좀 가지고 차분하게 작업하고 싶다.