요근래에 게임을 제작하며 대화 시스템 들어가는 경우가 많았다. 늘 반복적인 작업을 하게 되거나, 전에 만든 프로젝트에서 기능을 골라내어 뜯어와야 했다. (언제나 깔끔하게 디커플링이 되어있지를 않았다.🤯)
이런 과정을 줄이고자 비주얼노벨 어셋 프로젝트를 짧게 제작하였다. (+ 무언가 공유하고 싶어서)
소개
기본적으로 작동하는 기능은 다음과 같다. 아래는 샘플 시나리오로 녹화한 영상이다.
- 시나리오 파일 읽고 유효한 파일인지 검사하는 기능
- 시나리오 파일에 명시한대로 비주얼노벨이 작동하는 기능
- 캐릭터의 표정이 명시한대로 바뀌는 기능
- 대화 중인 멤버를 화면에 배치하고, 현재 화자를 하이라이팅하는 기능
- 선택지 기능
- 기본 대화, 라디오, 무전, 시스템 등 대화 방식에 따라 다른 연출 기능
- 로컬라이징 기능
- 스킵 기능
지금은 표정이 다른 이미지 어셋이 없어서 모두 동일한 표정으로 처리하였다.🥺 사운드, UI 연출, 배경 교체 등은 차차 추가될 예정이다. 아주 기본적인 기능을 우선적으로 만들었다.
시나리오 작성 룰
CSV 파일은 다음과 같이 구성되어있다.
Column Name | Description |
Code | 해당 라인의 타입. 현재는 SET, TALK, RADIO, SYSTEM, SELECT이 있음. |
Flag | 선택지 시스템에서 사용. |
Info | Code에 따라 다른 주요 정보를 전달. |
SubInfo | Info로 부족한 내용 보충. |
SubContents | Contents로 부족한 내용 보충. |
Contents | 내용. |
네이밍은 나중에 한 번 수정이 필요하다. 지금은 알아보기 조금 힘들다…!
코드에 따라 구성은 이렇게 했다.
- 공통
- Flag: 정수값 (선택지에서 flag가 선언되면 해당 flag만 읽어옴)
- SET
- 무언가를 세팅함
- Info: Member (화면에 보이는 멤버를 재구성)
- Contents: 멤버 목록을 공백없이 쉼표로 구분
- TALK
- 일반 대사
- Info: 화자
- SubInfo: 만약 UI에서 화자와 이름이 다르게 뜬다면 여기서 지정 (ex. ???)
- SubContents: 감정 (IDLE, TALK, SMILE, ANGRY, SAD, HURT, TIRED)
- Contents: 대사
- RADIO
- 일반 대화창에 뜨지만 따로 지정된 폰트 사용
- Info: 화자
- SubInfo: 만약 UI에서 화자와 이름이 다르게 뜬다면 여기서 지정 (ex. ???)
- Contents: 대사
- SYSTEM
- 검은 화면 중앙에 대사 출력
- Contents: 대사
- SELECT
- 선택지 시스템
- Info: 해당 선택지를 선택했을 때의 플래그
- Contents: 선택지에 뜨는 문구
시나리오는 ‘Assets/Resources/[언어코드]/Scenarios/’ 패스 안에 Scenario_[넘버링].csv의 형태로 삽입한다.
스탠딩 이미지를 사용할 캐릭터의 경우에는 캐릭터 시트에서 업데이트를 해주어야한다. ‘Assets/Resources/Characters.json’에 언어별 명칭과 ID를 입력해주어야 정상적으로 스탠딩 이미지가 출력된다.
인스펙터 창 사용
CSV를 삽입한 뒤, 런타임에도 문제없이 파일을 읽을 수 있는지 확인하는 도구를 추가했다.

Home 씬의 하이라키 최하단에 있는 DataCheckManager을 보면 데이터를 검사하는 버튼이 있다. 잘못된 데이터가 있는 경우 Fail code와 함께 오류 메세지를 보여준다.

캐릭터의 이미지는 따로 로드하는 부분을 아직 구현하지 않아서 인스펙터창을 이용하여 관리한다. 다음에 업데이트할 때, 이 부분은 자동으로 로드되도록 수정할 예정이다.
후기
계속 사용하면서 필요한 기능을 추가하고 불필요한 기능은 제거하는 방식으로 업데이트할 것 같다. 깊게 생각하지 않고 무작정 필요할 것 같은 기능들을 추가하는 방식으로 만들었기 때문에 구조 또한 개선되어야 한다. 직접 임포트하여 사용해보지 않았기 때문에 또 어떤 불편함이 있을지 모른다.
늘 프로젝트에 필요한 기능은 다시 만들곤 했는데, 앞으로도 자주 사용할 기능이나 모듈화할 수 있는 기능은 분리하는 방식으로 개발하고자 한다. 💻😃👍
유니티 종속적인 기능을 만든 것이라 편하게 비주얼 노벨을 만들 수 있는 ‘툴’이 아닌 ‘어셋’이 되어버렸다. 나중에는 빌드까지 편하게 진행할 수 있는 비개발자용 툴을 만들어보고 싶다.
프로젝트 구조
.
├── Assets
│ ├── Graphics
│ │ ├── Fonts
│ │ ├── Sprites
│ ├── Prefabs
│ │ ├── Home
│ │ │ ├── DataCheckManager.prefab
│ │ │ └── ScenarioButton.prefab
│ │ ├── LoadingUI.prefab
│ │ └── OptionUI.prefab
│ ├── Resources
│ │ ├── Characters.json
│ │ ├── EN
│ │ │ └── Scenarios
│ │ │ └── Scenario_0.csv
│ │ ├── JP
│ │ │ └── Scenarios
│ │ │ │ └── Scenario_0.csv
│ │ └── KR
│ │ └── Scenarios
│ │ └── Scenario_0.csv
│ ├── Scenes
│ │ ├── Home.unity
│ │ └── Story.unity
│ ├── Scripts
│ │ ├── Data
│ │ │ ├── CharacterStaticData.cs
│ │ │ ├── LocalData.cs
│ │ │ ├── PlayerSaveData.cs
│ │ │ ├── StoryBookmark.cs
│ │ │ └── StoryStaticData.cs
│ │ ├── Models
│ │ │ └── ScenarioLine.cs
│ │ ├── DataCheck
│ │ │ ├── DataCheckEditor.cs
│ │ │ └── DataCheckManager.cs
│ │ ├── DefaultSystem
│ │ │ ├── CSVReader.cs
│ │ │ └── EffectSoundSystem.cs
│ │ ├── DefaultUI
│ │ │ ├── ButtonAnimator.cs
│ │ │ ├── OptionManager.cs
│ │ │ └── SceneLoader.cs
│ │ ├── Home
│ │ │ ├── DropdownSetter.cs
│ │ │ ├── HomeManager.cs
│ │ │ ├── ScenarioButton.cs
│ │ │ └── ScenarioList.cs
│ │ ├── Story
│ │ │ ├── DialogSetter.cs
│ │ │ ├── SelectionSetter.cs
│ │ │ ├── SelectionSlot.cs
│ │ │ ├── StandingSetter.cs
│ │ │ ├── StoryManager.cs
│ │ │ ├── StroyAssets.cs
│ │ │ ├── SystemDialogSetter.cs
│ │ │ └── TalkDialogSetter.cs
└── quick-visual-novel.sln