はじめに
この記事は、UUUM Advent Calendar 2018 7日目です。
人生紆余曲折あってUUUM
に12月に入社した新入社員です優しくしてください。
自鯖のDocker Swarm
化を書こうかなーって思ったけど、まとまらなかったのでとりあえずゲームエンジンについて書いていこうと思ってます。
懇切丁寧にブログ記事書くのはめんどくさいので適当な解説しか書いてないし、今後も書かないんじゃないかなぁと思っています。
repoと使用ライブラリ
GitHub: https://github.com/takeokunn/takengine
- pixi.js
- stats.js
- keycode
- webpack
- babel
- eslint
動機
友達「え、エンジニアなのにエンジン作ったことないの????エンジニアなのに????」
僕「とても辛い」
ねねっち「 エ ン ジ ン に 頼 ら ず 一 か ら プ ロ グ ラ ミ ン グ し る ん だ ぁ 〜 」
僕「ねねっちでもできるならいけるやろ!!!!!」
僕「一応これでも金もらって(※要出典)働いているエンジニアだし、関数型チックに書いてクオリティ高いエンジン作るぞ!!!」
参考にしたもの
今回、参考にしたゲームエンジンは以下のyoutubeの動画です。
Functional Game Engine Design for the Web - Alex Kehayias
https://www.youtube.com/watch?v=TW1ie0pIO_E&t=2s
ClojureScript
で書かれているレポジトリ
alexkehayias/chocolatier
レギュレーション
ただ作るだけでは面白みがないので、縛りを入れました。
ClojureScriptで書かれたalexkehayias/chocolatierを一度もビルドせずに作る
これにより難易度が上がるので楽しさが倍増します。
進捗
・設計思想を固める
・プロジェクトをつくる ← 今回の記事はここまで
・ゲームループをつくる
・初期stateを生成し、ゲームループに合わせて更新する処理を書く
・グラフィックライブラリを導入する
・入力などのイベントを取得する ← イマココ
・当たり判定などのイベントを発火させる
・シーン管理をする
・チャリ走レベルのクソゲーを雑につくる
・シューティングゲームレベルのそれなりのものをつくる
・ゲームを自動でプレイするプログラムを書く
・ゲームを自動でクリアするプログラムを書く
描画して入力に合わせて画像を動かすところまでは動いたヤッター
設計思想
Entity Component Systemをベースに作っています。
オブジェクト指向というよりはデータ指向にコードを書くことができ、美しく書くことが出来て楽しいです。
ECSの説明書こうと筆を進めていたのですが先駆者の記事が素晴らしかったのでめんどくさくなりました。
以下を参考にしたりググってください。
【Unity】Unity 2018のEntity Component System(通称ECS)について(1)
エンティティ・コンポーネント・システム wiki
このプロジェクトではこんな感じでEntity/Component/Systemを定義しています。
// 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: {}
}
]
}
};
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: () => {}
}
}
},
];
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
で回してごにょごにょするとそれっぽい動きをしてくれます。
プロジェクトをつくる
.
├── 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
でjavascriptをbundleしてpublicに吐きだし、 webpack-serve
でサーバーを立ち上げています。
今回はここまで
読んだことも書いたこともないClojureScript
を解読するの、技術的体力がつきますね。
ClojureDocs 死ぬほど読みやすいので他の言語も参考にしてほしいです。
UUUMでエンジニアをするとヒルズから見える絶景を堪能できます!
仕事でゲームエンジンは開発しないけどね
詳しくはこちら →→→→→→ UUUM攻殻機動隊の紹介