PavilionDV7の雑多なやつ

Qiitaから移行しました。UE4に関する記事から興味のあることまで色々書きます。

【UE5】倉庫番を作ろう! プロジェクトセットアップ・ゲームプレイ編

はじめに

Unreal Engine (UE) Advent Calendar 2022 その3 16日目の記事です。

UE5を使って倉庫番を作ります。

全部で3パートあります。

プロジェクトセットアップ・ゲームプレイ編(ここ)
メインメニュー・ステージセレクト編
ゲームループ・ブラッシュアップ編

このチュートリアルではUnreal Engineの操作から教えるものではなく、Unreal Engineを使ってゲームを作るチュートリアルです。Unreal Engineの操作(例:プロジェクトの作成、レベルの作成、ブループリントの作成等)は把握していることを前提としています。

今回のプロジェクトで必要な操作を大まかに挙げてみます。

  • プロジェクトの作成ができる
  • 新規レベルを作成できる
  • 任意のクラスを継承してブループリントを作成できる
    • Actorを継承したブループリント、Game Modeを継承したブループリント等
  • ブループリントのDetailsパネルからプロパティを変更できる
    • Transformの変更、Collision Presetの変更等
  • 構造体アセットとDataAssetアセットを作成して任意のデータ形式を作成できる
  • ウィジェットブループリントでUIのデザインができる

挙げた内容に不安がある方は公式オンラインラーニングにある

dev.epicgames.com

dev.epicgames.com

この2つを受講すれば問題ないと思います。

注意点

使用するUEバージョン:UE5.1.0
エディタの言語:英語

UE5.1.0以外での制作についてはサポートしません。

エディタの言語は英語です。よってスクショや文章でも英語設定に準拠した名前・項目名となります。

完成図

このチュートリアルが無事に完了できれば次のビデオのような倉庫番ゲームができます。

GitHubでプロジェクトのダウンロード

お試しとしてGitHubにプロジェクトをアップしてみました。各ステップの最後にはコミットへのリンクを貼っておくのでエラーが出て動かない、手順通りにやったはずなのに動かないという場合はそのステップ時点でのプロジェクトをダウンロードして比較すれば解決しやすいはずです。

GitHubからのプロジェクトのダウンロードは緑色の「Code」というボタンを押して、表示されたメニューの一番下にある「Download ZIP」をクリックするとダウンロードできます。

GitHub - bullyleo/Tut_SOKOBAN

プロジェクトセットアップ編

ステップ1:プロジェクトを作成

Epic Games Launcherからプロジェクトを新規作成します。プロジェクト名はおまかせですが、ここでは「Tut_SOKOBAN」としました。

プロジェクトが作成できたら新規レベルを作成します。使用テンプレートは「Blank」とします。

作成したレベルを「Content -> [作成したプロジェクト名] -> Maps」フォルダに保存します。名前は「P_GamePlay」とします。

Project Settings -> Maps & Modes -> Editor Startup Mapに先程保存したP_GamePlayをセットします。


筆者のPCは8年前に購入したおじいちゃんスペックなので低スペック向け設定をしています。
設定変更の常連は次の記事にある「Lumenと仮想シャドウマップを無効化する」「スケーラビリティの品質を下げる」の2つです。

qiita.com

上記の設定に加えて、新規レベル作成時に配置されている「Directional Light」「Sky Light」のMobilityを「Stationaly」にしています。

  • [Directional Light] & [Sky Light]
    • Mobility : Stationaly

この設定によって高スペックPCを持つ方とはスクショやビデオでの見え方が大きく異なる可能性がありますが、動作については変化はないので心配はありません。

ゲームプレイ編

このチュートリアルでは実装する順番を次のようにします。

1.ゲームプレイ(ステージが読み込まれて、ステージクリア画面が表示されるまで)
2.メインメニュー&ステージセレクト
3.ゲームループ(メインメニュー -> ステージセレクト -> ゲームプレイ -> ステージセレクトまたはメインメニューのループ)
4.ブラッシュアップ

