WebDebugMenu


ゲーム屋の間ではおなじみのデバッグメニューをブラウザで実装してみました。
WebDebugMenu

デバッグメニューとは、変数や関数を事前に登録しておき、後でメニューから値を書き換えたり関数を呼んだりできるようにする代物で、周囲の人間の話を聞くにコンソールゲーム屋の間では国内外問わずどこでも開発中こういう機能を入れていて、ほぼ共通認識になっているようです。
通常ゲーム内の 2D レンダリング機能やら UI 機能やらを使って実装するのですが、ブラウザで実装すればそれらとの依存を断てるので汎用性を高められそうです。サーバープログラムのようなグラフィック表示がないプログラムへの応用も考えられますし、ブラウザは超強力な UI エンジンでもあるので、一般的なデバッグメニューでは難しい複雑な編集機能も実装できそうです。(STL などのコンテナの中身を弄るとか、スクリプトやシェーダコードを編集できるようにするとか)

HTTP サーバの実装はレベルエディタの時と同じく Poco を使いましたが、Poco を使ってる部分は dll に閉じ込めてあるので、ユーザー側が Poco をリンクする必要はありません。
ブラウザ側は polling で 1 秒ごとに最新のデータを取ってくるようになっていますが、この際ちゃんと表示状態の枝のデータだけ取ってくるようになっています。

現状はまだそこまで複雑なことはできませんが、組み込みは極めて容易かつ最低限必要になりそうな機能は大体揃っています。
Chrome と Safari で動作を確認済み。IE は 9 以前だとスライダが使えません。

以下使用例。
一般的な変数の表示。

int main(int argc, char *argv[])
{
    wdmInitialize();

    int int_value = 1;
    float float_value = 1.1f;
    bool end_flag = false;
    // ごく普通に表示
    wdmAddNode("Test/int_value", &int_value);
    wdmAddNode("Test/float_value", &float_value);
    // 範囲指定 (スライダで操作できるようになる)
    wdmAddNode("Test/ranged_int_value", &int_value, 0, 100);
    wdmAddNode("Test/ranged_float_value", &float_value, 0.0f, 10.0f);
    // const の値を追加すると readonly ノードになる
    wdmAddNode("Test/const_int_value", (const int*)&int_value);
    wdmAddNode("Test/const_float_value", (const float*)&float_value);

    wdmAddNode("Test/end_flag", &end_flag);
    while(!end_flag) {
        // wdmFlush() でブラウザから来たコマンドの処理が行われる
        // == 変数の更新、関数呼び出し、ブラウザへ送るデータの生成などはこのタイミングで行われる
        wdmFlush();
        ::Sleep(100);
    }
    wdmEraseNode("Test");

    wdmFinalize();
}

ブラウザ側の表示

class 内変数の操作。関数呼び出しなど。

class Hoge
{
public:
    Hoge() : m_int_value(0) {}
    int getValue() const { return m_int_value; }
    void setValue(int v) { m_int_value=v; }
    void print() const { printf("Hoge::m_int_value: %d\n", m_int_value); }
    void printx(int v) const { printf("Hoge::m_int_value x %d: %d\n", v, m_int_value*v); }

    // wdmScope は wdmDisable を define しているときは消え失せるスコープ
    // (最終リリースの時は当然デバッグメニューは消える必要があり、
    //  wdmDisable を define するだけで全て消え失せるようになっている)
    wdmScope(
    void addDebugNode(const wdmString &parent)
    {
        wdmString path = parent+wdmFormat("/Hoge:0x%p", this);
        // getter/setter で値を変更するノード (範囲指定付き)
        wdmAddNode(path+"/value", this, &Hoge::getValue, &Hoge::setValue, 0, 100);
        // getter のみの場合 readonly なノードになる
        wdmAddNode(path+"/const_value", this, &Hoge::getValue);

        // 関数を呼ぶノード
        wdmAddNode(path+"/print()", &Hoge::print, this);
        // 引数付き関数を呼ぶノード
        wdmAddNode(path+"/printx()", &Hoge::printx, this);
    }
    )
private:
    int m_int_value;
};

