2018年4月4日水曜日

Android版 東方VGS Lite(開発中)

東方VGSのAndroid版が新しいAndroidで動かなくなったという報告を頂いたので調べた所、VGSではなくネイティブUIで作り直してしまった方が手っ取り早そうだったので、作り直し中。
旧来の東方VGSはそのまま残しておき、東方VGS Lite(別アプリ)として公開予定です。
完成度は7割前後なのですが、ここから先が長い...
でも、4月中にはGooglePlayで公開できるかと思います。

2018年3月14日水曜日

サーバサイドのお勉強

スマホのソシャゲで使えそうなアカウントサーバを作ってみました。
https://github.com/suzukiplan/simple-account-server
https://github.com/suzukiplan/simple-ap-server

ただし、飽くまでも試作です。
実用的かは未知数。
現在の本職はクライアント畑の人間なのですが、サーバのことも把握しておこうかなと思いまして。要するに単なる興味本位です。

まずは、アーキテクチャ設計。

アカウントサーバというと、Web時代のソシャゲであれば、メアド(ID)とパスワードで作るのが一般的ですが、スマホ向けということで、POSTすれば自動生成され、アプリ側では preference / userDefaults にそれを記憶して使うのだろうと思います。この辺は実際にスマホのソシャゲを幾つか遊んでみた限り、だいたいそんな感じだろうと想像で作成。
初回起動時(アカウント生成)

自動生成されたIDとtoken(パスワードに相当するもの)を用いてログインするイメージは下図のような感じ。
ログイン

DBですが、アカウント情報というとRDB一択だろうとは思いつつ、スキーマ定義が面倒臭いというただそれだけの理由で MongoDB(NoSQL DBMS)にしてみました。MongoDBというと、どちらかというとログ用途というイメージなのですが。

どうしてもHiRDBなどのRDBじゃないとマズイのであれば、置き換えは割と簡単にできます(実際、試しにMariaDBに置き換えてみたのですが、コードの書き換えよりもスキーマ定義等の下準備の方が面倒だった)。なので、まぁこのまま良いかなと。

DBがMongoDBだから速いので、キャッシュとか要らないのではないだろうかと思ったのですが、実測してみた限りRedisの方が最大10倍ぐらい早かったので、Redisでキャッシュ(Read cache)も作ってみました。なお、Write cacheは現時点では実装してません。アカウントの情報更新量が多いとかで必要であれば、RabbitMQを使う感じでしょうか。

最後に言語。
言語の定番は PHP かな?と思いつつ, TypeScript + Node.js で。
理由としては、
・豊富なパッケージ(使おうとしたミドルのパッケージが全てnpmにあった)
・情報を見つけやすい(≒情報が多い?)
あたり。

サーバサイドの言語事情はよく分かりません。Goとかも興味ありましたが、習得するのも面倒だしNode.jsなら少し知っているから楽なので。実のところ、TypeScriptにする必要すら無い(=ダイレクトJSで良い)んじゃないかなと思ったのですが、modelの定義にclassが使えるのが便利だし、tscも色々良い感じになっていてコンパイル手間も無いに等しい状態だったから、ダイレクトJSはやめておきました。

ひとまずこんな感じで、ユーザ登録、ログイン、ユーザ情報参照、ユーザ情報更新といったアカウントサーバの基本的な仕組みを作ってみました・・・が、コレ、実用に耐えられるシロモノなんですかね?正直良くわからないので、誰かコレでソシャゲ作ってサービスインしてみて下さい。(責任は取らない)

2018年3月5日月曜日

NES emulator for iOS

先日作ったnes-emulator-androidをiOSにも移植。
https://github.com/suzukiplan/nes-emulator-ios

機能レベルでAndroidと同等にすることを目標にしてますが、この記事執筆時点ではサウンドキャプチャ機能が未実装です。(save stateとload stateが未実装であることはAndroid版と同じ)
一応以前、コア部分にLaiNESを使ったiOS用のインタフェースも作ったのですが、若干(というかかなり)作りが悪かったので、内部実装はかなり書き直しました。恐らくインタフェース的には実用に耐え得るレベルになったと思います。

あと、今回のnes-emulator-iosはcocoapodsでの配布もするようにしたので、Podfileに
pod 'NESView', '~1.0.0'
と書くだけで利用できます。
まぁ、利用するとそのアプリのライセンスをGPL3.0(か互換性のあるOSSライセンス)にする必要があるので、事実上商業利用不可みたいなものですが。

なお、READMEに補足してますが、ライセンス調整版(※ただしコア無し)というものも一応あります(そちらは private repo なので一般公開していませんが)。

2018年2月17日土曜日

NES Emulator for Android - version 1.5.0

先日公開したCycloaをAndroidで動かしたライブラリを色々と更新しました。
https://github.com/suzukiplan/nes-emulator-android
現時点の最新バージョンは1.5.0。

主な機能追加内容としては、以下3つ。

  1. マルチtick送信(n倍速)に対応
  2. 映像と音声のキャプチャインタフェースを追加
  3. ステートセーブ/ロードのインタフェースを追加

