Tiled Map Editor が吐き出せるデータ形式はデフォルトはXMLですが、今時XMLなんか使っている人なんて居るんですかね?JSON形式で吐き出せるので、JSON形式で吐き出したデータを構造体に変換します。
なお、JSONならダイレクトに読んで使っても良いかもしれませんが、C言語でJSONを読むのは少々面倒くさいので、JSONをバイナリ構造体に変換するCLIを(Cで書くのは面倒なのでC++11で)作ってみました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// make stage data from tiled map editor data (JSON) | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <iostream> | |
#include <string> | |
#include <string> | |
#include <fstream> | |
#include <streambuf> | |
#include "json11/json11.hpp" | |
// JSONから変換する構造体 | |
// - ゲーム本体はCで書くのでバイナリ構造体にしてDSLOTに突っ込む | |
// - 固定長にしている | |
// - ゲームが起動後にメモリの確保・開放はしない主義だという理由もあるが、結構余裕を持たせても 合計約24KB程度 の小さなデータなので、これなら動的メモリ確保にするデメリットの方が目立つ | |
struct StageEvent { | |
short x; /* x position */ | |
short y; /* y position */ | |
short type; /* type of event */ | |
short cnum; /* control-number of event */ | |
short arg1; /* argument 1 of event */ | |
short arg2; /* argument 2 of event */ | |
short arg3; /* argument 3 of event */ | |
short arg4; /* argument 4 of event */ | |
}; | |
struct StageData { | |
int width; /* width of map */ | |
int height; /* height of map */ | |
int gslot; /* number of GSLOT */ | |
int events; /* number of events */ | |
unsigned char data[8192]; /* map (max: 8KB) */ | |
struct StageEvent event[1024]; /* event (max: 16KB) */ | |
}; | |
// テキストファイルを std::string形式 で読み込む(手抜き版) | |
std::string load_text_file(const char* filename) { | |
std::ifstream t(filename); | |
std::string str; | |
t.seekg(0, std::ios::end); | |
str.reserve(t.tellg()); | |
t.seekg(0, std::ios::beg); | |
str.assign((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>()); | |
return str; | |
} | |
int main(int argc, char* argv[]) | |
{ | |
struct StageData stg; | |
::memset(&stg ,0, sizeof(StageData)); | |
// 引数チェック | |
if (argc < 3) { | |
fprintf(stderr, "usage: mkstg source-json output-data\n"); | |
return 1; | |
} | |
// tiled map editor が出力する JSONファイル を読み込む | |
auto jsonString = load_text_file(argv[1]); | |
std::string err; | |
auto json = json11::Json::parse(jsonString, err); | |
if (!err.empty()) { | |
fprintf(stderr, "json parse error: %s\n", err.c_str()); | |
return 2; | |
} | |
// 幅と高さを取得 | |
stg.width = json["width"].int_value(); | |
stg.height = json["height"].int_value(); | |
printf("map-size: %d x %d\n", stg.width, stg.height); | |
// チップセットに使うGSLOT番号を取得 | |
auto gslot = json["tilesets"][0]["name"].string_value(); | |
printf("chip-set: %s\n", gslot.c_str()); | |
if (::strncmp(gslot.c_str(), "GSLOT", 5)) { | |
fprintf(stderr, "invalid chip-set name\n"); | |
return 2; | |
} | |
stg.gslot = ::atoi(gslot.c_str() + 5); | |
// レイヤー解析 | |
auto layers = json["layers"].array_items(); | |
int n = 0; | |
for (auto& layer : layers) { | |
// レイヤーの種別を取得 | |
printf("layer #%d:\n", n++); | |
auto type = layer["type"].string_value(); | |
printf("- type: %s\n", type.c_str()); | |
// レイヤー種別に対応したパースをする | |
// 私は今回、 | |
// - マップデータとしてタイルレイヤー(tilelayer) を 1枚 | |
// - イベントデータ(敵キャラや各種制御全般)としてオブジェクトレイヤー(objectgroup)を 1枚 | |
// を使うので、その前提でパースしている | |
if ("tilelayer" == type) { | |
// todo: 後で tilelayer はマルチレイヤにするかも | |
// todo: とりあえず、シングルレイヤ想定で | |
auto data = layer["data"].array_items(); | |
int i = 0; | |
for (auto chipNumber : data) { | |
stg.data[i++] = (unsigned char)(chipNumber.int_value() & 0xff); | |
} | |
printf("- set %d chip-sets\n", i); | |
} else if ("objectgroup" == type) { | |
auto objects = layer["objects"].array_items(); | |
int i = 0; | |
for (auto& object : objects) { | |
stg.event[i].x = (short)(object["x"].int_value() + object["width"].int_value() / 2); | |
stg.event[i].y = (short)(object["y"].int_value() + object["height"].int_value()); | |
stg.event[i].type = (short)object["properties"]["type"].int_value(); | |
stg.event[i].cnum = (short)object["properties"]["cnum"].int_value(); | |
stg.event[i].arg1 = (short)object["properties"]["arg1"].int_value(); | |
stg.event[i].arg2 = (short)object["properties"]["arg2"].int_value(); | |
stg.event[i].arg3 = (short)object["properties"]["arg3"].int_value(); | |
stg.event[i].arg4 = (short)object["properties"]["arg4"].int_value(); | |
printf("- event[%d]: x=%d, y=%d, type=%d, cnum=%d\n", i, (int)stg.event[i].x, (int)stg.event[i].y, (int)stg.event[i].type, (int)stg.event[i].cnum); | |
i++; | |
} | |
stg.events = i; | |
printf("- number of events: %d\n", stg.events); | |
} else { | |
fprintf(stderr, "error: unknown type"); | |
return 3; | |
} | |
} | |
// JSONから構造体に読み込んだ内容をファイルに保存 | |
FILE* fp = fopen(argv[2], "wb"); | |
if (NULL == fp) { | |
fprintf(stderr,"file write error\n"); | |
return 4; | |
} | |
fwrite(&stg, sizeof(stg), 1, fp); | |
fclose(fp); | |
return 0; | |
} |
JSONパーサーにはDropbox社が提供しているjson11を使用。
C++界隈のJSONパーサーというと、国内ではpicojsonとかが推されているようですが、私が使ってみた限り、モダンC++で書くにはかなり野暮ったい気がするし、数値型がdouble限定なのもイケてないという印象でした。「数値なら何でも実数でいいじゃない」という考え方は、実用上問題無ければそれで良いと思いますがFPUの仕様上、実用上の問題(性能云々は今回気にしていないので性能上の問題ではなく正確性の方の問題ですね)があるので、整数で表現すべき箇所は明確に整数で定義しないとダメだということです。なお、性能云々を今回気にしていないのは、性能にシビアな部分ではJSONパース済みのバイナリデータしか使わないからです。上記CLIのソースをコンパイルする手順:
$ git submodule add https://github.com/dropbox/json11.git $ g++ -std=c++11 json11/json11.cpp mkstg.cpp -o mkstg
これで、あとは保存したバイナリをVGSのDSLOTに突っ込んでマップを表示するプログラムを書くだけで、C言語でTiled Map Editorで作ったマップが表示できるようになります。簡単ですね。
![]() |
C言語でマップを表示してみた図(Macで撮影) |
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。