まずはゲームの核となる要素(移動、押す・引くといった動作等)から手をつけます。そこから1ステージを始まりから終わりまできちんと遊べるようにした後、ステージを量産して正しく動作するかチェックします。それが終わればメインメニュー画面とステージセレクト画面に着手しますが、この時点では独立したシーンまたは一方通行でシーン遷移するだけです。メインメニュー -> ステージセレクト -> ゲームプレイと遷移ができれば、ゲームプレイからメインメニューもしくはステージセレクトへ戻れるようにします。ここが完成するとゲームがきちんとループするようになります。
ブラッシュアップはUIの項目を増やしたり、クリアしたステージに「CLEARED」といったラベルを貼ったりします。

ここから多くのアセットを作りますが、アセットの保存場所については特に指定はないのでわかりやすいようにフォルダ分けしてください。GitHubにアップしているプロジェクトを参考にしてもOKです。

ステップ2:Enhanced Inputで入力処理

Input Actionアセットを作成します。

  • 名前 : IA_Move

IA_Moveを次のようにセットアップします。

TriggerにPressedを設定して押下時に1度だけイベントが通知されるようにします。ボタンの長押しはサポートしません。


続いてInput Mapping Contextアセットを作成します。

  • 名前 : IMC_Move

IMC_Moveをセットアップします。


プレイヤーブループリントを作成します。

  • 親クラス : Pawn
  • 名前 : BP_Player

BP_Playerをセットアップします。

セットアップが完了すれば次のようになります。


続いてプレイヤーコントローラーブループリントを作成します。

  • 親クラス : Player Controller
  • 名前 : BP_PlayerController

BP_PlayerControllerを開き、次のように処理を組みます。


入力処理がうまく動作しているか確認していきます。
まずレベルにBP_PlayerとCamera Actorを設置し、次のように各プロパティをセットします。

続いてゲームプレイ用ゲームモードブループリントを作成します。

  • 親クラス : Game Mode(「Game Mode Base」では無い)
  • 名前 : GM_GamePlay
  • Class Defaults
    • Player Controller Class=BP_PlayerController
    • Default Pawn Class=None

GM_GamePlayのセットアップが終わればP_GamePlayにGM_GamePlayをセットします。

レベルブループリントを開き次のように処理を組みます。

BP_Playerというノードはレベルに設置したBP_Playerアクターを選択した状態で、レベルブループリントで右クリックすると「Create Reference to BP_Player」とありますので、それをクリックすると出てきます。

ここまで準備できれば実際にプレイをしてWASDキーを押して画面上に値が出てきているか確認します。人によってはプレイ時に一度画面をクリックしないとWASDキーが反応しないこともあります。

正しくセットアップできていればWSADの順でキーを押すと次のような値が出力されます。

  • W : X=0 or -0 Y=-1
  • S : X=0 or -0 Y=1
  • A : X=-1 Y=0 or -0
  • D : X=1 Y=0 or -0

このプロジェクトでは2DゲームのようにX軸とY軸で動き回ることになります。基準となる向きは右方向に正X軸、下方向に正Y軸としています。ですので、上方向に動くWキーを押せば負Y軸であるY=-1となり、Sキーを押せば下方向なので正Y軸であるY=1になります。左右も同様です。

Enhanced Inputについては以下のリンクが参考になります。

【UE5】Enhanced Input | キシロラボ

Unreal Engine の Enhanced Input | Unreal Engine 5.1 ドキュメント

ステップ3:プレイヤーの移動処理

BP_PlayerControllerを開き、次のように処理を組みます。

これでキー入力に応じてプレイヤーが移動するようになります。

ここで「100」という数値を移動距離としましたが、100という数値は後に登場するブロックの1辺の長さに由来します。この「100」という数値は今後、変更する予定も無く、意味のある数値ですのでしっかりと定数として扱います。しかしUnreal Engineのブループリントでは定数が扱えないのでマクロライブラリを利用し、それを定数の代わりとします。

定数用マクロライブラリブループリントを作成します。

  • 親クラス : Object
  • 名前 : BPML_Constants

マクロを組みます。

BP_PlayerControllerに戻り、先程の「100」としたところをBoxEdgeLengthに置き換えます。

動作確認をして置き換える前と変化がないことを確認します。

移動ができたので次は移動方向に回転させます。
BP_PlayerControllerを開き、Set Actor Locationノードに続いて次のように処理を組みます。

動作確認をして移動方向に回転することを確認します。

マクロライブラリを定数の代わりとして利用する方法は次の記事を参考にしました。

