【Unreal Engine】UE初心者の総仕上げ:コイン集めゲームを1本完成させる

作成: 2026-07-21

学んだ部品を1本のゲームに束ねる総仕上げ。60秒で20枚のコインを集める1ステージを、コインActor・GameStateでのスコアと残り時間・HUD・クリア/ゲームオーバー・ハイスコア保存・パッケージ化まで通しで作ります。最後は手触りを上げる3つの追加まで。

ここまでの記事で、キャラを動かす、当たり判定を取る、UIを出す、データを保存する、といった部品は一通り触ってきました。それでも「自分のゲーム」と呼べるものはまだ手元にない、という状態になりがちです。部品の作り方と、部品を1本のゲームに束ねる作業は別物だからです。

この記事では、 60秒で20枚のコインを集める1ステージのゲーム を、最初から最後まで作ります。ステージを組み、コインを配置し、スコアと残り時間を管理し、HUDに出し、クリアとゲームオーバーを分け、ハイスコアを保存して、exeにして渡すところまでです。

コインが並ぶステージとスコア・残り時間のHUD、ソフトブルーのクレイ人形がコインへ向かって走る完成イメージ

この記事でわかること

  • 作る前に 仕様を1行で確定させる 進め方
  • コインActor(回転・Overlapで取得・取った瞬間の演出)
  • スコアと残り時間を GameState に置く理由と実装
  • HUDを Event Dispatcher でつないで更新する
  • クリア/ゲームオーバーとリトライ、 ハイスコアの保存
  • 実践: 30分で手触りを上げる3つの追加

Sponsored

作るものを先に1行で決める

最初にやることは、エディタを開くことではありません。 完成の条件を1行で書く ことです。

制限時間60秒のあいだに、1ステージに置かれた20枚のコインを全部集めたらクリア。時間切れならゲームオーバー。

これだけです。敵もいません、ステージも1つです。物足りなく見えますが、この1行があると「今これは作るべきか」を毎回判断できます。

完成条件を書いたカードから、必要な部品(ステージ・コイン・スコア・タイマー・HUD・リザルト)が枝分かれする図

1行から必要な部品を並べると、こうなります。

必要なもの担当
走り回れるキャラとカメラThird Personテンプレート
取れるコインBP_Coin(Actor)
スコアと残り時間BP_CoinGameState(GameState)
画面表示WBP_HUD(Widget)
クリア/ゲームオーバーHUD内のリザルトパネル
ハイスコアBP_CoinSave(SaveGame)

作るBlueprintは4つだけです。ここから増やさないと決めておくと、最後まで到達できます。

ステージとプレイヤーを用意する

新規プロジェクトを Games > Third Person で作成します。設定は次の3つを合わせてください。

項目
Project DefaultsBlueprint
VariantNone
Starter Content有効にする

Starter Contentは任意追加なので、チェックを入れ忘れると、あとで使う Shape_Cylinder(コインの見た目)や効果音が入りません。 ここだけは必ず入れてください。

このテンプレートには操作できるキャラクター(BP_ThirdPersonCharacter)とカメラ、入力設定が最初から入っているので、移動まわりを作る手間が丸ごと省けます。

キャラの動きを自分で調整したくなったら Character Movement Componentでキャラを動かすSpring Armで作る三人称カメラ へ。ここではテンプレートのまま進めます。

ステージは凝らなくて構いません。テンプレートのレベルをそのまま使い、床の上に段差を2〜3個置く程度で十分です(段差の作り方は Modeling Modeでグレーボクシング が早いです)。プレイヤーの開始位置に Player Start があることだけ確認してください。

プロジェクトを作ったら、先に保存とバージョン管理の準備を済ませておくと安全です。 途中で壊してもやり直せます(→ Git LFSでUEプロジェクトを管理する)。

コインActorを作る

コンテンツブラウザで右クリック → Blueprint Class → Actor を選び、BP_Coin という名前で作ります。

