콘텐츠 지원 Part 4: 기획 데이터

게임 로직 구현을 위해 기획자가 생산하는 게임 데이터를 써야될 수 있습니다. 예를 들어, 아이템의 가격이나 속성 정보는 개개의 아이템을 별도의 C++/C# 클래스로 구현하는 것이 아니라, 외부에서 읽어들이는 item 기획 문서에 의존하는 식입니다.

아이펀 엔진에서는 이와 같은 경우를 위해서 게임 데이터들을 읽어들이는 ResourceManager 라는 인터페이스를 제공합니다. 해당 클래스의 자세한 정보는 여기에서 에서 확인하실 수 있습니다.

기획 데이터 읽어들이기

기획 데이터가 JSON 파일, 항목이 탭으로 구분된 파일, 그리고 MySQL DB 에 존재하는 경우를 지원합니다.

기획 데이터가 JSON 파일로 된 경우

MANIFEST.json 에 다음과 같이 JSON 파일들이 위치할 디렉토리 이름을 기재합니다.

"ResourceManager": {
  "game_json_data_dir": "game_data"
}

아이펀 엔진은 game_json_data_dir 디렉토리 안의 JSON 파일들을 읽어들이게 됩니다.

Important

이때 각 파일의 확장자는 대소문자 구분없이, json, txt, 혹은 text 중 하나여야 합니다.

Tip

game_json_data_dir 은 게임 서버가 패키징될 때 같이 포함되는 것이 자연스럽습니다. 그렇기 때문에, MANIFEST.json 뿐만 아니라 CMakeLists.txt 의 RESOURCE_DIRS 에도 나열하는 것이 좋습니다. 자세한 내용은 패키지에 게임 리소스 파일들 포함하기 를 참고하세요.

기획 데이터가 탭구분자 파일로 된 경우

JSON 파일과 유사하게 각 줄이 tab 으로 구분된 데이터 파일을 읽을 수 있습니다. game_json_data_dir 대신 game_tab_separated_data_dir 을 사용하는 것을 빼고는 MANIFEST.json 설정은 동일합니다.

파일 조건

이때 파일은 다음과 같은 조건을 만족해야됩니다.

인코딩 및 확장자
  • 파일은 ascii 이거나 utf-8 이어야 하며, utf-8 인 경우 BOM 을 포함한다.

  • 파일의 확장자는 .txt 이나 .text 여야 한다.

코멘트
  • // 나 # 로 시작하는 줄은 코멘트이다.

헤더
  • 코멘트를 제외한 첫 줄은 header 에 해당하며, tab 으로 구분하여 field 이름들을 나열한다.

데이터
  • Header 이후의 줄들은 data 에 해당하며, tab 으로 구분된 field 들이 나열된다.

  • header 의 field 개수와 data 의 field 개수는 같아야 한다.

  • 각 필드에서 앞뒤 공백은 모두 제거된다.

  • 만일 공백을 제거한 data field 가 null string 이라면 해당 field 는 null JSON object 가 된다.

  • null 이 아닌 field 는 정수, 실수, 문자열 형태가 될 수 있다.

JSON 데이터로의 변환

위와 같은 구조의 파일은 다음과 같은 규칙대로 JSON 데이터로 변환됩니다.

  • 각각의 줄은 {“header1”: “data1”, “header2”: “data2”} 형태가 된다.

  • 이렇게 변환된 줄 단위 내용은 배열의 원소가 된다.

JSON 으로 변환되기 때문에 게임내에서 사용할 때는 ResourceManager::GetJsonData(…) 함수를 이용합니다.

예제: 탭 구분자 파일의 JSON 변환

예를 들어 Item.txt 가 다음과 같다면, 최종 JSON 은 그 아래와 같습니다.

// comment
// comment
Index   Name      Price   Description
0       힐링 포션   100.0    힐링해주는 포션. 힐링힐링~
1       단검        50.0    가장 기본이 되는 검.
[
  {
    "Index": 0,
    "Name": "힐링포션",
    "Price": 100.0,
    "Description": "힐링해주는 포션. 힐링힐링~"
  },
  {
    "Index": 1,
    "Name": "단검",
    "Price": 50.0,
    "Description": "가장 기본이 되는 검."
  }
]

