【Unity】Unity初心者の総仕上げ:1画面コインゲームを完成させて、友達に渡す

作成: 2026-07-16

基礎記事を読んだのに、白紙のプロジェクトを前にすると手が止まる——その壁を越えるための総仕上げ記事です。60秒でコインを集める1画面ゲームを、Scene準備・Prefab・入力・Trigger取得・スコアと残り時間のUI・Title/Playing/Resultの状態管理・リトライ・ビルドまで一本道で完成させ、最後にEditorの外で遊べる形で友達に渡します。素材はCubeとSphereだけ、スクリプトは4本だけです。

GameObjectもC#の基礎も入力もUIも、記事は読んだ。なのに、新しいプロジェクトの白紙のSceneを前にすると——何から置けばいいのか分からない。部品の知識と「1本のゲームを完成させる」の間には、思っているより広い川があります。

この記事はその川に橋を架ける 総仕上げ(capstone) です。作るのは 「60秒以内にコインを5枚集める」1画面ゲーム。派手さはありませんが、タイトル・操作・取得・スコア・時間切れ・リザルト・リトライ・ビルドという、 どんなゲームにも必ずある背骨 が全部入っています。素材はCubeとSphereだけ、スクリプトは4本だけ。今日、最後まで行きましょう。

60秒コインゲームの完成イメージ

この記事でわかること

  • 白紙のプロジェクトから 遊べるビルド までの一本道
  • 4スクリプト構成(PlayerMover / Coin / GameFlow / GameHud)の役割分担
  • Triggerで コインを1回だけ 取る
  • Title → Playing → Result → リトライ の状態管理
  • スコアと残り時間の UI表示
  • ビルドしてEditorの外で確認 するスモークテスト

動作確認環境: Unity 2022.3 LTS / Unity 6・Input System有効

Sponsored

今日つくるもの:完成図から始める

迷子にならないよう、先にゴールを見ておきます。画面はこうなります——床の上をプレイヤー(Capsule)が走り、コイン(黄色いSphere)が5枚。画面の上にスコアと残り時間。タイトルで「Start」を押すと60秒のカウントダウンが始まり、全部集めるかタイムアップでリザルトへ。「Retry」でもう一度。

スクリプトは4本で、役割を1つずつ持ちます。この「責務で分ける」感覚は C#クラス入門 で学んだ通りです。

スクリプト役割それ以外はやらない
PlayerMover入力を読んで動くスコアも時間も知らない
Coin取られたら消えて通知する何枚目かは知らない
GameFlow進行のルール(状態・枚数・時間)画面表示はしない
GameHud表示だけルールの判断はしない

準備:SceneとPrefabの最小構成

新規3Dプロジェクト(Input Systemが有効なテンプレート)を作り、Sceneに次を置きます。見た目づくりに時間を使わないのがコツです——主役は「完成させる体験」です。

コインゲームのHierarchy最小構成図。Ground、Player、コイン5枚、GameFlow、Canvas配下のHUDとタイトル・リザルトパネルという構成
  1. Ground: 3D Object > Plane。Scaleを (2, 1, 2) 程度に
  2. Player: 3D Object > Capsule。名前を Player に。 Rigidbody を追加し、Constraints > Freeze Rotation の X/Y/Zすべてにチェック(転がって倒れるのを防ぐ)
  3. Coin: 3D Object > Sphere。Scale (0.5, 0.5, 0.5)、黄色いマテリアルを付け、Sphere Colliderの Is Trigger をオン。名前を Coin
  4. CoinをProjectウィンドウへドラッグして Prefab化 し、Scene上に5枚ばらまく(Prefabの記事 の出番です)
  5. GameFlow: 空のGameObject。進行管理スクリプトの置き場所
  6. Canvas: UI > Text - TextMeshPro を作るとCanvasとEventSystemが自動で入ります(初回はTMPのImportダイアログが出るのでImport)

確認: Hierarchyに Ground / Player / Coin×5 / GameFlow / Canvas が並んでいて、再生してもエラーが出なければOKです。

PlayerMover:動かす

1本目のスクリプトです。キーボード(WASD/矢印)とゲームパッドの左スティックを読み、Updateで読んでFixedUpdateで物理に適用 の型どおりに動かします。

// PlayerMover.cs — Playerに付ける
using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(Rigidbody))]
public class PlayerMover : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 6f;

    private Rigidbody rb;
    private Vector2 input;

    void Awake()
    {
        rb = GetComponent<Rigidbody>();
    }

    void Update()
    {
        // 入力はUpdateで読む(キーボードとパッドの両対応)
        input = Vector2.zero;

        if (Keyboard.current != null)
        {
            if (Keyboard.current.wKey.isPressed || Keyboard.current.upArrowKey.isPressed) input.y += 1f;
            if (Keyboard.current.sKey.isPressed || Keyboard.current.downArrowKey.isPressed) input.y -= 1f;
            if (Keyboard.current.dKey.isPressed || Keyboard.current.rightArrowKey.isPressed) input.x += 1f;
            if (Keyboard.current.aKey.isPressed || Keyboard.current.leftArrowKey.isPressed) input.x -= 1f;
        }

        if (Gamepad.current != null && input == Vector2.zero)
        {
            input = Gamepad.current.leftStick.ReadValue();
        }

        // 斜め移動が速くなりすぎないように長さを1までに抑える
        input = Vector2.ClampMagnitude(input, 1f);
    }

    void FixedUpdate()
    {
        // 物理への適用はFixedUpdateで
        Vector3 velocity = new Vector3(input.x, 0f, input.y) * moveSpeed;
        velocity.y = rb.linearVelocity.y; // 重力はそのまま
        rb.linearVelocity = velocity;
    }
}

