Electron と Kiwi.jsを使ってゲームを作ってみた

# [Electron](http://electron.atom.io/) と [Kiwi.js](http://www.kiwijs.org/)を使ってゲームを作ってみた

前からやってみたかった。ゼロからGame Framerowkを利用したゲームの設計を学びながら作っていくのはしんどかったので、[Kiwi.jsのexampleのコード](http://examples.kiwijs.org/)を参考+改造+機能追加する形で作っていった。

[![GamePlay](http://img.youtube.com/vi/_stv5nQJCak/0.jpg)](http://www.youtube.com/watch?v=_stv5nQJCak)

静止画だけだと伝わらないのでゲームプレイ動画を撮ってみた。(無音)

https://youtu.be/_stv5nQJCak

# ソースコードを管理しているリポジトリ

https://github.com/futoase/always-gravity

# ゲームのインストール

## Mac OS X向けのバイナリ

Mac OS X 向けにはアプリケーションのバイナリイメージを用意した。

[always-gravity-1.0.0-darwin-x64.zip](https://github.com/futoase/always-gravity/releases/download/v1.0.0/always-gravity-1.0.0-darwin-x64.zip)

以上のファイルをダウンロードじ、zipファイルを解凍する。

![Screen Shot 2015-07-20 at 19.10.04.png](https://qiita-image-store.s3.amazonaws.com/0/3985/6c5ce7fd-4a52-1bbb-292f-aa3e18532e2a.png)

アプリケーションアイコンをダブルクリックすれば、アプリケーションが起動し、ゲームが開始となる。

## npmコマンドを使ってインストール

npm リポジトリに登録済みなので、以下のコマンドを実行すればOK

```
> npm install -g always-gravity
```

インストール後、always-gravityコマンドを実行すれば立ち上がる。

```
> always-gravity
```


# ゲームについて

## タイトル

元にしていたサンプルゲームが、常に物体への加速度が下方向にかかるもので、
それを元に発展させていった。安直にゲームタイトルを**Always Gravity** とした。

![game-title.png](https://qiita-image-store.s3.amazonaws.com/0/3985/54a4d91b-115d-67d4-5461-ad5f233ebb4a.png)

## 操作方法

ここでは、ゲーム開始時のキー操作について書く。
(タイトルなどではどのキーがどの操作が可能か書いてあるので割愛)

![screen-shot-my-unit.png](https://qiita-image-store.s3.amazonaws.com/0/3985/26210796-bb27-c170-7cae-1a8c1dc01988.png)


|キー|操作|
|:--:|:--|
|←|自機が左方向に回転する|
|→|自機が右方向に回転する|
|↑|自機が向いている方向に加速し、前進する|
|Z|弾を発射する。* 同時に画面上にある弾は3発まで|

## 敵

|名前|画像|自機接触時ダメージを負うか|破壊可能か|点数|
|:--:|:--:|:--:|:--:|:---:|
|cube|![cube.png](https://qiita-image-store.s3.amazonaws.com/0/3985/cdd85ed5-12a3-5c1e-2a1a-9eceb9729d9a.png)|ノーダメージ|可能|100|
|cylinder|![cylinder.png](https://qiita-image-store.s3.amazonaws.com/0/3985/3e9b3fa2-7d56-0b90-3985-8ee2332eb214.png)|ノーダメージ|可能|200|
|circle|![circle.png](https://qiita-image-store.s3.amazonaws.com/0/3985/b2344543-bb93-8b40-2f47-b8acc441c57e.png)|ダメージ 1|可能|500|
|rhombus|![rhombus.png](https://qiita-image-store.s3.amazonaws.com/0/3985/3d5aa3e8-4164-e274-c159-ec00b7026263.png)|即死|不可能|-|

### 敵を倒すには

自機から弾を発射し、敵に当てる。

* rhombusだけは、弾を弾かれてしまう。効かない。

## クリア条件

- なし
- Game Overになるまでひたすら点数を稼ぐだけ。

## ゲームオーバーの条件

![gameover.png](https://qiita-image-store.s3.amazonaws.com/0/3985/8ead2235-01c0-75f7-05ea-6bb668911e30.png)

### 体力が0になる

体力が0になった時、ゲームオーバー。
0とはるが、バーで表示されてるためわかりづらいかも。
初期値は**5**。

![hitpoint.png](https://qiita-image-store.s3.amazonaws.com/0/3985/10c6e084-23e7-06e7-7582-22037b5d4826.png)


### 加速しすぎて、加速カウントが0になった時

加速し過ぎると**SLOW DOWN!!**の表示が出る。0までカウントダウンされる前に減速しなかった場合、ゲームオーバー。

![slow-down.png](https://qiita-image-store.s3.amazonaws.com/0/3985/95140d57-5016-1d94-ac6b-e1bf532aae0d.png)


# なぜ作ったか

- もともとJavaScript製のGame Frameworkに興味があった
    - [Pixi.js](http://www.pixijs.com/)とか、[CreateJS](http://www.createjs.com/)とか。
    - JavaScript Game Frameworkについて、学習コストは低そうに感じた
        - 実行環境はブラウザだし。
    - でも、assetsの管理をしなければならなくてめんどくさいなと感じた。
        - S3にassetsを置くべきか？でかいファイルの場合転送時間かかるよな、とかめんどい...
    - Kiwi.jsはTypeScript製で、exampleの数が豊富だったので良いかなと思って選択した。
- Electronでアプリケーションを作ってみたかった
    - 流行りものだし、いじってみたかった
    - JavaScript Game Frameworkでのassets読み取りの問題については、ローカルから読み出すから大丈夫かなと判断
    - Electronは[Chromium](https://www.chromium.org/) + [io.js](https://iojs.org/en/index.html)で構成されている(atom-shell時代からだけど)だから、assetsファイルについてフォーマットどうするとか、考えなくて済みそうだと思った。

## 作ってみた結果

- 込み入ったものを作るとなると、[MonoGame](http://www.monogame.net/)(C#)などの型保証のある言語で書きたくなる。
    - MonoGameは[FEZ](http://store.steampowered.com/app/224760)が作られたことで有名。XNA互換。
- [TypeScript](http://www.typescriptlang.org/)で書けばよかったかも、と思った
    - [Kiwi.d.ts](https://github.com/gamelab/kiwi.js/blob/master/build/kiwi.d.ts)ファイルがあるのでインタフェースについては問題ない



# コードの進化

## 初期のコードはひどかった！

以下のtagのコードを見て欲しい。かなりひどい。

[Always Gravity - v0.1.0](https://github.com/futoase/always-gravity/tree/v0.1.0)

言い訳になるが、最初Kiwi.jsのexampleコードを参考にし、
GameStateオブジェクトに対してどんどんattributeを追加していく形で
コードを肥大化させてしまった！

- [こことか(ディレクトリを分けてるだけ)](https://github.com/futoase/always-gravity/tree/v0.1.0/app/src/play-state)
- [こことか(一つのオブジェクトに対してattributeを付けて、function objectをつけてる...)](https://github.com/futoase/always-gravity/tree/v0.1.0/app/src/play-state/myunit)

もはや構造が見ても頭のなかに入ってこない。
ゲームとしては動作しているのだけど、機能拡張はおろか、
昨日追加した内容が思い出せなくなるんじゃないの！？ってぐらいひどい。
どのエネミーとどれが対応付けされてるんだっけ？とかわけがわからん。
HUDは正常に表示されなくなったらどこをいじればいいんだっけ！？とか。

コードを綺麗にしなければ...

## Babelを利用してES6使って構造化するか

[Babel](http://babeljs.io/)を利用し、ES6構文を使えるようにすればclassが使える、ということは
構造化できるんじゃないだろうか？ということで早速導入してみることにした。

[Always Gravity - v0.2.0](https://github.com/futoase/always-gravity/tree/v0.2.0)

[gulpfile.js](https://github.com/futoase/always-gravity/blob/v0.2.0/gulpfile.js)でBabelをpipeするtaskを書き、吐き出す。

[ソースの構造は少しはマシ](https://github.com/futoase/always-gravity/tree/v0.2.0/app/src/model)になった。(modelとあるけどゲームでモデルというとこれは合ってるのかどうか...)

## 構造化できたしテストかけるのでは...?

構造化ができたとすれば、全てではないけどテストはかけるんじゃないだろうか。
ということで取り掛かることにした。

[Always Gravity - v0.3.0](https://github.com/futoase/always-gravity/tree/v0.3.0)

この時、[power-assert](https://github.com/power-assert-js/power-assert)、[espower](https://github.com/power-assert-js/espower), [karma](http://karma-runner.github.io/)などを利用するようになった。

[karma.conf.js](https://github.com/futoase/always-gravity/blob/v0.3.0/karma.conf.js)

少しは前進していった。

## ESLintでコードチェック

ESLintを初期からインストールするよう、package.jsonに追記していたのだけど
利用していなかったので利用するようにした。

[Always Gravity - v0.5.0](https://github.com/futoase/always-gravity/tree/v0.5.0)

JavaScriptのコーディングスタイルは、[Airbnbのスタイル](https://github.com/airbnb/javascript)を利用し、
[eslint](https://github.com/airbnb/javascript/blob/master/linters/.eslintrc)についても[JSX](https://facebook.github.io/jsx/)を除いたものを拝借したが、フロントエンド向きではなかったので、
いくつかのerror警告をwarningに変更することにした。

ESLintを通して、letばっかりだったのがconstになったり、
if elseのブロックの書き方の癖を直したり。以下はダメ。

```js
if (true) {
  // consequence
}
else {
  // alternative
}
```

これが正しい。とか。大量にエラーがでた。

```js
if (true) {
  // consequence
} else {
  // alternative
}
```

あとObjectやArrayの最終要素にケツカンマ必須とか...

```js
const status = [
  {
     name: 'hoge',
     score: 500,  // これとか
  }, // これとか
];
```

## あれこれあって

[Always Gravity - v1.0.0](https://github.com/futoase/always-gravity/tree/v1.0.0)

tag v1.0.0までには、Rhombusに対する跳弾の実装とかもした。
すでにコードを構造化していたので、実装の追加、新しいassetの追加が楽だった。

[跳弾の実装コミット](https://github.com/futoase/always-gravity/commit/e16bd23afdc7186fdefe7b06dc094784860108aa)

ES6化したことで、だいぶ良くなった。

# スタンドアロンアプリケーションとしてビルドする

Electronアプリ作成のためのビルド用ツールはいろいろなものがあるけど、ビルドして他の環境でも問題なく動くバイナリが作れるものは、electron-packagerだった([Kobito for WindowsをMacで動かす方法](http://qiita.com/suin/items/2b5bd34ce7dc5e04dbe8)を参考)。

electron-packagerを利用し、パッケージ化するスクリプトを書いた。

```js
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](https://github.com/futoase/always-gravity/blob/v1.0.1/package.json#L37)に書いておいて、
以下のコマンドを実行する。

```
> 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](https://github.com/futoase/always-gravity/blob/v1.0.0/README.md#a-list-of-references)にまとめておいた。

- [JavaScript Style Guide](https://github.com/airbnb/javascript)
- [Learn ES2015](http://babeljs.io/docs/learn-es2015/)
- [ESLint](http://eslint.org/)
- [power-assertでJavaScriptのテストをする ブラウザ編](http://efcl.info/2014/0411/res3820/)
- [Building a Package Featuring Electron as a Stand-Alone Application](http://blog.soulserv.net/building-a-package-featuring-electron-as-a-stand-alone-application/)
- [Delete files and folders (gulp.js)](https://github.com/gulpjs/gulp/blob/master/docs/recipes/delete-files-folder.md)

# 技術的なこと

## [Electron](http://electron.atom.io/)

- [Github社](https://github.com/)がリリースした[Atom](https://atom.io/)というエディタの基盤となっているatom-shellが2015年4月頃リネームしたプロダクト。
- [Chromium](https://www.chromium.org/) + [io.js](https://iojs.org/)な環境が手に入る。性格上、Chromiumが動作する環境(Windows, Mac OS X, Linuxディストリ...)で動作するクロスプラットフォームアプリが作れる
    - これが便利で、1つの環境のことを気にかければ良いのでアプリ開発が楽。
- HTML + CSS + JSでアプリをかけるのでサクッとアプリを作れる環境がある
- Gulpなどのビルドツールとの連携を行うことにより、アプリケーションのスタンドアロン版を気軽にリリースできる。

## [Kiwi.js](http://www.kiwijs.org/)

- TypeScript製のGame Framework。
- PhaserというGame Frameworkと関係があるようだ。
    - [Kiwi.jsを使ってみる(Qiita)](http://qiita.com/tkyaji/items/67f4a3976b753f9c3fcc#phaser%E3%81%A8%E3%81%AE%E9%96%A2%E4%BF%82)を参考。
- exampleが豊富にあるので、何か作りたい！という場合とっかかりやすい印象を受けた。
- Stateもcreate, preloadのあと、updateを繰り返し呼び出す形(Observerパターン)になっているので、わかりやすい。
    - [Kiwi.jsのState遷移についてはこちらを参考のこと。](http://www.kiwijs.org/how-to-use-the-state-methods/)

## [Babel](https://babeljs.io/)

- ES6(ES2015)をサポートしていない、ES5をサポートしているJavaScriptの環境上で、ES6の構文をES5に変換する。
- Babelを利用して、ES6に先行して実装されているfor..of構文やReflect APIを利用できる。
    - 細かくはBabelの[Learn ES2015](https://babeljs.io/docs/learn-es2015/)を参考のこと。

## [Gulp](http://gulpjs.com/)

- 各処理をタスク化する便利ツール。
- [Grunt](http://gruntjs.com/)とよく比較されてる。
- Gruntと異なる点は、Streamを利用し、method chainしている(pipe)中でstreamの受け渡しをすることで処理を書ける点。
    - Gulp pluginがStreamを利用している点については[Dealing with streams](https://github.com/gulpjs/gulp/blob/v3.9.0/docs/writing-a-plugin/dealing-with-streams.md)を参考のこと。
    - [gulp API docs](https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulp-api-docs)
- 今回は[gulp-karma](https://github.com/karma-runner/gulp-karma)を利用したかったのでgulpを選択した。

## [power-assert](https://github.com/power-assert-js/power-assert)

- 前から気になっていた、アサーションでチェックするテスト用のライブラリ。
- 今回はmochaと組み合わせて利用してみた。
- 今回のアプリではテストの量が少なかったが、テスト書いてて楽しかった。
    - ゲームのテストってどれぐらいのものを書くべきなのだろうか...(Modelはいいとして、Viewというかスプライトの変化のテストとかどこまでやるか)
- 今回はBabelとの併用だったので、[espower-babel](https://github.com/power-assert-js/espower-babel)を使った。

## [Karma](https://github.com/karma-runner/karma)

- ブラウザに対して指定したJavaScriptファイルを読み込ませたうえで、mochaなどのTesting Frameworkを実行する
- Karmaを利用した理由
    - [power-assertでJavaScriptのテストをする ブラウザ編](http://efcl.info/2014/0411/res3820/)にKarmaでのpower-assertの使い方が載っていたこと
    - [karma-electron-launcher](https://github.com/lele85/karma-electron-launcher)というものがあり、Electronを起動し、Electron内でテストが可能にするのが容易だったこと
- karma.conf.jsに変換対象となるファイルを書いていくのだが、記述が完結で楽だった。
    - [Always Gravityのkarma.conf.jsを参考のこと](https://github.com/futoase/always-gravity/blob/v1.0.0/karma.conf.js)

- karma + mocha + power-assertという形で構成しているので、以下のnpmモジュールを利用している
    - [karma-mocha](https://github.com/karma-runner/karma-mocha)
    - [karma-espower-preprocessor](https://github.com/power-assert-js/karma-espower-preprocessor)
    - [karma-babel-preprocessor](https://github.com/babel/karma-babel-preprocessor)

# 雑感

- Electronを使った〜と言っても実質ゲームをレンダリングするための
Stageとしか利用していない。もう少しレンダリング側ガッツリ書くべきかも。
- フロントエンドのJavaScriptのテストでは、すでにオブジェクトがロード済みである前提テストを書かなければならないことが多い、というのがあるのでどうしたものかとおもったが、[Karma](http://karma-runner.github.io/)や[testem](https://github.com/airportyh/testem)を利用して、ブラウザを立ち上げて実行するのだな〜というので勉強になった
- [remote module](https://github.com/atom/electron/blob/v0.30.0/docs/api/remote.md)を利用すれば、レンダリング側に実装を寄せることが可能なので、
そうなるとまた違ったアプローチで実装が必要になるのかなと考えたりした。
- [ipc module](https://github.com/atom/electron/blob/v0.30.0/docs/api/ipc-main-process.md)はアプリを終了するときにしか利用していないが、うまくやれば既存のWebアプリケーションに対してAPI呼び出しをElectronアプリのメインプロセスで実行して、
通常のブラウザを利用しているのとは異なるユーザ体験を与えることは可能なんじゃないかと考えたりした。
- 当たり前なのだけど、アプリの実装は学びの段階であるプロトタイプで作成したものをリリースできるようにするために頑張って構造化して、形にしていく作業は骨が折れる。
- ゲームの実装はexampleを参考にしつつ1.5週間ぐらい(1日1-2時間程度つかって)でできたものの、構造の見直し・テストを書く・ES6ではアレってどうかけるのかとかやってるほうが時間かかった。かける時間は少なかったとはいえ1ヶ月以上かかってしまった。楽しかったけど。
- ポーズ機能つけとけばよかったな
- デバッグ時、無敵状態とかそういうデバッグ用の工夫をしなかったので動作検証で死にまくって大変だった。