20年オヤジのUnreal Engine 4 TIPS - SEGA TECH Blog

ちょっと小話 ~定数化する基準~ 自分が定数化する基準は大体、次のようなものです

  • 複数のブループリントに点在する
  • タイプミスをする可能性がある

特に「複数のブループリントに点在する」は定数化するきっかけとして最も多いです。同じ意味を持つ数値や文字列が点在してしまうと、検索や修正が非常に面倒なことになります。マクロで定数化してあげることで「名前を付ける」ことで検索しやすくなりますし、マクロ側の数値を変更すれば利用している箇所全てに適用できます。

1つのブループリントで複数のイベントで利用される場合、マクロは使わずに普通の変数を使います。

文字列を扱う場合、最も怖いのはタイプミスです。少文字の「i」と「l」はとても危険です。コピペすれば間違いようがないと思われるかもしれませんが、コピー元が間違っているときもあります(やらかしたことあります)。このようなタイプミスを防ぐにも定数化は有効です。

文字列の場合、定数化だけではなくEnum(列挙型)で指定できるようにすることも有効です。

ステップ4:スムーズに移動する

BP_PlayerControllerを開き、次の通りに変数を追加します。

続いてIA_Moveイベントの処理を組み直します。

Timelineノードを追加し、次のようにセットアップします。

  • 名前:Timeline_Move

Timeline_Moveノードから次のように処理を組みます。

再びIA_Moveイベントに戻り、bMovingにTRUEをセットしている箇所からTimeline_Moveコンポーネントを呼び出します。

TImelineノードを起動するには、左側にある実行ピンに別の実行ピンを繋げるだけではなく、「変数」の欄にある同名のTimelineコンポーネントを呼び出し、そこから「Play from Start」を呼ぶことで起動させることができます。

動作確認をすると1つ前のステップのときよりもスムーズに移動・回転します。bMovingフラグのお陰でTimelineノードの実行は入力を受け付けないようにしています。移動と回転の速度を早くしたければTimelineノードのLengthをより小さい値に、遅くしたければ大きい値にすれば調整できます。EaseノードのFunctionには多くの種類がありますが、それぞれ数値の加速具合が異なるので色々変えてみると面白いです。

[UE4] 動きに緩急をつけるEaseノードの紹介|株式会社ヒストリア

ステップ5:ステージを作る

ブロックの基底クラスとなるブループリントを作成します。

  • 親クラス:Actor
  • 名前:BP_Block_Base

BP_Block_Baseを開き、次のようにセットアップします。

BP_Block_BaseのTickは無効にしておきます。

BP_Block_Baseを継承してテスト用ブロックブループリントを作成します。

  • 親クラス:BP_Block_Base
  • 名前:BP_Block_Test

BP_Block_Testを次のようにセットアップします。

変数を追加してセットアップします。

続いてBegin Playから処理を組みます。


ステージ生成用ブループリントを作成します。

  • 親クラス:Actor
  • 名前:BP_StageBuilder

このアクターはTickが不要なのでStart with Tick Enabledのチェックを外します。

Begin Playノードから次のように処理を組みます。

Make Literal Stringの中身は次のようになっています。

11111
10131
10201
14001
11111

Make Literal Stringでは「Shift + Enter」を押すことで改行することができます。そこから続くParse Into ArrayノードでDelimiterに指定した文字を境にSource Stringを分割します。画像中では「Shift + Enter」をDelimiterに指定しています。For Each Loopノードで1行ずつ要素が取り出せるので、更にGet Character Array from Stringで文字列を1つの文字に分割します。

Switch on Intノードで文字を数値に変換しつつ、数値に対応するブロックを生成します。数値は次のように対応します。

  • 0:ブロックなし(床)
  • 1:壁ブロック
  • 2:押出し可能ブロック
  • 3:ゴールブロック
  • 4:プレイヤー

ここではテスト段階なので生成するブロックはすべてBP_Block_Testを生成します。違いはBlock Nameに指定する文字列だけです。

動作確認の前にステップ2でレベルに配置したBP_Playerとレベルブループリントの処理を削除します。

削除できたら、BP_StageBuilderをレベルに配置します。

動作確認すると文字の向きはおかしいですがMake Literal Stringで指定した数値がそのままブロックに置き換わっています。