コンポーネントは3つです。

  1. Sphere Collision を追加し、DefaultSceneRootの上へドラッグして ルート にする。Sphere Radius60Collision PresetsOverlapAllDynamic にし、Generate Overlap Events にチェックが入っていることを確認する
  2. Static Mesh を追加して Sphereの子にし、Shape_CylinderContent/StarterContent/Shapes)を割り当てる。Scale(1.0, 1.0, 0.1) にして薄い円盤にし、Rotation の X を 90 にして立てる。Collision PresetsNoCollision
  3. Rotating Movement(コンポーネント追加で Rotating Movement を検索)を追加し、Rotation RateZ を 90 にする

Rotating Movementを足すだけで、コインがくるくる回ります。Tickを書かずに回転を作れる、地味に便利なコンポーネントです(→ Event TickをやめてTimerとイベントで設計する)。

コリジョンを Sphereに持たせ、見た目のメッシュはNoCollision にしているのが要点です。薄い円盤の形どおりに当たり判定を取ると、真横から突っ込んだときに取り逃します。取得判定は見た目より少し大きい球にしておくのが定石です(→ SimpleとComplexコリジョンの違い)。

取ったときの処理をつなぐ

イベントグラフに、次のグラフを組みます。

On Component Begin OverlapからCast To BP_ThirdPersonCharacter、Spawn System at Location、Play Sound at Location、Add Coin、Destroy Actorへ順につながる完成ノードグラフ

読み上げ用の疑似コードはこうなります。

BP_Coin(イベントグラフ)

On Component Begin Overlap (Sphere)
  → Cast To BP_ThirdPersonCharacter(Object = Other Actor)
  → (成功)Spawn System at Location(System = NS_CoinPickup, Location = GetActorLocation)
  → Play Sound at Location(Sound = 短いSE, Location = GetActorLocation)
  → Get Game State → Cast To BP_CoinGameState
  → Add Coin(Amount = 1)        // GameState側の関数。次の節で作る
  → Destroy Actor(Target = Self)

Cast To BP_ThirdPersonCharacter を挟んでいるのは、 プレイヤー以外が触れても取れないようにする ためです。これがないと、飛んできた小石でもコインが消えます。

演出(エフェクトと音)を Add Coin より先に置いているのは意図的です。 Add Coin の中では、20枚目に到達した瞬間にゲーム終了処理まで一気に走ります。演出をあとに置くと、20枚目だけ音もエフェクトも出ないまま画面が止まります。 終了処理を呼ぶ前に、見せたいものを見せておく 。これは順番だけの話ですが、実際に遊ぶと差がはっきり出ます。

音は Play Sound at Location を使います。Spawn Sound Attached でコインに音をぶら下げると、既定の設定では直後の Destroy Actor で音まで一緒に消え、鳴っていないように聞こえます。 消えるActorから音を出すときは、Actorに紐づかない再生を選ぶ と覚えてください(→ Sound CueとSound Waveの違い)。

エフェクトと音はまだ用意していなくても構いません。その2ノードは後回しにして、先に最後まで通します。

作った BP_Coin を、ステージに 20個 配置します。1個置いて Alt を押しながらドラッグするとコピーできます。段差の上や隅にも置くと、探す楽しさが出ます。

Sponsored

スコアと残り時間はGameStateに置く

ここが、この記事でいちばん設計らしい判断をする場所です。

スコアと残り時間は、どこに置くべきでしょうか。プレイヤーキャラに持たせることもできます。ですが、この2つは 「そのキャラの状態」ではなく「ゲーム全体の状況」 です。プレイヤーが死んで作り直されてもスコアは消えてほしくありませんし、HUDやリザルト画面から見に行く先が1か所に決まっていてほしい。これが GameState の役割です。

GameModeがルール、GameStateがスコアと残り時間、CharacterがプレイヤーのHP、HUDが表示、と役割が分かれている図

役割の分け方は Game Frameworkの理解 で詳しく扱っています。ここでは「全体の状況はGameState」だけ押さえれば進めます。

