2016年5月22日日曜日

vgs-cpu

また、変なものを作り始めました。
https://github.com/suzukiplan/vgs-cpu

VGS専用の仮想CPUです。
この記事執筆時点ではまだ作りかけです。
完成するかどうかも分かりません。
ただし、VGSを設計した(2012年)当初から、最終的にCPUまで自前化する青写真を描いていましたが。

恐らく、普通は誰もこんなことを考えないだろうと思います。

CPU依存しないプログラムを書きたければ、Java等のVM持ちの処理系を選べば良いですし、もっとローレイヤーにしたければLLVMを使えば良いので。
Javaはネタですが、しかし、現状最も現実的だと思われるLLVMだとガチ過ぎる(というよりデカ過ぎる)。
実用レベルで耐えうる仮想CPUを作ろうとすると膨れ上がるのは仕方が無いことですが、私としてはVGSのゲームだけ動いてくれれば良い訳です。しかし、そうなると既成品では(LLVMに限らず)どの仮想アーキテクチャもしっくり来ない(8bit, 16bit, 32bitの色々なエミュレータを試食してみましたが断念)。

CPUではなくJSで...と一瞬揺れそうになった時期もありますが。しかし、JSは便利だけどJSだと似たようなものが既にあるんじゃないかと思うので、面白く無いなと。

完全オリジナルなCPUにすることの最大のデメリットはコンパイラの調達です。
コンパイラも全部自前で作る必要があります。
それは流石に面倒くさい。
だから、コンパイラについては一旦無くてもいいやと。

コンパイラが無ければアセンブラで作れば良いじゃない

です。

という訳で、32bit CPUなのに6502並にフルアセでゲームが作り易いCPUみたいなものを作ろうかなと。

2016年5月8日日曜日

ストックを増やす作業

ゲーム開発を再開したのは良いけど、曲のストックが切れているな...

あまり曲作りに時間を掛けたくないから、過去作品をアレンジして使い回そうかと思いつつも、とりあえず新しくストックを貯めとこうと思い、2日で3曲ほど作曲。

なお、まだVGS化はしてなくて、ピスコラで作ったラフ状態です。MML化するのは面倒くさいので、ピスコラで作ったストックの中からイイネと思った曲をチョイスしてMML化します。

まず1曲目(ボス想定)
http://twitsound.jp/musics/tsv8nu1nh

途中まではボスっぽいのですが、ラスト8小節のエスニックっぽい部分は詰まりました。
詰まるとだいたいこんな感じになります。
ボスっぽい曲って作るのが難しい。
ボスにキャラ付けがあれば割りと作り易いのですが、例によって私が作るゲームは基本的にキャラクターやストーリーの類は一切無いので、だから難しいのかもしれません。

2曲目(3面想定 / 明るめのステージ曲)
http://twitsound.jp/musics/tsE9yCZ4q

これも前半は良い感じですが、後半16小節のエスニックっぽい部分はやはり詰まっています。
詰まると手癖で作る→エスニック風(?)になる
というのが定石になりつつある。

3曲目(ステージ曲だと思う / いわゆるズンダラ節)
http://twitsound.jp/musics/ts7SVFMKq

これは完全に手癖だけでほぼ機械的に作曲。
中盤のサブメロディが主旋律よりも高くなっている部分のバランスが若干悪いです。

1面のボス曲だけ出来ればLite版は作れるので、ボスっぽい曲のストックを増やすのを優先したいところですが、何故かステージ曲の方を多く作ってしまってますね。困ったものです。

2016年5月5日木曜日

マイブーム

ここ暫く(4月以降)、東方BGM on VGSの曲追加をしていません。楽しみにお待ち頂いている方が少なからず居るだろうと思うと心苦しいところではありますが、今は私の興味の対象がズレているので仕方がありません。

先日、かなり以前にYouTubeで公開した SHOT05 のデモムービーに海外の方から「新作はよ」というコメントが付いたことがキッカケで少し我に返りました。

