キャラクターを動かす、触れたものを消す、画面に数字を出す。個別には作れるようになっても、それをつないでゲームを完成させるところで手が止まることがあります。
今回は、60秒で20枚のコインを集めるゲーム を作ります。まず「集める→結果を見る→もう一度遊ぶ」を一周できる形にし、その後でハイスコアと演出を足していきます。
新しい機能をたくさん覚えることより、すでに触れた部品の役割を決め、順番につなぐことが主題です。Blueprintの変数やBranchを使ったことがある人を想定しています。Branchは、条件が成り立つ場合はTrue、そうでない場合はFalseへ処理を分けるノードです。
この記事でわかること
- コイン・ゲームの状況・画面表示を分けて作る考え方
- 得点とタイマーをHUDへ伝える方法
- クリア、時間切れ、リトライ、記録の保存をつなぐ手順
- 音やエフェクトを加え、配布できる形まで仕上げる流れ
完成の条件と、必要な部品を決める
最初に、何ができれば完成かを1行で決めます。
制限時間60秒のあいだに、1ステージに置いた20枚のコインを全部集めたらクリア。時間切れならゲームオーバー。
敵や複数のステージは、今回は加えません。この条件を先に決めると、必要な部品を順に揃えやすくなります。

| 部品 | 今回の担当 |
|---|---|
| キャラクター、カ メラ、移動入力 | Third Personテンプレートを使う |
| コイン | BP_Coin。触れたことを知らせ、消える |
| スコア、残り時間、終了判定 | BP_CoinGameState |
| 数字、結果、リトライボタン | WBP_HUD |
| 前回までのハイスコア | BP_CoinSave |
新しく作るアセットはこの4つです。加えて、テンプレートのGameModeを編集し、GameStateの指定とHUDの表示を行います。1人で遊ぶゲーム として組み立てるため、オンライン対戦の同期は扱いません。

ステージとBlueprintを用意する
「Games → Third Person」でBlueprintプロジェクトを作ります。Variantを選べる版では「None」にします。Starter Contentは必須ではありません。
テンプレートのレベルを L_CoinStage という名前で保存します。キャラクターを動かせる床とPlayer Startを残し、段差を2〜3個置けば、最初のステージとして十分です。Playして移動とジャンプを確認しておきます(→ Modeling Modeで形を試す)。
Content/CoinGame フォルダを作り、先に次のアセットを用意します。中身はこのあと順に作るので、まず名前と種類を揃えます。
| 名前 | 作成する種類・親クラス |
|---|---|
BP_Coin | Blueprint Class → Actor |
BP_CoinGameState | Blueprint Class → All ClassesでGameStateBaseを検索 |
WBP_HUD | User Interface → Widget Blueprint → User Widget |
BP_CoinSave | Blueprint Class → All ClassesでSaveGameを検索 |
テンプレートの BP_ThirdPersonGameMode を開き、「Class Defaults → Game State Class」をBP_CoinGameStateへ変えます。Default Pawn Classなど、キャラクターの設定はそのまま使います。
この先は、各節の処理を組んだらBlueprintをCompileして保存し、Playで結果を確かめます。参照先の変数や関数が候補に出ない場合は、先にそのBlueprintをCompileしたかも確認してください。
レベルの「World Settings → GameMode Override」に、このGameModeを指定します。ここがNoneの場合は「Project Settings → Maps & Modes → Default GameMode」が使われます。いま開いているレベルで、どのGameModeが使われるか を確認するのが要点です。