現状ではブロックを無視して動き回れますが、後のステップで修正するので気にしないでOKです。

ステップ6:Paper2Dを使ってステージを作る

ステージをテキストで表現する方法はお手軽ですが一覧性が悪いのでPaper2DのTile SetとTile Mapを使って同様の表現をします。

Paper 2D | Unreal Engine 5.1 ドキュメント

その前にTile Setで使うテクスチャを作成する必要があります。ここでは筆者が作ったテクスチャを置いておきます。お好みの場所に保存してください。

自分の手でテクスチャを作ってみたい方に向けて上記のテクスチャを作った際の情報を載せておきます。

  • GIMPで作成(エディタはお好み)
  • 1パーツ:64 x 64
  • 全5パーツ:320 x 64
  • 左から床、壁、押出し可能ブロック、ゴールブロック、プレイヤー
  • 名前:T_StageTile

テクスチャが作成できたらプロジェクトにインポートし、Tile Setアセットを作成します。

  • 名前:TS_StageTile

TS_StageTileを開いてTile Sizeを1パーツのサイズに設定します。筆者のテクスチャをダウンロードした方は次の数値を入力してください。自作の方は1パーツあたりのサイズを入力します。

  • Tile Size:64 x 64

続いてTile Mapアセットを作成します。Tile Setアセットを右クリックすると「Create Tile Map」という項目があります。

  • 名前:1-1

Tile Mapアセットを開くとエディタが出現します。ざっくりと使い方を紹介します。

エディタ左にあるのがタイルパーツです。左から内部では0,1,2,3,4と数字が割り振られています。選択中のパーツは細いですが白枠が付きます。
パーツを選択しエディタ中央の白枠内をクリックすると選択したパーツが塗られます。エディタ中央の白枠のマス数を増やしたければエディタ左にある「Map Width、Map Height」の数値を増やしてください。上の画像では5 x 5のステージなのでMap Width、Map Heightは共に「5」という数値をセットしています。

では、テキストで表現したものと同じものを作ってください。上の画像と同じようになるはずです。


作成したTile MapアセットをBP_StageBuilderで読み込んでステージを生成します。

BP_StageBuilderを開き、次のようにセットアップします。

追加したPaper Tile Mapコンポーネントの「Visible」のチェックを外します。必要な情報は「各マスに何番のパーツが貼られているか」なので描画が不要なためです。

続いてBegin Play以降の処理を組み直します。

プレイするとテキストで表現していたときと全く同じステージが生成されたことが確認できます。 試しに1-1を再編集してブロックを変えてみたりステージのサイズを変えてみると正しく機能していることがよく分かると思います。

ちょっと小話 ~量産を手助けするツール~ 「物量」がものをいうゲームの場合、量産を手助けするツールの存在はとても重要です。今回、制作する倉庫番のようなシンプルなゲームでは「ステージ数」が大事なので、ステージを量産しやすくするためにPaper 2DのTile Mapエディタをステージエディタとして転用することにしました。

Unreal EngineにはEditor Utility Widget(EUW)という機能があります。UMGを使ってエディタを拡張することができ「あるアクターを全て、レベルから削除する」「レベルに多く配置されているアクターのプロパティに値をセットする」といったエディタに搭載されていない独自の機能を簡単に作ることができます。このEditor Utility Widgetを使ってアクターをレベルに配置する作業を楽にすることもできると思います。

例えば次のようなもの。

【UE4】NavLinkProxy配置作業をEditor Utility Widgetでちょっと楽にする - Qiita

2022年11月20日に開催されたUnreal Fest Westでの講演の1つ「「ライブアライブ」28年前の作品をHD-2Dで蘇らせる挑戦」では大量の技の演出や攻撃可能範囲等のチェックをEUWを使って効率よく実行できるようにしています。

EUWだけではなくData Asset使ってもいいです。例えば「スポーンタイミング」「スポーン位置」「タイプ(攻撃型、防御型等)」「性格(攻撃的、日和見的)」というデータをData Assetで定義すれば戦闘デザインを実験・調整・修正しやすくなります。

ステップ7:ステージを量産する

Tile Mapエディタを使うことでステージのデザインがとても簡単にできるので、あと3つほどステージを作ります。作成したTile Mapアセットの名前は次のようにします。

  • 名前:1-2
  • 名前:1-3
  • 名前:1-4

