【UE5 #3】 HexGridを作るために学んだことと試作品の公開 | 内向型人間の知恵ブログ

【UE5 #3】 HexGridを作るために学んだことと試作品の公開

UE5 #3 アイキャッチ

ゲームの盤面として使いたかった「六角形のマス目(HexGrid)」を作ろうとした記録です。
調査で得られた知識と実際に作ったものをまとめました。

環境
Unreal Engine 5.6.1

実施期間
2025.9.21~2025.9.28

作りたかったもの

ゲーム開発ド素人の私がUnreal Engineの基礎をほっぽり出して「作りたいもの」に飛びついたものが、「戦略シミュレーションゲームに用いられるようなグリッド(マス)」でした。
例えばチェスや将棋、ゲーム「ファイアーエムブレム」シリーズのような正方形のマスが並んだものがメジャーだと思います。
それの六角形バージョンです。

しかし、そのようなシミュレーションゲームを作りたいだけならマーケットプレイスの有料のツールを使った方が早いでしょう。その紹介動画を見るだけでも高機能であることがわかります。

Advanced Turn Based Tile Toolkit – Make Turn Based Strategy games in Unreal Engine – YouTube

私は仕組みを知らないと気が済まない性格なので、自分で作りたかったのです。
ですが、ド素人の私にはこの「六角形のグリッド」をどれだけ調べても実現させることができず、そもそもインターネット上には「ゲームとして動くレベル」のノウハウは公開されていないことも理解しました。(Fig1)
なので、途中までの調査と実装を備忘録として書き残しました。

UE5 #3 課題のある試作
Fig1. シミュレーションゲームたりうる機能を実現する、一貫性のある解説がない

六角形について知る

六角形の並んだグリッドを理解しようと思ったとき、最初は難しそうだと思いましたが、インターネットには六角形の特徴をまとめてあるページが存在し、参考にした解説でも紹介されていました。
このページには、六角形を並べるために必要な数値や計算式が掲載されています。

Hexagonal Grids – Red Blob Games

六角形の向き

UE5.x #3_六角形の向きが与える影響

まず最初に知ることになるのは、六角形の向きについてでしょう。
頂点が上を向いているものをPointy-Top、辺が上を向いているものをFlat-Topと呼ぶらしく、これがゲームの盤面では「接敵する数」を左右します。
ひとまず今回は、頂点を上にした六角形を並べることにしました。

ポケGO、大戦略、信長の野望…古今東西のゲームマップを7分類して徹底分析。なぜ四角形や六角形のマップを、私たちは好むのか?【徳岡正肇氏_講演レポ】

並べた際の差分

頂点を上に向けた六角形の場合、水平方向には横幅の分だけズラして配置することになります。
垂直方向へは、斜めに隣接することになります。
一見すると難しく見えますが、六角形を構成する数字を知れば「なるほど」と思ってもらえると思います。

向かい合う頂点を結ぶ補助線(外接円の直径)を引いたとしたら、他の頂点は補助線の4分の1、4分の3の位置で垂直に交わります。
なので、垂直方向には「高さの4分3」だけズレて、かつ水平方向には「横幅(内接円の直径)の半分」だけズラして並べることになります。

言葉で理解しようとするより、図を見ていただいたほうが早いでしょう。

ちなみに、この補助線を使うことで六角形はコンパスがなくても描くことができます。

Unreal Engine上でHexGridを実現するアプローチ

ゲーム制作の素人である私の再出発には何もかもが難しいものでした。
Unreal Engineでシミュレーションゲームで使うような盤面・グリッドを作成するには、2つの方法があることを知りました。
一つはすでに触れた六角形の3Dモデル(メッシュ)を並べることです(スタティックメッシュコンポーネント)。
二つめは、どこに・どのようなラインを描画するのかをプログラムで指示する方法です(手続き型・プロシージャルメッシュコンポーネント)。

選んだのは六角形のモデルを並べる方法ですが、とりあえずやってみるにはこちらのほうが簡単だと思います。
というのも、後者の手続き型を利用している解説動画を見たのですが、格子状のグリッドを描画するだけでもまるで意味がわからなかったので、「六角形なら一体どうなってしまうんだ」とまったく道筋が見えませんでした。

Unreal Engine Grid System _ Part 1 – YouTube

