UnrealEngine4を使って簡単な3Dゲームを作る(前)
2020/12/16 追記
はじめに
この記事は、香川大学創造工学部のプログラミングサークル SLP の アドベントカレンダー 2020 15日目の記事です。サークル生やOBの方々が繋いでいきますので、そちらもぜひ目を通していただけると嬉しいです。
昨年に引き続き、アドベントカレンダーへ参加させてもらえることになりました。
前回は、「うどん屋にお昼を食べに行く学生がよくいるが、うどん屋までの横断歩道に信号がなく、車の通りも多くて危ない」ということから、うどん屋までの横断歩道を渡りきるのに挑戦するゲームを作りました。
今年は視点を変えて、「車を運転している人が、横断歩道を渡ろうとしている学生を轢かない」ように挑戦するゲームを作ることにしました。
学生でも車を運転する人がいるということを実感したことから、なんかこうなりました。
最近よく使っているのもあって、今回はUnreal Engine 4(UE4)で作ろうと思います。バージョンは 4.25.4 です。
この記事は、プロジェクトの作成から、プレイヤー、マップ、エネミーの作成までを書きます。後日、当たり判定や終了条件などの完成までを書きます(予定)。
Unreal Engine 4 について
Epic Gamesが開発しているゲームエンジンです。2021年にUnreal Engine 5がリリースされるとかで話題になっていますね。
UE4で作られたゲームとしては、フォートナイト、ドラゴンクエスト11、鉄拳7などがあります。ゲーム開発以外でも、アニメの背景制作などに使われています。
最大の特徴は、ブループリントと呼ばれる機能です。プログラミングの知識がなくても、感覚的に繋ぎ合わせて作ることができます。
他の魅力として、ハイクオリティーなグラフィック性能がよく挙げられます。感覚的な操作で作り込めるグラフィックが綺麗に創作可能なため、リアルな作品創作に向いているエンジンと認知されています。
では、さっそく作っていきます。
ゲーム制作
プロジェクトの作成
まず初めにプロジェクトを作成します。Unreal Engine4を開きます。
新規プロジェクトのカテゴリに「ゲーム」を選択して、「次へ」を押します。
テンプレート選択で「サードパーソン」を選択して、「次へ」を押します。
「ブループリント、スケーラブルな3D・2D、レイトレーシング無効、デスクトップ/コンソール、スターターコンテンツ有り」にします。
これで作成は完了です。この時点で既に、実行するとマネキンを動かせます。
プレイヤーの作成
今回、プレイヤーを車にしたいので、マネキンを消してキューブを追加します。
「ThirdPersonBP/Blueprints/ThirdPersonCharacter」がプレイヤーキャラクターのファイルです。ダブルクリックで開きます。
マネキンの削除
ビューポートを開くとマネキンがいるので、これを消します。
コンポーネントの「Mesh」の「Skeletal Mesh」を黄色い矢印を押して空にします。
車体のつもり(キューブ)の追加
「+コンポーネントを追加」から「キューブ」を追加します。
「CapsuleComponent」の子要素に追加して、回転と拡大・縮小を変更します。
マップの作成
Unreal Engineでは、ゲーム中のフィールドのことをレベルと呼んでいます。このレベルの中に、いろいろと配置してマップを作っていきます。
配置されているものは右上のアウトライナに表示されています。
要らないものを削除
「ArenaGeometry内全部」と「CubeMesh」、「TextRenderActor」はいらないので削除します。
マップの作成
アウトライナに「UdonMap」フォルダを作成して、マップを作っていきます。
[基本] → [キューブ]をレベルにドラッグします。
x軸100倍、y軸50倍、マテリアルを「M_Concrete_Tiles」に設定します。
こんな感じで作っていきます。
真ん中に植え込みがある、片側2車線の道路がいります。ぽんっ。
歩道が要ります。横断歩道が要ります。ぽぽんっ。
うどん屋がいります。ぽぽぽんっ。できました!
各種オブジェクトの設定
それぞれの設定は以下のようになっています。
各種オブジェクトの設定
Panel1 | Implantation1 | Pedestrian3 |
[基本] → [平面] | [基本] → [キューブ] | [基本] → [平面] |
---|---|---|
Sidewalk1 | Udonshop | Shop |
[基本] → [キューブ] | [基本] → [キューブ] | [基本] → [キューブ] |
エネミーの作成
Enemyクラスの作成
マップ上を歩くEnemyを作成します。
「右クリック」→「ブループリント」→「Character」を選択します。ファイル名はEnemyにしています。
「Mesh」の設定を変更します。位置Zを-90、回転Z-90を、アニメーションを、メッシュです。
Enemyクラスをワールドにスポーンさせる
メインツールバーで「ブループリント」をクリック、「レベルブループリントを開く」を選択します。
レベルブループリントは、レベルに固有で、レベル全体のグローバルグラフとしての役割を果たす、特別なタイプのブループリントです。
ゲーム開始時に、Enemyを1体スポーンさせるようにしています。
実行すると、マップ中央にマネキンが一体出現していると思います。
エネミーの動き作成
マネキンの動きを作ります。
簡単に、定期的にランダムな場所に移動するだけの動きを作ります。
必要なファイルの作成
エネミーの動きのパターンを組み立てる「ビヘイビアツリー」を作成します。
「右クリック」→ 詳細なアセットを作成「AI」→「ビヘイビアツリー」を選択します。ファイル名は「BT_Enemy」(BT:Behavior Tree)にしています。
エネミーの動きに必要な変数を管理する「ブラックボード」を作成します。
「右クリック」→ 詳細なアセットを作成「AI」→「ブラックボード」を選択します。ファイル名は「BB_Enemy」(BB:Black Board)にしています。
エネミーの動きの中身を作る「ビヘイビアツリータスク」を作成します。
「右クリック」→ 「ブループリント クラス」 → すべてのクラスで検索「BTTask_BlueprintBase」を選択します。ファイル名は「BTTask_GetMovePoint」にしています。
エネミーの動きをクラスに適用するのに必要な「コントローラー」を作成します。
「右クリック」→ 「ブループリント クラス」 → すべてのクラスで検索「AIController」を選択します。ファイル名は「AIC_EnemyController」にしています。
使用する変数の作成とビヘイビアツリーとの連携
BB_Enemyを開きます。
「新規キー」→「vector」を選択します。名前は「MoveLocation」にしています。
次に、BT_Enemy を開きます。
右の詳細タブの BlackBoard Asset に BB_Enemy を選択することで連携できます。
エネミーの動きの作成
BTTask_GetMovePointを開きます。
左のタブから変数を作成します。変数の型を「BlackBoard Key」、名前を「BBK_MoveLocation」、インスタンス編集可能にチェックを入れます。
左のタブの関数から Receive Execute をオーバーライドします。
エネミーの座標を取得、ランダムな座標を取得、移動先座標を持っている変数 MoveLocation にキー BBKMoveLocation を使って格納、処理を終了という流れになっています。
動きのパターンを作成
BT_Enemyを開きます。
ROOTとかの黒くなっているところから引っ張るとノードを作れるので、図のようにつくります。BTTask_GetMovePoint がさっき作ったタスクで、Move To と Wait は最初から用意されているタスクです。
左にあるノードから(右上の数字が小さい順に)処理が行われます。
次に、BTTask で宣言した BlackBoard Key に変数を紐づけます。
BTTask_GetMovePoint と Move To に紐づけるキーがあります。
コントローラーへの設定
AIC_EnemyController を開きます。
図のように、使用するブラックボードの設定とビヘイビアツリーの実行を設定します。
Enemyクラスにコントローラーを設定
Enemyを開きます。
右の詳細タブのボーンから、Auto Process AI を Placed in World or Spawned、AI Controller Class を AIC_EnemyController に設定します。
エネミーが動ける場所を設定する
2020/12/16 追記
左の「アクタを配置」タブから、「ボリューム」→「ナビメッシュ バウンズ ボリューム」をドラッグします。
位置を(0.0, 2000.0, 0.0)、拡大・縮小を(50.0, 50.0, 20.0)に設定します。
キーボードのPを押すことで、エネミーの動ける範囲が緑色で表示されます。
これでエネミーが動くはずです。本記事はここまでです。
ソースコード
おわりに
Unreal Engine 4 を使用して簡単な3Dゲームを半分作りました。
適当にはっつけただけでもそれなりのマップが作れた気がしたり、敵の動きのあたりは初めて触ったりしたのでとても楽しかった。
サンプルが用意されていたり、面倒な部分を実装する必要がなくてとても楽だった。
当たり判定や終了処理が無くてゲームっぽくないので、そのうち作りたいと思います。
そのうちgithubのリンクも載せます。
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も使ってみたいかもしれないかもしれない。
自分で遊んだけど、結構楽しかった。