로그인게임하기
현장 강의록

[현장 강의록 2] 클라이언트 데이터베이스, SQLite 로 게임 데이터 읽기

2026.10.11

현장 강의록 · 애니메이션고 2022 · 2편

캐릭터의 이름과 공격력을 코드에 직접 적으면, 숫자를 고칠 때마다 프로그래머가 코드를 고쳐야 합니다. 이 수업에서는 게임 데이터를 SQLite 데이터베이스에 담고, Unity 에서 읽고, 자주 쓰는 값은 메모리에 담아 두는 과정까지 실습했습니다.

수업 정보

2022년 1학기 한국애니메이션고등학교 2학년 「게임 개발과 미들웨어」 2강 「게임 데이터 처리 #1」에서 가르친 내용입니다.

WHY

데이터베이스는 무엇을 하고, 어떻게 쓰나

로라가 표 양피지를 채우면 변환 기계가 빛나는 큐브로 바꿔 유리 항아리에 담고, 항아리에서 작은 캐릭터들이 무대로 걸어 나오는 그림
기획 데이터, 변환기, 게임 데이터. 세 단계를 거칩니다.
  1. T1. 기획 데이터 생성기획자가 엑셀에서 값을 만듭니다
  2. T2. 미들웨어Unity 에서 불러올 수 있는 형식으로 바꾸는 데이터 변환기
  3. T3. 게임 데이터 로드게임이 실행될 때 읽어 화면과 전투에 씁니다

엑셀은 사람이 고치기 좋은 형식이고, 게임은 빠르게 찾아 읽기 좋은 형식이 필요합니다. 두 형식을 잇는 가운데 단계가 이 수업의 제목에 들어 있는 미들웨어입니다. 이 단계가 있으면 기획자는 엑셀만 고치고, 프로그래머는 읽는 코드를 만들어 두면 됩니다.

CHOICE

클라이언트 데이터베이스 고르기

엑셀과 가까움

Microsoft Access

같은 회사의 엑셀 시트를 Access DB 로 쉽게 바꿀 수 있습니다. C# API 로 연결할 수 있어 모바일 기기에서도 씁니다.

수업에서 선택

SQLite3

Unity, Unreal 같은 멀티 플랫폼 엔진에 라이브러리를 넣어 여러 플랫폼에서 씁니다. 커뮤니티 지원이 활발해 지금 가장 많이 쓰는 클라이언트 DB 입니다.

파일

JSON · XML · Binary

데이터를 표준 형식 파일에 저장해 두고 필요할 때 읽습니다. 업계 표준 형식을 써도 되고, 직접 정한 바이너리 형식을 쓰기도 합니다.

수업에서는 SQLite 를 골랐습니다. 데이터베이스 파일이 그대로 게임에 들어가고, SQL 로 원하는 행을 골라 읽을 수 있기 때문입니다.

INSTALL

도구 세 가지

도구하는 일
SQLite3명령 프롬프트(터미널)에서 데이터베이스를 만들고 관리하는 유틸리티입니다
SQLite Browser창 화면으로 데이터베이스를 봅니다. 터미널보다 쉽게 표를 만들고 고칠 수 있습니다
SQLite C# LibraryUnity 에서 SQLite 를 읽는 라이브러리입니다. 프로젝트의 Assets/Plugins 아래에 플러그인 파일이, Sources 아래에 SQLite.cs 가 들어갑니다
CONSOLE

SQLite 콘솔로 표 만들기

첫 실습은 명령 프롬프트에서 데이터베이스 파일을 만드는 것이었습니다. sqlite3 파일이름 으로 실행하면 파일이 없을 때 새로 만듭니다.

SQLite3 Console2강 7쪽
C:\SQLite> sqlite3 ClientGameData.db
SQLite version 3.x
Enter ".help" for instructions
sqlite> .databases
seq  name   file
0    main   C:\SQLite\ClientGameData.db

다음은 캐릭터 표를 만들고, 두 캐릭터를 넣고, 읽어 보는 차례였습니다.