GameStateでスコアと時間を管理する
GameState は、そのゲームでいま何が起きているかを置く場所です。今回は「何枚取ったか」「あと何秒か」「もう終了したか」をまとめます。コインは加点を頼み、HUDは数字を表示する。両方が同じ場所を参照するので、表示と実際の得点が食い違いにくくなります。
小さなゲームではプレイヤーにスコアを置く作り方もできます。今回は部品の役割を分ける練習としてGameStateを使います(→ Game Framework入門)。
値と、変化を知らせる通知を用意する
BP_CoinGameStateに次の変数を作り、Compile後に既定値を設定します。Integerは整数、Booleanはtrue/falseの2つの値を扱う型です。
| 変数 | 型 | 既定値 | 用途 |
|---|---|---|---|
Score | Integer | 0 | 取った枚数 |
TargetCoins | Integer | 20 | クリアに必要な枚数 |
TimeRemaining | Integer | 60 | 残り秒数 |
bFinished | Boolean | false | 終了済みか |
BestScore | Integer | 0 | 結果画面へ出す最高記録 |
CountdownHandle | Timer Handle | 未設定 | このタイマーを後で止めるための識別情報 |
続いて「My Blueprint → Event Dispatchers」の「+」で、次の3つを作ります。
| Dispatcher | 入力名と型 | 知らせること |
|---|---|---|
OnScoreChanged | NewScore:Integer | スコアがこの値へ変わった |
OnTimeChanged | NewTime:Integer | 残り時間がこの値へ変わった |
OnGameFinished | bCleared:Boolean | 終了した。trueならクリア |
Event Dispatcher は、変化があったときに、登録された処理を呼ぶ仕組みです。GameState側で通知を呼び、後でHUD側に「通知されたら表示を書き換える」と登録します。作っただけでHUDへ自動接続されるわけではありません。
終了処理を先に作る
「Functions」の「+」から関数 FinishGame を作り、入力にBooleanの bCleared を追加します。関数は、ひとまとまりの処理を名前で呼び出せる形にしたものです。
次は実行順を読むための表記です。条件 はBranchのCondition、値を入れる は変数のSetノードに対応します。
FinishGame(入力:bCleared)
条件:bFinished
True → そのまま終了
False → bFinishedへtrueを入れる
→ Clear and Invalidate Timer by Handle
Handle = CountdownHandle
→ Call OnGameFinished(bCleared = 関数の入力)
bFinishedを先にtrueへ変えると、後からもう一度この関数が呼ばれても、終了通知を繰り返しません。まだタイマーを作っていない段階では、未設定のHandleを渡しても止める対象がないだけです。

コインを1枚数える
関数 AddCoin を作り、出力にBooleanの Accepted を追加します。Acceptedは「この1枚を数えたか」という返事です。コイン側はこの返事を受け取ってから、自分を消します。
AddCoin(出力:Accepted)
条件:bFinished
True → Return(Accepted = false)
False → Scoreへ「Score + 1」を入れる
→ Call OnScoreChanged(NewScore = Score)
→ 条件:Score >= TargetCoins
True → FinishGame(bCleared = true)
→ Return(Accepted = true)
False → Return(Accepted = true)
Return Nodeは、関数を終えて結果を呼び出し元へ返すノードです。終了済みの場合だけfalse、数えた場合はクリアしたかどうかにかかわらずtrueを返します。2つのtrue側には、それぞれReturn Nodeを置いて構いません。

図ではSet Score右側の値の出力を使い、保存した値を通知へ渡しています。通知の後にGet Scoreで現在値を読み、TargetCoinsと比べます。
1秒ごとに残り時間を減らす
Event GraphにCustom Eventの StartCountdown と Tick1Second を作ります。Custom Eventは、自分で名前を付けて呼べる処理の入口です。
StartCountdownの白い実行線を Set Timer by Event へつなぎ、Timeを1.0、Loopingをオンにします。Tick1Secondの赤いデリゲート出力を、タイマーノードの赤いEvent入力へつなぎます。これは「1秒たつたび、このイベントを呼ぶ」という登録です。Tick1Secondの白い実行出力は、下の減算処理へ使います。
Set Timer by EventのReturn Valueを Set CountdownHandle へ渡し、白い実行線もSetへつなぎます。Timer Handle を保存するのは、FinishGameから同じタイマーを指定して止めるためです。

Tick1Second
条件:bFinished
True → 終了
False → TimeRemainingへ Max(TimeRemaining - 1, 0)を入れる
→ Call OnTimeChanged(NewTime = TimeRemaining)
→ 条件:TimeRemaining <= 0
True → FinishGame(bCleared = false)
Maxは2つの値の大きいほうを選ぶので、残り時間がマイナスになりません。タイマーは、後でHUDを表示した後にStartCountdownを呼んで始めます。ここではGameStateのBeginPlayからは開始しません。

