なにを作ったのか
冬ハッカソン23班です。宇宙でロボが対戦するゲーム「Mechanised Carrasius」を作りました。宇宙空間で銃を撃ちあって当たったら勝利という非常にシンプルなゲームです。



メンバー
aruze_pino: 3Dグラフィック、エフェクト
kavos: プログラミング
Liscome: サウンド
制作風景
3Dモデル

blenderにて制作しました。
メカの理想は大体虫に近しいものがあると思っています。歪に長い腕だったり、銃だったり、そういったアンバランスさこそがロボをロボ足らしめるんですね。人型ロボットを否定するつもりは全くありませんが、やはり異形のメカのロマンパワーははかり知れませんね。53万くらいあります。
デザインに関していうことはこれがすべてなんですが、何分突貫工事だったのでUV関係が終わっています…キャラクターモデリング等ならばきれいなトポロジーのノウハウはいくらでも転がってますが、人型から離れるとかなりトポロジーを意識するのは難しくなりますね…通常ならばリトポロジーをするのがセオリーです。みなさんはちゃんとやりましょう。
エフェクト
effekseerというツールを使って作成しました。

3Dゲームでのエフェクトは基本的に小さなパーティクルを何重にも同時に重ねて表現することでリッチな表現をします。
ノードでエフェクト制作が可能なので直感的にリッチなエフェクトを作ることができます。
初めての使用でもすぐに簡単にこのくらいのエフェクトは制作できます。↓

サウンド
FedoraのAudio Productionグループに含まれていたソフトをいくつか適当に弄ったらできました。
ほぼ未経験だったのでとても苦労しました。
プログラミング
~2か月前~
私「最近DirectXとかやってみたいと思ってるんだよね」
「そうなんだ!じゃあゲームエンジン作ってよ!」
私「おもしろそう」
「じゃあそれで冬ハッカソン出ようよ!」
私(あと2か月で...??)
私「ゲームを起動したときに毎回表示される〇nityのロゴってうざくない?」
「わかる!わかる!」
このゲームは、UnityやUnreal Engineなどのゲームエンジンを使わず、DirectX12を使って自作したゲームエンジンを使用して開発しました。使った技術をいろいろ紹介します。
ゲームエンジン
2か月で作ったゲームエンジンです。AquaEngineといいます。
ゲームエンジンといっても、中身はDirectXの薄いラッパーライブラリみたいなものなので、そんなに大したものではありませんが、一応置いておきます。
あまり設計がよろしくないので、作り直したいですね。
ゲームエンジンを作るときに得られた知見は後々別の記事にしたいと思っています。
モデルの描画
3DモデルにはFBXファイルを使いました。プロプライエタリなファイル形式なのでデータのフォーマットが公開されておらず、FBX SDKという専用のライブラリを使う必要があります。つらいです
UI: Direct2D/DirectWrite
UIにはD3D11On12を利用してDirect2DとDirectWriteを使いました。
ImGuiなども考えましたが、せっかくDirectXからやるのでなるべくライブラリを使いたくないと思い、OS標準のDirect2Dを使うことにしました。割と柔軟に描画の設定ができるので良い感じです。
対戦: winsock2
このゲームの目玉でもある対戦機能ですが、1週間でサーバー・クライアント方式のものを作るのは無理があります。そこで...

