きれいな成功談ではありません。

Unity 6 で iOS 向けゲームを個人で作る。それだけのことで、こんなに詰まるとは思っていませんでした。エラーは7本。半分はゲームロジックとは無関係の「環境系」でした。

でも全部解決しました。Claude Code と一緒に。

この記事は、6月22日から26日の5日間、短距離走ミニゲームを動かすまでに起きたことをすべて記録したものです。 Unity を触り始めた人、iOS ゲームを個人で作ろうとしている人、Claude Code × Unity の組み合わせが気になっている人——同じところで詰まったとき、この記録が地図になれば十分です。

今回作ったもの

最終的に作ったのは「短距離走ミニゲーム」のテスト版です。本当に作りたいのは Eオリンピックゲーム(複数ミニゲーム × 世界ランキング)ですが、まずシンプルなゲームループが正しく動くかを検証しました。ゲームのシーケンスはこうなっています。

① タイトル画面
   「SPRINT GAME」  「START ボタン」
        ↓ START を押す
② カウントダウン(3・2・1・GO!)
   「Click repeatedly!」ヒント表示
        ↓ GO! 後に自動スタート
③ プレイ
   クリック連打で加速 → 摩擦で減速
   タイマーが動作
        ↓ ゴールライン到達
④ ゴール演出(2.5秒)
   「GOAL! ○○.○○ s」表示
        ↓ 自動遷移
⑤ ランキング
   上位5件を表示(端末内保存)
   「Play Again ボタン」
        ↓ ①に戻る(ループ)

使用環境

項目内容
OSmacOS 26.5.1(Apple Silicon / arm64)
Xcode26.5
Unity Hub最新版
Unity Editor6000.5.0f1(Unity 6)
テンプレート2D (URP)
ターゲットiOS(iPhone)
開発補助Claude Code

旧「2D」テンプレートは Unity 6 で非推奨です。「2D (URP)」を選ぶこと。また、File → Build Settings は Unity 6 から File → Build Profiles に名称変更されています。

スクリプト構成

今回実装したスクリプトは3本 + Editorツール1本です。

Assets/
├── Scripts/
│   ├── GameManager.cs      ← ゲーム全体の状態管理・タイム計測・ランキング保存
│   ├── PlayerController.cs ← タップ入力・プレイヤー移動
│   └── UIManager.cs        ← 画面表示の切り替え
└── Editor/
    └── SprintSceneSetup.cs ← シーン自動構築ツール(開発専用)
オブジェクト役割
Player(オレンジの四角)タップで加速する走者
FinishLine(黄色の縦線)ゴール判定ライン
Ground(緑の横線)地面の見た目
Canvas → 各パネルUI 全体
GameManagerゲーム状態管理・タイム計測・ランキング
UIManager各画面の表示制御

実装に必要な知識

Unity 基礎

MonoBehaviour のライフサイクル

複数オブジェクト間では「全オブジェクトの Awake が終わってから全 Start が始まる」という順序が保証されています。これを知らないと参照エラーの原因が分からなくなります。

Awake() → Start() → Update()(毎フレーム)

Coroutine(コルーチン)

カウントダウンのような時間差処理に使います。

IEnumerator CountdownRoutine()
{
    foreach (var count in new[] { "3", "2", "1", "GO!" })
    {
        uiManager.ShowCountdown(count);
        yield return new WaitForSeconds(1f);
    }
}

enum でゲーム状態を管理

public enum GameState { Start, Countdown, Playing, Goal, Ranking }

状態を enum で管理することで「今どのフェーズか」を明確に保てます。UIManager はこの状態に応じてパネルの表示・非表示を切り替えるだけになります。

PlayerPrefs(端末内ランキング保存)

PlayerPrefs.SetFloat("Record_0", time);
PlayerPrefs.Save();

インターネット接続不要でシンプルに保存できます。今回はローカルランキングに使用しています。

新 Input System

Unity 6 では旧来の Input.GetMouseButtonDown(0) が使えない場合があります。com.unity.inputsystem パッケージを使った書き方が必要です。

