안녕하세요, 데브시스터즈 스튜디오 킹덤에서 전투 클라이언트 프로그래머로 일하고 있는 김용욱이라고 합니다. 게임 데이터를 JSON 형태로 엑셀에 수기 입력할 때, 오타나 잘못 입력된 값을 런타임 실행 전에 발견하기 어렵다는 문제가 있었습니다. 이 문제를 해결하기 위해 JSON Schema를 이용해 JSON 데이터를 자동 검증하게 되었는데, 그 과정에서 JSON Schema를 역직렬화되는 게임 데이터 클래스로부터 자동 생성하고 Contract Resolver를 이용하거나 JSON Schema 자체를 후처리하여 이용했습니다. 또한 텍스트 에디터인 Visual Studio Code와 연동하여 자동완성과 실시간 검증이 가능한 시스템을 만들었고, 그 결과 생산성을 높이고 오류를 줄일 수 있었습니다.
이 글에서는 (1) 왜 JSON으로 데이터를 입력하는지 (2) JSON Schema의 필요성 (3) Schema 생성 및 커스터마이징 방법 (4) VS Code 연동 활용까지 순서대로 다룹니다.
#JSON으로 데이터를 입력하는 이유
게임 개발을 할 때, 많이 쓰이는 패턴이 “로직을 데이터로 작성하는” 것입니다. 예를 들어
민트사탕맛 쿠키가 스킬을 사용하면, 스킬 사용 직후 아군에게 공격력 증가, 방어력 증가 버프를 부여하고, 0.5초 뒤에 가로 세로 4 범위의 적군에게 공격력 비례 피해를 입힌다. 1.5초 뒤에 가장 체력이 낮은 아군에게 최대 체력 비례 회복을 부여한다.
는 스킬이 있다고 하면, 이를 코드에서 모두 처리할 수도 있지만 아래와 같이 데이터로 표현할 수도 있습니다. (설명을 위해 만든 가상의 데이터 구조입니다)
| SkillId | SkillType | SkillValues | Timing | Target |
|---|---|---|---|---|
| MintCandy-001 | Add_Buff | AttackPoint, 0.2, DefencePoint, 0.2 | 0 | Allies |
| MintCandy-002 | Attack | AttackPoint, 0.5 | 0.5 | Enemies, Rectangle, 4x4 |
| MintCandy-003 | Heal | AttackPoint, 0.25 | 1.5 | Allies, LowestHP, 1 |
데이터 형식으로 로직을 분 리할 경우, 기획자가 데이터만 수정해 가면서 빠른 이터레이션이 가능하다는 장점이 있으며, 데이터 수정만으로 버그 수정이나 콘텐츠 업데이트를 진행 가능하다는 장점이 있습니다. 테이블 형식을 이용할 경우 기획자에게 익숙한 인터페이스인 스프레드시트를 활용할 수 있고, 데이터 역직렬화(텍스트 혹은 바이너리 형태로 된 데이터 포맷에서 코드에서 활용 가능한 데이터를 생성하는 것을 말합니다)도 용이하다는 장점이 있습니다.
이런 테이블 형식의 데이터는, 아래와 같은 경우에 적합하다고 생각합니다.
- 순서가 없거나, 시계열에 따라 순서대로 발생하는 경우
- 입력해야 할 값의 종류가 적고 그 중 전체 혹은 대다수를 입력해야 하는 경우
- 다수의 데이터를 한눈에 살펴보면서 값을 정해야 하는 경우
하지만 가상의 예시로 그 뒤에 다음과 같은 기능을 만들게 되었다고 합시다.
민트사탕맛 쿠키가 마법사탕을 착용하면, 스킬이 다음과 같이 바뀐다. 게임 시작 시 민트 스택을 1개(최대 10스택) 가지고 시작하며, 스킬을 사용하거나 아군이 전투불능이 되면 스택을 1개 획득한다. 스킬을 사용하면 기존 스킬에 더해, 현재 민트 스택이 5개 이상이면 아군 전체에게 치명타 증가 버프를 부여한다. 1.5초 시점에, 민트 스택이 10개 이상이면 민트 스택을 모두 소진하고 적군 전체에게 민트 중독(전용 아이콘과 이펙트가 있음)을 부여한다. 민트 중독을 가지고 있다면 일정 시간마다 피해를 입으며, 대상이 스킬을 사용할 때마다 해로운 효과 증가 디버프와 큰 피해를 입는다.
- 그 자체로는 아무 역할도 하지 않지만, 전투 중 특정 조건에 따라 증감해야 하는 “스택” 개념이 생겼습니다.
- 다른 경우에는 쓰이지 않지만 특정한 경우에만 쓰이는 “최대 스택”, “전용 아이콘”이나 “이펙트” 를 입력할 수 있어야 합니다.
- 반복문, 분기처리 등 상대적으로 복잡한 상태를 관리해야 합니다.
이를 테이블 형태로만 입력하자니, 선택적으로 입력해야 하는 필드의 양이 너무 많아지고 가독성이 떨어집니다. 코드로 로직과 상태 관리를 작성할 수도 있겠지만, 로직을 데이터로 옮겼을 때의 장점을 유지하고 싶을 때가 있습니다. 이런 경우 JSON 형태로 데이터를 입력하고, 그 데이터를 런타임에 역직렬화하여 사용하는 것을 고려할 수 있습니다.
JSON 포맷은 값에 숫자나 문자열, 배열이나 다른 JSON 객체를 담을 수 있어 유연한 적용이 가능하며 그러면서 가독성이 좋은 편이므로 수제로 작성하는 식으로도 활용할 수 있습니다. “민트 스택이 10스택 이상이 되면 다른 행동을 한다”는 동작을 JSON 포맷으로 표현하자면 아래와 같은 형태가 될 것입니다(마찬가지로 설명을 위한 가상의 데이터입니다).
{
"type": "Check",
"version": 1,
"check": [
{
"condition": "Stack",
"target": "MintStack",
"operator": ">=",
"value": 10,
"then": [
"FirstAction",
"SecondAction"
]
}
]
}다음과 같은 조건에서
- 시계열이 아니라, 특정 조건에 따라 복잡한 분기처리를 해야 하는 경우
- 입력해야 할 값의 종류가 많지만 실제로는 개중 일부만 입력해서 이용해야 하는 경우
- 상대적으로 소수의 데이터를 복잡하게 설정하는 경우
JSON 형태로 값을 입력하는 것을 고려해볼 수 있습니다.
#JSON Schema의 필요성
JSON Schema를 도입하기 전에는, 위 전투 시스템을 구현하는 각 필드를 기획자가 수동으로 입력하고 간단한 포매팅만 거쳐 Microsoft Excel에 입력하여 연동을 하고 있었습니다. JSON 포맷은 손으로 직접 작성할 수 있을 만큼 간단한 편이기 때문에 가능한 일입니다.
그러나 JSON 포맷의 텍스트를 메모장이나 Microsoft Excel에 직접 입력하기에는 상당히 번거롭습니다. 또한 쿠키런: 킹덤에서는 JSON 포맷을 이용할 때 유니티 내장 JSON 파서를 이용하여 역직렬화를 하고 있습니다. 속도가 빠르고 별도의 외부 라이브러리를 사용할 필요가 없다는 장점이 있지만, 아래와 같은 단점도 있습니다.
- JSON 포맷에 맞기만 하면 데이터에 잘못된 값이 들어가더라도 최대한 Graceful하게 살리려 한다.
- 예를 들어 존재하지 않는 필드를 입력하더라도, 조용히 무시한다.
- 존재하는 필드의 타입을 잘못 입력하더라도, 혹은 잘못된 값을 입력하더라도 조용히 무시한다.
그 때문에
- 테이블 형식에 비해 오히려 입력이 어려워진다.
- 잘못 입력하더라도 어디가 잘못되었는지 확인하기 어렵다.
- 잘못된 필드에 값을 입력하고, 어디가 문제인지 찾는데 오래 걸린다.
라는 문제가 생깁니다.
이럴 때 사용할 수 있는 것이 JSON Schema입니다. 스키마(Schema)란 특정한 데이터가 가져야 할 구조나 제약조건을 말하는 것으로 JSON Schema는 JSON 타입의 데이터를 정의하고 제약하는 문서라고 할 수 있습니다.
예를 들어 JSON 데이터에 아래와 같은 제약 조건을 설정하고 싶을 때
- “key” 필드는 문자열이어야 하며 필수적으로 존재해야 한다.
- “id” 필드는 정수여야 한다.
- 위 필드 외에 다른 값을 입력하면 안 된다.
다음과 같은 JSON Schema를 이용할 수 있습니다.
{
"type": "object",
"properties": {
"key": {
"type": "string"
},
"id": {
"type": "integer"
},
},
"additionalProperties": false,
"required": ["key"]
}JSON Schema는 JSON 포맷을 검증한다는 그 자체의 기능 외에도, 아래와 같은 장점이 있습니다.
- 그 자체로도 JSON 파일이기 때문에 가독성이 좋 고 다양한 프로그램에서 로드와 수정이 쉽다.
- 특정 값이 포함되어야 하는지, JSON Schema에서 정의한 키값 외에 다른 값들이 존재해도 되는지와 같은 메타적 요소도 검사 가능하다.
- 복잡한 조건도 원한다면 검증 가능하다. (JSON Schema에는 각 JSON 필드에 사용 가능한 다양한 조건들이 정의되어 있습니다.)
- 사용 환경에 독립적으로, JSON과 JSON Schema를 지원하기만 한다면 검증을 할 수 있다.
C#에서는 Json.NET 라이브러리를 이용해 JSON 포맷을 다룰 수 있으며 유니티 최신버전에 포함되어 있어 바로 이용 가능합니다. Json.NET에서 스키마 검사를 하는 부분은 별도의 유료 라이브러리로 분리되어 있지만, 기본적인 스키마 검사 기능은 오픈소스 Json.NET에 포함되어 있습니다. 현재 JSON 형태로 데이터를 입력하는 곳이 소수이기 때문에, 별도의 라이브러리 구입 및 임포트를 하는 대신 Json.NET의 내장 스키마 검증 기능을 활용하기로 하였습니다.
#JSON Schema 생성하고 활용하기
#스키마 작성하고 검증하기
스키마를 손 으로 직접 작성하는 것은, 위 예시와 같이 그렇게 어렵지 않습니다. 이렇게 작성한 스키마를 텍스트 파일에서 읽어와, 검증하는 것도 간단하게 가능합니다. 어려운 부분은, 우리 목적에 맞는 올바른 스키마를 생성하는 것입니다.
// <https://www.newtonsoft.com/JSON/help/html/JTokenIsValidWithMessages.htm>
// 적절한 곳(이 경우는 C:\\schema.JSON)에서 읽어 온다.
JSONSchema schema = JSONSchema.Parse(File.ReadAllText(@"C:\\schema.json"));
// JSON string을 파싱한다.
JObject person = JObject.Parse(@"{
'name': null,
'hobbies': ['Invalid content', 0.123456789]
}");
IList<string> messages;
// 검증한다.
bool valid = person.IsValid(schema, out messages);
Console.WriteLine(valid);
// false
// 에러 메시지가 나온다.
foreach (string message in messages)
{
Console.WriteLine(message);
}#스키마 자동 생성하기
JSON Schema를 손으로 작성하는 것은 필드가 한 두 개 정도 있을 때 가능한 일이며, 그보다는 코드에서 자동으로 생성해주는 것이 바람직합니다. Json.NET에서는 JSONSchemaGenerator 클래스를 이용하여 특정 클래스 타입으로부터 스키마를 생성할 수 있습니다.
var generator = new JSONSchemaGenerator();
var schema = generator.Generate(type, new JSONSchemaResolver());실제 예시를 통해 설명하겠습니다. 아래와 같은 클래스가 있다고 합시다. JSON 값은 이 클래스로 역직렬화되고, 이후 Initialize()가 불린 뒤 활용됩니다. 클래스는 다음과 같습니다.
[Serializable]
public class CookieData
{
[Serializable]
public enum CookieType
{
Normal = 0,
Uncommon = 1,
Bright = 2,
Crispy = 3,
}
public int Id => id;
public CookieType TypeOfCookie => typeOfCookie;
public Vector2 Position => position;
[SerializeField]
private int id;
[SerializeField]
private string type;
private CookieType typeOfCookie;
[SerializeField]
private float[] positionValue;
private Vector2 position;
public void Initialize()
{
typeOfCookie = Enum.Parse<CookieType>(type, false);
position = new Vector2(positionValue[0], positionValue[1]);
}
}아래는 위 클래스로 역직렬화 가능한 JSON 예시 데이터입니다.
{
"type": "Crispy",
"id": 10000,
"positionValue": [
2,
3
]
}이 클래스에서 그대로 JSON Schema를 아무 처리 없이 JSONSchemaGenerator() 로 생성하면 다음과 같이 됩니다.
{
"id": "CookieData",
"type": [
"object",
"null"
],
"properties": {
"Id": {
"required": true,
"type": "integer"
},
"TypeOfCookie": {
"required": true,
"type": "integer",
"enum": [
0,
1,
2,
3
]
},
"Position": {
"id": "UnityEngine.Vector2",
"required": true,
"type": [
"object",
"null"
],
"additionalProperties": false,
"properties": {
"x": {
"required": true,
"type": "number"
},
"y": {
"required": true,
"type": "number"
},
"normalized": {
"$ref": "#/properties/Position"
},
"magnitude": {
"required": true,
"type": "number"
},
"sqrMagnitude": {
"required": true,
"type": "number"
}
}
}
}
}검증을 해 봅시다.
Required properties are missing from object: Id, TypeOfCookie, Position.이상하게도 제대로 된 JSON 데이터를 오류라고 인식합니다. JSON Schema가 잘못된 것 같습니다. 대표적으로,
- 우리가 입력할 때 이용하는 키값은
camelCase인데, 키값이PascalCase에 대해 생성되었습니다. - 역직렬화에 사용할 수 없는 퍼블릭 프로퍼티에 대해서 키값이 생성되었습니다.
typeOfCookie는 Enum 타입인데, 불편하게 숫자를 직접 입력하도록 하고 있습니다.
이럴 경우, 스키마를 생성하는 Generator를 직접 수정하는 대신 크게 두 가지 방법으로 생성되는 스키마를 커스터마이징할 수 있습니다.
Contract Resolver: 역직렬화 대상이 되는 클래스에서 JSON Schema를 생성할 때, 어떤 값에 대해 어떻게 동작해야 하는지를 재정의합니다. 예를 들어 데이터의 키값이 클래스의 어떤 값과 매칭되어야 하는지, 키값의 데이터 타입이 어떤 것이 되어야 하는지 역직렬화되는 클래스를 읽어들이며 수정합니다.- JSON Schema 후처리: JSON Schema도 결국 JSON 형태의 데이터이므로, 원본 데이터와 각 필드의 타입을 가지고 Schema를 재정의할 수 있습니다.
#1. Contract Resolver 튜닝하기: 입력해야 하는 값을 가져오는 문제
기본적으로 JSONSchemaGenerator는 해당 타입의 퍼블릭 필드와 프로퍼티가 직렬화된다고 가정하며, 퍼블릭 프로퍼티의 값에서 JSON 오브젝트를 생성합니다. 또한 모든 값들을 필수적으로 입력해야 한다 고 간주합니다. 그런데 우리는 [SerializeField] 어트리뷰트가 붙은 (camelCase인) private 변수 이름을 가져오고, 타입은 이름이 일치하는 퍼블릭 프로퍼티를 따르게 하고 싶습니다. 그리고 대부분의 경우는 필수로 입력할 필요는 없고, 대신 다른 값을 잘못 입력하는 것을 막고 싶습니다.
첫 번째 문제부터 해결합시다. 간단히는 퍼블릭 프로퍼티의 이름이 private 변수와 대응하도록 수정해 주는 것입니다. 예를 들어 public SomeNumber ⇒ someNumberProperty.Value 혹은 public SomeClass ⇒ someClass 과 같이 지정해주는 것으로 해결이 가능합니다.
그러나 레거시 데이터 중에서 데이터의 키값 이름을 수정하기 어려운 경우가 있습니다. 이럴 때 Contract resolver를 커스터마이징하여 클래스가 있을 때 어떤 식으로 값을 인식할지를 튜닝할 수 있습니다.
위에서도 말했듯이, Contract Resolver는, 각 변수를 읽어들일 때 그 변수에 해당하는 JSON 값이 어떻게 매핑되어야 하는지 결정하는 클래스입니다. 예를 들어서, 해당 변수가 int 타입이라면 정수로 매핑이 되고, string 타입이면 문자열로 매핑이 되는 식입니다. 타입과 함께 다른 세부 정보들도 읽어들일 수 있습니다.
Contract Resolver를 오버라이드하여 커스터마이징을 해 줍시다. 커스터마이징의 예시로, 전용 어트리뷰트를 하나 만들어, 이 어트리뷰트에서 정보를 가져오게 할 수 있습니다. 예를 들어, 명명 규칙이 대응하지 않는 경우 해당 프로퍼티에 어트리뷰트 [GenerateSchemaAs(”someOtherName”)]를 붙인 뒤 Contract Resolver 단계에서 인식하는 식으로 대응이 가능합니다. 타입이 대응하지 않는 경우(입력은 double[]로, 출력은 Vector3으로 하는 경우 등)는 어트리뷰트 [GenerateSchemaAs(typeof(OtherType))] 식으로 하여 OtherType을 타입으로 가져오게 할 수 있습니다.
이 Contract Resolver가 값을 읽을 수 있게 클래스를 수정해주면 다음과 같이 됩니다.
[Serializable]
public class CookieData
{
[Serializable]
public enum CookieType
{
Normal = 0,
Uncommon = 1,
Bright = 2,
Crispy = 3,
}
public int Id => id;
public CookieType Type => typeOfCookie; // 명명 규칙이 대응하지 않는 경우
[GenerateSchemaAs(typeof(float[]), "positionValue")] // 명명 규칙과 타입이 모두 불일치하는 경우
public Vector2 Position => position;
[SerializeField]
private int id;
[SerializeField]
private string type;
private CookieType typeOfCookie;
[SerializeField]
private float[] positionValue;
private Vector2 position;
public void Initialize()
{
typeOfCookie = Enum.Parse<CookieType>(type, false);
position = new Vector2(positionValue[0], positionValue[1]);
}
}이제 JSON Schema는 다음과 같이 변경되었습니다.
{
"id": "CookieData",
"type": [
"object",
"null"
],
"properties": {
"Id": {
"required": true,
"type": "integer"
},
"type": {
"required": true,
"type": "integer",
"enum": [
0,
1,
2,
3
]
},
"positionValue": {
"id": "System.Float[]",
"required": true,
"type": [
"array",
"null"
],
"items": {
"type": "number"
}
}
}
}하지만 아직 enum 등에 대해 제대로 대응이 되어 있지 않으므로, Contract Resolver만을 이용해 수정이 불가능한 경우 커스텀 Contract Resolver를 이용해 생성한 JSON Schema에 대해 추가적으로 후처리를 해 주어야 합니다.
#2. JSON Schema 후처리하기: Enum as String
전투 데이터에서 열거형(Enum) 타입을 입력할 때는 string으로 입력하고, 이 값을 나중에 파싱을 해 집어 넣고 있습니다. 그런데 내장된 Generator에서는 열거형 타입을 숫자로 대응하고 있었습니다. 열거형 데이터를 숫자로 입력하는 것은 의도한 것도 아니고, 실제로 입력하는 것도 직관적이지 않습니다. Schema Generator 자체를 커스터마이징하는 방법도 생각할 수 있지만, 외부 라이브러리에 존재하는 Deprecated된 클래스를 직접 수정하는 것은 배보다 배꼽이 더 큰 일이라고 생각했습니다.
그래서 Enum이 숫자로 매핑된 스키마를 가져와, 원본 타입 데이터를 이용해 그 타입이 다시 string 타입에 매핑되도록 후처리를 해주었습니다. 스키마를 재귀적으로 탐색하면서 Enum을 만나면 해당 Enum의 문자열을 가져와, 숫자 대신 그 문자열만 입력이 가능하도록 제한해주면 됩니다.
JSON Schema를 후처리하면서 다른 문제도 해결해봅시다. JSON Schema에서는 각 값의 메타데이터를 지정할 수 있습니다. 각 object나 array, enum 등에 additionalProperties = false를 추가하고(정의되지 않은 불필요한 값을 입력한 경우 에러가 발생함), 불필요한 required는 Null로 설정해 제거해줍시다(일부 값을 미입력하더라도 에러로 취급하지 않음).
이렇게 후처리를 하게 되면 결과적으로 아래와 같은 스키마가 생성됩니다.
{
"id": "CookieData",
"type": [
"object",
"null"
],
"additionalProperties": false,
"properties": {
"id": {
"type": "integer"
},
"type": {
"type": "string",
"enum": [
"Normal",
"Uncommon",
"Bright",
"Crispy"
]
},
"positionValue": {
"id": "System.Single[]",
"type": [
"array",
"null"
],
"items": {
"type": "number"
}
}
}
}#JSON 데이터를 이용하는 방법
일단 JSON Schema를 이용해 전투 데이터를 제대로 입력했는지 검증할 수 있습니다. 쿠키런: 킹덤에서는 매 전투 테스트마다 별도의 스레드에서 JSON Schema 검증을 실행하고, 오류가 존재하는 경우 유 니티 로그로 출력해주는 방식을 이용하고 있습니다.
여기서 데이터를 검증하는 데서 그치지 않고, 데이터를 만들 때 Visual Studio Code의 JSON Schema 지원을 활용할 수 있습니다. Visual Studio Code는 마이크로소프트에서 만든 범용 텍스트 에디터로, 다양한 포맷을 지원하는 것이 특징 중 하나입니다. VS Code에서는 JSON Schema를 읽어 오고, 이를 이용해 원하는 JSON 파일에 대해 자동완성 및 값 검증을 할 수 있습니다.
이때 기획자나 QA도 별도의 클라이언트 조작 없이 편리하게 JSON 데이터를 입력할 수 있도록 일종의 “올인원 패키지”를 구성해 배포해주었습니다. 필드나 Enum의 값이 추가되는 등 데이터 구조가 바뀌면, JSON Schema를 사용자가 정의한 폴더로 내보내고, 업데이트합니다. 아래와 같이 정의하면, 특정 폴더 내의 모든 JSON 파일에 대해 해당 경로의 JSON Schema로 검증합니다. 이때 git 저장소 형태로 배포를 한다고 하면, 폴더의 하위 경로를 .gitignore 파일에 추가해주면 각 사용자는 원하는 대로 폴더를 만들고 하위에 JSON 파일을 생성하여 콘텐츠 작업에 활용할 수 있습니다. 이제 이 폴더와 JSON Schema, .gitignore를 합쳐서 git 저장소나 그 일부로 배포해주면 됩니다. 이때, settings.json 파일 내의 주석은 제거해야 합니다.
# 폴더 내 구성 예시
- .vscode/settings.json
- .gitignore
- json-schema.json
- SandBox/
- README.md
# .gitignore의 예시
!.vscode/* # VS Code 워크스페이스 설정은 동기화되어야 함
SandBox/**/*.json # SandBox 폴더 내의 모든 JSON 파일을 Git에서 무시함
# settings.json의 예시
{
"[Json]": {
"editor.tabSize": 2 # 탭 사이즈를 통일해줍니다
},
"editor.unicodeHighlight.nonBasicASCII": false, # 한글이 입력된 것을 에러로 판단하지 않게 합니다
"json.schemas": [
{
# SandBox 폴더 내의 모든 JSON 파일에 대해 해당 스키마를 적용합니다 (자기 자신 제외)
"fileMatch": ["SandBox/**/*.json", "!json-schema.json"],
"url": "./json-schema.json"
}
]
}결과적으로, 자동완성을 이용하여 빠르고 정확하게 콘텐츠 추가가 가능해집니다. 이미 입력한 JSON 데이터에서 어떤 문법적 문제가 있는지도 쉽게 체크할 수 있습니다. 필요한 경우, 자주 쓰이는 구문은 Code Snippet으로 정의하여 상용구와 같이 입력하는 것도 가능합니다.
#결론
게임 개발을 진행하다 보면 JSON 포맷으로 데이터를 손수 입력하고 이를 역직렬화하여 사용해야 할 때가 있습니다. 이럴 때 역직렬화 대상이 되는 클래스에서 JSON Schema를 추출하고, 이를 이용해 수제 데이터를 검증할 수 있었습니다. JSON Schema가 생성되는 방식을 튜닝하고 후처리를 해주어 우리가 원하는 대로 JSON Schema를 정의할 수 있었고, 이를 바탕으로 Visual Studio Code와 연동하여 콘텐츠 작성의 효율을 극대화할 수 있었습니다.
해당 작업은 2023년부터 쿠키 캐릭터 및 여타 전투 콘텐츠의 규모와 복잡도가 높아지는 것에 발맞추어 진행하였으며 이후 수 차례의 고도화를 거쳐 현재 클라이언트 프로그래머와 기획자, QA 담당자가 모두 사용하고 있는 시스템으로 발전시켰습니다. 이 작업을 통해 개개인의 생산성을 높인 것은 물론 전투 콘텐츠에서 실수와 오류 발생을 줄일 수 있어서 보람이 있는 작업이었습니다.
앞으로도 JSON Schema 시스템과 같은 소소한 개선과 함께 플레이어 여러분께 즐거운 경험을 선사할 수 있도록 노력해 나가겠습니다. 감사합니다.