GameStateを作って登録する

  1. Blueprint Class → 親クラスに Game State Base を選び、BP_CoinGameState として作成
  2. コンテンツブラウザで Content/ThirdPerson/Blueprints/BP_ThirdPersonGameMode を開き、Class DefaultsGame State ClassBP_CoinGameState に変更
  3. レベルを開き、Window → World SettingsGameMode OverrideBP_ThirdPersonGameMode になっていることを確認

3を飛ばすと、そのレベルは Project Settings → Maps & Modes → Default GameMode のほうを使います。テンプレートのままなら結果的に動きますが、自分で新規レベルを作った場合は別のGameModeになり、 Cast To BP_CoinGameState が黙って失敗して「コインを取ってもスコアが増えない」に直行します。 どのレベルを開いてもこのルールで遊べるように、World Settingsで明示しておくのが安全です。

変数とディスパッチャ

BP_CoinGameState に、次を用意します。

名前種類既定値
Score変数Integer0
TimeRemaining変数Float60.0
TargetCoins変数Integer20
bFinished変数Booleanfalse
BestScore変数Integer0
CountdownHandle変数Timer Handle-
OnScoreChangedEvent Dispatcher入力:NewScore(Integer)-
OnTimeChangedEvent Dispatcher入力:NewTime(Float)-
OnGameFinishedEvent Dispatcher入力:bCleared(Boolean)-

TargetCoins を変数にしておくと、20枚が難しすぎたときに数字ひとつで調整できます。マジックナンバーを直接書かないのは、あとで自分を助ける習慣です。

ディスパッチャを3つ置いているのは、 GameStateがUIを知らずに済ませる ためです。GameStateは「スコアが変わった」と叫ぶだけで、誰が聞いているかを気にしません。HUDが勝手に聞きに来ます(→ Event Dispatcherによるイベント駆動設計)。

カウントダウンを回す

Event BeginPlay からタイマーを1秒ループで回します。

Event BeginPlayからSet Timer by Eventで1秒ループのカウントダウンを始め、Custom Event Tick1SecondでTimeRemainingを減らし0以下ならFinish Gameを呼ぶノードグラフ
BP_CoinGameState(イベントグラフ)

Event BeginPlay
  → Set Timer by Event(Time = 1.0, Looping = true)
       Event ピン → Custom Event「Tick1Second」へ接続
       Return Value → Set CountdownHandle          // 止めるために保存しておく

Custom Event: Tick1Second
  → Set TimeRemaining(TimeRemaining - 1.0)
  → Call OnTimeChanged(NewTime = TimeRemaining)
  → Branch(TimeRemaining <= 0.0)
       True → Finish Game(bCleared = false)

Set Timer by Event の返り値(Timer Handle)を 必ず変数に保存 してください。これを持っていないと、ゲームが終わったあともカウントダウンが回り続け、リザルト画面の裏で Finish Game が何度も呼ばれます。

コインを数える

関数を2つ作ります。まず Add Coin(入力:Amount(Integer))。

関数: Add Coin(Amount: Integer)
  → Branch(bFinished == true)→ True: Return    // 終了後は数えない
  → Set Score(Score + Amount)
  → Call OnScoreChanged(NewScore = Score)
  → Branch(Score >= TargetCoins)
       True → Finish Game(bCleared = true)

次に Finish Game(入力:bCleared(Boolean))。

関数: Finish Game(bCleared: Boolean)
  → Branch(bFinished == true)→ True: Return    // 二重終了を防ぐ
  → Set bFinished = true
  → Clear and Invalidate Timer by Handle(Handle = CountdownHandle)
  → Save High Score(後述)
  → Call OnGameFinished(bCleared = bCleared)

どちらの関数も、先頭で bFinished を見て打ち切っています。 終わりの処理は必ず一度だけ通す 。ここを守らないと、20枚目を取った瞬間と時間切れが同じフレームで重なったときに、リザルト画面が2枚出ます。