で、最初は何故か「よっしゃHTML5で60fpsでヌルヌル動くSTG作ってみっか」というノリで作り始めたのですが、次の公開先はSteamにしたかったので、「よっしゃcocos2dxで作ってみっか」という流れになったものの、cocos2dxは(スマホ版は知りませんが)PC版がかなり微妙だったので、「よっしゃVGSをしっかりPC対応させてPC用STG作ってみっか」という流れになり下図のゲームを今作っているという感じです。
見事なまでの右往左往。
何故、最初っからVGSでやろうとしなかったし...

あぁ、でもJSに寄り道したのはそこそこ実りがありました。
Duktapeなどの小さめのJSエンジンを積んで、VGS+JSでPCネイティブのゲームを作れるようにするのも悪くないかもしれないとか一瞬思ったりしました。

2016年5月4日水曜日

Tiled Map Editor で生成したJSONをC言語で読めるようにする

前回記事で紹介した Tiled Map Editor は、JavaScriptであれば割りと何も考えずに使えますが、これをC言語で扱える構造体形式に変換するツールを作ってみました。

Tiled Map Editor が吐き出せるデータ形式はデフォルトはXMLですが、今時XMLなんか使っている人なんて居るんですかね?JSON形式で吐き出せるので、JSON形式で吐き出したデータを構造体に変換します。

なお、JSONならダイレクトに読んで使っても良いかもしれませんが、C言語でJSONを読むのは少々面倒くさいので、JSONをバイナリ構造体に変換するCLIを(Cで書くのは面倒なのでC++11で)作ってみました。
// 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;
}
view raw mkstg.cpp hosted with ❤ by GitHub

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で撮影)
マップを表示させただけでかなりゲームっぽくなるのでテンションが上がります。

2016年5月3日火曜日

マップエディタ

2Dゲームを作る上で結構重要なのがマップエディタです。

私はこれまで、自作のマップエディタ(S/MAPEDIT)を使って2Dゲームのマップを作っていました。既成品を使えば良いのかもしれませんが、色々と細かいことをやろうとすると既成品では機能不足だったりします。
自作マップエディタ S/MAPEDITの画面
(NOKOGI Rider開発当時のもの)

ただ、これを今のメイン開発環境(Mac)で動かすのは辛すぎる。wineで動かすのが手っ取り早いですが、せっかくMacで動かすなら、トラックパッドと相性の良いUIに修正したい。とはいえ、思いっきりWindows依存しまくりのS/MAPEDITをMacポーティングするのはかなりキツイ。(スクロールバーなどのMFCとかではなく全部DirectXで自前描画しているので、普通のWindowsプログラムと比べればまだ移植は簡単な方ですが)

という訳で、素直に外部ツールを使い、コンバータを作って都合の良いデータ形式に変換してあげるのが得策でしょう。

先日、少し実験的にJSでゲームを作っていた時に見つけた Tiled Map Editor というツールがかなり良い感じです。
http://www.mapeditor.org/
・任意サイズに設定可能なタイル上に16x16マップチップを配置できる。
・イベントオブジェクトとして使えるオブジェクトレイヤーがある。
・オブジェクトに任意プロパティが持たせられる。
とりあえず、最低限必要な上記の要件は満たせています。
ただし、出力データ形式が XML, JSON, JS だからVGS(C言語)では扱い難いので、独自バイナリ形式に変換するツールだけは自作する必要がありそうです。まぁ、それぐらいboostを使ってCLIで作れば一瞬で作れるので大した労力ではないですね。

合理的ではないものを作りたい

ここ最近、実機版の東方VGSの開発が忙しくて、東方VGSの曲追加が滞っています。 東方VGS(実機版)のデザインを作りながら検討中。基本レトロUIベースですがシークバーはモダンに倣おうかな…とか pic.twitter.com/YOYprlDsYD — SUZUKI PLAN (...