図のSetノード右側にある値の出力は、保存した値を次へ渡すために使えます。減算の結果を保存してから通知し、最後の条件判定では現在のTimeRemainingをGetで読みます。
触れると取れるコインを作る
BP_Coinを開き、Componentsに次を追加します。
| 部品 | 設定 |
|---|---|
| Sphere Collision | DefaultSceneRootへドラッグしてルートにする。Sphere Radiusは60 |
| Static Mesh | Sphereの子にする。Cylinderを割り当て、Scaleは (1, 1, 0.1)、RotationのXは90、Collision PresetsはNoCollision |
| Rotating Movement | Rotation RateのZを90にする |
CylinderはUEの基本形状を使えます。メッシュの選択欄で見つからなければ「Show Engine Content」をオンにし、Engine/BasicShapes/Cylinder を探します。回る速さは1秒あたり90度です。
SphereのCollision PresetsをCustomにし、Collision Enabledは「Query Only」、各チャンネルへの応答はIgnore、PawnだけOverlap にします。Generate Overlap Eventsもオンにします。プレイヤーのCapsule Component側でも、Generate Overlap Eventsがオンかを確認してください。
Overlap は、2つの当たり判定が重なったことを知らせる仕組みです。薄いコインの見た目は衝突させず、少し大きい球で取得を判定すると、横から触れても取りやすくなります。
プレイヤーが触れた1回だけ数える
Sphereを選び、Detailsの「On Component Begin Overlap」の「+」からイベントを作ります。
Cast To BP_ThirdPersonCharacterを置き、ObjectへOverlapのOther Actorをつなぎます。白い実行線もつなぎます。- 成功側から
Cast To BP_CoinGameStateへ進め、ObjectにはGet Game StateのReturn Valueを渡します。 - 成功側に
DoOnceを挟み、そこからAddCoinを呼びます。AddCoinのTargetは、CastのAs BP Coin Game Stateです。 - AddCoinのAcceptedをBranchのConditionへつなぎ、True側から
Destroy Actor(TargetはSelf)を呼びます。False側は何もしません。
Cast は、取得した相手を指定の種類として扱えるかを確かめます。最初のCastでプレイヤーを、次のCastで自作のGameStateを確認しています。失敗側は加点へ進めません。

2つの確認を通ってから、加算と後始末へ進みます。

DoOnce は、最初に通った1回だけ先へ進めるノードです。1枚のコインへ複数のOverlapが届いても、加点を繰り返さないようにします。今回はコインを取り直さないのでResetはつなぎません。
まず1枚だけレベルへ置き、Playして触れます。コインが消えれば、Overlap→GameStateへの加点→取得成功の返事が通っています。Playを止めるとレベル上のコインは元へ戻ります。ここまで動いたら、Alt を押しながらドラッグして20枚に増やします。
HUDに数字を出す
HUD は、遊びながら見るスコアや残り時間の表示です。WBP_HUDのDesignerにCanvas Panelをルートとして置き、その中へ次の部品を並べます。

| 部品名 | 種類 | 配置・初期設定 |
|---|---|---|
Text_Score | Text Block | 左上。初期文字はCOINS 0 / 20 |
Text_Time | Text Block | 上中央。初期文字はTIME 60 |
Panel_Result | Vertical Box | 画面中央。VisibilityはCollapsed |
Text_Result | Text Block | Panel_Resultの子。結果表示用 |
Text_BestScore | Text Block | Panel_Resultの子。記録表示用 |
Text_SaveStatus | Text Block | Panel_Resultの子。初期文字は空 |
Btn_Retry | Button | Panel_Resultの子。その中にRETRYというText Blockを置く |
Graphから指定するこれらの部品は「Is Variable」をオンにします。Btn_Retryの「Is Focusable」もオンにします。Canvasの子のアンカーを配置に合わせ、Panel_Resultは画面中央のアンカーとAlignment (0.5, 0.5)、Position (0, 0) にすると中央へ揃えられます。文字が切れないサイズへ調整してください(→ UMG入門)。
通知と、表示を変える処理を結ぶ
WBP_HUDに、BP_CoinGameStateのObject Reference型で GameStateRef 変数を作ります。参照 は、後で同じGameStateを指定するために持っておくものです。Class Referenceではありません。
Event Constructから、次の順で接続します。
- Get Game StateのReturn ValueをObjectにして、Cast To BP_CoinGameStateを実行する。
- 成功側でAs BP Coin Game Stateを
Set GameStateRefへ入れる。 - GameStateRefから
Bind Event to OnScoreChanged、Bind Event to OnTimeChanged、Bind Event to OnGameFinishedを作り、白い実行線をこの順でつなぐ。3つともTargetはGameStateRef。 - 各Bindの赤いEventピンからドラッグし、対応するCustom Eventを作る。名前を順に
UpdateScore、UpdateTime、ShowResultにする。
Bind は「この通知が来たら、この処理を呼んで」と登録する操作です。赤い線は通知先の登録、白い線は処理の実行順を表します。Bindの白い出力をCustom Eventの入口へつなぐ形ではありません。