HUDをつなぐ

Widget Blueprintを WBP_HUD の名前で作ります(右クリック → User Interface → Widget Blueprint → User Widget)。

Designerタブで、次を置きます。

ウィジェット名前配置
TextText_Score左上(アンカーも左上)
TextText_Time上中央(アンカーも上中央)
Vertical BoxPanel_Result画面中央。下の3つをこの中に入れる
TextText_ResultPanel_Result の中(CLEAR! / TIME UP を出す)
TextText_BestScorePanel_Result の中(ハイスコアを出す)
ButtonBtn_RetryPanel_Result の中(中に「RETRY」のTextを入れる)

Panel_Result は、詳細パネルで VisibilityCollapsed にしておきます。ゲーム中は隠れていて、終了時に出します。配置とアンカーの考え方は UMG入門 にまとめています。

GameStateの通知を受け取る

Graphタブで Event Construct から、GameStateのディスパッチャに接続します。

WBP_HUDのEvent ConstructからGet Game State→Cast→Bind Event to OnScoreChanged/OnTimeChanged/OnGameFinishedへ3本つながり、各カスタムイベントがText BlockのSet Textを呼ぶノードグラフ
WBP_HUD(グラフ)

Event Construct
  → Get Game State → Cast To BP_CoinGameState
  → Bind Event to OnScoreChanged   → Custom Event「UpdateScore」(NewScore)
  → Bind Event to OnTimeChanged    → Custom Event「UpdateTime」(NewTime)
  → Bind Event to OnGameFinished   → Custom Event「ShowResult」(bCleared)
  → UpdateScore(NewScore = GameState の Score)   // 初期表示を1回自分で呼ぶ
  → UpdateTime(NewTime = GameState の TimeRemaining)

Custom Event: UpdateScore(NewScore)
  → Text_Score → Set Text(Format Text "COINS {0} / {1}" に NewScore と TargetCoins)

Custom Event: UpdateTime(NewTime)
  → Text_Time → Set Text(Format Text "TIME {0}" に Truncate(NewTime))

Bind Event to ... ノードの左側にある赤い Event ピンからワイヤーを引き出して Add Custom Event ... を選ぶと、 引数の型と数が合ったCustom Eventが自動で作られます。 手で作ると引数を間違えて繋がらないので、この作り方を覚えてください。

Truncate は小数を切り捨てて整数にするノードです(検索も Truncate)。これで TIME 42.0 ではなく TIME 42 と出ます。

最後の2行、 初期表示を自分で1回呼んでいる のがポイントです。ディスパッチャは「変わったとき」しか飛んでこないので、これがないと最初の1秒間だけ COINS 0 / 0 のような初期テキストが見えてしまいます。

Text の更新に Binding(バインド)を使わない のは意図的です。Bindingは毎フレーム評価されるので、1秒に1回しか変わらない表示には過剰です(→ UMGのフォーカス管理とInvalidation Boxによる最適化)。

画面に出す

BP_ThirdPersonGameModeEvent BeginPlay で、HUDを作って画面に足します。

BP_ThirdPersonGameMode(イベントグラフ)

Event BeginPlay
  → Create Widget(Class = WBP_HUD, Owning Player = Get Player Controller (0))
  → Add to Viewport

ここまでで、プレイすると左上にコイン数、上中央に残り秒数が出て、コインを取るたびに数字が増えるはずです。 一度Playして、ここまでを確かめてください。 先へ進む前に動くところを見ておくと、あとで壊れた場所がすぐ分かります。

クリアとゲームオーバー

ShowResult イベントの中身を作ります。

Custom Event: ShowResult(bCleared)
  → Text_Result → Set Text(bCleared ? "CLEAR!" : "TIME UP")   // Selectノードで分岐
  → Text_BestScore → Set Text(Format Text "BEST {0}" に GameState の BestScore)
  → Panel_Result → Set Visibility(Visible)
  → Get Owning Player → Set Show Mouse Cursor(true)
  → Set Input Mode UI Only(Player Controller = Owning Player, In Widget to Focus = Panel_Result)
  → Set Game Paused(true)

