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で)作ってみました。

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で作れば一瞬で作れるので大した労力ではないですね。

2016年4月17日日曜日

新作ゲームの解像度検討メモ(256x240にした経緯)

今までスマホ前提ということで、SUZUKI PLANのゲームは基本的にQVGA縦(240x320)という解像度を使ってきたが、PC用ゲームの解像度としてQVGA縦というのは、横画面でフルスクリーンにした時に微妙だと思っていたりする。

NOKOGI RiderのWindows版はそれでも(スマホがメインということで)QVGA縦でゴリ押ししたが。(NOKOGI Riderは)縦置きディスプレイでのフルスクリーンにも対応していたというのもある。しかし、今回はMacにも対応する。Macで縦置きディスプレイというのはあまり聞かない。デスクトップ版のMacの事情はよく分からないが、少なくともMacBookで外付けディスプレイを使わずに縦置きというのは無い。

という訳で、横置きディスプレイでのフルスクリーンをプライマリに想定する必要がある。その場合、解像度をどうしたものかが悩ましい。とりあえず「正方形でいいや」と考え 256x256 で作り始めてみたものの、ひとつ重大な問題を思い出す。

WindowsのDirectX9のDirectGraphicでオフスクリーンサーフェイス描画をする場合、ビュー領域の拡大を自前処理でやらなければならない。DirectGraphicだと、サーフェースロックをしてメモリ領域にダイレクトに点を打つ(※この場合GPUを使わない)ローレベルなインタフェースがある(OpenGLには無い)。

ピクセル単位で描画する描画方式(VGSやエミュレータ全般で一般的な描画方式)の場合、GPU経由で描画すると、GPUはシステム・メモリ間のデータのやりとりが絶望的に遅いため、リアルタイムに描画内容が変わるラスタ形式グラフィックのようなものはGPUで描画できない。

GPUは使っていないので、回転、拡大縮小、半透明といった加工ロジックを全て自前実装する必要がある。ゲームを作ったことがない人だと「こんなオーパーツ的な技術は今でも必要なのか?」と疑問に思うかもしれないが、例えばメニューやメニューの上に載せる描画(文字など)、体力ゲージ的なものの表示などで(フル3Dのゲームだったとしても)割とよく使う。

レトロチックな純2Dのゲームの描画であれば、GPUを一切使わずこの方式だけで描画するのが最も高速になる。だから、VGSはそれで描画する前提のレンダリング機能しか実装しないようにしている。(スマホゲームの場合、GPUを使うゲームと比べて消費電力量や本体の温度上昇を大幅に抑えられるメリットがあったのでかなりうま味だったと思う)

そして、Windowsの場合、フルスクリーンの解像度は640x480などの固定サイズにする必要がある(昔は320x240でフルスクリーンにするmode-xというものがあったが、今は無いので最低解像度は640x480)。

少し前置きが長かったが、解像度を256x256にした場合の問題とは、縦のピクセル(256)を綺麗に480に拡大できないということ。等倍じゃないと pixel by pixel で拡大ができないので画面が汚くなるし、せめて間引き方式で拡大できる1.5xとかでないと拡大縮小時に浮動小数点数演算が必要になる関係で(自前拡大に要する)処理コストも増えてしまうので不味い。

という訳で縦のピクセル数は 240 (480÷2なので綺麗 & 低コストで拡大できる)にして、横は256ピクセルにしてみた。これをMacでフルスクリーン表示した場合のキャプチャが下図。
Mac + 解像度256x240 で フルスクリーン にした キャプチャ

妙に既視感のある解像度だな...

と思ったら、256x240 ってファミコンの(表示上の)解像度と同じか。

ファミコンの解像度は正確には256x256の正方形で、縦は上下8ピクセルが切れている。その切れている間がBLANK割り込み期間で、その間にスプライトの情報などを更新するとチラつき無くスプライトが動かせたりする感じだったような気がする。(ファミコンのコードを最後に書いたのが結構昔の話なので、若干記憶が曖昧だが、ファミコンだと非描画系処理を非BLANK時、描画系処理をBLANK時という具合に実装する必要があって苦労したような記憶がある)

