C++とDXライブラリを使って簡単な3Dゲームを作る
はじめに
この記事は、香川大学工学部(創造工学部)のプログラミングサークル SLP のアドベントカレンダー11日目の記事です。 サークル生やOBの方々が繋いでいきますので、そちらもぜひ目を通していただけると嬉しいです。
アドベントカレンダーへの参加は今回が初めてであるため、とある人物に何を書くを相談した結果、タイトルの通りになりました。
3Dゲームを作るのも初めてで、その他にもいろいろと変な部分があるかもしれませんが、頑張ります。
DXライブラリについて
DXライブラリは、オープンソースのゲーム開発用ライブラリです。 ゲームエンジンとはまた違ったゲーム開発ができます。
Visual Studio 2017 C++ を使用しているため、Windows版 VisualStudio(C++) 用 のDXライブラリを使用しています。
本記事では、DXライブラリのダウンロードや導入のあたりは省きます。
DXライブラリの詳細やダウンロード、導入については、ホームページを見てください。
ゲームコンセプト
この道路は、香川大学工学部(創造工学部)の学生がうどんを食べるために通らなければならない道路です。 しかし、見てもらったらわかるように信号がありません。さらに、交通量も普通に多いです。
これは大変危険です、なんとかしないといけません。
そうだ、道路を渡る練習ができるゲームを作ろう。
3Dモデルの設置
まずは3Dモデルを設置します。
必要な3Dモデルは、プレイヤー、車、道路、ゴールぐらいです。
DXライブラリで使用できる3Dモデルには、x, mqo, mv1, pmd( + vmd ), pmx( + vmd ) があります。 今回、プレイヤーは pmd( + vmd ) と pmx( + vmd )、 道路などは mv1 を使用しています。
以下のコードは、3Dモデルのデータを読み込み、大きさなどを調整したりしてからウィンドウ内に設置します。 これは他の3Dモデルも共通の処理なので、同様にして全部設置します。
ここで注意する点は2つぐらいです。
1つ目は、MV1LoadModel
関数を使いすぎるとメモリ不足で大変なことになります。同じモデルを複数描画する場合は、MV1DuplicateModel
関数を使いましょう。必要なくなったらMV1DeleteModel
関数でモデルを削除できます。
2つ目は、MV1SetScale
関数でモデルを拡大した後に、拡大した分だけ輪郭線を小さくする処理を行うことです。
作っている途中で結構調べたところなんですが、この処理をしないと、モデルが黒い何かに覆われた状態で見えるようになってしまいます。
// モデルの読み込み int model_handle = MV1LoadModel("modelname.pmd"); // 指定のモデルと同じ基礎データを使用してモデルを作成する int model_handle_copy = MV1DuplicateModel(model_handle); // モデルの拡大値をセットする // -- VGet(float, float, float) 3次元のベクトル型 VECTORを返す MV1SetScale(model_handle, VGet(20.0f, 20.0f, 20.0f)); // 3Dモデルの輪郭線の修正 int MaterialNum = MV1GetMaterialNum(model_handle); for (int i = 0; i < MaterialNum; i++) { // マテリアルの輪郭線の太さを取得 float dotwidth = MV1GetMaterialOutLineDotWidth(model_handle, i); // マテリアルの輪郭線の太さを拡大した分小さくする MV1SetMaterialOutLineDotWidth(model_handle, i, dotwidth / 50.0f); } // モデルの座標をセットする MV1SetPosition(model_handle, VGet(320.0f, -250.0f, 600.0f));
プレイヤーの動き
詳しくはリファレンスを見たほうが良いですが、モデルファイルpmdとpmxは、モーションファイルvmdのファイル名をモデルファイル名+3桁の番号にすることで一緒に読み込んでくれるようになっています。例えば、model.pmd、model000.vmd といった風です。
読み込んだモーションを3Dモデルにアタッチし、再生時間を都度設定することでアニメーションになります。
また、プログラム内の3Dモデルの座標とは別に、3Dモデル内部のローカル座標があります。その関係で、プログラム内の座標は変化していないのに、実際には動いて見えてしまう状態になります。そこで、3Dモデル内部のローカル座標移動を無効にすることで、プログラム側だけで座標移動ができるようになります。
設定する再生時間の調整をなんか頑張ることで、3Dモデルに入力に応じた動きをぽくすることができました。
/* ----- アニメーションの設定 ----- */ // 付属モーションのアタッチ int attach_index = MV1AttachAnim(model_handle, 0, -1, FALSE); // 総再生時間の取得 int anim_total_time = MV1GetAttachAnimTotalTime(model_handle, attach_index); // 再生時間の初期化 int anim_play_time = 0.0f; // アニメーションによるローカルの座標移動を無効にする MV1SetFrameUserLocalMatrix(model_handle, MV1SearchFrame(model_handle, "センター"), MGetIdent()); /* ----- アニメーションの再生 ----- */ while (bool) { // 再生時間の調整 if (anim_play_time++ >= anim_total_time) { anim_play_time = 0; } // 再生時間をセットする MV1SetAttachAnimTime(model_handle, attach_index, anim_play_time); }
カメラの扱い
カメラの注視点は3Dモデルの座標にしています。 3Dモデルが動くと、距離が一定になるようにカメラの位置も再設定しています。 左CTRLキーで向きが左右に変化するようにしています。
以下の関数でカメラの位置と注視点を設定しました。引数は両方VECTOR型です。
SetCameraPositionAndTarget_UpVecY(model_position, camera_position)
カメラの動きがよくわからない場合は、Unityみたいな座標軸を表示してやるとわかりやすい。
その他の実装
車は2種類用意し、ランダムに道路の端から出現するようにしました。
ゲーム開始前にカウントダウンを入れました。でも1の時点で動けます。
画面上に赤文字で開始からの経過時間を表示するようにしました。ミリ秒です。
車に当たるとゲームオーバー、奥にある黒い物体に当たるとゲームクリアとなるようにしました。 あたり判定は、大変そうだったので三平方の定理を使う距離のやつになっています。
タイトル画面を作り、難易度選択とキャラクター選択を追加しました。
完成したゲーム
以下が完成したゲームの様子です。
車らしき物体を避けながら進み、黒い建物に当たってゲームクリアしています。
ちなみに、車に当たると、回転しながら吹き飛んでいきます。
これできみもうどん道路渡るマスターだ!(謎)
お借りしたもの
プレイヤー用のpmd形式の3Dモデル:
プレイヤー用のvmd形式の走るモーション:
【第17回MMD杯本選】モーション(歩き・走り)配布【フルボイス】 - ニコニコ動画
この中の、「走り(歩幅20・直進・長距離)」を使わせていただきました。
つくったもの
以下に、今回作成したコードがあります。
GitHub - unomitsu/Cross-the-road
一応、DXライブラリの初めにするやつをして、モデル関連の処理を修正したら動くと思います。 モデル関連の処理は、宣言、読込、複製、それからgame_managerのキャラクターセレクトのところぐらいだったと思います。
まとめ
以前DXライブラリの3D関数を触ってみた時にまったくわからなかったカメラ周りのことが、今回ゲームを作ったことでだいぶわかるようになったっぽくて嬉しい。
モデルにモーションを付けたりすることも初めてだったので良い経験が得られた。 モーションのつなぎ目とか方向転換とかがガタツクので、もっとスムーズに動くようにしたい。 メッシュとかポリゴンとか、3D関連の用語がよくわかってないことがよくわかった。
そのうちDirectXも使ってみたいかもしれないかもしれない。
自分で遊んだけど、結構楽しかった。