確認: 再生してWASDでカプセルが床の上を走ればOK。ゲームパッドがあればスティックでも動きます。

Coin:1回だけ取れる

2本目。コインは「プレイヤーが触れたら、1回だけ通知して消える」だけの部品です。Triggerの基礎 の実戦投入と、C#イベント による疎結合な通知がここで効きます。

コイン取得の場面図。走ってきたプレイヤーがコインのTrigger範囲に入った瞬間、コインが光の粒になって消え、取得の通知が飛ぶ
// Coin.cs — CoinのPrefabに付ける
using System;
using UnityEngine;

public class Coin : MonoBehaviour
{
    // 「コインが取られた」という全体通知(GameFlowが聞く)
    public static event Action OnCollected;

    private bool collected; // 二重取得ガード

    void Update()
    {
        // ちょっとした存在感:くるくる回す
        transform.Rotate(0f, 180f * Time.deltaTime, 0f);
    }

    void OnTriggerEnter(Collider other)
    {
        if (collected) return;                    // 同フレームの多重ヒットを弾く
        if (!other.CompareTag("Player")) return;  // プレイヤー以外は無視

        collected = true;
        OnCollected?.Invoke();
        Destroy(gameObject);
    }
}

Playerオブジェクトの Tagを Player に設定 するのを忘れずに(Inspector上部のTagドロップダウン)。collected フラグは「Colliderが複数あっても、通知は1回だけ」の保険です——この「1回だけ」問題を深く掘ると ダメージ処理の記事 につながります。

確認: 再生してコインに突っ込むと、コインが消えます。まだ数は出ませんが、Consoleにエラーが出ないこと。

Sponsored

GameFlow:Title・Playing・Resultを回す

3本目が進行の心臓部です。ゲームの状態を Title → Playing → Result の3つで管理し、コインの枚数と残り時間のルールを握ります。この形は GameManagerパターン の最小版です。

Title、Playing、Resultの3状態がループする状態遷移図。StartでPlayingへ、全コイン取得または時間切れでResultへ、RetryでSceneが読み直されてTitleへ戻る
// GameFlow.cs — 空のGameObject「GameFlow」に付ける
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameFlow : MonoBehaviour
{
    public enum State { Title, Playing, Result }

    [SerializeField] private float timeLimit = 60f;
    [SerializeField] private int targetCoins = 5;
    [SerializeField] private PlayerMover player;

    public State Current { get; private set; } = State.Title;
    public int Coins { get; private set; }
    public float TimeLeft { get; private set; }
    public bool IsCleared { get; private set; }

    void OnEnable()
    {
        Coin.OnCollected += HandleCoinCollected;
    }

    void OnDisable()
    {
        // staticイベントはSceneをまたいで残るので、解除は必須
        Coin.OnCollected -= HandleCoinCollected;
    }

    void Start()
    {
        TimeLeft = timeLimit;
        player.enabled = false; // タイトル中は動けない
    }

    void Update()
    {
        if (Current != State.Playing) return;

        TimeLeft -= Time.deltaTime;
        if (TimeLeft <= 0f)
        {
            TimeLeft = 0f;
            FinishGame(cleared: false); // 時間切れ
        }
    }

    public void StartGame() // タイトルのStartボタンから呼ぶ
    {
        Current = State.Playing;
        player.enabled = true;
    }

    public void Retry() // リザルトのRetryボタンから呼ぶ
    {
        // Sceneごと読み直す=スコアもコインも全部が初期状態に戻る
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }

    void HandleCoinCollected()
    {
        if (Current != State.Playing) return; // プレイ中以外は数えない

        Coins++;
        if (Coins >= targetCoins)
        {
            FinishGame(cleared: true); // 全部集めた
        }
    }

    void FinishGame(bool cleared)
    {
        IsCleared = cleared;
        Current = State.Result;
        player.enabled = false; // リザルト中は動けない
    }
}

読みどころは3つあります。 開始前・終了後はプレイヤーを enabled = false で止める(Title中に走り回れない)。 プレイ中以外はコインを数えないHandleCoinCollected の先頭ガード)。そして リトライはScene再読み込み——タイマーもコインも全部が確実に初期化されるので、「リトライ3回目でタイマーが2倍速になる」ような残留バグが原理的に起きません。その代償が OnDisable での購読解除で、これを忘れるとSceneを読み直すたびに古い購読が積み重なります。

