게임 데이터는 대부분 엑셀에서 태어납니다. 이 수업에서는 엑셀로 밸런스 점수를 계산하고, CSV 로 내보내 데이터베이스로 옮기고, 캐릭터 · 장비 · 아이템 · 몬스터 · 무기 · 퀘스트 표를 설계해 게임 콘텐츠를 SQL 로 다뤘습니다.
2022년 1학기 한국애니메이션고등학교 2학년 「게임 개발과 미들웨어」 3강 「게임 데이터 처리 #2」에서 가르친 내용입니다.
원본 시트와 더미 시트
엑셀 파일에는 시트를 두 장 둡니다. 원본 시트는 기획자가 보고 고치는 시트라서 머리글(ID, 이름, 공격력…)과 계산 칸이 있습니다. 더미 시트는 데이터베이스로 내보내기 위한 시트라서 머리글 없이 값만 원본 시트를 참조해 채웁니다.
원본 시트
- 머리글이 있습니다
- 종합 밸런스 같은 계산 칸이 있습니다
- 기획자가 직접 고칩니다
더미 시트
- 머리글이 없습니다
- 데이터베이스 칸 순서와 똑같습니다
- 원본 시트를 참조하므로 손대지 않습니다
이렇게 나누면 기획자는 보기 좋은 시트에서 일하고, 내보내기는 언제나 같은 모양으로 나옵니다.
실습: 종합 밸런스 점수
- 공격력 1배, 생명력 1.2배, 방어력 1배를 더한 종합 밸런스 점수를 계산합니다.
- 종합 밸런스 점수가 낮은 순으로 정렬합니다.
엑셀 수식: =C2*1 + D2*1.2 + E2*1 (C 공격력, D 생명력, E 방어력)
| ID | 이름 | 공격력 | 생명력 | 방어력 | 종합 밸런스 |
|---|---|---|---|---|---|
| 3 | 스미스 | 4 | 2 | 0 | 6.40 |
| 2 | 레온 | 3 | 3 | 0 | 6.60 |
| 4 | 소피아 | 5 | 1 | 1 | 7.20 |
| 5 | 마이클 | 6 | 5 | 0 | 12.00 |
| 1 | 루시아 | 2 | 7 | 2 | 12.40 |
| 6 | 헬룬 | 7 | 7 | 2 | 17.40 |
숫자로 늘어놓으면 스미스와 레온이 가장 약하고 헬룬이 가장 강하다는 것이 보입니다. 게임을 고치기 전에 엑셀에서 먼저 비교해 보는 습관이 기획자와 프로그래머 모두에게 필요합니다. 생명력에 1.2 를 곱한 것은 이 게임에서 오래 버티는 힘을 조금 더 높게 친다는 기획 판단입니다.
CSV 로 내보내고 UTF-8 로 바꾸기
- 엑셀원본 시트에서 값을 고칩니다
- CSV 내보내기더미 시트를 CSV 로 저장
- UTF-8 변환Notepad++ 에서 인코딩 변경
- 가져오기SQLite 콘솔의 .import
CSV 는 값을 쉼표로 나눈 텍스트 파일입니다. 한국어 Windows 의 엑셀은 CSV 를 ANSI(CP949) 로 저장하는데, SQLite Browser 와 Unity 는 UTF-8 로 읽습니다. 그래서 가져오기 전에 Notepad++ 같은 편집기로 인코딩을 UTF-8 로 바꿉니다.
C:\sqlite3> sqlite3 ClientGameData.db
sqlite> .separator ","
sqlite> DELETE FROM Characters;
sqlite> .import Characters.csv Characters
sqlite> SELECT * FROM Characters;
1,루시아,2,7,2,0,0
2,레온,3,3,0,0,0
3,스미스,4,2,0,0,0
4,소피아,5,1,1,0,0
5,마이클,6,5,0,0,0
6,헬룬,7,7,2,0,0| 명령 | 하는 일 |
|---|---|
| .separator "," | 값을 나누는 글자를 쉼표로 정합니다 |
| DELETE FROM Characters | 표의 행을 모두 지웁니다. 다시 가져올 때 같은 ID 가 겹치지 않게 합니다 |
| .import 파일 표 | CSV 의 줄마다 표에 행을 넣습니다 |
SQLite 콘솔은 ANSI 로 읽고, SQLite Browser 와 Unity 는 UTF-8 로 읽습니다. UTF-8 로 바꿔 가져온 뒤 콘솔에서 한글이 깨져 보이는 것은 정상입니다. Browser 나 Unity 에서 바르게 보이면 성공입니다.
- 엑셀 데이터 시트에 캐릭터를 10행까지 늘립니다.
- 더미 시트에서 데이터를 받아 CSV 로 내보냅니다.
- Notepad++ 로 인코딩을 UTF-8 로 바꿉니다.
- SQLite 콘솔로 데이터를 가져와 SELECT 로 확인합니다.
여러 표를 설계하기

