プロジェクト

こちらは最近取り組んでいたプロジェクトになります。(2年間) 全てはCかC++で書きました。 English Version

literally me
Kevin Michalev
kevin@michalev.net

Game Engine
ダウンロードはこちら!
ソースコード
動く: wasd; ジャンプ: space; ズーム: 5

何かを始めるには、内部の壁を越える必要がある。 始めるのに必要なステップが少なければ少ないほど、その壁は低くなる。人々に試してもらいたい小さな体験には、このコンセプトを理解することが重要だ。 これは、小規模な3Dゲーム、video-toy、interactive-artを実現するために作られた3Dゲーム・エンジン(WIP)である。コンセプトは、配布のしやすさ、そして最終的には開発のしやすさだ。

アセットシステム

ゲームを1つのexeにまとめ、ダウンロードしてダブルクリックするだけです。配布の管理も簡単で、exeを送るだけで、すぐに試してもらえる。 そのためには、実行ファイル自体にアセットを詰め込む必要がある。これを実現する方法はたくさんあるが、課題を解消した結果、以下の方法に落ち着いた: アセットを作成し、gltf-model-formatにエクスポートした後、プログラムがgltf-model-dataをカスタムC-structベースのフォーマットに変換する。この構造体はC-declarationとして出力され、コンパイラがこれを解析して実行ファイルに入れる。 データはプログラムの一部で、外部ではないので、そのデータへの参照は、ふだんならstringのようなものである必要がありますが、コードで使用可能な単なるC変数にもなります。 hashtableなどは必要ない。ただ変数名を使うだけだ。お気に入りのIDEのリネーム機能も、これらの変数名への参照を正しくリネームする。さらに、データに何か問題があった場合、データが欠落していた場合、名前を間違えてタイプしてしまった場合、コンパイル時に何が間違っていたかを知ることができて、コンパイラーが正確に教えてくれる。データはコードの一部となり、それに付随するすべての効用を得る。 では、このデモの他の部分を見てみよう:

フィジックスシステム

ゲームフィジックスのコードは私が書いた。 材料は、AABB-BVH、raycast、capsule-triangle-penetration-test、non-rotational-contact-resolutionといくつかの数学関数。 約800locで、キャラクターを走り回らせ、物にぶつからないようにするのに十分なものができる! 最終的には、より完全なフィジックスライブラリを追加したいので、サイドプロジェクトとしてフィジックスコードを生成する(下記参照)。多くのゲームではもうこれだけで十分だし、欲しい人は誰でもフリーのフィジックスライブラリを使える。とはいえ、フィジックスライブラリに見られるsimulation-world的なアプローチではなく、ストレートなフィジックスコードを持つことで、キャラクター操作やその他のゲームプレイ機能をより正確に実装することができる。

アニメーション

単純なgpu-skinned-skeletal-animationと、アニメーション間の補間。シンプルさを第一に考えています。 skinningはcompute-shaderで行われ、結果はbufferに書き込まれるため、skinningされたvertexは再利用できる。これには、static-geometryとdynamic-geometryに同じshaderを使用できるという利点もある。 IK機能(FABRIKアルゴリズムを使用)も含まれています。 デモでは、地形に基づいてキャラクターの脚を調整するのに使われています。シンプルですが、多くの付加価値があります。

stencil-volume-shadowsを使うことにしました。このアイデアがカッコイイと思ったからだ。CarmackのReverseの特許も切れたので、使えるはずだ。 stencil-volume-shadowsの利点は、shadow-volumeを計算するのに十分な予算や、十分なrasterization-headroomがあれば、開発者が考える必要があまりないことだ。画像全体の影の質は同じで、微調整はほとんど必要ありません。 shadow-volumeの計算はcompute-shaderで行われる。

その他

- コマンドリストを使ったGPU状態管理と描画コード; - 3Dデバッグ描画; - jsonとgltfパーサー(アセット生成のプリパスのみ) - など。

最後の言葉とクレジット

コードの大部分は私が書きました。 物事をシンプルに保つなら、それほど多くのコードは必要ありません。 使用したライブラリは以下の通り:

stb_image by nothings png/jpg画像を解凍する。 incbin by mmozeiko バイナリデータをc-array宣言として書き出すことなくexeに取り込み、コンパイル時間を短縮する。

両者に感謝!

使用したアセット

skybox from skiingpenguins skybox-pack

テクスチャ、3Dモデル、アニメーションなど、その他のアセットはすべて自作が作ったものだ。

3D Modeling Software

Blenderでモデリングをしていたところ、ワークフローに不満が残ることが多かった。 パワーフルなツールである一方、扱いにくいところもある。 アドオンで拡張することは可能ですが、モデリングエンジンのコアを変更するのは簡単ではありません。 この状況を改善するためにはどうしたらいいかと考えた結果、自分でモデリングソフトを作れば、モデリング以外のことにも使えるのではないかと思いついた。モデラーをゲームエンジンに組み込むことで、さまざまな可能性が生まれます。レベルエディターの基礎として使う。コードでモデルやアートを生成する。ゲームプレイに使う。などなど。 このツールのプログラミングに着手した。 ツールのコアはBMesh Datastructureに似ている。meshのデータ構造で、meshのトポロジーにほとんど制限がないため、小さな操作の組み合わせとして機能を簡単に実装できる。BMeshと異なり、私はポインタの代わりにIDを使っている。この構造とそれに作用するいくつかのモデリング関数は、小さなC99ライブラリで実現した。他のプログラムに組み込んだり、他のプログラミング言語からも使用することができる。 開発者として興味深い点は、undo機能がどのように実現されているかということだ。私がmesh構造を実装した方法は、(ポインターの代わりに)int-idを使用しているため、データ構造自体は容易にコピー可能である。そのため、操作を取り消すには、最後の状態をbinary-copyして現在のデータを置き換えればよい。 commitは最後の状態からのbinary-diffとして保存でき、メモリを節約できる。