SQLite3 Console2강 8~9쪽
sqlite> CREATE TABLE "Characters" (
   "ID" INTEGER,
   "Name" TEXT,
   "AttackPoint" INTEGER,
   "HealthPoint" INTEGER,
   PRIMARY KEY("ID")
);
sqlite> .tables
Characters
sqlite> INSERT INTO Characters (ID, Name, AttackPoint, HealthPoint) VALUES (1, '제임스', 4, 4);
sqlite> INSERT INTO Characters (ID, Name, AttackPoint, HealthPoint) VALUES (2, '루이스', 5, 6);
sqlite> SELECT Name, AttackPoint, HealthPoint FROM Characters;
제임스|4|4
루이스|5|6
sqlite> SELECT Name, AttackPoint, HealthPoint FROM Characters WHERE ID=1;
제임스|4|4
명령하는 일
CREATE TABLE표를 만들고 칸(열)의 이름과 형식을 정합니다. PRIMARY KEY 는 행을 구별하는 기본 키입니다
INSERT INTO표에 행을 넣습니다
SELECT … FROM표에서 원하는 칸을 읽습니다. WHERE 를 붙이면 조건에 맞는 행만 고릅니다
UPDATE … SET이미 있는 행의 값을 고칩니다
.tables · .databasesSQL 이 아닌 SQLite 콘솔 명령입니다. 표와 데이터베이스 목록을 봅니다

같은 일을 SQLite Browser 로도 해 봤습니다. UPDATE Characters SET Name='소피아' WHERE ID=2; 를 실행하고 표를 보면 루이스가 소피아로 바뀐 것이 눈으로 보입니다. 콘솔로 원리를 익히고, 실제 작업은 Browser 로 하는 순서입니다.

UNITY

Unity 에서 읽기

Unity 가 데이터베이스 파일을 읽으려면 파일이 있어야 할 위치에 있어야 합니다. 쓰기와 읽기가 모두 되는 위치는 Application.persistentDataPath 입니다. Debug.Log(Application.persistentDataPath); 로 위치를 찍어 보고 그 폴더로 파일을 옮겼습니다.

다음은 표의 행을 C# 클래스로 옮기는 일입니다. 칸 이름과 같은 이름의 속성을 만들면, 라이브러리가 SELECT 결과를 그대로 객체에 채워 줍니다. 1편에서 배운 객체가 여기서 다시 나옵니다. 데이터베이스의 행이 곧 객체가 됩니다.

GameDB.cs2강 12~13쪽
public class CharacterRow
{
    [PrimaryKey]
    public int ID { get; set; }
    public string Name { get; set; }
    public int AttackPoint { get; set; }
    public int HealthPoint { get; set; }
}

public List<CharacterRow> GetCharacter(int id)
{
    SQLiteConnection conn = GetConnection();
    List<CharacterRow> result = null;
    try
    {
        SQLiteCommand command = conn.CreateCommand(string.Format(
            "SELECT ID, Name, AttackPoint, HealthPoint FROM Characters WHERE ID={0};", id));
        result = command.ExecuteQuery<CharacterRow>();
    }
    catch (Exception ee)
    {
        Debug.LogError(ee.Message);
        Debug.LogError(ee.StackTrace);
    }
    return result;
}
GameScene.cs · Character.cs2강 14쪽
// GameScene.cs
List<GameDB.CharacterRow> result = gameDB.GetCharacter(2);
if (result != null && result.Count > 0)
{
    heroCharacter.SetName(result[0].Name);
}

// Character.cs
public void SetName(string characterName)
{
    myNameText.text = characterName;
}
실습: 캐릭터의 공격력과 생명력을 DB 에서 불러오기
  1. 공격력과 생명력을 띄울 Text 두 개를 Scene 에 추가합니다.
  2. 두 Text 를 Character 오브젝트에 연결합니다.
  3. Character.cs 에 SetAttackPoint(int point), SetHealthPoint(int point) 를 만듭니다.
  4. DB 에서 불러온 공격력과 생명력을 캐릭터에 넣습니다.

2번 캐릭터를 불러오면 화면에 공격력 5, 생명력 6 이 나오면 성공입니다.

CACHE

자주 읽는 값은 메모리에

멀리 사다리를 타야 닿는 책장과, 로라의 손 옆에 놓인 작은 책 쟁반을 비교하는 도서관 그림
파일을 읽는 길은 멀고, 메모리는 손 옆에 있습니다.

같은 캐릭터를 읽을 때마다 SELECT 를 실행하면 매번 파일을 열고, SQL 을 해석하고, 행을 찾습니다. 게임에서는 같은 값을 아주 자주 읽기 때문에 이 비용이 쌓입니다.

File I/O디스크의 데이터베이스 파일을 엽니다. 가장 느립니다
DatabaseSQL 을 해석하고 행을 찾습니다
Memory미리 담아 둔 목록에서 꺼냅니다. 가장 빠릅니다

