今作ってるゲームエンジンについて
皆さんこんにちは、Kazakami_9です。
この記事はKMC Advent Calendar 2018 - Adventarの23日目の記事です。
先日はかたおーしゃんさんの
kaptambns.hatenadiary.jp
になりました。
はじめに
皆さんゲーム制作ってしたことありますか?ゲームを作るとき何かしらのゲームエンジンやツールを使うことが多いと思います。私は2012年に大学に入ってからサークルでXNAを使ってゲームを作ろうとしていました。しかし、XNAを頑張って習得していたさなか突如XNAが死亡してしまいます。このことによって私は既存のツールに依存することにトラウマのようなものを感じるようになりました。その結果、自分でゲームエンジンを作る・オープンソースとして公開し誰でも維持できるようにすることが目標となりました。始めはc++/OpenGLで書いていこうとしましたが、プレイヤにダウンロード・インストールを意識させないブラウザゲームに魅力を感じTypeScript/WebGLで開発していくこととなりました。名前はnew gameの登場人物桜ねねさんに由来し、「ねねエンジン」としています。
概要
ねねエンジンは描画ライブラリとしてthree.js - Javascript 3D libraryを、物理演算ライブラリとしてCannon.jsを利用し、TypeScriptで記述されています。また、ソースコードはgithubで公開しています。
github.com
npmパッケージとしても公開しています。
www.npmjs.com
ねねエンジンではUnitというクラスを継承したものを1キャラクタなどの処理の1単位として用い、Unitを継承したクラスのインスタンスははSceneを継承したクラスのインスタンスに属します。複数のScene継承クラスのインスタンスの内どのクラスを有効化しておくかでゲームシーンの切り替えを行います。
これが簡略化したクラス図です。
サンプル
サンプルを交えて使い方を紹介していきます。
hello.ts
helllo.tsがこんにちは世界的なサンプルです。
import * as nene from "nene-engine.ts"; import * as THREE from "three"; class InitialScene extends nene.Scene { public Init() { this.backgroundColor = new THREE.Color(0x6688cc); } public DrawText() { this.core.DrawText("Hello, World!", 0, 0); } } nene.Start("initial", new InitialScene());
InitialSceneクラスは初期化処理で背景色を青っぽい色にして、文字描画処理にて"Helo, World!"と描画しています。注意事項として、ねねエンジンでの2次元座標は、画面中心を原点・上がy軸正方向・右がx軸正方向となっています。hello.htmlを開くとでは青色の画面で中央に"Hello, World!"と表示されるはずです。
units.ts
units.tsは3Dモデルを読み込んだりUnitクラスを用いたりするサンプルです。
まずはInitialSceneクラスを見ていきます。
class InitialScene extends nene.Scene { public Init() { this.backgroundColor = new THREE.Color(0x6688cc); this.onLoadError = (e) => {console.log(e)}; this.core.LoadGLTF("resources/ball.glb", "ball"); } public Update() { if (this.core.IsAllResourcesAvailable()) { this.core.AddAndChangeScene("game", new GameScene()); } } public DrawText() { this.core.DrawText("Now Loading!", 0, 0); } }
Init関数にてGLTF形式の3Dモデルの読み込みを開始するLoadGLTF関数を呼び出しています。この関数は非同期に3Dモデルを読み込みま、読み込み後にモデルを呼び出すための文字列をキーとして指定します。現在他にもOBJ形式を読み込むLoadObjMtl関数も用意しています。3Dモデルデータのほかに画像ファイルやテキストファイルも非同期に読み込む関数を用意しています。
Update関数では非同期に読み込んでいるすべてのリソースが読み込み完了しているか取得するIsAllResourcesAvailable関数を用い、読み込み完了していればGameSceneインスタンスを生成してシーン遷移します。
次にGameSceneクラスを見ます。
class GameScene extends nene.Scene { public Init() { this.backgroundColor = new THREE.Color(0x6688cc); this.camera.position.set(0, 100, 100); this.camera.lookAt(0, 0, 0); const light = new THREE.DirectionalLight("white", 1); light.position.set(50, 100, 50); this.scene.add(light); } public Update() { if (this.frame % 60 === 0) { this.AddUnit(new Ball()); } } }
Init関数では背景色の設定とカメラ位置の指定・光源の追加を行っています。
Update関数ではthis.frameが60の倍数であるときにBallクラスのインスタンスを生成し、自身に追加しています。this.frameはそのクラスのUpdate()関数が呼ばれるたびに1だけ増える変数です。
そしてBallクラスを見ます。
class Ball extends nene.Unit { private obj: THREE.Object3D; private pos: THREE.Vector3 = new THREE.Vector3(0, 0, 0); private speed: THREE.Vector3; public Init() { this.speed = new THREE.Vector3(Math.random(), Math.random(), Math.random()); this.obj = this.core.GetObject("ball"); this.obj.scale.set(300, 300, 300); this.AddObject(this.obj); } public Update() { this.pos.add(this.speed); this.obj.position.set(this.pos.x, this.pos.y, this.pos.z); if (this.frame > 50) { this.isAlive = false; } } }
Init関数で乱数により速度を設定し、"ball"というキーで読み込んでおいた3Dオブジェクトを読み込んでthis.objに束縛し、this.objをAddObject関数によって自身に追加しています。AddObject関数に3Dオブジェクトを渡すと描画の対象となり、このUnitインスタンスが削除された際に自動で描画の対象から外されます。
Update関数ではthis.posにthis.speedを加算し、this.objのpositionに反映させています。そしてthis.frameが50を超えるとthis.isAliveをfalseにします。isAliveがfalseになったUnitインスタンスは次回のtickで削除されます。
そして
nene.Start("initial", new InitialScene());
でゲームエンジンの処理を開始します。
ねねエンジン本体のサンプルコード
ねねエンジン本体のレポジトリのexamplesディレクトリにも複数のサンプルがあります。物理エンジン・GLSLシェーダ・2Dスプライト・マウスやキーボードによる入力・レイキャスト・地形ジオメトリなどのサンプルがあります。サンプルコードは今後も充実させていく予定です。
終わりに
Unityなどの優れたゲームエンジンがある今、個人でゲームエンジンを作るのは浪漫以外の利点は恐らくないです。しかし、オープンソースでブラウザで動いて型の有るいい感じの言語なゲームエンジンは他にはあまりなさそうです。もしかしたら隠れた需要があるかもなんて思っています。ねねエンジンは現在まだ開発途中で機能もドキュメントも少ないですがこれからどんどん充実させていくつもりです。もしねねエンジンを使って何かしらのフィードバックをもらえると幸せです。またissueやpull requestもお待ちしています。
さて明日のKMC Advent Calendar 2018 - AdventarはAotsukiさんのmac book airの速度を犠牲にして容量を増やした話です。お楽しみに!