GameHud:スコアと残り時間を見せる

最後の1本は表示係です。Canvasの下に次を作ります。

  • ScoreTextTimeText(TextMeshPro): 画面の上に横並び
  • TitlePanel: 半透明のPanel+「Start」Button
  • ResultPanel: 半透明のPanel+結果表示用Text+「Retry」Button(最初は非表示でOK。スクリプトが制御します)
// GameHud.cs — Canvasに付ける
using TMPro;
using UnityEngine;

public class GameHud : MonoBehaviour
{
    [SerializeField] private GameFlow gameFlow;
    [SerializeField] private TextMeshProUGUI scoreText;
    [SerializeField] private TextMeshProUGUI timeText;
    [SerializeField] private GameObject titlePanel;
    [SerializeField] private GameObject resultPanel;
    [SerializeField] private TextMeshProUGUI resultText;

    void Update()
    {
        scoreText.text = "コイン " + gameFlow.Coins + "/5";
        timeText.text = "残り " + Mathf.CeilToInt(gameFlow.TimeLeft) + "秒";

        titlePanel.SetActive(gameFlow.Current == GameFlow.State.Title);
        resultPanel.SetActive(gameFlow.Current == GameFlow.State.Result);

        if (gameFlow.Current == GameFlow.State.Result)
        {
            resultText.text = gameFlow.IsCleared ? "クリア!" : "時間切れ…";
        }
    }
}

InspectorでGameFlowと各UIパーツをつなぎ、 StartボタンのOnClickに GameFlow.StartGame、Retryボタンに GameFlow.Retry を割り当てます。仕上げに、Coinの OnTriggerEnterAudioSource.PlayClipAtPoint(pickupSound, transform.position); を1行足して取得音を鳴らすと、急にゲームらしくなります(好きなフリー効果音を [SerializeField] private AudioClip pickupSound; で受けてください)。

完成したゲーム画面の場面図。上部にコイン枚数と残り時間のHUD、床の上を走るプレイヤーとコイン、取った瞬間の光の演出

確認: Title「Start」→ 60秒カウントが動く → コインを取るたびスコアが増える → 5枚で「クリア!」/放置で「時間切れ…」→「Retry」でタイトルから再挑戦。この一巡が回れば、ゲームは完成です。

ビルドして、友達に渡す

Editorの中で動くものは、まだ「プロジェクト」です。 Editorの外で動いて初めて「ゲーム」になります

  1. File > Build Settings(Unity 6では File > Build Profiles)を開く
  2. Add Open Scenes で今のSceneを登録する(これを忘れると空のビルドができます)
  3. PlatformがWindows/Macになっていることを確認して Build。出力フォルダを選ぶ
  4. できた実行ファイルを起動して、 エディタなしで一巡遊ぶ——Title→Playing→クリアと時間切れの両方→Retry。これが スモークテスト です
ビルドを友達に渡す場面図。ノートPCで完成したコインゲームを遊ぶ友達と、それを隣で見守る作者のクレイ人形

そして最後の仕上げが、 ビルドのフォルダをzipにして、友達に渡す ことです。自分以外の人間が、あなたの説明なしで遊べたら——おめでとうございます。あなたは「Unityの機能を知っている人」から「ゲームを完成させた人」になりました。この差は、思っているよりずっと大きいです。

もしビルドで画面が真っ暗なら、Scenes In Buildへの登録忘れがほぼ犯人です。Editorでは動くのにビルドで音が出ない場合は、AudioClipの割り当て漏れを疑ってください。

おまけ:改造チャレンジ4選

完成したゲームは、最高の実験場です。1つ選んで、自分の手で壊して直してみてください。

  • 危険物を置く: 触れるとタイムが5秒減る赤いSphereを追加。作り方はCoinの複製+通知の中身違いです。本格的なダメージ処理は 体力・ダメージの記事
  • ハイスコアを残す: クリアタイムを PlayerPrefs で保存して、タイトルに「ベスト記録」を表示
  • コインを増やして配置を変える: targetCoins とPrefabの配置だけでレベルデザインが体験できます。取りにくい場所の1枚が、ゲームを面白くします
  • WebGLでブラウザ配信: ビルド先をWebGLにすると、URLひとつで遊んでもらえます。ビルド設定の詳細は ビルド設定の記事

まとめ

  • ゲームの背骨は Title → Playing → Result → リトライ の一巡。どんな大作もこの骨格の肉付けです
  • スクリプトは 役割で4本に分ける:動く係・取られる係・ルール係・表示係
  • コインの「1回だけ」は フラグ1つ から。リトライは Scene再読み込み が最も確実(staticイベントの解除だけ忘れずに)
  • ビルドしてEditorの外で一巡遊ぶ までが完成。zipにして人に渡せたら卒業です

次に作りたいものは、もう頭に浮かんでいるはずです。2Dのアクション? セーブのあるRPG?——このコインゲームに使った背骨は、そのままどれにでも流用できます。さて、次は何を完成させますか?