UpdateScoreとUpdateTimeの中身を作ります。Text BlockをGetで置き、そこから Set Text (Text) を作ると、表示先のTargetを指定できます。
| イベント | Set TextのTarget | In Textへ渡すFormat Text |
|---|---|---|
| UpdateScore(NewScore:Integer) | Text_Score | COINS {Score} / {Target}。ScoreへNewScore、TargetへGameStateRefのTargetCoins |
| UpdateTime(NewTime:Integer) | Text_Time | TIME {Seconds}。SecondsへNewTime |
Format Textは、文中の {名前} を指定した値で埋めるノードです。数字をそのままText Blockへ渡すのでなく、表示したい文章にしてから渡します。

Constructの3つ目のBindの後に、UpdateScoreとUpdateTimeの呼び出しも置き、GameStateRefの現在のScoreとTimeRemainingを渡します。通知を待つだけでは最初の値が届かないため、初期表示は自分から一度行います。 ShowResultの中身は次の節で作ります。
HUDを表示してから、時計を動かす
BP_ThirdPersonGameModeのEvent BeginPlayへ、次を組みます。テンプレートに既存処理がある場合は、それにつなげて追加します。
Create Widget(Class = WBP_HUD、Owning Player = Get Player Controller 0)
→ Add to Viewport(Target = Create WidgetのReturn Value)
→ Cast To BP_CoinGameState(Object = Get Game StateのReturn Value)
→ StartCountdown(Target = As BP Coin Game State)
このHUDは1プレイにつき1つだけ作り、終了画面も同じWidgetの中で表示します。Playして、最初に「COINS 0 / 20」「TIME 60」が出るか、1秒ごとに時間が減るか、コイン1枚につき1点増えるかを確認します。今の段階では、終了時の結果画面はまだ出ません。
結果を表示して、もう一度遊べるようにする
WBP_HUDのShowResultへ処理を追加します。入力のbClearedがtrueならクリア、falseなら時間切れです。
- bClearedを条件にした
Selectで、true側はCLEAR!、false側はTIME UPのTextを選び、Text_ResultへSet Textする。 - Panel_Resultを
Set VisibilityでVisibleにす る。 Get Player Character (0)からCharacter Movementを取得し、それをTargetにStop Movement Immediately、続いてDisable Movementを実行する。Get Owning PlayerをTargetに、Show Mouse Cursorをtrueにする。Set Input Mode UI Onlyを実行する。Player ControllerはGet Owning Player、In Widget to FocusはBtn_Retry、Flush Inputを選べる場合はオン。
移動の停止と、操作の切り替えは別の仕事です。前者で走っていたキャラクターを止め、後者でボタンを操作できる状態にします。ゲーム全体にはポーズを掛けません。 後で足すコインの光や音を、結果画面が出た後も再生するためです。得点はbFinishedで、時計はタイマーの停止で、それぞれ止まっています。
リトライボタンをつなぐ
Btn_RetryのOn Clickedを作り、次の順で組みます。
Set Input Mode Game Only(Player Controller = Get Owning Player)
→ Set Show Mouse Cursor(Target = Get Owning Player、false)
→ Remove from Parent(Target = Self)
→ Open Level (by Name)
Level Name = Get Current Level NameのReturn Value
Get Current Level NameのRemove Prefix Stringはオンにします。エディタ内プレイ用に付いた接頭辞を除き、保存したレベル名で開き直すためです。
Open LevelではキャラクターやGameStateが作り直されるので、移動できるキャラクター、20枚のコイン、60秒の時計から再開します。画面切り替えの前に入力をゲームへ戻すところまでが、リトライの処理です(→ Open Level入門)。