int main(int argc, char *argv[])
{
    wdmInitialize();

    bool end_flag = false;
    Hoge hoge;
    wdmScope( hoge.addDebugNode("Test") );
    wdmAddNode("Test/end_flag", &end_flag);
    while(!end_flag) {
        wdmFlush();
        ::Sleep(100);
    }
    wdmEraseNode("Test");

    wdmFinalize();
}

配列、文字列、SIMD 変数など。(現状文字列はマルチバイト文字を正しく扱えません)

int main(int argc, char *argv[])
{
    wdmInitialize();

    bool end_flag = false;
    int int_array[8];
    __m128 simd_value;
    char char_array[32] = "hoge-";
    char *char_ptr = char_array;

    std::fill_n(int_array, _countof(int_array), 10);
    simd_value = _mm_set1_ps(1.0f);
    // 配列
    wdmAddNode("Test/int_array", &int_array, 0, 100);
    // SIMD
    wdmAddNode("Test/simd_value", &simd_value, 0.0f, 10.0f);
    // 文字列 (char[])
    wdmAddNode("Test/char_array", &char_array);
    // 文字列 (char* もいける。安全性を考えて readonly ノード)
    wdmAddNode("Test/char_ptr", &char_ptr);

    wdmAddNode("Test/end_flag", &end_flag);
    while(!end_flag) {
        wdmFlush();
        ::Sleep(100);
    }
    wdmEraseNode("Test");

    wdmFinalize();
}


[2013/09/21 追記] デバッグ情報を使ってメンバ変数の自動登録ができるように改良しました。runtime member variable editing

experimental codes

最近書いた雑多な実験コードのまとめ。


GetThisOfCaller()
現在の関数の呼び出し元がメンバ関数であればその this を返すというもの。
スタックフレームをさかのぼり、デバッグ情報を使って該当スコープの変数を巡回して this が見つかればそれを返す、ということをやっています。(たまたま今回 this が欲しかっただけで、それ以外の変数も可能です)
非 Debug ビルドの時はよくデタラメな値が返ってくるため、使いものにならないですが、Debug で限定的な状況で役立つ可能性が無きにしもあらずです。


メンバ関数の中と外で違う処理を行うマクロ
VisualC++ の拡張 __if_exits, __is_not_exits を使って this の有無で違う処理をさせるというもの。
__if_exits は指定シンボルの有無で分岐するという代物で、主にメタプログラングに使われる機能ですが、こういうこともできるようです。
また、clang にも __if_exits はありますが、this を認識できないようでコンパイルできませんでした。こちらはメタプログラングしか想定してない実装になってるんじゃないかと思います。


8bit atomic operation
VisualC++ には _InterlockedIncrement() 一族の atomic 操作系 intrinsics がありますが、なぜか 8bit 整数版が用意されていません。
必要な場合アセンブラで自力で書く必要があるようで、書いてみました。


dll の export,import 関数を巡回するコード
ロードされている dll のアドレス (HMODULE) からその export,import 関数を巡回するコード。
既存のシンボルが一切見えない特殊な状況に置かれることが稀にあり、そのとき kernel32.dll の export 関数を巡回する必要に迫られます。
また、MemoryLeakBuster は import 関数テーブルを書き換えて HeapAlloc() に hook を仕込むことで実装しました。


shellcode
shellcode を書いてみる実験。shellcode とは大雑把に言うと既存のプログラムにねじ込んで実行させるバイナリコード片です。
既存のプロセスの中で実行されるため、外部シンボルが一切見えないという特殊な条件を強いられることになります。
FS レジスタから辿れる Process Environment Block にロードされている dll のリストがあるため、それを辿って kernel32.dll を探し、(上記の dll export を巡回する手順で) LoadLibrary() と GetProcAddress() を得ることで既存の関数を呼べるようにします。
今回の例はメッセージボックスを出しています。


buffer_overflow
buffer overflow のバグがある場合、入力データで return address を書き換えて任意の場所に制御を飛ばせる可能性があります。任意のコードを実行可能な脆弱性がうんぬんとよくニュースになるあれの原因の一つです。
実際に buffer overflow を突いて任意のコード (ここでは上の shellcode) を実行させてみたものです。