そして3Dモデルを使いつつ、先ほど紹介した六角形の向きが異なる解説動画を見つけて、機械翻訳を見ながらの解読が始まりました。
二つの解説動画は六角形の向きが異なるだけでなく、作成した変数や関数の数、大きさの変更やタイル同士の間隔の実装の有無があります。(Fig2)

今回試行錯誤した結果、改善点を盛り込むことができたので参考になればと思います。

UE5 #3_解説動画と制作した実装の比較
Fig2. 参考にした解説動画と今回の実装との比較

HexGridを実現する処理のイメージ

私にとってはスタティックメッシュコンポーネントを使うこと自体が初めてなのですが、計算が複雑なこと以外はシンプルな内容でした。

プログラミングをかじったことのある人であれば、ネストしたForLoopを使って格子状に文字や記号を並べた経験があると思います。
今回はネストしたForLoopを使うことで六角形の座標をそれぞれ演算して並べます。

そのために事前に六角形モデルの大きさを取得するGet Bounding Boxノードを使い、最後にゲーム世界に3Dモデルを配置するAdd Instanceノードを実行します。(Fig3)

UE5 #3_処理のフローチャート
Fig3. おおよその処理のイメージ・フローチャート

Step1:六角形の高さ・幅を取得する

それでは、処理のポイントを見ていきましょう。

作業するブループリントは、学習用のフォルダを作ってもらえればと思います。
解説動画のように、Blenderで作成した六角形のモデルをコンテンツブラウザにインポートして、ブループリントの編集画面で「インスタンス化メッシュコンポーネント(Instanced Mesh Component)」を追加しておきます。

ノードはイベントグラフではなく、Construction Scriptで組みます。

【学習ポイント】イベントグラフではなくConstraction Scriptで組まれたノードは、プレビューと異なりビューポート上で実行されます。

Construction Scriptと書いてある場所でノードを作成するとゲーム中には実行されずに、レベルにそのブループリントを配置、変更した時点で処理が実行されます。

処理が行われるのは具体的には以下の場合です。
・コンパイルした時
・アクタがレベル上に追加された時
・配置されたアクタの変数やプロパティを変化した時
・配置されたアクタのトランスフォームが変化した時

主にアクタの配置や、ライトの色の変化などに使用したりしてレベル(ステージ)を作成する時にコンストラクションスクリプトを使用します。コンストラクションスクリプトはゲーム中に処理の負荷がかからないため、可能なものはイベントグラフではなくコンストラクションスクリプトに記載する方が良いでしょう。

UE4の基本的なメモ|okazu

(※引用の引用で恐縮ですが、説明がわかりやすいので掲載。大元のURLは404になっている。)

【学習ポイント】デバッグのお供であるPrint Stringsノードですが、Construction Scriptのデバッグはスクリーンには表示されません
スクリーンには表示されず、ログにしか表示されないので最初はそれでつまづきました。

インポートした六角形のモデルは、解説動画のようにシンプルにつくった場合は「向かい合う頂点を結ぶ線(外接円の直径)」が2メートルになっています。
実際に六角形のサイズは200×173となっており、ビューポート上に配置して「右クリックでドラッグ」で計測しても半径100が表示されました(Fig4)

UE5.x #3_インポートした六角形の大きさ
Fig4. Unreal Engineにインポートした六角形のサイズを確認

Unreal Engine上では長さの単位が「uu(Unreal Unit)」というものですが、1uu=1cmなので気にしなくいいと思います。

Get Bounding Boxノードで内接円・外接円の半径を取得します。
ノードのピンにあるReturn Value Max X(Y)は、モデルの中央からX軸・Y軸方向の端の座標(ベクトル)を示していて、それがそのまま内接円・外接円の半径として利用できます。
素人ながらにX軸・Y軸方向が分かりづらかったので、二つの値の大小を比較することで変数に間違えずにSETする方法を作ってみました。(Fig5)

UE5.x #3_六角形の高さと幅を仕分けるノード
Fig5. バウンディングボックスの正の値(幅・高さの半分)をそれぞれの変数にSETするノード

「ゲーム開発はベクトルを使うぞ」とは大昔から聞いていて、これが初めて扱った瞬間です。実践がそのまま勉強になりました。

解説動画だと緑色のライン(データフロー)が複雑なので、ここで変数にSETしたものを演算の都度GETしていきます。
これは「スパゲティノード」にならないようにするために参考にした知恵です。(Fig6)

現役GDが考えるUE4ブループリントのきれいな書き方 – ConquestArrow.com