using UnityEngine.InputSystem;

bool tapped = (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame) ||
              (Touchscreen.current != null && Touchscreen.current.primaryTouch.press.wasPressedThisFrame);

マウス(PC・エディター)とタッチ(iPhone)を両方まとめて検出できます。

Editor スクリプト

Assets/Editor/ フォルダに置いたスクリプトは Unity メニューにツールを追加できます。今回は「シーンの全オブジェクトを自動生成・設定する」ツールを作り、毎回手作業でオブジェクトを配置する手間を省きました。スクリプトを修正するたびにシーンを組み直す手間がなくなり、開発サイクルが大幅に速くなります。

[MenuItem("SprintGame/Setup Scene")]
static void SetupScene()
{
    // GameObject の生成・Component のアタッチ・参照の設定を自動化
}

実際に起きた不具合7本

正直に言うと、今回の開発で詰まった時間の大半は「ゲームロジック」ではなく「Unity 6 のセットアップと参照管理」でした。以下、起きた順に全部記録します。

1
BUG

起動時に Safe Mode で開く

The project you are opening contains compilation errors.

原因:プロジェクトに含まれるパッケージのコンパイルエラー。

対処:「Enter Safe Mode」を選択して起動 → 再起動で自動修復されます。

2
BUG

不要パッケージのコンパイルエラー(複数)

2D URP テンプレートには Unity 6.5 で動作しないパッケージが複数含まれていました。

パッケージエラー内容
com.unity.2d.tilemap.extrasTileTemplate 型が見つからない
com.unity.2d.asepriteUnityEditor.Tilemaps 名前空間なし
com.unity.2d.tilemapFileUtil.CombinePaths が存在しない
com.unity.2d.animation ほか今回のゲームで不使用

対処:Packages/manifest.json から該当パッケージを手動で削除します。

// 削除した行(今回のゲームには不要)
"com.unity.2d.tilemap.extras": "8.0.3",
"com.unity.2d.aseprite": "5.0.3",
"com.unity.2d.tilemap": "1.0.0",
"com.unity.2d.animation": "15.1.0",
"com.unity.2d.psdimporter": "14.0.3",
"com.unity.2d.spriteshape": "15.0.3",
"com.unity.2d.tooling": "3.0.1"

ポイント:manifest.json を変更後、Unity エディターをクリックしてフォーカスを当てると自動で再インポートが始まります。

3
BUG

GameManager クラス名の衝突

error CS0101: The namespace '<global namespace>' already contains a definition for 'GameManager'

原因:Visual Scripting パッケージに同名の GameManager クラスが存在し、自作クラスと競合。

対処:自作スクリプト全体を namespace SprintGame { } で囲みます。

namespace SprintGame
{
    public class GameManager : MonoBehaviour { ... }
}

Add Component 時は「Game Manager (SprintGame)」を選択してください。

4
BUG

新 Input System エラー

InvalidOperationException: You are trying to read Input using the UnityEngine.Input class,
but you have switched active Input handling to Input System package in Player Settings.

原因:Unity 6 は新 Input System がデフォルト設定。旧 Input クラスが使えません。

対処:using UnityEngine.InputSystem; を追加して入力検出コードを書き直します。

bool tapped = (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame) ||
              (Touchscreen.current != null && Touchscreen.current.primaryTouch.press.wasPressedThisFrame);
5
BUG

Edit モードで設定した参照が Play 時に失われる

症状:Editor スクリプトでオブジェクト間の参照を設定したのに、Play ボタンを押すと NullReferenceException が大量発生。

原因:Unity の Editor スクリプトで AddComponent した直後にフィールドへ代入した参照が、Play モード開始時にシリアライズされないケースがある。static Instance(Singleton)パターンでの回避も今回の環境では安定しませんでした。

対処(2段構え):

Start() 内で名前検索 + GetComponent する。

void Start()
{
    gm = GameObject.Find("GameManager")?.GetComponent<GameManager>();
}