この他にも色々と細かい修正はしていますが。
これらの機能を全てテストアプリで触れるようにしておいたので、大分テストアプリの画面のガジェット感が上がりました。

1. マルチtick送信(n倍速)

これは普通のエミュレータでもよくある倍速プレイとかですね。
tickの内容を流すことでリプレイ再生することもちゃんとできるようにしておきました。

2. 映像と音声のキャプチャインタフェース

これは普通のエミュレータにはあまり無い機能ですが、映像と音声をキャプチャするというもの。例えば、プレイしている内容をライブストリーミングに流したりといった機能を作る時に役立ちます。
もっとも、実際に配信するにはこれだけだと不十分で、別途MediaCodecを使ってエンコードして、ライブストリーミング・プロトコルでの配信する機能を(これはAndroidの標準機能ではないので自前で)実装する必要があり、少し大変ですが。

3. ステートセーブ/ロードのインタフェース

Cycloaにはステートセーブ/ロードの機能は無いので、NES Emulator for Android側のインタフェースのみですが、とりあえず作っておきました。余力があれば、Cycloaにステートセーブ/ロード機能を追加した魔改造バージョンをforkして作るかもしれません。(作らないかもしれません)

ここ一週間でもりもりアップデートしてましたが、これで私が当初作ろうと思った機能は(少なくともインタフェースレベルでは)一通り揃ったかなという感じ。

2018年2月13日火曜日

Kotlinで100行以内で書けるファミコンエミュレータ

前回の記事で書いたnes-emulator-androidというライブラリをjcenterで公開しました。

jcenterに公開したので、Android projectの build.gradle の dependency 区に
implementation 'com.suzukiplan:nes-emulator-android:1.2.1'
と1行追加してあげるだけで、Androidアプリで簡単にファミコンエミュレータを動かすことができます。

Androidアプリでファミコンエミュレータを動かすには, nes-emulator-android の NESView という View派生モジュール を使います。
NESView は 通常のAndroidのViewと同様に、レイアウトXMLで配置できます。

(レイアウトXMLの記述例)
<com.suzukiplan.emulator.nes.core.NESView android:id="@+id/nes_view" android:layout_width="match_parent" android:layout_height="match_parent" />

ファミコンの表示解像度は256x240pixel(アスペクト比16:15)なので、ConstraintLayoutを用いて配置してあげても良いですが、Viewを配置したレイアウト内へ自動的にアスペクト比を保った状態で最大限に拡大してセンタリングした状態で表示されます。(余った領域は黒く塗りつぶされます)

このNESViewに対して、
    nesView?.load(romByteArray)
とすればROMファイルをロードできます。

そして、
    nesView?.tick(keyP1.code, keyP2.code)
とすれば1フレーム実行することができます。

NESView#tickを実行すると自動的に垂直同期の待機が行われ、また、サブスレッド(非UIスレッド)から実行できるので、これを連続的に実行してあげればゲームが動きます。

実際のNESViewを用いて実装したエミュレータアプリの本体コードが以下にありますが、Kotlinで100行未満で記述できることが分かります。
https://github.com/suzukiplan/nes-emulator-android/blob/master/test/src/main/java/com/suzukiplan/emulator/nes/test/MainActivity.kt

なお、このNESViewを用いて作成したアプリはGPLv3またはGPLv3と互換性のあるライセンス(Apache v2, MIT, 修正BSD等のOSSライセンス)で公開する必要があるので、ご注意ください。(OSSで公開されているエミュレータのソースコードはだいたいGPL系なので)

2018年2月12日月曜日

CycloaをAndroidで動かしてみた

OSSのファミコンエミュレータのソースを眺めていたところ、Cycloaというエミュレータが中々良くできていそうだった。
https://github.com/ledyba/Cycloa

ただし、quick-save等は実装できていないし対応mapperも少ないので、実用的かというと結構微妙なところですが、内部の実装が結構キレイに書かれていて、フロント実装も結構し易そうな感じだったので、「とりあえずファミコンエミュでも作ってみっかー」みたいな軽い気持ちで作ろうとした時に、最短時間で動くものを作ることに適していそう。

論より証拠ということで、CycloaをAndroidで動かしてみたフロントを書いてみました。
https://github.com/suzukiplan/nes-emulator-android