こういったデザインが苦手な方はジェネレータがweb上にありますので是非活用しましょう。画面左にあるつまみを全て左側に寄せると簡単な問題が作りやすくなります。

alliballibaba.github.io

3つステージが出来れば、BP_StageBuilderを開いてSet Tile Mapノードの「New Tile Map」を読み込みたいステージに変更して正しくステージが作れているか確認します。

ステップ8:ステージデータのロード処理

いくつかのステージデータをロードしたいとき、わかりやすいものは「Tile Mapのオブジェクト参照型の配列を用意し、予めロード対象を格納しておく」という方法があります。しかしこれには問題があり、配列に格納したTile Mapデータをすべてメモリに展開してしまいます。もしステージを100個、200個作るとしたら無視できないメモリ消費量になる可能性があります。

このプロジェクトではロード対象のTile Mapアセットをソフト参照という形で保持することにします。ソフト参照にすることで「このアセットをロードします」と明示的に宣言したときだけ対象のアセットがメモリに展開するようになります。


構造体アセットを作成します。

  • 名前:SStageAssetPath
  • 変数1
    • 名前:StageDataPath
    • 型:Paper Tile Map Soft Object Reference

Data Tableアセットを作成します。

  • Row Structure:SStageAssetPath
  • 名前:DT_StageAssetPath

DT_StageAssetPathを開いて次のようにデータを追加します。

Row Nameは自分で入力する必要があります。Row NameとStageDataPathの末尾部分が一致していること確認してください。


次にBP_StageBuilderを開きます。Begin Play以降の処理をまるっと別のイベントに移します。カスタムイベントを作成します。

  • 名前:BuildStage
  • 引数
    • 名前:Target
    • 型:Paper Tile Map Object Reference

空いたBegin Play以降にステージ読み込みの処理を組みます。

動作確認をします。前ステップと全く変わらない結果が出ればOKです。


このステップによって変化した部分はステップの冒頭でも書いた「アセットの参照」です。
Content BrowserでBP_StageBuilderを右クリックして「Reference Viewer」を選択すると、そのアセットが参照を持つアセットを閲覧することができます。Set Tile Mapに読み込みたいTile Mapアセットを直接指定したときのReference Viewerは次のようになります。(一部抜粋)

BP_StageBuilderから「1-1」というTile Mapアセットに白い矢印でつながっています。これは「ハード参照」を意味し、BP_StageBuilderをロードすると自動的にTile Mapアセットがロードさせることを意味します。

新しくBegin Play以降に追加したロード処理を挟むとReference Viewerは次のようになります。(一部抜粋)

Tile Mapアセットが無くなり、ロード対象から外れます。


この「アセットの参照」は重要なトピックではあるものの、ぷちコンで作るようなコンパクトなゲームでは過敏に反応する必要はないと思います。ですが、大まかでいいのでアセット参照の問題点と解決法は知っておいて損はありません。幸い日本語で読める資料はたくさんあるので興味が出たら一読してみることをオススメします。

アセットの参照 | Unreal Engine ドキュメント

第007回UE4のアセットの参照方法について、そのロードの違い | CC2の楽屋裏

ハード参照とソフト参照 - おかわりはくまいのアンリアルなメモ

BPの参照連鎖を断つ手法 | 株式会社ヘキサドライブ | HEXADRIVE | ゲーム制作を中心としたコンテンツクリエイト会社

ステップ9:壁ブロックと押せるブロック その1

現状ではカメラがレベルの中心で固定になっており見た目が悪いので、ステージ生成時にカメラをステージの大体中心に持ってくるようにします。

BP_StageBuilderを開き、次のように変数を追加します。

  • 名前:CameraActor
  • 型:Actor Object Reference
  • Instance Editable:チェック入れる

イベントBuild Stageの外側のループ(「Row」とコメントが有るところ)のCompletedから続いて次のように処理を組みます。

動作確認をし、カメラがおおよそステージの中心に来ていればOKです。


ブロックのブループリントを作る前にそれぞれに割り当てるマテリアルを作成します。
まずブロックに適用するマテリアルの親マテリアルを作ります。

  • 名前:M_Block

M_Blockを開いて次のように処理を組みます。