DLL Injection
Windows には、既存の任意のプロセスのアドレス空間にメモリを確保する VirtualAllocEx()、既存の任意のプロセスにスレッドを作る CreateRemoteThread() という強力な API があります。
これを利用すると、既存の任意のプロセスに任意のコードを仕込んで実行させることができます。
この仕込みを使って既存のプロセスに DLL を inject し、malloc() を乗っ取ってみた、という実験です。
ムービーキャプチャソフトや Nsight のような解析ツールの類もこの仕組みを使ってるんじゃないかと思います。

eval() in C++

しばらく前にやったことですが、ネタとして面白い気がするのでサルベージ。

DynamicObjLoader を使って C++ で eval() を実装しました。
C++er が夢に見たり見なかったりしたこういうコードが動きます。

#include "DynamicObjLoader.h"

class Hoge
{
public:
    Hoge() : m_value(100) {}
    void doSomething() { printf("Hoge::doSomething(): %d\n", m_value); }
private:
    int m_value;
};

void* CreateAnyClass(const char *class_name)
{
    volatile void *obj = NULL;
    std::string source = std::string("obj = new ")+class_name+"();";
    DOL_Eval(source.c_str());
    return (void*)obj;
}

int main()
{
    Hoge *hoge = (Hoge*)CreateAnyClass("Hoge");
    hoge->doSomething();
    delete hoge;
}

DOL_Eval() で C++ ソースを生成し、コンパイルし、できた .obj を DynamicObjLoader でロード&リンクして実行しています。

DOL_Eval() は実際にはマクロになっていて、指定の C++ コード片以外に __FILE__, __FUNCTION__, その地点のスタックポインタ (esp) を実装関数に渡しています。

ソースの生成が今回の肝で、DOL_Eval() がある地点で見えているシンボルが見えるようにする必要があります。
global なシンボルについては __FILE__ を #include することで解決できます。
問題なのは関数内変数などの現在の scope にしかない変数ですが、これらは関数名と esp があれば ::SymSetContext() & ::SymEnumSymbols() で巡回することができます。(デバッガで見れる変数であれば情報を取れるのだと思われます)
変数名、型名、アドレスを取れるので DOL_Eval() がある地点のコンテキストを復元したように見せかけることもできそうです。

最終的に上の CreateAnyClass() の DOL_Eval() は以下のようなソースを生成、実行します。

#include "c:\somewhere\to\source.cpp"

extern "C" void evalblock()
{
    char*& class_name= *((char**)0x2cfa08);
    std::string& source= *((std::string*)0x2cf9c0);
    void*& obj= *((void**)0x2cf9e8);

    obj = new Hoge();
}

(リテラルのポインタ値!)

色々厳しい問題もあって、
scope 内変数は最適化が有効な場合メモリ上に存在しないことがよくあります。なので上の例の obj ように volatile をつけるなどで無理矢理メモリに繋ぎ止める対応が必要になります。
現状メンバ関数は未対応です。メンバ関数の場合 this を完璧に偽装するのが困難です。
現状複雑な書き方を要求される型 (配列や関数ポインタ) の場合うまいことソースが生成できません。
DynamicObjLoader の制限が全部ついてくるとか、逐次コンパイル&リンクするので超絶遅いなどの問題もあります。

あまりに問題が多すぎて結局ネタ以上の価値を見出だせず放棄してしまったんですが、イミディエイトウィンドウから任意の C++ コード片を任意の場所に注入可能にできたりしたら何か使い道があるかもしれません…。

memory leak buster (2)

以前作ったメモリリーク検出器がとても役立っていて、今も機能追加を加えつつ使っています。
最近大きな改良を加えたので再掲。
MemoryLeakBuster

実行時にイミディエイトウィンドウから呼ぶデバッグ機能を追加したのが主な変更で、以下のようなことができるようになりました。

・範囲指定リークチェック
デバッガで止めて範囲指定することで、そこで確保されたまま開放されなかった領域のコールスタックを出します。

