JavaScriptで関数型ゲームエンジンを作るよ！part1

## はじめに
この記事は、[UUUM Advent Calendar 2018](https://qiita.com/advent-calendar/2018/uuum) 7日目です。
人生紆余曲折あって`UUUM`に12月に入社した新入社員です優しくしてください。
自鯖の`Docker Swarm`化を書こうかなーって思ったけど、まとまらなかったのでとりあえずゲームエンジンについて書いていこうと思ってます。

懇切丁寧にブログ記事書くのはめんどくさいので適当な解説しか書いてないし、今後も書かないんじゃないかなぁと思っています。

## repoと使用ライブラリ

GitHub: https://github.com/takeokunn/takengine

* pixi.js
* stats.js
* keycode
* webpack
* babel
* eslint

## 動機
友達「え、エンジニアなのにエンジン作ったことないの？？？？エンジニアなのに？？？？」
僕「とても辛い」
[ねねっち](https://dic.pixiv.net/a/%E6%A1%9C%E3%81%AD%E3%81%AD)「　エ　ン　ジ　ン　に　頼　ら　ず　一　か　ら　プ　ロ　グ　ラ　ミ　ン　グ　し　る　ん　だ　ぁ　〜　」　

<img width="518" alt="Screen Shot 2018-12-06 at 14.44.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/125325/23a908fa-f023-14ad-e8ab-f9a5bbe9f216.png">

僕「ねねっちでもできるならいけるやろ！！！！！」
僕「一応これでも金もらって(※要出典)働いているエンジニアだし、関数型チックに書いてクオリティ高いエンジン作るぞ！！！」

## 参考にしたもの
今回、参考にしたゲームエンジンは以下のyoutubeの動画です。

`Functional Game Engine Design for the Web - Alex Kehayias`
[https://www.youtube.com/watch?v=TW1ie0pIO_E&t=2s](https://www.youtube.com/watch?v=TW1ie0pIO_E&t=2s)


`ClojureScript`で書かれているレポジトリ
[alexkehayias/chocolatier](https://github.com/alexkehayias/chocolatier)

## レギュレーション

ただ作るだけでは面白みがないので、縛りを入れました。

**ClojureScriptで書かれた[alexkehayias/chocolatier](https://github.com/alexkehayias/chocolatier)を一度もビルドせずに作る**

これにより難易度が上がるので楽しさが倍増します。

## 進捗

・設計思想を固める
・プロジェクトをつくる ← **今回の記事はここまで**
・ゲームループをつくる
・初期stateを生成し、ゲームループに合わせて更新する処理を書く
・グラフィックライブラリを導入する
・入力などのイベントを取得する ← **イマココ**
・当たり判定などのイベントを発火させる
・シーン管理をする
・チャリ走レベルのクソゲーを雑につくる
・シューティングゲームレベルのそれなりのものをつくる
・ゲームを自動でプレイするプログラムを書く
・ゲームを自動でクリアするプログラムを書く



描画して入力に合わせて画像を動かすところまでは動いたヤッター
<img width="1680" alt="Screen Shot 2018-12-06 at 16.00.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/125325/cb3d89a1-ef88-eb18-6f54-99d5d9a5b5f9.png">



## 設計思想

Entity Component Systemをベースに作っています。
オブジェクト指向というよりはデータ指向にコードを書くことができ、美しく書くことが出来て楽しいです。
ECSの説明書こうと筆を進めていたのですが先駆者の記事が素晴らしかったのでめんどくさくなりました。
以下を参考にしたりググってください。

[【Unity】Unity 2018のEntity Component System（通称ECS）について（１）](http://tsubakit1.hateblo.jp/entry/2018/03/25/180203)
[エンティティ・コンポーネント・システム wiki](https://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%B3%E3%83%86%E3%82%A3%E3%83%86%E3%82%A3%E3%83%BB%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%83%BB%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0)

このプロジェクトではこんな感じでEntity/Component/Systemを定義しています。

```javascript:Entity周りのコード
// src/game/entity.js
import { player } from 'entities';

export const entity_state = (loader, resources, stage) => ([
    {
        type: 'entity',
        opts: player.create('player1', loader, resources['player'], stage)
    }
]);

// src/entities/player.js
import { pixi } from 'engine_utils';
import { position, renderable, moveable } from 'engine_components';

export const create = (uid, loader, resource, stage) => {
    return {
        uid: uid,
        components: [
            {
                uid: 'sprite',
                state: renderable.mk_sprite_state(loader, stage, resource.name)
            },
            {
                uid: 'position',
                state: position.mk_position_state(20, 20, 0, 0, 0)
            },
            {
                uid: 'controller',
                state: {}
            }
        ]
    }
};

```


```javascript:Component周りのコード
import { animate, attack, controller, damage, ephemeral, moveable, position, renderable, text } from 'engine_components';

export const component_state = [
    {
        type: 'component',
        opts: {
            uid: 'position',
            component: {
                fn: position.fn,
                select_systems: [],
                select_components: ['controller'],
                subscriptions: [],
                cleanup_fn: () => {}
            }
        }
    },
    {
        type: 'component',
        opts: {
            uid: 'controller',
            component: {
                fn: controller.react_to_input,
                select_systems: ['key_input'],
                select_components: [],
                subscriptions: [],
                cleanup_fn: () => {}
            }
        }
    },
    {
        type: 'component',
        opts: {
            uid: 'sprite',
            component: {
                fn: renderable.sprite_fn,
                select_systems: [],
                select_components: ['position'],
                subscriptions: [],
                cleanup_fn: () => {}
            }
        }
    },
    {
        type: 'component',
        opts: {
            uid: 'animate',
            component: {
                fn: animate.fn,
                select_systems: [],
                select_components: ['action'],
                subscriptions: [],
                cleanup_fn: () => {}
            }
        }
    },
    {
        type: 'component',
        opts: {
            uid: 'text_sprite',
            component: {
                fn: state => state,
                select_systems: [],
                select_components: ['position', 'text'],
                subscriptions: [],
                cleanup_fn: () => {}
            }
        }
    },
    {
        type: 'component',
        opts: {
            uid: 'movement',
            component: {
                fn: state => state,
                select_systems: [],
                select_components: [],
                subscriptions: ['move_change', 'collision'],
                cleanup_fn: () => {}
            }
        }
    },
];
```

```javascript:system周りのコード
import { audio, collision, event, input, meta, renderer, replay, tiles } from 'engine_systems';

export const system_state = [
    {
        type: 'system',
        opts: {
            uid: 'events',
            fn: event.system
        }
    },
    {
        type: 'system',
        opts: {
            uid: 'key_input',
            fn: input.system
        }
    },
    {
        type: 'system',
        opts: {
            uid: 'meta',
            fn: meta.system
        }
    },
    {
        type: 'system',
        opts: {
            uid: 'tiles',
            fn: tiles.system
        }
    },
    {
        type: 'system',
        opts: {
            uid: 'render',
            fn: renderer.system
        }
    },
    {
        type: 'system',
        opts: {
            uid: 'audio',
            fn: audio.system
        }
    }
];
```

こんな風にEntity/Component/Sysytemを定義し、State化して`game loop`で回してごにょごにょするとそれっぽい動きをしてくれます。

## プロジェクトをつくる
```shell:2018年12月6日現在
.
├── LICENSE
├── README.md
├── package.json
├── public
│   ├── bundle.js
│   ├── img
│   │   └── icon.png
│   └── index.html
├── src
│   ├── engine
│   │   ├── components
│   │   │   ├── animate.js
│   │   │   ├── attack.js
│   │   │   ├── controller.js
│   │   │   ├── damage.js
│   │   │   ├── ephemeral.js
│   │   │   ├── index.js
│   │   │   ├── moveable.js
│   │   │   ├── position.js
│   │   │   ├── renderable.js
│   │   │   └── text.js
│   │   ├── core
│   │   │   ├── core.js
│   │   │   ├── ecs
│   │   │   │   ├── get.js
│   │   │   │   ├── index.js
│   │   │   │   └── make.js
│   │   │   ├── events.js
│   │   │   └── index.js
│   │   ├── systems
│   │   │   ├── audio.js
│   │   │   ├── collision.js
│   │   │   ├── event.js
│   │   │   ├── index.js
│   │   │   ├── input.js
│   │   │   ├── meta.js
│   │   │   ├── renderer.js
│   │   │   ├── replay.js
│   │   │   └── tiles.js
│   │   └── utils
│   │       ├── index.js
│   │       ├── pixi.js
│   │       └── stats.js
│   ├── entities
│   │   ├── index.js
│   │   └── player.js
│   ├── game
│   │   ├── component.js
│   │   ├── entity.js
│   │   ├── index.js
│   │   ├── renderer.js
│   │   ├── scene.js
│   │   ├── system.js
│   │   └── tilemap.js
│   ├── main.js
│   └── manifest.json
└── webpack.config.js
```

こんな感じでDirectory/Fileを作りました。

[`Webpack`](https://www.npmjs.com/package/webpack) でjavascriptをbundleしてpublicに吐きだし、 [`webpack-serve`](https://www.npmjs.com/package/webpack-serve) でサーバーを立ち上げています。


## 今回はここまで

読んだことも書いたこともない`ClojureScript`を解読するの、技術的体力がつきますね。
[ClojureDocs](https://clojuredocs.org/) 死ぬほど読みやすいので他の言語も参考にしてほしいです。


UUUMでエンジニアをするとヒルズから見える絶景を堪能できます！
仕事でゲームエンジンは開発しないけどね
詳しくはこちら　→→→→→→　[UUUM攻殻機動隊の紹介](http://system.blog.uuum.jp/entry/2018/12/01/132950)