これまでに追加した機能:

loop-cutやextrudeなどによるポリゴンモデリング。 Surface-distance-based-proportional-fallof-vertex-displacement(これは「スカルプト」とも呼ばれる)。 最適化するため、bmeshデータ構造を、より反復しやすいvertex-connectionデータ構造に変換する。 このデータからおおよそのフォールオフが毎フレーム計算され、vertexはこのフォールオフに基づいて変位する。 Projective-texture-painting。(まだプレアルファだが、基本は機能している)。 このために、trisをTexture/UV-Spaceでラスタライズして、テクスチャのラスタライズされたピクセルごとに、それが描画された線に投影されているかどうかをチェックします。 もしそうなら、選択されたブラシに基づいて色を受け取る。

Credits

使用したライブラリ:

xatlas by jpcy fork of thekla_atlas maintained by jpcy UV生成に使用。 作成されたアトラスはシャドウマップ生成に適しており、モデルテクスチャにはあまり適していないので、このライブラリを置き換えたい。 microui by rxi 簡単に拡張できるとても小さなUIライブラリです。新しいプロジェクトでは、すでに自分のUIコードを作っているので、これも置き換えるつもりだ。 とても良いライブラリだけど、最近imguiから別のパラダイムにシフトした。 sokol by floooh 3Dグラフィックスに使われるクロスプラットフォームのグラフィックスAPI。 これも良いライブラリだが、新しいプロジェクトでは自分のコードに置き換えた。このプログラムでもこれを置き換える予定だ。

'comf' - An Interpreted Programming Language

C言語のようなプログラミング言語で、memory-operand-based-bytecodeにコンパイルされ、インタープリタ/VMによって読み込まれ実行される。 プログラミング言語よりは、VMに重点を置いている。 この言語は、asmを書かずにプログラム・コードをVMに送り込む方法を作るために作られました。

完全な比較ではないが、私のVMでは、python、lua、lua-jit(jitは無効)よりも速くコンパイルし、fib(35)を計算している。 時間はこちら。

この感じになります:

proc fib (int n) (int r) { if (n <= 1) { r = n; return; } r = fib(n - 1).r + fib(n - 2).r; } proc main (int input) (int output) { int a; a = fib(35).r; print a; }

以下は上記の関数のvmバイトコードです。 スタックのレイアウトは'val'で記述されている。 これらのスタック値は、命令自体でその名前を参照することができ、純粋なスタックマシンのアプローチと比較して、多くの場合、全体的な命令数が少なくなります。 実際のバイトコードでは、これらの参照は単なるスタックポインタオフセットであることに注意してください。

asm: | JUMP_C main,0, 0 fib: | | val: r; size: 4, align: 4, offset: 0, type: int | | val: n; size: 4, align: 4, offset: 4, type: int | | val: tv_0; size: 4, align: 4, offset: 16, type: int | u4_LE_SSC tv_0, n, 1 | J0_CS then_0,tv_0, 0 | COPY_SS r, n, size(int) | RETURN_I 0, 0, 0 then_0: | | val: tv_0; size: 4, align: 4, offset: 32, type: ( int r) | | val: tv_1; size: 4, align: 4, offset: 36, type: int | | val: tv_2; size: 4, align: 4, offset: 40, type: int | u4_SUB_SSC tv_1, n, 1 | CALL_CC fib,32, 0 | | val: tv_3; size: 4, align: 4, offset: 48, type: ( int r) | | val: tv_4; size: 4, align: 4, offset: 52, type: int | | val: tv_5; size: 4, align: 4, offset: 56, type: int | u4_SUB_SSC tv_4, n, 2 | CALL_CC fib,48, 0 | u4_ADD_SSS r, tv_0, tv_3 | RETURN_I 0, 0, 0 | STOP 0, 0, 0 main: | | val: output; size: 4, align: 4, offset: 0, type: int | | val: input; size: 4, align: 4, offset: 4, type: int | | val: a; size: 4, align: 4, offset: 8, type: int | | val: tv_0; size: 4, align: 4, offset: 32, type: ( int r) | | val: tv_1; size: 4, align: 4, offset: 36, type: int | SET_SC tv_1, 35, 0 | CALL_CC fib,32, 0 | COPY_SS a, tv_0, size(int) | u4_PRINT_S a, 0, 0 | STOP 0, 0, 0 \asm

Game-Physics Experiment

進行中のゲーム物理システムコード。 Position-based-dynamicsベースなコンタクト制約充足。 基本的に自分の単純なゲームのための物理ライブラリ欲しくて作りました。

Final Words

こちらは価値があると考えた最近取り組んでいたプロジェクトです。 ご質問、提案、オファーがありましたら、お気軽にメールまたは Discord(ユーザー名:_ymd_)でお問い合わせください。