연속되는 탭 구분자에 대한 처리

만일 탭구분자가 연속으로 이루어질 경우, 탭 구분자 사이에 빈 값이 있는 것으로 취급할 수도 있고 아니면 연속되는 구분자를 무시하고 하나의 구분자처럼 취급하고 싶을 경우도 있을 겁니다. 이런 동작 방식을 제어하기 위해서는 game_compress_repeating_separators 라는 값을 MANIFEST.json 에 기재합니다. Boolean 값으로 true 혹은 false 가 가능합니다.

기획 데이터가 콤마 구분자 파일 (CSV 파일)로 된 경우

기획 데이터가 CSV 파일인 경우는 데이터 항목의 구분자가 탭 대신 콤마라는 것을 제외하고는 앞에서 설명한 탭 구분자로 이루어진 경우와 동일한 방식으로 동작합니다. MANIFEST.json 설정 파일에서는 game_tab_separated_data_dir 대신 game_comma_separated_data_dir 을 사용합니다. 읽어들인 파일을 JSON 형태로 접근하는 것 역시 동일합니다.

기획 데이터가 DB 테이블 형태로 존재할 때

MANIFEST.json 설정

MANIFEST.json 를 다음과 같이 설정합니다.

"ResourceManager": {
  "enable_game_data_mysql": true,
  "game_data_mysql_server": "tcp://localhost:3306",
  "game_data_mysql_username": "funapi",
  "game_data_mysql_password": "funapi",
  "game_data_mysql_database": "funapi",
  "game_data_mysql_tables": "game_data_table1,game_data_table2"
}

기술하는 내용은 DB 접속 정보와 읽어들일 DB table 의 이름들입니다. 읽어들일 table 이름들은 game_data_mysql_tables 에 기술하며, 콤마(,) 로 구분하여 여러 테이블을 나열할 수 있습니다.

JSON 데이터로의 변환

각 table 은 다음의 규칙대로 JSON 문서로 변환됩니다.

  • Table 이름이 JSON 문서의 파일명이 된다.

  • 각 row 는 {“column1”: “data1”, “column2”: “data2”} 형태로 JSON object 로 변환된다.

  • 이렇게 변환된 줄 단위 내용은 배열의 원소가 된다.

값을 읽는 방법은 ResourceManager::GetJsonData(TABLE_NAME) 를 이용합니다.

예제: DB 에 있는 기획 데이터의 JSON 변환

예를 들어 Item 이라는 DB table 이 다음과 같다면, 최종 JSON 은 그 아래와 같습니다.

mysql> select * from Item;
+-------+----------+-------+----------------------------+
| Index | Name     | Price | Description                |
+------------------+-------+----------------------------+
| 0     | 힐링 포션| 100.0 | 힐링해주는 포션. 힐링힐링~ |
| 1     | 단검     | 50.0  | 가장 기본이 되는 검.       |
+------------------+-------+----------------------------+
2 rows in set (0.00 sec)
[
  {
    "Index": 0,
    "Name": "힐링포션",
    "Price": 100.0,
    "Description": "힐링해주는 포션. 힐링힐링~"
  },
  {
    "Index": 1,
    "Name": "단검",
    "Price": 50.0,
    "Description": "가장 기본이 되는 검."
  }
]

지원하는 데이터 타입 목록

기획 데이터를 DB 테이블에서 읽을 경우 지원하는 데이터 타입 목록은 아래와 같습니다. 읽어들인 기획 데이터의 각 타입의 값은 아래 표의 JSON 인터페이스를 사용하여 읽을 수 있습니다.

타입

JSON 인터페이스

BIT

GetInteger()

TINYINT

SMALLINT

MEDIUMINT

INT

BIGINT

DECIMAL

FLOAT

GetDouble()

DOUBLE

CHAR

GetString()

BINARY

VARCHAR

VARBINARY

DATE

TIME

DATETIME

TIMESTAMP

목록 이외의 타입은 지원하지 않습니다.

기획 데이터 사용하기

일단 읽어들인 JSON data 는 ResourceManager::GetJsonData(…) 를 통해 접근할 수 있습니다. 위의 예에서, 만일 Item.json 이라는 파일이 있다면, 이 JSON 데이터는 다음과 같이 호출할 수 있습니다.

