Emacsで3DなマルチプレイヤーなFPSを作ってみた
関西Emacsで発表したデモです。
まず、「FPS」は CoD MW のようなシューティングとかではなくて(それは是非やりたかったのですが)、一人称散歩(First Person Sanpo)プログラムです。FPSは釣りです。ごめんなさい。
(2011/05/06 追記: Vimにも出来てました!! 3D in Vim — KaoriYa)
動かし方:シングルプレーヤー
必要な物:
- 64bit版Emacs23.x
- 22でも動くかも知れません
- 32bitだと整数桁あふれするそうです
- banner
- deferred.el, concurrent.el
- 非同期ライブラリ
- matrix.el
- 簡易行列計算
- 3dmaze.el (本体)
各elispファイルは、auto-install.elで以下の式を評価して入れるか、ダウンロードしてload-pathの場所に置いてください。ダウンロードした場合は、なるべく高速に動作させるためにバイトコンパイルしてください。(多分2倍くらい速くなります)
;; auto-installを使う場合 (auto-install-from-url "https://github.com/kiwanami/emacs-deferred/raw/master/deferred.el") (auto-install-from-url "https://github.com/kiwanami/emacs-deferred/raw/master/concurrent.el") (auto-install-from-url "https://github.com/kiwanami/emacs-3d-demo/raw/master/matrix.el") (auto-install-from-url "https://github.com/kiwanami/emacs-3d-demo/raw/master/3dmaze.el")
- ダウンロード
準備出来たら、以下のように操作します。
- M-x load-library RET / 3dmaze RET
- Scratchバッファで (require '3dmaze) を評価しても可
- 迷路を適当なバッファに描く
- 迷路のバッファで M-x d3m:open-maze-buffer
初期位置はランダムで決まりますので、壁の方を向いていたら真っ青から始まるかも知れません。あわてず横を向いてください。
あと、行頭行末スペース強調をしている人は、止めておいてください。
キーバインド
キー | 動作 |
---|---|
←→ | 左右に回転する |
↑↓ | 前に進む、後ろに進む |
a,s | 左右に進む |
b | 後ろを向く |
m | 2Dマップ表示のトグル |
終了は kill-buffer (C-x k) してください。
迷路について
「#」や「*」は壁になります。スペースは道になります。
アルファベットを書くと、アルファベットのオブジェクトになります。迷路バッファとして適当なソースファイルとかも指定できますが、オブジェクトが多いと重くなりますので注意してください。
アルファベット以外の文字(正確にはbannerが表示できない文字)を含むとエラーになるかも知れません。
動かし方:マルチプレーヤー
シングルプレーヤー版が動けば、こちらもすぐに動くと思いますが、MacのEmacs22だとIPv6の関係でサーバーになれないかもという情報があります。
シングルプレーヤー版に加えて、サーバーは server-maze.el をインストールしてください。クライアント側は client-maze.el をインストールしてください。もちろん、両方入れて自作自演も可能です。
;; auto-installを使う場合 (auto-install-from-url "https://github.com/kiwanami/emacs-3d-demo/raw/master/server-maze.el") (auto-install-from-url "https://github.com/kiwanami/emacs-3d-demo/raw/master/client-maze.el")
- ダウンロード
サーバーの起動
カレントディレクトリの迷路ファイル(maze.txt)を読み込みますので、ホームディレクトリに迷路ファイルを置いてください。その後、Scratchバッファで以下を評価すると起動します。
(require 'server-maze) (setq ssm:server-address "192.168.1.1") ; ←自分のマシンのIPv4アドレス (ssm:server-start)
起動すると、以下のような画面が表示されます。
サーバーの画面
プレーヤー(クライアント)の起動
スクラッチバッファから以下を評価してください。
(require 'client-maze) (csm:client-start)
そうすると、以下のような画面が出てきます。
接続情報画面
「Name」は適当なアルファベット、「Server Address」はサーバーのIPアドレスやホスト名、「Server Port」は8765(デフォルトの場合)を入れてください。
OKを押して、うまくいくと迷路画面が出てきます。
操作の仕方はシングルプレーヤーと変わりません。
他人がどっちを向いているかは色で分かります。
- 赤:こっちを向いているとき
- 黄:横を向いているとき
- 緑:向こうを向いているとき
散歩ゲーなので、撃つ殴るなどのインタラクションは出来ません。仮想空間でのEmacs同士のリアルなコネクションを感じながら和んでください。
なんだこれ
目的
ちょっとEmacsの本気を調べてみようということでやりました。
調べている途中で、EmacsのVMやバイトコンパイルの内容まで興味が出てきたので、VMインストラクション表を作りました。中の人にとってもあった方が便利だと思うのですが、今後もメンテナンスが続くと良いですね。
技術いろいろ
3D表現方法
元ネタは米チャ氏の名作激走ラビリンスからです。MSXではSCREEN1でぐりぐりやるのが当時の常套手段でした。
3Dはポリゴンを計算しているわけではなくて、レイトレーシングのように画面上の各点から透視方向へのスキャンを行って、壁や床を描画しています。
画面の描画は、画面全体をスペースで埋めて、背景色を一つ一つセットすることで表現しています。裏画面としてvectorで配列を持っておき、3D描画はそのvectorに対して行って、最後にバッファに転送しています。
文字
コマンドラインからbannerを実行してフォント情報を取ってきて、それを拡大縮小することで文字を書いています。計算に余裕がなかったので向きによる傾きなどは考慮してないです。
前後関係や壁の裏に隠れたりするアルゴリズムは、Zバッファを使っています。先の裏画面vectorと一緒にZ情報も持っています。
間に合えばTwitterからアイコンを取ってきて表示しようとか思っていたのですが、時間がありませんでした。技術的には検証していますので、 straightfoward に出来ると思います。
キー操作
普通にキーバインドから描画するとEmacsが固まってしまうので、キー入力はイベントを通知するだけにして、描画側は非同期にイベントを拾って処理するようにしました。これにより、ある程度なめらかなキー入力移動が実現できました。
通信
通信はTCPストリームとS式のやりとりを concurrent.el のイベント通知でラップして、メッセージパッシングによる非同期通信で行っています。通信フォーマットがS式だとやっぱり楽ですね。
今回はこの方法がすごくうまくマッチしました。プログラム的にもシンプルになり、非同期なので各クライアントとサーバーがお互いを気にせずに自分のペースで処理を進めることが出来るようになりました。
いろいろ実験してみましたが、特定のクライアントがもたついても全体に影響が出ないところがおもしろかったです。全然規模とか比較になりませんが、MMOとかこんな風になっているのかなとか考えたりしました。
ベンチ取って最適化
最適化は定石通り、一度全体が動くようになってから行いました。大体以下のようなことをやりました。
- 幾何計算のキャッシュ(vectorへのアクセス)
- Emacs上の計算は遅いので、出来るだけ先に計算しておく
- インスタンス(vector)キャッシュ
- インスタンス生成すると遅いが、1.2倍程度しか違わない
- ほとんどの計算を固定小数点演算で行う
- 浮動小数点演算より整数演算の方が6倍程度早かったため
- faceの色文字列のキャッシュ
- ある程度種類を絞り込んで、テーブルから文字列を引いてくる。無限に色があると辛い。
後はバイトコンパイル結果を見て、ある程度VMの動きを考えながらコードを削っていきました。
まだまだ最適化・高速化の余地はあると思いますが、自分の中である程度目標が達成されたので、もうこれ以上はしないと思います。