少し目新しい点としては、
・Android Studio 2.2の時に対応した cmake スタイルのNDKビルドを適用
・エミュレータコア機能の実装を library module に分割(bintrayとかにデプロイはしてませんが追記: bintrayにデプロイしてjcenterへのリンク申請中
・↑を用いたアプリ本体の実装を Kotlin で書いてみた
ぐらいでしょうか。

少し話しが逸れますが、私はSwiftが大嫌いなのですが、Kotlinは割と良い感じだと思い始めてます。Swiftがキライな理由は、言語的な意味での良さを全て食い潰すバージョン間互換性の欠如とIDE(XCODE)の対応がIDEAとかと比べるとお粗末過ぎるところでしょうか。言語自体は結構良い感じです。しかし、言語の良し悪しというのは、言語自体の良し悪しではなくIDEの良し悪しに左右されるというのが恐らく真理です。(だから、iOS用のコードを書くならObjective-Cにしておいた方が良い。Swiftと比べればまだ安定している方なので。)

2018年2月11日日曜日

東方VGS(iOS)コードレビュー③ VGSView

VGSViewについて解説します。
https://github.com/suzukiplan/tohovgs-ios/blob/master/Touhou%20VGS/VGSView.m

VGSViewはiOS版VGSのグラフィックス描画処理を実装したViewです。
VGSのグラフィックス描画は、vgs2_putBG(BGを描画)やvgs2_putSP(スプライトを描画)といったVGS API(C/C++)を用いて行いますが、それらを用いて描画された情報はVGS VRAMというメモリ上に記憶されています。

VGSViewの責務(役割)を箇条書きで書くと、

  • VGSのメインループを回す(VGS VRAMに1フレームの描画がされる)
  • VGS VRAMを、iOSの描画処理系に合わせた形式に変換
  • 画面に出力(表示)
  • 上記を1秒間に60回の間隔(60fps)で繰り返し実行

となります。

iOSのゲームに適したグラフィックス描画機能としてはOpenGL/ESやMetal(GPU Renderer)がありますが、これらは上述の用途で使うのは不適当です。というのも、GPU Rendererで上記要件を満たそうとすると、

  • 毎フレームVGS VRAMをテクスチャ変換して描画 or
  • 1pixelを2つのポリゴンで表現して、(変化があったpixelの)色情報を設定

といった処理方式で実現することになりますが、前者(テクスチャ変換)は変換オーバヘッドが物凄く大きく、VGS VRAMのサイズ(東方VGSの場合は240x320)のテクスチャを毎フレーム生成することは現時点最新のGPUでも恐らく不可能です。仮にできたとしてもGPUが火を吹く勢いで回り熱暴走を起こします。

後者の処理は前者よりは現実的ですが、その場合全画面のpixelの色情報を変えようとすると最大240x320(76800)回のパレット変更命令をGPUに飛ばす必要があり、GPUへのデータ伝送は物凄く遅いので60fpsではとても動けないようになるでしょう。

つまり、VGSの要件を満たすことができるのは、GPU描画ではなくCPU描画一択になります。

まず、VGSViewを生成するとCALayer(Core Animation Layer)を独自のもの(VGSLayer)へ差し替えます。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L156-L159

そして、CADisplayLinkを用いて垂直同期の発生間隔でsetNeedsDisplayをコールするようにしています。これでVGSLayerの描画更新が1/60秒間隔で行われるようになります。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L170-L171

VGSLayerが初期化されると、まず2枚のCore GraphicsのBitmap Contextを生成します。これが変換後のVGS VRAMバッファになります。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L101-L115
2枚準備している理由は、VGSLayerがダブルバッファリングと呼ばれる方式で描画出力を行っている為です。ダブルバッファリングとは、一方のバッファの内容を表示中にもう一方のバッファ更新を非同期で行うことで、60fpsの描画がスムースに行われるようにするためのものです。(実のところ、最近のiPhoneならこんなことしなくてもシングルバッファで余裕で回せますが...)

その後、ダブルバッファリングを実現するためのGameLoopスレッドを生成して起動しています。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L117-L121

GameLoopスレッドでは、100μs間隔で描画指示が発生するのを待機して、
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L34
描画指示が発生したら、VGSのメインループを1回回し、
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L35-L42
その後、VGS VRAMの内容をCore GraphicsのBitmap Contextの形式に変換しています。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L48-L61

GameLoopが本来必要な処理は以上ですが、VGSにはグローバルリクエストという機能があり、それに応じてAppStoreのページを開くといった要求処理を実装しています。(東方VGSでは使ってませんが、VGSからのネイティブ広告の表示制御などはこのグローバルリクエストで実現しています)
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L63-L88

最後に、CADisplayLinkにより1/60間隔で発生するVGSLayerの描画要求のコールバック(display関数)では、描画指示を出しつつ、描画が確定している方のCore Graphics/Bitmap ContextをViewの表示内容(content)としてセットする処理を行っています。
https://github.com/suzukiplan/tohovgs-ios/blob/71464aae09f107fbf02e9ce1989d9bfb80381e37/Touhou%20VGS/VGSView.m#L131-L139

以上でVGSViewの解説は終わりです。

この部分は東方VGSに限らず、VGSを最初にiOSへ移植した時から一切変更していないので、あまり面白い内容ではないですね。だから、実装内容を淡々と解説しましたが、このVGSViewを最初に作った当初はもの凄く苦労しました。
というのも、市販のiOSアプリ関連の書籍には、役立つ内容が書かれたものが一切無かったので。(仕方が無いのでApple公式の分かり難いCore Graphicsのドキュメントを読むなどして理解しました)
ゲームのグラフィックス描画処理というと、最近ではOpenGL or MetalといったGPU Rendererを用いた方式に一本化されていて、スプライトやラスタといった昔ながらのコンピュータグラフィックスの描画方式は、完全に過去のものになってしまったのかな。

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

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