M_Blockからマテリアルインスタンスを2つ作成します。Content BrowserでM_Blockを右クリックし「Create Material Instance」を選べばOKです。各マテリアルインスタンスの名前と設定する色は次のようにしました。色については特に指定は無いので好きな色で構いません。テクスチャを用意できるならそれを使ってもOKです。その場合はマテリアルに少し変更を加える必要があります。

  • 名前:MI_Block_Push
    • 色(Hex Linear):FF990000
  • 名前:MI_Block_Wall
    • 色(Hex Linear):4D1A0000

BP_Block_Baseを継承して壁ブロックブループリントを作成します。

  • 名前:BP_Block_Wall

BP_Block_Wallを開いたら、まずアクタータグを設定します。アクタータグは「Class Defaults -> Actor -> Advanced -> Tags」にあります。

  • Tags:Wall

続いてCubeメッシュコンポーネントに先程作成したMI_Block_Wallマテリアルを割り当てます。

次にBP_Block_Baseを継承して押せるブロックブループリントを作成します。BP_Block_Wallと同様にアクタータグを設定しマテリアルを割り当てます。(画像省略)

  • 名前:BP_Block_Push
  • Tags:Push
  • マテリアル:MI_Block_Push

BP_StageBuilderで壁ブロックと押せるブロックを生成している箇所をBP_Block_Wall、BP_Block_Pushにそれぞれ差し替えます。

BP_StageBuilderを開き、次に示すノードに渡すパラメータを変更します。

Spawn ActorノードのClassを変更したことで「Block Name」が無くなり、エラーが出ますがノードを右クリックして「Refresh Nodes」を選択することで解決することができます。

動作確認をして正しく各ブロックブループリントが生成されているかチェックします。

ステップ10:壁ブロックと押せるブロック その2

まず進行方向にブロックがあるときは移動できないようにします。

BP_PlayerControllerを開き、新しく関数を追加します。

  • 名前:CanMoveTo
  • 返り値:Boolean型
  • 返り値2:Actor Object Reference

続いてイベントIA_Moveを書き直します。

動作確認をして移動先にブロックがあるときは移動できないかチェックします。ここではLine Traceが「何にも衝突しなかったとき」は移動可能としているので、押せるブロックでもゴールブロックでも移動できないようになっています。


次は移動方向に押すブロックがあるとき、押せるブロックを押して自身も移動するようにします。

まずはBP_Block_Pushに「移動する処理」と「移動可能かチェックする処理」を追加します。
BP_Block_Pushを開いて変数を追加します。

  • 名前:StartLoc
  • 名前:GoalLoc

続いてTimelineノードを追加します。ここでの処理はBP_PlayerControllerにあるTimeline_Moveと同じです。

続いて関数を追加します。

  • 名前:TryMoveTo
  • 引数:Vector2D
  • 返り値:Boolean型

再びBP_PlayerControllerを開いてCan Move To関数から続けて次のように処理を組みます。(画像一部省略)

動作確認して押せるブロックが目の前にある時、押して移動することができる事と押せるブロックと壁ブロックが隣り合ったとき、押せるブロックは壁ブロック方向に押せないことを確認できればOKです。

ステップ11:ゴールブロックとゴール検出

ゴールブロックに割り当てるマテリアルを作成します。M_Blockからマテリアルインスタンスを作成します。

  • 名前:MI_Block_Goal
    • 色(Hex Linear):00FF0000

次にBP_Block_Baseを継承してゴールブロックブループリントを作成します。

  • 名前:BP_Block_Goal

BP_Block_Goalを開いてセットアップします。はじめにCubeメッシュコンポーネントに先程作成したMI_Block_Goalを割り当てます。

続いてCubeメッシュコンポーネントのScaleとCollision Presetを変更します。

  • Scale:XYZ = 0.8
  • Collision Preset:Invisible Wall

次にBox CollisionコンポーネントのBox Extentを変更します。

Box CollisionのCollision PresetをInvisible Wallにすることでプレイヤーの移動時に実行するLine Trace処理でBP_Block_Goalが衝突することが無くなります。BP_PlayerControllerで実行するLine Traceは「Visibilityチャンネル」で衝突の判定をしているため、Visibilityチャンネルの衝突が無効になっているInvisible WallプリセットにすることでLine Traceの判定を無視します。