スーファミの場合も確か同じ解像度があったような。ただし、SFCには解像度に幾つかの種類があって、どちらかといえば 512x240 の方がよく使われていたかな(だからスーファミには縦より横の方が木目が細かいグラフィックのものが多い: )。

Windows(基本的にVGSベース)と比べて横長のMac(640x400というPC-9801相当の解像度がベース)でも左右の帯が概ね気にならないので、コレで良い感じか。

2016年4月10日日曜日

iOSとOSXでのvsyncの取り方の違い

私がiOSアプリを開発した時に最初に詰まったのが、vsync(垂直同期)の発生タイミングを検知する方法で、色々調べて(確かmameか何かのソースを読んで) CADisplayLink を使えば良いことが分かりました。

CADisplayLinkの使い方は大雑把に説明すると下記のような感じです。

MyView.h
@interface MyView : UIView
@property CADisplayLink* displayLink;
@end

MyView.m
- (id)initWithFrame:(CGRect)frame {
  if ((self = [super initWithFrame:frame])!=nil) {
    ...CALayerの初期化など...
    _displayLink=[CADisplayLink displayLinkWithTarget:self selector:@selector(setNeedsDisplay)];
    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  }
  return self;
}

こういう風にすることで、vsyncの発生間隔の都度setNeedsDisplayが呼び出されて、60fpsでViewの描画がされるようになります。

そんなのNSTimerで良いのではないか?と思われるかもしれませんが、NSTimerで1/60秒間隔でsetNeedsDisplayをコールすると、若干画面時にブレ(ガクガク感)みたいなものが発生してしまうのでダメです。
何故、NSTimerではダメなのかというと、垂直同期の呼び出し間隔は常に一定ではなく、17ms, 17ms, 16ms 17ms, 17ms, 16ms... という風になっているので、常に一定間隔でコールしているとズレが生じてしまう為ではないかと思われます。

そして、OSX用のゲームを開発しようとした時、CADisplayLinkはUIじゃないからOSXでもそのまま使えるよね(※)などと考えていたのですが、甘かった。OSXの場合、CVDisplayLinkを使う必要があります。
iOSとOSXでは同じCocoaアプリでも作り方が微妙に違います。例えば、iOSのUIViewはOSXには無くて、代わりにNSViewがあったりとか。プレフィックス「UI」のクラスはiOS用で「NS」はOSX用という感じです。名前が違うので当然機能も微妙に違います。ですが、CADisplayLinkはプレフィクスがUIじゃなくてCA(CoreAnimation)だから、OSXでもそのまま使えるよねなどと考えていた時期が私にもありました。
CVDisplayLinkを用いた場合の実装は下記です。

MyView.h
@interface MyView : NSView
@property CVDisplayLinkRef displayLink;
@end

MyView.m
- (id)initWithFrame:(CGRect)frame
{
  if ((self = [super initWithFrame:frame]) != nil) {
    ...CALayerの初期化など...
    CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
    CVDisplayLinkSetOutputCallback(_displayLink, MyDisplayLinkCallback, (__bridge void *)self);
    CVDisplayLinkStart(_displayLink);
  }
  return self;
}

static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *context)
{
  [(__bridge VGSLayer *)context performSelectorOnMainThread:@selector(vsync) withObject:nil waitUntilDone:NO];
  return kCVReturnSuccess;
}

- (void)vsync
{
  [self.myLayer setNeedsDisplay];
}

若干気をつけないといけないのは、CVDisplayLinkSetOutputCallbackで指定できるコールバックの呼出し規約がC(Objective-cではない)ということですかね。(なので、若干面倒なブリッジが入っています)

あと、上記の例では省略していますが、何度も使い回されるタイプのViewの場合、確保したディスプレイリンクの停止(CVDisplayLinkStop)や解放(CVDisplayLinkRelease)が必要です。
詳細は下記を参照。
https://developer.apple.com/library/mac/documentation/QuartzCore/Reference/CVDisplayLinkRef/