こうなりました。家に転がっていたスイッチングハブを介してLANケーブルで接続し、winsockを使ってUDP通信をすることで通信対戦を実現しました。なんか時代がいくらか巻き戻った感じです。ちなみに、有線で接続しているため遅延はほとんどないです。ワイヤレスにこだわらなければね、これでも十分なんです。ソースコードはここのファイルに全部書いてあります。
Network.cpp
サウンド: XAudio2
サウンドの再生にはXAudio2を使いました。X3DAudioが意外とうまく動いてくれました。XAudioも極めればもっといろいろ使えそうなので、勉強したいですね。最終日の3時間くらいで気合で書いたコードがこちらになります。雑な実装でしたが、サウンドを入れたことでゲームが華やかになりました。
AudioManager.cpp
ちなみに、BGM再生を別スレッドで行っているためメインのウィンドウを閉じてもBGM再生が止まらず、タスクマネージャーからキルしないといけないという残念なバグがあります。
DirectXを使ってつらかったところ
DirectXを使っていると、つらいところがたくさんありました。
the object is NULL
DirectXを使ったアプリケーションのソースコードは膨大になりますが、そのうち1カ所でも1が0に変わっただけで全体が動かなくなります。無限にデバッグです。
重い
低レイヤなAPIを直接使っているんだから軽いだろ~とか思っていましたが、普通に重いです。対戦ゲームなのにFPSが15くらいしか出ません。バッテリー駆動のノートPCだとFPS5です。オブジェクトもそんなに多いわけではないのに... Unityとかはちゃんと最適化しているんだなあと思いました。
要因1: インスタンシングをしていない
インスタンシングとは、同じオブジェクトを複数描画するときに、一回一回描画するのではなく、すでに描画したものをコピーして描画する手法のことです。今回はインスタンシングをやらなかった結果DrawIndexInstanced()が1フレーム当たり200回くらい呼ばれてしまっています。
また、同じオブジェクトなのに別のバッファとして利用しているため、同じデータが100個くらいあり、メモリを圧迫している可能性があります。
要因2: カリングをしていない
カリングにはいろいろな意味がありますが、ここでは「画面外のオブジェクトを描画しないようにする」処理のことです。これをやっていないために、常に世界全体を描画しており、重くなっている気がします。Mesh ShaderやWork Graphなどを使ってやってみたいです。
3次元回転がわからない
2次元では単純に何度回転するか、という情報だけでよいのですが、3次元空間では「どの軸を中心にどの方向に回転するか」という情報が必要になるため、回転がとても難しくなります。x, y, z方向にそれぞれ何度回転するかを指定しても、順番によって回転後の位置が変わってきます(行列積は非可換なので)。そのため、「今どの向きを向いているか」などを正確に把握する必要があり、結構大変でした。
ちょうど教養の線形代数の授業で学んだところが役に立ちました。ありがとう、線形代数学第二
ビュー行列を掛けても2次元座標系にならない
なぜ...?
相手のプレイヤーに的を表示するUIがあるのですが、そのためには相手のプレイヤーの3次元座標を2次元座標に変換する必要があります。このためにビュー行列を掛ければよさそうと思ったのですが、全然的外れでした。結局ごまかしながら調整しているため、完璧に相手を追跡できていません。
the object is NULL
ごめんなさい
テクスチャがおかしい
なんかおかしいんですよね。なんとかごまかしてはいますが、本来のモデルではこのような黒い部分は存在しないです。

シェーダーがわからない
シェーダーで法線情報を処理しようとしたら、いきなり全部の色が0になりました。具体的な値が見づらいのはつらいところです。
情報がない
DirectXを直で使っている人は少ないですし、使っている人もゲーム会社でゲームエンジンを作っている人とかなので、ネットの海に情報がなかなか流れてきません。GitHubの検索に大変お世話になりました。
Linker Error: このライブラリはMultiThreadedであり、MultiThreadedDLLとは異なります
統一してくれ~~
CMakeListsを書き換えて解決しました。
failed: find_package
vcpkg、難しい
DirectXを使ってよかったところ
DirectXはつらいだけじゃないですよ。
すべてのものに感動できる
DirectXで作るのは大変です。さらに、影やアニメーションなども自分で書かなければいけません。思い通りに動かないことがほとんどなので、それを難なく動かしている世間の3DCGに感動します。
特に、アニメでは顕著です。影が地面の起伏に合わせて変形していることに感動します。人間が階段を上るときに自然に足を動かせていることに感動します。すべてのものに感動できます。
全能感が得られる
DirectXでは全部自分でプログラミングしなければいけません。なので、プログラムが動いたときはもうどんなCGでも理解できるんじゃないかという気持ちになります。
すごいグラフィックができる(たぶん)
シェーダーを自分でプログラミングするので、作れるライティングの可能性は無限大です。きっと既存のゲームエンジンにもできないようなきれいなグラフィックを作ることもできるでしょう(たぶん)。私はそれ以前のテクスチャ描画でつまずいていましたが。
ひとこと
aruze_pino
全体の進捗管理とグラフィックを担当しました。終わると確信を持っていましたがkavosくんが頑張ってくれました。最終日に2時間仮眠を取っていたらカメラやエフェクトが実装されていたのは少しビビりました。彼らを奴隷のように酷使しつつ定期的に進捗を催促することによってブラックな労働環境を提供してしまったことは正直申し訳なく思っていなくもないです。ゲームエンジンを自作することの最大のメリットというのはやはりオリジナルのスプラッシュスクリーンを実装できるということでしょう。これがゲームエンジンを自作しようとした最大の動機にはなるかなぁと思っています。
Liscome
作曲のセンスを感じられなかったので機材のコレクターに戻ろうと思います。Audio-Technicaのモニターヘッドホンは定番ですがとても良いです。
kavos
すべてのプログラミングを担当しました。
正直終わるとは思っていなかったのですが(実際終わってはいないですが)、なんとか形あるものができてよかったです。DirectXからでもゲームが作れるということが分かったのはよい収穫になりました。
なかなかうまくモデルが描画できず、初日に完璧な3Dモデルを作ってくれたaruze君に申し訳ないですね。
もっとゲームエンジンの完成度を上げて、また自作でリベンジしたいです。