ここで一度、取らずに60秒待つ→TIME UP→RETRY→20枚取る→CLEAR! を試します。クリアしづらければ、確認中だけコインを近くへ並べて構いません。結果が出た後にスコアと時間が変わらず、リトライで初期状態へ戻れば、ゲームの基本形は完成です。
ハイスコアを次のプレイへ残す
GameStateの値はレベルを開き直すと消えます。最高記録を次のプレイへ残すために、SaveGame を使います。保存したい値を入れる器を作り、その中身をファイルへ書く仕組みです。
BP_CoinSaveへIntegerの BestScore を追加し、既定値を0にします。BP_CoinGameStateには次を追加します。
| 変数 | 型 | 既定値 |
|---|---|---|
SaveObject | BP_CoinSaveのObject Reference | 未設定 |
SaveStatus | Text | 空 |
保存データを用意する
BP_CoinGameStateに関数 SaveHighScore と WriteBestScore を作ります。2つに分け るのは、「既存データを読む/初めて作る」の分岐の後を、同じ処理へつなぐためです。
SaveHighScoreの先頭でBestScoreへScoreを入れ、SaveStatusを空にします。次に Does Save Game Exist の結果でBranchを分けます。Slot Nameは CoinHighScore、User Indexは0で統一します。スロット名は、どの保存ファイルを読み書きするかを指定する名前です。
| 保存ファイル | 実行する処理 |
|---|---|
| ない(False) | Create Save Game Object(ClassはBP_CoinSave)→ Return ValueをSet SaveObjectへ入れる → WriteBestScore |
| ある(True) | Load Game from Slot → Return ValueをCast To BP_CoinSaveのObjectへ → 成功したらAs BP Coin SaveをSet SaveObjectへ入れる → WriteBestScore |
| 読み込み側のCastが失敗 | SaveStatusへ「記録を読み込めませんでした」を入れて関数を終える。保存へ進めない |
2つの正常な枝には、それぞれSet SaveObjectとWriteBestScoreの呼び出しを置きます。どちらから来ても、同じ変数に入った保存データを使う形です。Load Game from Slotにも、同じスロット名とUser Indexを渡してください。
今回と以前の記録を比べて書く
WriteBestScoreでは、最初にSaveObjectを Is Valid で確認します。無効ならSaveStatusへ「記録を保存できませんでした」と入れて終えます。有効なら、次の順です。
- GameStateのBestScoreへ
Max(Score, SaveObjectのBestScore)を入れる。 - SaveObjectをTargetにした
Set BestScoreで、その値を保存データにも写す。 Save Game to Slotを呼ぶ。Save Game ObjectはSaveObject、Slot NameはCoinHighScore、User Indexは0。- Return Valueがfalseなら、SaveStatusへ「記録を保存できませんでした」と入れる。
同じBestScoreという名前でも、GameState側は画面に出す値、SaveObject側はファイルへ書く値 です。SetノードのTargetを区別します。例えば以前12枚、今回8枚なら12を残し、今回15枚なら15へ更新します。

記録を決めたら、ファイルへ書き出します。

保存に失敗しても結果画面は出します。その場合、表示中のBESTが次回にも残ったとは限らないため、SaveStatusで知らせます。読み込めない既存ファイルを、新しい空の記録で上書きする処理にはしません。
終了処理と表示へ組み込む
FinishGameの「タイマーを止める」と「Call OnGameFinished」の間へ、SaveHighScoreを挟みます。WBP_HUDのShowResultでは、Panel_Resultを表示する前に次を足します。
- Text_BestScoreへ、GameStateRefのBestScoreを
Format TextのBEST {Score}に入れて表示する。 - Text_SaveStatusへ、GameStateRefのSaveStatusを表示する。
保存の処理を終えてから通知するので、HUDはその結果を読むだけで済みます。まず3枚取って時間切れ、リトライして1枚で時間切れ、次は5枚で時間切れ、と試すとBESTが3→3→5になるか確認できます。確認中だけTimeRemainingの既定値を10へ変え、終わったら60へ戻します。
すでに高い記録を保存している場合は、その値が残るのが正しい動作です。3→3→5の順を試すなら、読み書きすべてのSlot Nameを練習用の別名へ揃えてから確認します。
さらにPlayを止めて再び始め、記録が残ることも確かめます。セーブとロードを詳しく作り込みたい場合は、Save/Load入門を参照してください。
実践:音・光・残り時間の警告を足す
ゲームが一周できたら、同じルールのまま反応を分かりやすくします。触れた瞬間の音、コインの位置から散る光、残り10秒の点滅を加えます。1つ加えるたびにPlayし、変化を確かめてください。