② ゴール判定を OnTriggerEnter2D(当たり判定)から位置比較に切り替える。

// 当たり判定より位置比較の方が今回は安定した
if (playerTransform.position.x >= finishX)
    SetState(GameState.Goal);

当たり判定コールバックはコンポーネント参照の問題が重なると不安定になります。「ゴールラインを越えたか」は毎フレームの位置比較の方がシンプルで確実でした。

6
BUG

日本語テキストが表示されない

The character with Unicode value ! was not found in the [LiberationSans SDF] font asset

原因:TextMeshPro のデフォルトフォント(LiberationSans SDF)は日本語非対応。

対処:テスト版ではテキストを英語に変更して回避しました。本番では日本語フォント(Noto Sans JP など)をプロジェクトに追加し、TextMeshPro のデフォルトフォントとして設定する必要があります。

7
BUG

ボタンの onClick が Play 時に参照を失う

原因:Editor スクリプトで btn.onClick.AddListener(() => method()) のようにラムダで設定した場合、Play 時にクロージャの参照がシリアライズされません。

対処:UnityEditor.Events.UnityEventTools.AddPersistentListener を使います。

UnityEditor.Events.UnityEventTools.AddPersistentListener(btn.onClick, uiManager.OnRetryButton);

これで Edit モードで設定したボタンイベントが Play 時も保持されます。

検証できたこと

この「短距離走ミニゲーム」で検証したゲームループ構造(State Machine + Coroutine + PlayerPrefs)は、ハンマー投げ・戦艦落としなど他のミニゲームにもそのまま応用できます。テスト版で土台を固めておくことが、本番開発の速度に直結します。

伝えておきたいアドバイス

1

Unity 6 で 2D URP を使うなら、まず manifest.json を整理せよ

2D URP テンプレートはデフォルトで多くのパッケージが入っていますが、Unity 6.5 時点で動作しないものが複数あります。最初にまとめて削除しておくと後が楽です。

2

クラス名はユニークにするか namespace を使え

GameManager UIManager のような汎用的な名前はパッケージとの衝突リスクが高いです。プロジェクト固有の namespace で囲む習慣をつけましょう。衝突に気づかないまま進むと、謎の参照エラーに長時間悩まされます。

3

Singleton は Unity 6 の Editor スクリプト環境では要注意

静的インスタンスは便利ですが、Editor スクリプトで生成したオブジェクトと Play モードの組み合わせでは参照が失われる場合があります。GameObject.Find + GetComponent または FindFirstObjectByTypeStart 内で使う方が安定します。

4

ゴール判定は当たり判定より位置比較が安定することがある

OnTriggerEnter2D は強力ですが、コンポーネント参照の不安定さが重なると原因の特定が難しくなります。シンプルな「ラインを越えたか」の判定は毎フレームの位置比較で十分で、むしろ安定します。

5

テスト版で「遊べる状態」を早く作ることが大事

グラフィックやサウンドは後回しにして、まずゲームループ(開始→プレイ→終了→繰り返し)が動く状態を最優先に作ること。面白いかどうかの判断を早く下せるし、構造の問題を早期に発見できます。

6

AI との共同開発について

今回は Claude Code とペアで開発を進めました。エラーメッセージを貼り付けるだけで原因と対処を提案してもらえるため、Unity に慣れていない段階でも詰まり続けることなく前進できました。ただしAI の提案が必ずしも一発で正解とは限りません。 今回も参照エラーの解決に複数のアプローチを試しました。「提案を試す → 結果を見る → 次の手を考える」というサイクルで使うのが正解です。

まとめ

「動くものにしか、人は興味を持たない」 — グラフィックも音もない四角と線だけのゲームでも、ループが動いた瞬間に「作れる」という確信に変わります。まず遊べる状態を作ること。そこからが本当の開発です。

NEXT

第3回:Unity → Xcode → iPhone 実機テストの手順
ビルドフロー・署名設定・よくある証明書エラーの対処法を実録で記録します。