그래서 게임이 시작할 때 모든 캐릭터를 목록에 담아 두는 인메모리 캐시를 만들었습니다.

실습: 모든 캐릭터를 메모리에 캐시하기
  1. GameDB.cs 에 List<CharacterRow> preLoadedCharacters 를 만듭니다.
  2. Start 에서 모든 캐릭터를 SELECT 해 preLoadedCharacters.AddRange() 로 담습니다.
  3. GetCharacter(int) 는 목록에 있으면 목록에서 돌려주고, 없을 때 DB 에 SELECT 합니다.
GameDB.cs설명용 예시 — 위 실습의 답
List<CharacterRow> preLoadedCharacters = new List<CharacterRow>();

void Start()
{
    SQLiteCommand command = GetConnection().CreateCommand(
        "SELECT ID, Name, AttackPoint, HealthPoint FROM Characters;");
    preLoadedCharacters.AddRange(command.ExecuteQuery<CharacterRow>());
}

public CharacterRow FindCharacter(int id)
{
    CharacterRow cached = preLoadedCharacters.Find(row => row.ID == id);
    if (cached != null) return cached;          // 메모리에 있으면 그대로
    List<CharacterRow> rows = GetCharacter(id);  // 없을 때 DB 에 묻는다
    return rows != null && rows.Count > 0 ? rows[0] : null;
}
SINGLETON

어디서든 같은 GameDB

캐릭터도, 무기도, 장면도 GameDB 를 씁니다. 저마다 GameDB 를 새로 만들면 캐시도 여러 벌이 되고 파일도 여러 번 열립니다. 그래서 GameDB 는 게임 전체에서 객체 하나를 함께 쓰도록 싱글턴으로 만들었습니다.

GameDB.cs2강 19쪽
private static GameDB SingletonInstance = null;

public static GameDB GetSingleton()
{
    if (SingletonInstance == null)
    {
        SingletonInstance = new GameDB();
    }
    return SingletonInstance;
}

// 쓰는 쪽 (GameScene.cs)
List<GameDB.CharacterRow> result = GameDB.GetSingleton().GetCharacter(2);

처음 부를 때 만들어 두고, 다음부터는 만들어 둔 객체를 돌려줍니다. Character, Weapon, GameScene 이 모두 같은 GameDB 를 보게 됩니다.

PREFAB

데이터베이스에 있는 캐릭터를 모두 불러오기

마지막 실습은 데이터베이스에 들어 있는 캐릭터 수만큼 화면에 캐릭터를 만드는 것이었습니다. 캐릭터 모양은 프리팹(Prefab)으로 만들어 두고, 행마다 프리팹을 복제(Instantiate)해 이름과 능력치를 넣습니다.

GameScene.cs설명용 예시 — 2강 23~24쪽 실습
public Character characterPrefab;

void Start()
{
    float x = -4f;
    foreach (GameDB.CharacterRow row in GameDB.GetSingleton().GetAllCharacters())
    {
        Character c = Instantiate(characterPrefab, new Vector3(x, 0, 0), Quaternion.identity);
        c.SetName(row.Name);
        c.SetAttackPoint(row.AttackPoint);
        c.SetHealthPoint(row.HealthPoint);
        x += 2f;
    }
}

이제 기획자가 데이터베이스에 캐릭터를 더 넣으면, 코드는 그대로인데 화면의 캐릭터가 늘어납니다. 데이터와 코드를 나눈 효과가 처음 눈에 보이는 순간입니다.

NOW

지금 100턴에서는

「졸업까지 100턴」은 엑셀 58개를 원본으로 두고, 도구가 클라이언트용 JSON 과 서버 DB 를 함께 만듭니다. 수업의 세 단계가 실제 서비스에서 어떻게 커졌는지는 구조 읽기 1편에서 볼 수 있습니다.

정리

  1. 기획, 변환, 로드. 사람이 고치는 형식과 게임이 읽는 형식 사이에 변환기를 둡니다.
  2. 표의 행은 객체가 됩니다. 칸 이름과 같은 속성을 가진 클래스로 SQL 결과를 받습니다.
  3. 자주 읽는 값은 메모리에, GameDB 는 하나만. 캐시와 싱글턴으로 같은 일을 반복하지 않습니다.

다음 편에서는 엑셀 데이터를 다듬어 데이터베이스로 옮기고, 여러 표를 설계하는 실습을 다룹니다.

← 개발 노트 목록