① 取った音を鳴らす
短い効果音をSound Waveなどで用意します。BP_Coinの「AcceptedのTrue」とDestroy Actorの間へ、Play Sound at Location を入れます。Soundに用意した音、LocationにSelfの Get Actor Location を渡します。
このノードは、指定した場所で音を再生します。直後にコインを消しても、音をコインへ付けたまま一緒に消す構成にはなりません。
慣れたら、Pitch Multiplierへ次の値を渡すと、取るたびに音が少しずつ高くなります。Floatは小数を扱う型で、この計算は小数として行います。
Clamp(1.0 + (GameStateのScore - 1) × 0.03, 1.0, 1.6)
コインのCastで得たGameStateからScoreを読みます。AddCoinの後なので、1枚目はScoreが1、音の倍率は1.0です。2枚目は1.03、20枚目は1.57になります。Clampは指定範囲へ値を収めるノードです。Pitch Multiplierが見えない場合は、ノード下端の矢印で追加ピンを展開します。
② コインの場所から光を散らす
1回弾けて終わるNiagara Systemを用意し、NS_CoinPickup とします(→ Niagara入門)。ループし続けるものではなく、短い取得演出を使います。
BP_CoinのDestroy Actorの前に Spawn System at Location を入れます。System TemplateはNS_CoinPickup、LocationはSelfのGet Actor Location、Auto ActivateとAuto Destroyはオンにします。
今回はSphereとメッシュの中心を揃えているので、その位置から出します。Niagara Systemと、旧形式のCascade Particle Systemは別のアセットです。Spawn System at Locationへ渡すのはNiagara側です。
終了時にゲーム全体を止めていないため、20枚目でも光を最後まで再生できます。結果画面が出た瞬間だけ演出が止まる場合は、追加した処理にSet Game Pausedが入っていないかを確認します。
③ 残り10秒で時間を点滅させる
WBP_HUDのDesignerで Anim_Warning というアニメーションを作りま す。Text_TimeのColor and Opacityに、0秒で白、0.25秒で赤、0.5秒で白のキーを置きます。文字の不透明度は1のままにします。
UpdateTimeのSet Textの後に、次の条件をANDでまとめたBranchを置きます。
- NewTimeが10以下。
- NewTimeが0より大きい。
Is Animation Playing(In AnimationはAnim_Warning)がfalse。
True側で Play Animation を呼び、TargetはSelf、In AnimationはAnim_Warning、Num Loops to Playは0にします。この0は無限ループの指定 です。すでに再生中なら呼び直さないので、1秒ごとに先頭へ戻らず点滅を続けられます。
ShowResultの先頭には Stop Animation(Anim_Warning)を加えます。これで結果画面になった後の点滅も止まります。

最後に、残り10秒まで待って点滅を見る、コインを取って音と光を見る、20枚目でも演出が出る、リトライで警告のないTIME 60へ戻る、の順に確かめます。
Windowsで遊べる形にして確かめる
「Project Settings → Maps & Modes → Game Default Map」をL_CoinStageにし、パッケージへ含めるマップにも指定します。Windows向けにまずDevelopmentでパッケージ化し、出力先のexeから遊びます。手順はパッケージ化入門にまとめています。

| 確認すること | 期待する結果 |
|---|---|
| exeを起動する | コインのステージ、COINS 0 / 20、TIME 60が出る |
| コインを1枚取る | 1点だけ増え、そのコインだけ消える |
| 時間切れ/20枚取得 | TIME UP/CLEAR!が出て、移動・得点・時計が止まる |
| RETRYを押す | 移動とジャンプができ、20枚・60秒から再開する |
| アプリを閉じて開く | 以前のハイスコアが次の結果画面にも反映される |
配布用をShippingへ変えた場合は、その出力でも同じ確認をします。渡すのはexe単体ではなく、出力フォルダ全体 をまとめたZIPです。自分でも展開したフォルダから起動してみると、渡す形で遊べるかを確認できます。
おまけ:遊びながら調整する
19枚しか置いていないとクリアできない
TargetCoinsは必要な枚数で、配置数を自動で数える変数ではありません。OutlinerでBP_Coinを確認し、20枚揃って いるかを見ます。枚数を変えるときは、配置数とTargetCoinsを合わせます。
次の工夫は、配置だけでも試せる
同じ20枚でも、一直線に並べる、段差の上へ置く、遠回りするとまとめて取れる場所を作る、と配置を変えるだけで遊び方が変わります。まず自分が60秒で届くかを試し、次に誰かに遊んでもらうと、作った本人には気付けない取りにくさが見えてきます。
追加ステージへ進めたくなったら、レベル遷移とGameInstanceで値を持ち越す方法が次の題材になります。
まとめ
コインは取得を知らせ、GameStateは得点と時間を管理し、HUDは通知を受けて表示する。この役割分担で、コイン集めから結果、リトライまでをつなぎました。SaveGameを加えると、前回の記録も次のプレイへ残せます。
小さなゲームでも、最後まで遊べる形にすると、部品をどうつなぐかが具体的に見えてきます。ここからは配置や制限時間を変え、自分がもう一度遊びたくなる調整を試してみてください。