・指定アドレスの確保時のコールスタック出力
近隣領域も表示し、stack 領域か static 領域か判別する機能も備わっています。デバッグの強力な手助けになっています。

アロケーションカウント
リークチェックではなく、単純に HeapAlloc() が呼ばれた回数をコールスタックと共に出力します。プロファイリングのお供に。
ちなみに、デバッガをつないでいる時は HeapAlloc() が非常に遅くなるらしく、1 フレームに数千とか数万のオーダーでメモリ確保しているような場合、F5 で起動すると遅いけど Ctrl+F5 で起動すると速いという、知ってないと混乱する自体が起きます。これはデバッグの設定で環境変数 _NO_DEBUG_HEAP=1 を指定しておくことでとりあえず回避できます。 (解説: http://preshing.com/20110717/the-windows-heap-is-slow-when-launched-from-the-debugger )


従来通り終了時にリーク箇所を出力しますが、設定ファイルを書くことで特定パターンを無視したり、リークチェック機能自体無効にしたりできるようになりました。


実装の詳細については以前の記事を参照。

implementing level editor by HTML


レベルエディタを実装中です。
今は DyncmicObjLoader があるので全部 C++ でレベル (ステージ) を書くのも十分アリなんですが、それとは別に、レベルエディタ的なものを実装してそれをゲームの一要素にしたいと考えています。


exception conflict が、サーバープレイヤーが動画を配信しながらプレイして視聴者が気まぐれに乱入、という遊ばれ方をしているのを見て、これに加えて見てる人が敵出したり敵に指示出したりできたら面白そうだ、と考えていました。
直接ゲームにつないでるプレイヤーは STG な一方、見てる人はそのプレイヤーを阻止する RTS になるイメージです。
見てる人が気軽に敵出したり敵に指示出したりできる、となるとそれはブラウザ上で動くことが必須であり、事実上 HTML であることが必須です。
そういうわけで、HTML でレベルエディタです。


上の動画はとりあえずブラウザでゲームの状態を表示しつつ、クリックで敵出す処理が動いたところ。
これを実現するには当然ゲーム本体に HTTP サーバ機能を実装する必要がありますが、これは Poco というライブラリのおかげで非常に楽にできました。(実装例)
表示部分には WebGL を使っています。WebGL は三角形出す最小限のサンプルが見当たらなくて割と苦労しました。(ので、自分で最小サンプルを作ってみました)
サーバー -> HTML の状態の反映は、HTML から Ajax でリクエストを送信し、サーバーは必要なデータを JSON 化して送りつけることで実現しています。今は補間処理はなにもやってないのでカクカクですが、いずれ滑らかになる予定。
(主に Poco のおかげで) 意外と実装は簡単で、しかし面白い要素になりそうな気がする、というのが今の感触です。


最近のゲームを見てて、アンロック要素は今作にも是非入れたいと考えているんですが、RTS モードで出せる敵の種類は面白いアンロック要素になりえそうです。
exception conflict で有志がやってくれた、スクリプトでゲームの内部状態を見て動的に難度調節する、というのもより高いレベルで実現できそうです。
体験版はサーバー機能はないけどクライアントとしてはネットワークモードをフルに遊べる、とかしておけば集客力を得られるかもしれません。
他にもなんか色々応用の余地がありそうな気がします。
あと、私は基本的にスマートフォンやタブレットでゲームは遊びたくない派ですが、こういうサテライトゲームに使うのはアリかもしれないという気がしていて、いい実験台になってくれそうです。



夏コミ申し込みました。
今回は落選しても委託で何か出すくらいの気概です。

retrospect 2012

2012 年は例年にはないくらい得たもの、考えさせられたことが多い年でした。


仕事


仕事では主に Agni's Philosophy に携わっていました。

ゲーム屋はもっと自分の仕事を自慢した方がいいんじゃね?とこの頃よく思います。
個人間で切磋琢磨している感じが薄い気がするし、今の学生さんにゲーム屋が魅力的に見えているとはあまり思えないし、一方で同人/インディーズのコミュニティを見ているとそのへんとてもアツく感じます。そして、こういう "熱" の影響は割と大きいと実感しています。
そうえいば去年、複数の知り合いに「twitter 見てると仕事楽しそうですね」と言われて、なんだか嬉しかったのを覚えています。


ゲーム

2012 年の後ろの方は何をやってたかといえばほとんどゲームなんですが、得られたものは大きかったので、きっと必要な時間だったのでしょう。以下は感じ入るものが多かったものたち。


宇宙船で敵を撃退しつつ銀河の奥を目指し、最後に待ち構えるボスを倒す roague like 系のゲーム。roague like 系と分類されているものの、戦闘システムがかなり独創的で、独創的過ぎて人に薦める時いつも説明に苦労しています。
いうなれば 宇宙船運用ゲーム で、例えば酸素設備が攻撃されて損壊したらやがて酸素がなくなってクルーが窒息して死んでゲームオーバーになる (ので、クルーを修理に向かわせないといけない) し、攻撃されて船内に火事が発生したら設備が壊れたりクルーがダメージを受けたりするのでドアを開けて減圧して火を消さないといけない。敵がテレポートで直接クルーをワープさせてきて白兵戦になったりすることもある。などなど、敵艦を撃つだけではなく、クルーに指示を出して船内で起きるトラブルへの対処もするゲームです。
グラフィックはインディーズの中でもしょぼい部類だし、システムも非常にとっつきが悪く、おまけにきわめて難度が高いゲームですが、適度な戦略性、忙しさと、良い感じにチャレンジ精神を煽ってくれるアンロック要素が相まって中毒性が高いゲームに仕上がっています。
これは Kickstarter で fund を得て制作されたインディーズゲームだそうで、クラウドファンディングがこんなゲームを生んで、しかもかなり高い評価を得ているのはすごいことだと思いました。


  • Crystal Conquest

各プレイヤーが所謂 tank, nuker, healer などのロールの職業につき、6 人~十数人のチームで対戦する RTS 色の強い対人専用ブラウザゲーム。Fantasy Earth がこれに非常に近いゲームらしいです(そちらは未プレイ)。
均衡している戦況をチームワークで切り崩して勝った時が本当に楽しくて、しばらくはまりこんでいました。
しかし、このゲームはアイテム課金制で、課金アイテムがキャラの強さに及ぼす影響が非常に大きく、リアルマネーをつぎ込んでガチャでレア装備を出し、それを時間をかけて強化 (レア度が高い装備ほど強化の上限が高い) することで、無料装備orそこまで時間をつぎ込んでいないプレイヤーが束になってかかっても倒せないキャラになります。
ヘビー課金プレイヤーがそうじゃないプレイヤーを単身で蹴散らしていくのをよく見るようになって、一気に冷めてやめてしまいました。自分が遊びはじめたのは正式サービス開始から 1 ヶ月も経っていない時期で、一番盛り上がる時期だったのだと思います。
課金形態やマッチングシステム次第では末永く遊べるゲームになっていたはずで、アイテム課金ゲーが行き着くディストピアを垣間見た気分になりました。


Crystal Conquest の後、対人がアツいネトゲーをやりたくなって手を出したタイトル。
対人戦目当てで始めたものの、PvE 要素が非常によくできてて、まずはそちらにはまりました。
Skyrim よりも数倍広いと思われる世界。細部までかなり作りこんであって探索してるだけでも楽しく、たまに遭遇するダイナミックイベントがいいアクセントになります。
このダイナミックイベントという要素が面白くて、例えばある街にいると海賊の集団が襲ってきて「海賊から街を守れ」というイベントが発生。防衛成功したら報酬が貰えて一定時間後また同じイベントが起きる、の繰り返しですが、防衛に失敗した場合、街が海賊に占拠されて、今度は「海賊から街を取り戻せ」というイベントが発生、という展開になります。
これは単純な例ですが、複数のイベントが連動したり、分岐したり、数十人のプレイヤーが集まらないと達成できないような大規模イベントもあったりなど、良い感じに世界が生きてる感を演出してくれます。
ダイナミックイベントはあくまで一例で、レベリング、戦闘システム等などこのゲームに感銘を受けた部分は他にも数多いのですが、ともかく昨今の大型 MMORPG はすごい進化を遂げているんだと実感させられました。
俺 Game of the Year 2012。ペース落としつつも今もプレイ中。


最近、流行りのラノベの類を全然楽しめなくなってきていて、このタイトルまで楽しめなかったらもうヲタ引退かな、とか思いながらプレイ開始したものの、大変面白くてほぼ間髪入れず 2 周目までプレイ。まだまだヲタでいられそうです。
あとこのインタビュー記事も大変面白くて、今後もノベルゲーの新境地を切り開いていく事を期待する所存。


  • モバイル / ソーシャル

iOS ゲー、Android ゲー、Facebook ゲー (主に Zynga ゲー) などにもいくつか手を出してみたんですが、どれもいまいち楽しめない。のめりこめない。
自分は一度はまったら飽きるまで徹底的に攻略し、気づいたら土日で 30 時間経過とかザラ、とかそういうプレイスタイルなので、最低限それにある程度耐える戦略性を求めるし、長時間腰を据えて遊ぶので小さなディスプレイではやりたくない。
なので、自分には昨今のモバイル/ソーシャルゲームはなかなか全力で攻略しにかかる気になれないようです。思い返せば自分は携帯ゲーム機のゲームにのめりこんだ記憶もない。
今の時代作り手としてはマズい思考なのだろうとは思いますが、どうしようもなく自分が望むゲームとはそういうものらしく、可能なかぎりそういう世界にしがみついていたい、と思わされました。


同人

職業ゲームプログラマーになって、間違いなく技術面では大きくレベルが上がりましたが、同時に単身で作品を作って完成させる力が薄らいだように感じます。仕事で C++ 書くのでプログラム書きたい欲がそちらで満たされてしまうとか、持てる選択肢が増えた分行動に移すのにより大きなエネルギーが必要になったとか、たぶんそのへんが理由なんじゃないかと思います。

そして、たまに同人方面で活躍されている方と話すと、ものすごい焦燥感、危機感、劣等感を感じます。
今の仕事は楽しいですが、満たされない部分もあるし、逆に職業ゲームプログラマーじゃなかった頃の自分は「もっとテクノロジー志向なことがやりたい!」と常々考えていた覚えもあるので、たぶん自分は両方やらねば満たされないんだと思います。
未だにこの先どうやって生きていけばいいのか思い悩む事が多いですが、さしあたって今年の最大の目標は、個人作品の完成品を出すこと、になりそうです。

atomic C83 preview +

コミケお疲れ様でした。ブースに足を運んでいただいた方々、ありがとうございました。
事前の告知通り、コミケ版に若干手を加えたものを公開します。

atomic C83 preview +
(コミケ版にはたまに数秒止まったり BGM が途切れたりするなどのバグがありましたが、それが修正されています…。あと若干高速化)


ゲーム内容は 1 年前のと全く同じで、自機に引き寄せられてくる流体を誘導して敵にぶつけて倒すというものです。自機ができることは移動だけ (1 ボタンでダッシュ)。自機が死ぬまで適当に敵が出続けます。
Radeon でも動くようになったのが最大の変化です。あとはグラフィックの改良。


流体シムを ispc による CPU 実装に切り替えた結果そこそこポータブルになったと思いますが、代償としてかなり遅くなりました。私のマシンでは以前の CUDA 実装で 80000 パーティクル超えても 60 FPS 維持できていましたが、今回の CPU 実装では 50000 くらいが限界です。やっぱり GPGPU は強かった。
しかし現在はまだ CPU 実装にしといた方が何かと無難な感じはあるので、この方針を貫く予定です。
今年こそは何かしら完成品に仕立てあげたいところ。
atomic_C83


ちなみに、ソースは現在 github に丸々公開しています。
https://github.com/i-saint/atomic
今回公開したパッケージのバージョンにはタグをつけています。最後まで公開し続けるかは悩んでいるところですが、何らかの形で欲しい人にはソースも提供する予定。



祭りの後
Untitled