Ptr<const Json> items = ResourceManager::GetJsonData("Item.json");
BOOST_ASSERT(items && items->IsArray());

for (size_t i = 0; i < items->Size(); ++i) {
  const Json &item = (*items)[i];
  LOG(INFO) << "item name: " << item["Name"].GetString();
  LOG(INFO) << "item price: " << item["Price"].GetDouble();
}
JToken items = ResourceManager.GetJsonData ("Item.json");
Log.Assert (items != null && items.Type == JTokenType.Array);

foreach (JObject item in items) {
  Log.Info ("item name: {0}", item["Name"].Value<string>());
  Log.Info ("item price: {0}", item["Price"].Value<double>());
}

기획 데이터 인덱싱하기

테이블 형태로 읽어들인 게임 데이터는 키 필드로 쉽게 접근할 수 있게 인덱싱되어 관리하는 것이 편리합니다. iFun Engine 에서는 이를 위해 ResourceManager::IndexJsonArray(…) 라는 함수를 제공합니다.

Ptr<const IndexedJsonData> indexed_item_table;

bool MyGameServer::Install(const ArgumentMap &arguments) {
   ...
   Ptr<const Json> json = ResourceManager::GetJsonData("Item.json");
   indexed_item_table = ResourceManager::IndexJsonArray(*json, "Index");
   BOOST_ASSERT(indexed_item_table);
}

void OnItemUse(const Ptr<Session> &session, const Ptr<Json> &message) {
   ...
   int item_index = ...

   IndexedJsonData::const_iterator it = indexed_item_table->find(item_index);
   BOOST_ASSERT(it != indexed->end());
   const Json &item_info = *(it->second);

   int price = item_info["Price"].GetInteger();
   ...
}
public class Server
{
  Dictionary<int, JObject> the_indexed_item_table = null;

  public static void Install(ArgumentMap arguments)
  {
    ...
    JToken json = ResourceManager.GetJsonData ("data.json");

    the_indexed_item_table = ResourceManager.IndexJsonArray (
        (JArray) json, "Index");

    Log.Assert (indexed_item_table != null);
  }

  void OnItemUse(Session session, JObject message)
  {
    ...
    int item_index = ...
    JObject item_info = null;
    Log.Assert (the_indexed_item_table.TryGetValue(item_index, out item_info));

    int price = (int) data ["Price"];
    ...
  }
}

기획 데이터 관리 설정 파라미터

  • game_json_data_dir: JSON 형태로 된 게임 기획 데이터가 있는 디렉토리 경로. (type=string, default=””)

  • game_tab_separated_data_dir: Tab 구분자로 구분된 게임 기획 데이터가 있는 데릭토리 경로. (type=string, default=””)

  • game_comma_separated_data_dir: Tab 구분자로 구분된 게임 기획 데이터가 있는 데릭토리 경로. (type=string, default=””)

  • game_compress_repeating_separators: 탭 구분자 파일이나 콤마 구분자 파일의 경우 연속되는 구분자를 하나로 취급할지 여부를 지정합니다. (type=bool, default=false)

  • enable_game_data_mysql: 게임 기획 데이터를 담고 있는 MySQL 서버 연결 정보. (type=bool, default=false)

  • game_data_mysql_server: 게임 기획 데이터를 담고 있는 MySQL 서버 연결 정보. (type=string, default=”localhost:3306”)

  • game_data_mysql_username: 게임 기획데이터를 담고 있는 MySQL 서버의 유저명. (type=string, default=”funapi”)

  • game_data_mysql_password: 게임 기획데이터를 담고 있는 MySQL 서버의 비밀번호. (type=string, default=”funapi”)

  • game_data_mysql_database: 게임 기획 데이터를 담고 있는 MySQL 데이터베이스 이름. (type=string, default=”game_data”)

  • game_data_mysql_character_set: 게임 기획 데이터 DB 와의 연결에 사용될 character set (type=string, default=”utf8”)

  • game_data_mysql_tables: 게임 기획데이터에 해당되는 MySQL 테이블들 이름. 콤마로 구분됨. (type=string)