水無月と作業報告の候
また1ヶ月コースの長いブランチを作ってしまいました。悪い癖です。
オフスクリーンレンダリングの実装中
作成中のゲーム、最初にその作品の中で一番の山場にあたりをつけてざっくりと作ってみています。 そこでどうしてもやりたい演出があるのですが、それに必要なので機能を追加しています。
鏡とかね。
空の作成中
この表現も重要なので作ってます。
プロファイラの実装中
テスト戦略成功の秘訣は日常的な実施である。
ということでパフォーマンステストしやすいように簡易的なプロファイラを作っています。
コーディングスタイルの変更(というか回帰)
関数名をすべて lower camel case に直しました。何年か前に upper に直してから、やっぱり lower に戻した感じです。
勤めている会社にまつわるいくつかの事情から upper にしていましたが、もうそういうのは気にしなくていいかなというのと、
など諸々の理由で lower にしました。
upper にした時はある意味後ろめたい理由だったけど、今回はそうじゃ無いのでまるっきりバカな変更にはならない・・・はず。
今後
思うところあって7/7に一度リリースしようとインストーラやサンプルを準備していたのですが、多分無理そうです。 ちょっと仕事忙しくなっちゃった。
7月中には出したいですね。というか、もうブログ始めて半年だ。ダラダラしないようにしよう。
皐月と作業報告の候
新しくゲームを作り始めました
ゲームエンジンはゲームを作るためのものです。使ってみてはじめてわかる粗がたくさんあるわけです。その修正でまだAPIは安定しません・・・。
今後はゲームを作りながら、その都度 Lumino のほうも更新していきます。
ちなみにキャラ2D、マップ3Dなゲームが好きです。
シーングラフへの Entity Component System の導入
ゲームというのは仕様変更が頻発しがちな、ある種の特殊なシステムです。
変更に対して柔軟に対応するため一般的にはオブジェクト指向で作りますが、 is-a の関係を簡単に破壊するような変更よく起こります。
その解決策のひとつが Entity Component System です。Unity をはじめとして昨今のゲームエンジンはみんな備えているアレです。 機能を部品として分割し、オブジェクトは部品の集合体として定義する、そんな仕組みを入れています。
シリアライズ
Jsonファイルを簡単にシリアライズできる機能を作りました。 boost::serialization と同じようなものですが、Object を継承したオブジェクトをシリアライズする場合、型情報を利用した高度なシリアライズが可能になっています。
class EventData : public Object { public: String name; float x; float y; float z; String textureName; List<Variant> commands; void Serialize(Archive& ar, int version) { ar & LN_NVP(name); ar & LN_NVP(x); ar & LN_NVP(y); ar & LN_NVP(z); ar & LN_NVP(textureName); ar & LN_NVP(commands); } }; struct MapData { String displayName; String meshName; List<RefPtr<EventData>> events; void Serialize(Archive& ar, int version) { ar & LN_NVP(displayName); ar & LN_NVP(meshName); ar & LN_NVP(events); } };
{ "displayName": "map1", "meshName": "MapMesh1.mqo", "events": [ { "name": "EV000", "x": 1.0, "y": 1.0, "z": 1.0, "textureName": "chara_1.png", "commands" : [100, 101, 102] }, { "name": "EV001", "x": -1.0, "y": 1.0, "z": 1.0, "textureName": "chara_1.png", "commands" : [100, 101, 102] } ] }
MapData data = JsonSerializer::Load<MapData>(Assets::LoadText("MapData.json")); data.events[1]->name; // => "EV001"
とりあえずマップデータとかをツクールMVライクな JSON で用意していく方向で。
ビルド方法
Lumino のビルド方法を簡単にまとめました。 https://github.com/lriki/Lumino/wiki/BuildingWindows
卯月と作業報告の候
もうちょっとレンダリングフローの見直し中
前月に引き続き。
2D/3Dシーンと UI モジュールの関係がかなり変わってきています。
前はシーンのほうが偉くて、それの後描画的な立ち位置で UI の描画が入り込んでました。 今は UI のほうが偉くて、UIFrameWindow クラスをルートとした、ちょうど WPF や HTML のような UI要素の階層構造の一部(Canvas とか)としてシーンが描画されるようになりました。
なんでこんなふうになったの?
ウリというかコンセプトもちょっと変わってくると思います。 今までは「ゲームエンジン」だったけど、それプラス、「リアルタイムグラフィックスアプリケーション」。 なんかかっこいい。字面が。
あと、描画はコールバックの外側でもできるようになりました。 コードはこんなイメージ。
void Main() { // コードをコンパイルして Shader オブジェクトを作る auto shader = Shader::Create("test.fx"); while (Engine::UpdateFrame()) { // Lumino 自体の描画処理 Engine::RenderFrame(); // 画面全体への、シェーダを用いた直接描画 auto* r = Engine::GetSceneGraph3D()->GetRenderer(); r->SetShader(shader); r->DrawScreenRectangle(); // 描画結果をウィンドウに表示する Engine::PresentFrame(); } }
ピクセルシェーダをゴリゴリ書くとこんなのが出たりします。
ドキュメントとホームページ
ドキュメント書きたい。でもデザインめんどい。
こんなことがしたい。
サイトジェネレータとか今時は星の数ほどあるけど、 がっつりとしたマニュアルやAPIドキュメント作りたいときはイマイチ力不足を感じます。
で、自作とかしてた最中に DocFX を見つけました。 この子はねぇ・・・つよいよ。
自作とかするまえにかなりたくさん調べたつもりだったけど、なんでヒットしなかったのか・・・。無能なだけか。
日本語情報もまだ少ないみたいだからそのうち使い方まとめとこう。
お試し版
ごめんね
弥生と作業報告の候
忙しい仕事が、今日、ようやく終わった。予定。
レンダリングフローの見直し中
今は OnRender コールバックの中とか、決められた範囲でしか描画できないけど それだとデバッグ描画するときにやり辛いので、制限無くどこでも描画できるように調整してます。
ハードウェアでアウトラインフォント書きたい
今の Lumino の文字描画は全部ビットマップベースです。一度テクスチャに書いてからスプライトとして描画しています。 シェーダをサポートしようと決めたときから気になってたのが、シェーダで文字を装飾するときに 普通のメッシュと同じシェーダを再利用しづらいことでした。
もう何年も前からやりたいやりたいで後回しになってましたが、レンダリングのアーキテクチャとかパイプラインとかを整理する中で 「やっぱり実現性の検討くらいはやっておいた方がいいよね」と思いました。まる。
そこそこキレイに書けてるんじゃないかな?
テッセレーションは glues を使いました。 後で poly2tri も試してみたいけど、多分 gluTess のほうが FreeType とは相性良さそう?
あと、それだけだとアンチエイリアス無くてとても見れたものじゃなくなるので、 辺の押し出しでアンチエイリアスを表現してます。nanovg と同じやりかた。
で、とりあえず何とか使い物になる品質まで持っていけたと思うんだけど、やっぱり最終的な描画結果はグラボ依存です。 「このピクセル、GeForce よりも Radeon の方が濃く見えます」とかすごく細かいところで微妙に差があります。 多分もうこの辺が限界だと思うので、安定して描けるビットマップとは互いに長所短所を補いながら両立していく方向で調整。
最後に
Mac の文字キレイ(羨望)
睦月と作業報告の候
今月は黙々と作ってました。
ティーポット作れるようにしました
姿勢制御系のテストに使いたかったので。
Gizmo 作ってます
3Dゲームの設計始めた途端シーンエディタが欲しくなったので、その取っ掛かりです。
リポジトリ構成を大きく変えました
いままでサブモジュールとしてたものをすべて Lumino リポジトリに取り込みました。 最近 Issues を絡めたフローを意識して開発進めてますが、各リポジトリに頻繁に変更が入るのでかえって管理が面倒。 安定してきたらまた分けるかも。(何年先になるだろうか・・・)
C++ から言語バインダの自動生成できるようにしてます
UE4 の UCLASS マクロみたいに、
LN_CLASS() class Sprite : public Object { ... };
と書いてコマンドラインツールに突っ込むと、ほかのプログラム言語用のラッパーを自動生成します。
今までは C 言語用の公開インターフェイスから変換してたけど、Lumino の機能が増えるにつれて限界になってきたので直接 C++ から変換できるようにしてます。
今は C# のみ。
SWIG 使わないの?
- プロパティシステムやリフレクション、なんちゃってGC、イベントのブロードキャストなど、Lumino の機能が大きくなってきたので SWIG じゃカバーしきれない。
- HSP その他、いずれは対応したいと思っているプログラム言語に SWIG が対応していない。将来的にも和製のマイナー言語なんて対応される見込みも無く。
今後の予定
これから毎月報告していく予定ですが、そのたびにお試し版をリリースできるようにしたいです。
ゲームエンジン「Lumino」の紹介
この記事は「ゲームエンジン・ライブラリ・ツールの開発 Advent Calendar 2016(http://qiita.com/advent-calendar/2016/gameengine)」の21日目です。 それなりのゲームエンジンを目指しつつも未だぴよぴよですが、せっかくなので公開したい。ということで、こんなことやっています、という紹介です。
Lumino とは
Lumino はゲームやグラフィックスソフトウェア を簡単に開発できるよう支援するための C++ ライブラリです。 ゲーム開発をメインのユースケースとしていますが、グラフィックスを中心とした様々な機能を、既存のアプリケーションに組み込んで使用することもできます。
特徴
さて、紹介、という体で改めて特徴に向き合うのもなかなか難しいです。
というのも、今の Lumino は単なるリアルタイムレンダリングエンジンにちょっとゲーム寄りの機能が付いた、という感じです。 それがどんなに効率よく綺麗な絵を描くことができるとしても、今できることはレガシーなマテリアルシステムで簡単な絵を描くだけです。 これまで作りこんできたシステムでどこまで行けるかは、今後の課題です。
そんな感じでまだまだ開発中ですが、今は以下のような方向を向いて進んでいます。
広く使われている API デザイン
Lumino はできるだけ Lumino らしさを求めないようにしています。 もしかしたら「ライブラリの色が薄い」と言うとピンと来る人もいるかもしれません。 Lumino で得た知識は他の開発ツールで利用できますし、逆に他の開発ツールで得た知識も Lumino で利用しやすい、ということを常に意識しています。
高パフォーマンス
Lumino はマルチコア環境に最適化されています。 昨今はPCに限らずモバイルでもマルチコアは当たり前になってきました。 そういったデバイスの性能を活かしきれるような設計にしています。
組み込みやすさ
Lumino に研究開発やプロトタイピングでも使いやすいように、という要求を持たせたのは、 私がエンジニアの仕事に就いてしばらく経ってからでした。 以来、既にある別のツールキットに組み込んで使うようなケースも考えています。
使い方
チュートリアルを書き始めています。よろしければ参考にしてみてください。
ゲームエンジン Lumino のチュートリアル https://github.com/lriki/LuminoTutorial
開発環境の準備 https://github.com/lriki/LuminoTutorial/blob/master/Common/1.md
以下、いくつかの例を紹介します。 Lumino の雰囲気を見ていただければと思います。
Lumino で Hello world
#include <Lumino.h> using namespace ln; void Main() { // Lumino の初期化処理 Engine::Initialize(); // 2D テキストを作成する auto text = TextBlock2D::Create("Hello, world!"); // Lumino の更新処理 (ウィンドウへの描画などを行い、ウィンドウが閉じられていれば false を返す) while (Engine::Update()) {} }
スプライトを表示する
#include <Lumino.h> using namespace ln; void Main() { Engine::Initialize(); // 画像を読み込み、2Dスプライトを作成する auto sprite = Sprite2D::Create("Assets/icon256.png"); // 2Dスプライトの位置を設定する sprite->SetPosition(200, 100); while (Engine::Update()) { } }
ユーザー入力を受け取る
void Main() { Engine::Initialize(); // アクションマッピング (キーコンフィグ) // "left" と "right" に キーボードの左右キーと、ゲームパッドの1軸を割り当てる Input::AddButtonBinding("left", KeyboardBinding::Create(Keys::Left)); Input::AddButtonBinding("left", GamepadBinding::Create(GamepadElement::Axis1Minus)); Input::AddButtonBinding("right", KeyboardBinding::Create(Keys::Right)); Input::AddButtonBinding("right", GamepadBinding::Create(GamepadElement::Axis1Plus)); auto text = TextBlock2D::Create("A"); while (Engine::Update()) { Vector3 pos = text->GetPosition(); if (Input.IsPressed("left")) // "left" が押されているか? pos.x -= 5; if (Input.IsPressed("right")) // "right" が押されているか? pos.x += 5; text->SetPosition(pos); } }
音を鳴らす
void Main() { Engine::Initialize(); // BGM を再生する GameAudio::PlayBGM("bgm.ogg"); int count = 0; while (Engine::Update()) { // およそ2秒ごとに効果音を鳴らす if (count % 120 == 0) { GameAudio::PlaySE("se.wav"); } count++; } }
活用事例
開発中であったり、身内公開のままプロジェクトが終わったものが多いので、いくつかスクリーンショットでのご紹介です。
今後のロードマップ
一応、2D/3Dゲームを作る力はあります。が、それは私自身が開発に携わっていてヘルプが出せるからであって、第三者に Lumino だけ渡しても多分無理です。
なので、まずはなによりもドキュメントの整備と、開発中のゲームで必要な機能を優先的に実装したため生まれてしまった 滅茶苦茶汚いコード 負債の返済です。
そんなことと、開発中のゲームの完成を経て ver1.0 でしょうか。 そのあとはマルチプラットフォームとか、IDE とか、物理ベースレンダリングとかやりたいですね。
もし何かご意見などありましたら、GitHub の Issues に投げていただけると幸いです。