Electron と Kiwi.jsを使ってゲームを作ってみた
前からやってみたかった。ゼロからGame Framerowkを利用したゲームの設計を学びながら作っていくのはしんどかったので、Kiwi.jsのexampleのコードを参考+改造+機能追加する形で作っていった。
静止画だけだと伝わらないのでゲームプレイ動画を撮ってみた。(無音)
ソースコードを管理しているリポジトリ
ゲームのインストール
Mac OS X向けのバイナリ
Mac OS X 向けにはアプリケーションのバイナリイメージを用意した。
always-gravity-1.0.0-darwin-x64.zip
以上のファイルをダウンロードじ、zipファイルを解凍する。
アプリケーションアイコンをダブルクリックすれば、アプリケーションが起動し、ゲームが開始となる。
npmコマンドを使ってインストール
npm リポジトリに登録済みなので、以下のコマンドを実行すればOK
> npm install -g always-gravity
インストール後、always-gravityコマンドを実行すれば立ち上がる。
> always-gravity
ゲームについて
タイトル
元にしていたサンプルゲームが、常に物体への加速度が下方向にかかるもので、
それを元に発展させていった。安直にゲームタイトルをAlways Gravity とした。
操作方法
ここでは、ゲーム開始時のキー操作について書く。
(タイトルなどではどのキーがどの操作が可能か書いてあるので割愛)
キー | 操作 |
---|---|
← | 自機が左方向に回転する |
→ | 自機が右方向に回転する |
↑ | 自機が向いている方向に加速し、前進する |
Z | 弾を発射する。* 同時に画面上にある弾は3発まで |
敵
名前 | 画像 | 自機接触時ダメージを負うか | 破壊可能か | 点数 |
---|---|---|---|---|
cube | ノーダメージ | 可能 | 100 | |
cylinder | ノーダメージ | 可能 | 200 | |
circle | ダメージ 1 | 可能 | 500 | |
rhombus | 即死 | 不可能 | - |
敵を倒すには
自機から弾を発射し、敵に当てる。
- rhombusだけは、弾を弾かれてしまう。効かない。
クリア条件
- なし
- Game Overになるまでひたすら点数を稼ぐだけ。
ゲームオーバーの条件
体力が0になる
体力が0になった時、ゲームオーバー。
0とはるが、バーで表示されてるためわかりづらいかも。
初期値は5。
加速しすぎて、加速カウントが0になった時
加速し過ぎると**SLOW DOWN!!**の表示が出る。0までカウントダウンされる前に減速しなかった場合、ゲームオーバー。
なぜ作ったか
- もともとJavaScript製のGame Frameworkに興味があった
- Electronでアプリケーションを作ってみたかった
作ってみた結果
- 込み入ったものを作るとなると、MonoGame(C#)などの型保証のある言語で書きたくなる。
- MonoGameはFEZが作られたことで有名。XNA互換。
-
TypeScriptで書けばよかったかも、と思った
- Kiwi.d.tsファイルがあるのでインタフェースについては問題ない
コードの進化
初期のコードはひどかった!
以下のtagのコードを見て欲しい。かなりひどい。
言い訳になるが、最初Kiwi.jsのexampleコードを参考にし、
GameStateオブジェクトに対してどんどんattributeを追加していく形で
コードを肥大化させてしまった!
もはや構造が見ても頭のなかに入ってこない。
ゲームとしては動作しているのだけど、機能拡張はおろか、
昨日追加した内容が思い出せなくなるんじゃないの!?ってぐらいひどい。
どのエネミーとどれが対応付けされてるんだっけ?とかわけがわからん。
HUDは正常に表示されなくなったらどこをいじればいいんだっけ!?とか。
コードを綺麗にしなければ...
Babelを利用してES6使って構造化するか
Babelを利用し、ES6構文を使えるようにすればclassが使える、ということは
構造化できるんじゃないだろうか?ということで早速導入してみることにした。
gulpfile.jsでBabelをpipeするtaskを書き、吐き出す。
ソースの構造は少しはマシになった。(modelとあるけどゲームでモデルというとこれは合ってるのかどうか...)
構造化できたしテストかけるのでは...?
構造化ができたとすれば、全てではないけどテストはかけるんじゃないだろうか。
ということで取り掛かることにした。
この時、power-assert、espower, karmaなどを利用するようになった。
少しは前進していった。
ESLintでコードチェック
ESLintを初期からインストールするよう、package.jsonに追記していたのだけど
利用していなかったので利用するようにした。
JavaScriptのコーディングスタイルは、Airbnbのスタイルを利用し、
eslintについてもJSXを除いたものを拝借したが、フロントエンド向きではなかったので、
いくつかのerror警告をwarningに変更することにした。
ESLintを通して、letばっかりだったのがconstになったり、
if elseのブロックの書き方の癖を直したり。以下はダメ。
if (true) {
// consequence
}
else {
// alternative
}
これが正しい。とか。大量にエラーがでた。
if (true) {
// consequence
} else {
// alternative
}
あとObjectやArrayの最終要素にケツカンマ必須とか...
const status = [
{
name: 'hoge',
score: 500, // これとか
}, // これとか
];
あれこれあって
tag v1.0.0までには、Rhombusに対する跳弾の実装とかもした。
すでにコードを構造化していたので、実装の追加、新しいassetの追加が楽だった。
ES6化したことで、だいぶ良くなった。
スタンドアロンアプリケーションとしてビルドする
Electronアプリ作成のためのビルド用ツールはいろいろなものがあるけど、ビルドして他の環境でも問題なく動くバイナリが作れるものは、electron-packagerだった(Kobito for WindowsをMacで動かす方法を参考)。
electron-packagerを利用し、パッケージ化するスクリプトを書いた。
var path = require('path');
var packager = require('electron-packager');
var packageJson = require(path.resolve(__dirname, '../app/package.json'));
var option = {
'dir': path.resolve(__dirname, '../app'),
'name': packageJson.name,
'platform': 'darwin',
'arch': 'x64',
'version': '0.30.0',
'out': path.resolve(__dirname, '../release'),
'icon': path.resolve(__dirname, '../always-gravity.icns'),
'app-version': packageJson.version,
'overwrite': true,
'asar': true,
};
packager(option, function (err, appPath) {
if (err) {
console.log('BuildError! :' + err);
process.exit(1);
} else {
console.log('Build to -> ' + appPath);
}
});
このスクリプトを利用するよう、package.jsonのscriptsに書いておいて、
以下のコマンドを実行する。
> npm run package
とコマンドを打つと、パッケージとしてreleaseディレクトリ以下に実行可能な形のバイナリを作成してくれる。
アイコン指定(Mac OS Xの場合はicnsファイル)も可能で楽だった。
Windowsとか
Mac OS X上でしか動作確認を今はしてないが、Windowsでも
環境が整ったらアプリケーションビルドを試してみたい。
開発に関連したツール・サービス
URL | 説明 |
---|---|
http://www.bfxr.net/ | 効果音を作成。弾の発射音などをこれで作った |
http://www.piskelapp.com/ | ドット絵を作成するのに利用。スタンドアロン版アプリもある。 |
https://iconverticons.com/online/ | icnsファイル(Mac OS X向けのアプリアイコン作成)を作成する |
http://opengameart.org/ | 今回ゲームで利用したBGMはここで探してきた。 |
https://soundation.com/ | 今回は利用しなかったが、オンライン上でパターンを登録することでBGMを作成することができるサービス |
参考にしたブログ記事など
README.mdにまとめておいた。
- JavaScript Style Guide
- Learn ES2015
- ESLint
- power-assertでJavaScriptのテストをする ブラウザ編
- Building a Package Featuring Electron as a Stand-Alone Application
- Delete files and folders (gulp.js)
技術的なこと
Electron
- Github社がリリースしたAtomというエディタの基盤となっているatom-shellが2015年4月頃リネームしたプロダクト。
-
Chromium + io.jsな環境が手に入る。性格上、Chromiumが動作する環境(Windows, Mac OS X, Linuxディストリ...)で動作するクロスプラットフォームアプリが作れる
- これが便利で、1つの環境のことを気にかければ良いのでアプリ開発が楽。
- HTML + CSS + JSでアプリをかけるのでサクッとアプリを作れる環境がある
- Gulpなどのビルドツールとの連携を行うことにより、アプリケーションのスタンドアロン版を気軽にリリースできる。
Kiwi.js
- TypeScript製のGame Framework。
- PhaserというGame Frameworkと関係があるようだ。
- exampleが豊富にあるので、何か作りたい!という場合とっかかりやすい印象を受けた。
- Stateもcreate, preloadのあと、updateを繰り返し呼び出す形(Observerパターン)になっているので、わかりやすい。
Babel
- ES6(ES2015)をサポートしていない、ES5をサポートしているJavaScriptの環境上で、ES6の構文をES5に変換する。
- Babelを利用して、ES6に先行して実装されているfor..of構文やReflect APIを利用できる。
- 細かくはBabelのLearn ES2015を参考のこと。
Gulp
- 各処理をタスク化する便利ツール。
- Gruntとよく比較されてる。
- Gruntと異なる点は、Streamを利用し、method chainしている(pipe)中でstreamの受け渡しをすることで処理を書ける点。
- Gulp pluginがStreamを利用している点についてはDealing with streamsを参考のこと。
- gulp API docs
- 今回はgulp-karmaを利用したかったのでgulpを選択した。
power-assert
- 前から気になっていた、アサーションでチェックするテスト用のライブラリ。
- 今回はmochaと組み合わせて利用してみた。
- 今回のアプリではテストの量が少なかったが、テスト書いてて楽しかった。
- ゲームのテストってどれぐらいのものを書くべきなのだろうか...(Modelはいいとして、Viewというかスプライトの変化のテストとかどこまでやるか)
- 今回はBabelとの併用だったので、espower-babelを使った。
Karma
-
ブラウザに対して指定したJavaScriptファイルを読み込ませたうえで、mochaなどのTesting Frameworkを実行する
-
Karmaを利用した理由
- power-assertでJavaScriptのテストをする ブラウザ編にKarmaでのpower-assertの使い方が載っていたこと
- karma-electron-launcherというものがあり、Electronを起動し、Electron内でテストが可能にするのが容易だったこと
-
karma.conf.jsに変換対象となるファイルを書いていくのだが、記述が完結で楽だった。
-
karma + mocha + power-assertという形で構成しているので、以下のnpmモジュールを利用している
雑感
- Electronを使った〜と言っても実質ゲームをレンダリングするための
Stageとしか利用していない。もう少しレンダリング側ガッツリ書くべきかも。 - フロントエンドのJavaScriptのテストでは、すでにオブジェクトがロード済みである前提テストを書かなければならないことが多い、というのがあるのでどうしたものかとおもったが、Karmaやtestemを利用して、ブラウザを立ち上げて実行するのだな〜というので勉強になった
-
remote moduleを利用すれば、レンダリング側に実装を寄せることが可能なので、
そうなるとまた違ったアプローチで実装が必要になるのかなと考えたりした。 -
ipc moduleはアプリを終了するときにしか利用していないが、うまくやれば既存のWebアプリケーションに対してAPI呼び出しをElectronアプリのメインプロセスで実行して、
通常のブラウザを利用しているのとは異なるユーザ体験を与えることは可能なんじゃないかと考えたりした。 - 当たり前なのだけど、アプリの実装は学びの段階であるプロトタイプで作成したものをリリースできるようにするために頑張って構造化して、形にしていく作業は骨が折れる。
- ゲームの実装はexampleを参考にしつつ1.5週間ぐらい(1日1-2時間程度つかって)でできたものの、構造の見直し・テストを書く・ES6ではアレってどうかけるのかとかやってるほうが時間かかった。かける時間は少なかったとはいえ1ヶ月以上かかってしまった。楽しかったけど。
- ポーズ機能つけとけばよかったな
- デバッグ時、無敵状態とかそういうデバッグ用の工夫をしなかったので動作検証で死にまくって大変だった。