ハイスコアは、次の節でGameStateの BestScore に入れておきます。 ShowResult はそれを読むだけです。 OnGameFinished が飛んでくる時点で、GameState側の保存処理は終わっている ので、順番の心配は要りません。

Set Input Mode UI Only を使うのは、リザルト画面では マウスでボタンを押すだけ で、ゲーム側の入力が要らないからです。逆に、ポーズ画面のように 同じキーで閉じたい 場面ではこれを使うと閉じられなくなります。そちらは Game And UI を使います(→ ポーズ機能を正しく実装する)。

リトライボタンは、Designerで Btn_Retry を選び、詳細パネルの On Clicked を押してグラフに追加します。

On Clicked (Btn_Retry)
  → Set Game Paused(false)      // ポーズを解除してから遷移する
  → Open Level (by Name)(Level Name = Get Current Level Name)

ポーズを解除してから Open Level を呼ぶ 順番にしておきます。多くの場合はレベルごと作り直されるので解除しなくても動きますが、ポーズ状態を残したまま画面を切り替える設計はバグの温床です。 止めたものは、その場で戻す。

Get Current Level Name は既定で Remove Prefix String が有効なので、エディタ内プレイ(PIE)のときに付く UEDPIE_ の接頭辞を落として返してくれます。そのまま Open Level に渡せます。レベルをまたぐ遷移そのものは レベル遷移入門 を参照してください。

ハイスコアを保存する

Open Level でレベルを読み直すと、GameStateもろとも作り直されます。ハイスコアはそこで消えてしまうので、ディスクへ保存します。

Blueprint Class → 親クラスに Save Game を選び、BP_CoinSave として作成。変数を1つだけ持たせます。

変数名既定値
BestScoreInteger0

BP_CoinGameState に、BP_CoinSave 型の変数 SaveObject を1つ追加してから、Save High Score 関数を作り、Finish Game から呼びます。

Does Save Game ExistのBranchが、Load Game from Slot経由とCreate Save Game Object経由の2つに分かれ、どちらもSet SaveObjectへ合流してからSave Game to Slotへ進む完成ノードグラフ図
関数: Save High Score

  → Branch(Does Save Game Exist(Slot Name = "CoinHighScore", User Index = 0))
       True  → Load Game from Slot("CoinHighScore", 0)
             → Cast To BP_CoinSave → Set SaveObject
       False → Create Save Game Object(Class = BP_CoinSave)
             → Set SaveObject
  → (合流)Branch(Score > SaveObject.BestScore)
       True → SaveObject.BestScore = Score
            → Save Game to Slot(Save Game Object = SaveObject, "CoinHighScore", 0)
  → Set BestScore(SaveObject.BestScore)     // 表示用にGameStateへ写す

SaveObject という変数をいったん経由するのが要点です。 Blueprintでは、Branchの2本の枝から出てきたオブジェクトを、実行ピンを合流させただけでは1つとして扱えません。どちらの枝でも同じ変数に入れてから、その先で使います。ここを飛ばすと「合流した先でピンが繋がらない」で手が止まります。

読み込み側には Cast To BP_CoinSave が要ります。Load Game from Slot が返すのは基底の Save Game 型で、そのままでは BestScore を読めません(Create Save Game Object はクラスを指定しているので、こちらはCast不要です)。保存まわりの詳細は Save/Loadシステムの実装 にあります。

最後の1行で、GameStateの BestScore に写しています。これでリザルト画面が表示するだけで済みます。 「前より上手くなった」が見える だけで、もう一回遊ぶ理由になります。

Sponsored

実践:30分で手触りを上げる3つの追加

ここまでで、仕様の1行は満たしました。遊べます。ただ、遊んで気持ちいいかは別の話です。