게임에는 캐릭터 표 하나로 담을 수 없는 정보가 많습니다. 수업에서는 MMORPG 를 떠올리며 표를 여섯 개로 나눴습니다.
| 표 | 역할 | 주요 칸 | 기본 키 |
|---|---|---|---|
| Characters | 캐릭터 정보 | 기본 정보, 전투 능력치, 실시간 정보(위치 등) | ID |
| Equipment | 장착하고 있는 장비 | 장착 칸 4개 (ItemList 의 ID 를 가리킴) | CharacterID |
| ItemList | 게임에서 얻은 모든 아이템 | 아이템 ID, 종류, 수량, 주인 ID | ID, 자동 증가 |
| Monster | 몬스터 정보 | 기본 정보, 전투 능력치, 스폰 시간, 스폰 확률, 대기 위치 | ID |
| Weapon | 무기 정보 | 이름, 공격력, 방어력 | ID |
| Quest | 퀘스트 정보 | 제목, 대사, 임무, 보상 ID, 보상 수량, 선행 퀘스트 ID | ID |
| 말 | 뜻 |
|---|---|
| 기본 키 (Primary Key) | 행을 구별하는 칸입니다. 같은 값이 두 번 나올 수 없습니다 |
| 외래 키 (Foreign Key) | 다른 표의 기본 키를 가리키는 칸입니다. 장비 칸이 아이템을 가리키는 식입니다 |
| 자동 증가 (Auto Increment) | 행을 넣을 때 기본 키를 데이터베이스가 1 씩 늘려 정합니다. 아이템처럼 계속 새로 생기는 행에 씁니다 |
Equipment 표의 기본 키가 CharacterID 라는 점이 재미있습니다. 캐릭터마다 장비 줄이 하나씩 있으니 캐릭터 ID 가 곧 장비 줄의 이름이 됩니다.
- SQLite Browser 로 여섯 표를 만듭니다.
- 표마다 엑셀 시트를 원본 · 더미 두 장씩 만들고 데이터를 1~3행 넣습니다.
- 인코딩을 바꾸고 SQLite 콘솔로 데이터를 가져옵니다.
- CharacterRow 를 참고해 표마다 스키마 클래스(WeaponRow 등)를 만듭니다.
실습: 캐릭터에 무기 쥐여 주기
- GameDB 에
public List<WeaponRow> GetWeapon(int id)를 만듭니다. - Weapon 클래스에
Init(int id)를 만들고 안에서 GetWeapon 을 부릅니다. - Character 의 Init 에서
myWeapon.Init()을 부릅니다. - Weapon 에 Text 를 붙이고 공격력을 띄웁니다.
public class Weapon : MonoBehaviour
{
public Text attackText;
public int AttackPoint { get; private set; }
public void Init(int id)
{
List<GameDB.WeaponRow> rows = GameDB.GetSingleton().GetWeapon(id); // SELECT * FROM Weapons WHERE ID={id}
if (rows == null || rows.Count == 0) return;
AttackPoint = rows[0].AttackPoint;
attackText.text = AttackPoint.ToString();
}
}캐릭터 → 무기 → GameDB 로 이어지는 호출이 생겼습니다. 이 관계는 7편의 클래스 다이어그램과 시퀀스 다이어그램에 다시 나옵니다.
게임 콘텐츠를 위한 쿼리
# 캐릭터 강화
UPDATE Characters SET AttackPoint = AttackPoint + 1, HealthPoint = HealthPoint + 1 WHERE ID={0};
# 새 아이템 획득 (자동 증가 키 주의)
INSERT INTO ItemList (ItemType, Amount, OwnerID) VALUES ({0}, {1}, {2});
# 가진 아이템 조회
SELECT * FROM ItemList WHERE OwnerID={0};아이템을 넣을 때 ID 를 적지 않는 이유가 자동 증가입니다. ID 는 데이터베이스가 정하므로 INSERT 에서 ID 칸을 빼야 합니다.
- GameDB 에 AddAttackPoint(int id, int point) 를 만듭니다.
- GameDB 에 AddHealthPoint(int id, int point) 를 만듭니다.
- Character.cs 에 EnchantAttackPoint(int point), EnchantHealthPoint(int point) 를 만듭니다.
- Character.cs 에 RefreshCharacterInfo() 를 만들어 이름 · 공격력 · 방어력 · 생명력 글자를 새 값으로 바꿉니다.
수업에서는 값을 {0} 자리에 끼워 SQL 을 만들었습니다. 실제 서비스라면 값을 SQL 문자열과 따로 넘기는 파라미터 바인딩을 씁니다. 이유는 ElServer 리뷰에 정리했습니다.
지금 100턴에서는
「졸업까지 100턴」의 엑셀은 58개이고, 사람이 손으로 CSV 를 옮기지 않습니다. 도구가 엑셀을 읽어 JSON 과 DB 로 함께 내보냅니다. 수업에서 손으로 했던 "엑셀 → CSV → 인코딩 → import" 가 어떻게 자동화되었는지는 구조 읽기 1편에서 볼 수 있습니다.
정리
- 원본 시트와 더미 시트를 나눕니다. 사람은 보기 좋은 시트에서, 내보내기는 같은 모양의 시트에서.
- CSV 는 UTF-8 로. 인코딩이 다르면 한글이 깨집니다.
- 표를 나누고 키로 잇습니다. 기본 키로 행을 구별하고, 외래 키로 다른 표를 가리킵니다.
다음 편에서는 네트워크의 기초, 통신 규약과 직렬화와 패킷을 다룹니다.