UE5.x #3_修正前のスパゲティノード
Fig6. ノードを整理する前に解説動画の真似をしたスパゲティノード

Step2:X軸・Y軸方向へ、ネストしたForLoopを用いる

ForLoopのノードは、二つ並べるだけでネストしたものと同じ挙動をします。
今回はループ処理の中に次のステップで触れる「ゲーム空間にインスタンスを出現させる」までが含まれているので、ループを完了した後の処理はありません。

ループに用いるIndex値は解説動画では「1」から始めており、これでも動作します。
ただ、これは追加したアクタの基準位置よりもX、Y軸ともに1枚分離れた位置から始まります。(Fig7)(Fig8)

UE5.x #3_タイルの生成がアクタの基準から離れている
Fig7. タイルの生成がアクタの基準位置から離れていることに気づいた
UE5.x #3_Index値の開始が0と1の場合の比較
Fig8. Print StringsノードでループのIndex値とタイルの座標を表示したログの比較

紙に書いて方程式を検証していたのですが、開始のIndex値が1では計算が合わなかったのです。
このときに「本来1枚目であるはずの場所からズレている」ことを推測し、その原因が「Index値が1から始まっているから」だと疑いました。

それを違和感だと思う場合は、タイルの枚数指定はそのままにIndex値を「マイナス1」することで1枚目のタイルを(X=0, Y=0)から始めることができます。(Fig9)

UE5.x #3_ネストしたForLoopのノード
Fig9. ネストしたForLoopのノード

Step3:タイルを並べるための座標を計算する

タイルを並べるには、「1枚あたりの差分」に「枚数(ループ回数)」をかけ合わせた値を座標(ベクトル)に挿入します。

水平方向(Y軸方向)へのループは、先に解説したように「六角形の幅」の分ズレているだけです。
複雑に思えるのは、「垂直方向(X軸方向)が偶数(または奇数)のとき」の計算だと思いますが、これは「水平方向へ六角形の幅の半分」を加算(または減算)するだけです。

Step1で六角形の幅と高さの半分の値を変数に格納したので、計算にそのまま利用できます。
これが高さの値しか取得していない場合、幅の値を計算するために「√3(SQRTノード)」を使う必要が出てきます。

そしてSelect Vectorノードの判定にモジュロ演算(Xを2で割り切れるか)をつなぐことで偶数または奇数時に取り出す値を切り替えます。(Fig10)
(ちなみに、ゼロは偶数。)

取り出した値は(デバッグの必要があれば変数に格納し)Add Instanceノードに必要なMake Transformノードに渡されます。
これでタイルを並べることはできました。

UE5.x #3_タイルのベクトルの計算とゲーム空間への生成
Fig10. ベクトルの計算とゲーム空間へのタイルの生成を処理したノード

このノード、めっちゃ整理されてて読みやすいと自画自賛してます。

課題

これをシミュレーションゲームの盤面として使うには、足りないものばかりです。
すでに判明していることですが、例えばユニットを配置したり操作するときは「マス(タイル)をクリックする」必要が出てきます。

しかし見てわかるように、ここで並べたタイルはオレンジのラインで囲まれ「並んだ状態が一つのアクタ」となっています。
そのためどこをクリックしてもこのアクタが選択されてしまいます。
その後に見つけた「クリックしたタイルを選択する解説動画」では、「インスタンス化されたスタティックメッシュ」を使っていません。
個別にタイルを選択できるようにするには、まずはここから直していくことになるでしょう。

Select Tiles in Grid (Unreal Engine Tutorial)

あとがき

3年ぶりに、フェードアウトしていたUnreal Engineに関する記事を書きました。
依然として何が必要なのか、何が正しいのか、初歩的な問題だとしてもゲーム開発・UE素人にはわからないままです。

このように、作りたいものがあっても「一貫した解説が無いが故に挫折せざるを得ない状況」があります。
完成した暁にはレポートを出したいですが、そのためにも「作りたいものにこだわるのではなく、出来ることを増やす」という方針で学び始めることを決めました。

個人でもゲームエンジンの恩恵を受けられる時代になりましたが、「誰でもゲームを作れる」とは私は思っていません。
ゲーム業界にいなくても夢を叶えられるように知識の共有ができればと思います。

また、解説垂れ流しの無編集動画の機械翻訳を解読するのは本当に大変でした。眠くなる。
日本人・日本語の情報がもっと増えてもいいと個人的には思うところ。