ここからは 仕様を1つも変えずに、手触りだけを上げる3つの追加 をやります。アクションゲームでもパズルでもレースでも、最後に効いてくるのはこの層です。

動かすとこうなります。コインに触れた瞬間に光の粒が散り、音の高さが取るたびに少しずつ上がっていく。残り10秒を切ると、上中央のタイマーが赤く点滅し始める。数字の増え方は同じなのに、追い込まれている感じがはっきり出ます。

左は演出なしの素の画面、右はコイン取得のNiagaraと上がるSEピッチ、赤く点滅する残り時間が加わった画面の対比図

再現条件

項目
BP_CoinGameStateTargetCoins20
BP_CoinGameStateTimeRemaining 既定値60.0
追加する変数(BP_CoinGameStateComboPitch(Float / 既定 1.0
使うエフェクト自分で作った Niagara System 1つ(名前は NS_CoinPickup
使うSEStarter Contentの Content/StarterContent/Audio/ にある短い音ならなんでも

エフェクトだけは1つ用意が要ります。 Starter Contentに入っているパーティクルは旧世代のCascade(P_Sparks など)で、Spawn System at Location には渡せません(そちらは Spawn Emitter at Location を使います)。 Niagara入門 の手順で、粒が弾けるだけの NS_CoinPickup を1つ作ってください。5分で終わります。

追加1:取得音の高さを少しずつ上げる

コインを取るたびに音が高くなると、連続で取っている感覚が生まれます。 BP_CoinGameStateAdd Coin に2行足します。

関数: Add Coin(Amount: Integer)内、Set Score の直後
  → Set ComboPitch(Clamp(1.0 + Score * 0.03, 1.0, 1.6))

BP_Coin 側では、Play Sound at LocationPitch Multiplier ピンに、GameStateの ComboPitch をつなぎます。このピンは既定で隠れているので、ノード下端の 小さな下向き矢印(Advanced Pin)をクリックして展開 してください。「そんなピンが無い」の正体はたいていこれです。

Clamp で上限を 1.6 にしているのは、20枚目が甲高すぎて不快にならないようにするためです。

音を鳴らすのは Add Coin より前なので、ピッチが上がるのは 次の1枚から です。1枚目が素の高さで鳴り、そこから少しずつ上がっていく形になるので、聞こえ方はむしろ自然です。

音そのものの設計を詰めるなら Sound CueとSound Waveの違い へ。ピッチのランダム化はSound Cueの Modulator ノードでもできます。

追加2:取った瞬間に粒を散らす

BP_Coin のグラフに置いておいた Spawn System at Location に、Niagaraシステムを割り当てます。位置は GetActorLocationZ + 50 程度を足すと、コインの中心あたりから散って見栄えが良くなります。

エフェクトを自分で作りたくなったら Niagara入門 へ。ここでは既製のものを1つ置くだけで十分です。

追加3:残り10秒で赤く点滅させる

WBP_HUDUpdateTime に分岐を足します。

UpdateTimeイベントからBranchで残り10秒以下を判定し、Playing Animationで赤い点滅アニメーションを再生、それ以外は白のまま表示するノードグラフ

先にDesignerで、Animationsパネルから Anim_Warning を作ります。トラックに Text_TimeColor and Opacity を追加し、0秒で白、0.25秒で赤、0.5秒で白のキーを打ちます。詳細パネルで Loop はチェックせず、呼ぶ側でループさせます。

Custom Event: UpdateTime(NewTime)
  → Text_Time → Set Text(Format Text "TIME {0}" に Truncate(NewTime))
  → Branch(NewTime <= 10.0 AND Is Animation Playing(Anim_Warning) == false)
       True → Play Animation(Anim_Warning, Num Loops To Play = 0)   // 0で無限ループ

Is Animation Playing を条件に噛ませているのは、1秒ごとに再生し直して先頭へ戻るのを防ぐためです。 毎秒呼ばれるイベントから再生を始めるときは、すでに再生中かを必ず見る 。これは点滅に限らず、演出全般で効く型です。

確かめる

Playして、20枚集めるまで走ってみてください。取るたびに音が少しずつ高くなり、粒が散り、残り10秒を切ると上のタイマーが赤く脈打つ。20枚目を取った瞬間に CLEAR! が出て、時間が止まります。

うまくいかないときの切り分けです。

  • 音が高くならないPitch Multiplier にGameStateの ComboPitch を渡せていません。Advanced Pinを展開したうえで、Get Game State → Cast を経由しているか確認してください
  • タイマーが点滅しっぱなし/先頭に戻り続けるIs Animation Playing の条件が抜けています
  • 20枚目だけ音とエフェクトが出ないBP_Coin のグラフで、Add CoinSpawn SystemPlay Sound より前に来ています。20枚目は Add Coin の中でそのままゲームが止まるので、 演出を先に通す 並びに直してください

ポイントは2つです。

  • 手触りは仕様の外側にある: 3つとも、ルールは1ミリも変えていません。コイン20枚・60秒のままです。それでも遊んだ印象は変わります。 仕様を先に完成させてから、この層に時間を使う 順番が大事です
  • 演出は既存の通知に相乗りさせる: 3つとも、すでにある Add CoinUpdateTime に数行足しただけです。新しい仕組みを作っていません。演出のために設計を変え始めると完成が遠のきます(→ ゲームフィールを作る:ヒットストップ・画面シェイク

おまけ:先に知っておくと良いこと

渡せる形にするまでが1本です。 ツールバーの Platforms → Windows → Package Project でexeを書き出し、出力フォルダごとzipにして渡します。書き出す前に Project Settings → Maps & Modes → Game Default Map を、遊ばせたいレベルに設定しておいてください。ここが既定のままだと、起動しても何もない画面が出ます。ここまでやって初めて「作った」と言えます。手順とつまずきどころは パッケージ化入門 にまとめました。書き出したものは、必ず自分で一度遊んでください。エディタでは動くのにパッケージでは動かない、はよくあります。

次に足すなら、敵より先に「もう1ステージ」です。 敵AIは楽しそうですが、作ると仕様が一気に膨らみます。まずはレベルをもう1つ作り、クリア後に次のステージへ送るほうが、完成の手応えが続きます(→ レベル遷移入門)。ステージをまたいでスコアを持ち越したくなったら GameInstanceでデータを持ち越す です。

コインが20個の「置き忘れ」に注意。 配置数が19個だと、どれだけ探してもクリアしません。TargetCoins を手打ちで20にしているためです。心配なら Event BeginPlayGet All Actors of Class(BP_Coin) の要素数を TargetCoins に入れてしまう手もあります。このノードはレベル中の全Actorを走査するので重く、 BeginPlayで1回だけ 呼ぶのが鉄則です。

遊んでもらう相手を1人決めておくと、完成が早まります。 「誰かに渡す」という締め切りがあると、作り込みたい欲を切り上げられます。これは 学習ロードマップと目標設定 で書いた「小さく完成させる」の実践編です。

まとめ

作ったものを並べると、Blueprintは4つだけでした。

Blueprint役割
BP_Coin触れたら数えて消える
BP_CoinGameStateスコア・残り時間・終了判定を持ち、変化を叫ぶ
WBP_HUD叫びを聞いて表示し、リザルトを出す
BP_CoinSaveハイスコアをディスクに残す

この4つの関係が、そのまま多くのゲームの骨格です。 状況を持つ人(GameState)・状況を変える人(Actor)・状況を映す人(UI) に分かれていて、間はイベントでつながっている。敵を足しても、ステージを増やしても、この形は変わりません。

そして、いちばん大事だったのは最初の1行です。「60秒で20枚集めたらクリア」を先に決めたから、途中で迷わずここまで来られました。

あなたが次に作る1本は、どんな1行で書けるでしょうか。それが書けたなら、もう半分は終わっています。

さらに学ぶために