セットアップが完了すると緑色のCubeの頭からひょっこりBox Collisionがはみ出てるようになります。

ゴールブロックができたのでBP_StageBuilderのゴールブロック生成部分をBP_Block_Goalに差し替えます。

動作確認をしてゴールブロックがBP_Block_Goalに差し替わっていること、プレイヤーがBP_Block_Goalに移動できること、BP_Block_PushをBP_Block_Goalに押せることをチェックします。


次に押せるブロックがゴールブロックに重なったとき、メッセージを出力するようにします。

BP_Block_Goalを開き、Box Collisionの「On Component Begin Overlap」「On Component End Overlap」イベントを作成します。
それぞれ次のように処理を組みます。

動作確認し押せるブロックがゴールブロックに重なったとき、メッセージが出力されることをチェックします。ゴールブロックから外れたときもメッセージが出力されることもチェックします。


押せるブロックがゴールブロックに重なったとき(外れたときも)同じ内容のメッセージが2つ同時に出力されたはずです。これは不具合なので修正します。

原因を突き止めるためOn Component Begin OverlapのOther ActorとOther ComponentでそれぞれGet Display Nameでオブジェクト名をメッセージとして出力させます。

プレイをしてメッセージを出力されると次のように表示されます。

下から上に向かって順に「重なったことのメッセージ」「重なった相手のアクター名」「重なった相手のコンポーネント名」が出力されます。重なった相手のアクター名はどちらも「BP_Block_Push」ですが重なった相手のコンポーネント名が異なります。これはBP_Block_Pushが持つBox CollisionコンポーネントとCubeメッシュコンポーネントが2つともBP_Block_Goalと重なったイベントを発生させていることを示しています。

原因がわかったのでBP_Block_PushのBox CollisionコンポーネントとCubeメッシュコンポーネントのどちらかを重なったときイベントを発生させないようにします。今回はCubeメッシュコンポーネントを修正します。

BP_Block_Pushを開いて、Cubeメッシュコンポーネントの「Generate Overlap Events」のチェックを外します。

再び動作確認をして押せるブロックとゴールブロックが重なったときに「1度だけ」メッセージが出力されることをチェックします。

確認できればBP_Block_GoalのGet Display Nameを繋いだPrint Stringノードは不要なので削除してOKです。

ステップ12:ゲームクリアの検出

このゲームでのゲームクリア条件は「全ての押せるブロックがゴールブロックと重なった」です。これを検出する処理を組んでメッセージを出力します。

GM_GamePlayを開いて変数を2つ追加します。

  • 変数1
    • 名前:NumOnGoalBlock
    • 型:Integer
  • 変数2
    • 名前:TargetNumGoalBlock
    • 型:Integer

続いてカスタムイベントを2つ追加します。

  • 名前:IncreaseNumOnGoalBlock
  • 名前:DecreaseNumOnGoalBlock

まずはGM_GamePlayとBP_Block_Goalの連携部分を作っていきます。2つのカスタムイベントの処理を次のように組みます。

次はBP_Block_Goalを開き、GM_GamePlayに作成した2つのカスタムイベントを呼び出す処理を組みます。

動作確認をして押せるブロックとゴールブロックが重なると数値が出力され、外れると1減った数値が出力されます。


実はこの実装では次のようにゴールブロックが隣り合うステージのとき...

1つの押せるブロックをゴールブロックからゴールブロックへ移動すると一瞬ですが「押せるブロックが2個ゴールブロックと重なった」と判定されてしまいます。

この不具合の原因はBP_Block_GoalのコリジョンとBP_Block_Pushのコリジョンの大きさにあります。


原因がわかったので修正していきます。

BP_Block_Pushを開き、Box CollisionコンポーネントのBox Extentを次のように設定します。

設定を終えたら動作確認をして、ゴールブロックが隣り合った状況で押せるブロックを移動しても正しい数値が出力されることを確認します。


GM_GamePlayは「ステージにゴールブロックがいくつあるか」という情報を知らないので、誰かが教えて上げる必要があります。

BP_StageBuilderを開き、変数を1つ追加します。

  • 変数
    • 名前:NumGoalBlock
    • 型:Integer

Spawn Actor from ClassでBP_Block_Goalをスポーンしたあとに続いて次のように処理を組みます。

同じBP_StageBuilderのCamera ActorのSet Actor Locationを呼び出したあとに次のように処理を組みます。

次はGM_GamePlayを開いてイベントIncreaseNumOnGoalBlockの続きに次のような処理を組みます。

動作確認をして全ての押せるブロックがゴールブロックと重なったときステージクリアを示すメッセージが出力されればOKです。今はメッセージが出力するだけなので、ステージクリアしてもプレイヤーは引き続き操作でき、ブロックを動かせてしまいますが問題ありません。

ステップ13:やり直し機能

詰み防止のために「やり直し機能」を実装します。ここでの「やり直し」はブロックとプレイヤーの位置をステージ開始時点に戻すものであり、いわゆるUNDO(1つ前に戻る)ではありません。

ゲームプレイ用ウィジェットブループリントを作成します。

  • 親クラス:User Widget
  • 名前:WBP_GamePlay

ここからウィジェットブループリントを作っていきますが、その前にこの記事でのウィジェットのパーツ構成の表記を紹介します。

  • WBP_GamePlay
    • [CanvasPanel]
      • [CanvasPanel_GamePlay]
        • Anchor : 全面
        • Offset Right & Bottom : 0.0

この記事では上の例のように箇条書きでパーツ構成を表記していきます。

[](大かっこ)で囲われたものがパーツで[パーツ名_識別名]と表します。かっこが無く:(コロン)で区切られたものは1段上のパーツのプロパティを表します。表記がないプロパティはデフォルト値とします。CanvasPanel_GamePlayの1段下にAnchor:全面とありますが、このときはCanvasPanel_GamePlayのAnchorをプリセットの一番右下にあるものを選択します。

Offset Right & BottomはOffset RightとOffset Bottomの両方とも0.0にセットします。


WBP_GamePlayを開いてパーツを配置します。

  • WBP_GamePlay
    • [CanvasPanel]
      • [CanvasPanel_GamePlay]
        • Anchor:全面
        • Offset Right & Bottom:0.0
        • [Button_Retry]
          • Position X&Y:50.0
          • Size to Content:チェック入れる
          • [Text]
            • Text:やり直す
            • Font Size:36

Button_RetryのOn Clickedイベントを作成します。


作ったウィジェットを画面に表示します。GM_GamePlayを開いてBegin Playに続けて次のように処理を組みます。

動作確認をします。適当にブロックを動かしたあと、画面左上にある「やり直し」ボタンを押すとステージがリセットされればOKです。

ステップ14:ステージクリアUIを出す

ステージクリア用ウィジェットブループリントを作成します。

  • 親クラス:User Widget
  • 名前:WBP_GameClear

WBP_GameClearを開いて次のようにパーツを配置します。

  • WBP_GameClear
    • [Canvas Panel]
      • [Border]
        • Brush Color(Hex Linear):00000080
      • [Text]
        • Anchor : 中心
        • Position Y : -100
        • Alignment : X & Y=0.5
        • Size to Content : チェックを入れる
        • Text : STAGE CLEAR
        • Font Size : 144
      • [Button_StageSelect]
        • Anchor : 中心
        • Position X : -300
        • Position Y : 200
        • Alignment : X & Y=0.5
        • Size to Content : チェックを入れる
        • [Text]
          • Text : ステージセレクト
          • Font Size : 36
      • [Button_Next]
        • Anchor : 中心
        • Position X : 300
        • Position Y : 200
        • Alignment : X & Y=0.5
        • Size to Content : チェックを入れる
        • [Text]
          • Text : 次へ
          • Font Size : 36


次にGM_GamePlayを開いて変数を1つ追加します。

  • 名前:WBP_GamePlay_Ref
  • 型:WBP_GamePlay Object Reference

前ステップで組んだWBP_GamePlayの生成部分に処理を追加します。

続いてゲームクリア判定の処理から次のように処理を組みます。

動作確認をして、ゲームクリア時にやり直しボタンのUIが消えて「STAGE CLEAR」のUIが表示され、操作もキーボード操作も受け付けない状態になればOKです。STAGE CLEARのUIにあるボタンは何も処理を書いていないので押しても反応はありません。

次のパートへ...

メインメニュー・ステージセレクト編