きれいな成功談ではありません。
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 ボタン」
↓ ①に戻る(ループ)
使用環境
| 項目 | 内容 |
|---|---|
| OS | macOS 26.5.1(Apple Silicon / arm64) |
| Xcode | 26.5 |
| Unity Hub | 最新版 |
| Unity Editor | 6000.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 基礎
- Hierarchy / Inspector / Project ウィンドウの役割と操作
- GameObject・Component の概念(オブジェクトに機能を追加する仕組み)
- Rigidbody2D:物理演算コンポーネント(移動・速度の管理)
- BoxCollider2D:当たり判定
- Canvas / TextMeshPro:UI テキスト表示
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 のセットアップと参照管理」でした。以下、起きた順に全部記録します。
起動時に Safe Mode で開く
The project you are opening contains compilation errors.
原因:プロジェクトに含まれるパッケージのコンパイルエラー。
対処:「Enter Safe Mode」を選択して起動 → 再起動で自動修復されます。
不要パッケージのコンパイルエラー(複数)
2D URP テンプレートには Unity 6.5 で動作しないパッケージが複数含まれていました。
| パッケージ | エラー内容 |
|---|---|
com.unity.2d.tilemap.extras | TileTemplate 型が見つからない |
com.unity.2d.aseprite | UnityEditor.Tilemaps 名前空間なし |
com.unity.2d.tilemap | FileUtil.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 エディターをクリックしてフォーカスを当てると自動で再インポートが始まります。
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)」を選択してください。
新 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);
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);
当たり判定コールバックはコンポーネント参照の問題が重なると不安定になります。「ゴールラインを越えたか」は毎フレームの位置比較の方がシンプルで確実でした。
日本語テキストが表示されない
The character with Unicode value ! was not found in the [LiberationSans SDF] font asset
原因:TextMeshPro のデフォルトフォント(LiberationSans SDF)は日本語非対応。
対処:テスト版ではテキストを英語に変更して回避しました。本番では日本語フォント(Noto Sans JP など)をプロジェクトに追加し、TextMeshPro のデフォルトフォントとして設定する必要があります。
ボタンの 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)は、ハンマー投げ・戦艦落としなど他のミニゲームにもそのまま応用できます。テスト版で土台を固めておくことが、本番開発の速度に直結します。
- Unity 6.5 + iOS Build Support の環境構築(パッケージ整理が必要)
- 2D オブジェクトのタップ操作・物理移動
- ゲーム状態管理(State Machine)
- コルーチンによるカウントダウン演出
- PlayerPrefs によるローカルランキング
- ゲームループ(タイトル→カウントダウン→プレイ→ゴール→ランキング→繰り返し)
- Editor スクリプトによるシーン自動構築
- Claude Code との共同開発フロー(エラー対処・コード生成を AI と協力して実施)
伝えておきたいアドバイス
Unity 6 で 2D URP を使うなら、まず manifest.json を整理せよ
2D URP テンプレートはデフォルトで多くのパッケージが入っていますが、Unity 6.5 時点で動作しないものが複数あります。最初にまとめて削除しておくと後が楽です。
クラス名はユニークにするか namespace を使え
GameManager UIManager のような汎用的な名前はパッケージとの衝突リスクが高いです。プロジェクト固有の namespace で囲む習慣をつけましょう。衝突に気づかないまま進むと、謎の参照エラーに長時間悩まされます。
Singleton は Unity 6 の Editor スクリプト環境では要注意
静的インスタンスは便利ですが、Editor スクリプトで生成したオブジェクトと Play モードの組み合わせでは参照が失われる場合があります。GameObject.Find + GetComponent または FindFirstObjectByType を Start 内で使う方が安定します。
ゴール判定は当たり判定より位置比較が安定することがある
OnTriggerEnter2D は強力ですが、コンポーネント参照の不安定さが重なると原因の特定が難しくなります。シンプルな「ラインを越えたか」の判定は毎フレームの位置比較で十分で、むしろ安定します。
テスト版で「遊べる状態」を早く作ることが大事
グラフィックやサウンドは後回しにして、まずゲームループ(開始→プレイ→終了→繰り返し)が動く状態を最優先に作ること。面白いかどうかの判断を早く下せるし、構造の問題を早期に発見できます。
AI との共同開発について
今回は Claude Code とペアで開発を進めました。エラーメッセージを貼り付けるだけで原因と対処を提案してもらえるため、Unity に慣れていない段階でも詰まり続けることなく前進できました。ただしAI の提案が必ずしも一発で正解とは限りません。 今回も参照エラーの解決に複数のアプローチを試しました。「提案を試す → 結果を見る → 次の手を考える」というサイクルで使うのが正解です。
まとめ
「動くものにしか、人は興味を持たない」 — グラフィックも音もない四角と線だけのゲームでも、ループが動いた瞬間に「作れる」という確信に変わります。まず遊べる状態を作ること。そこからが本当の開発です。
- Unity 6 + 2D URP テンプレートは最初にパッケージ整理が必要
- クラス名は最初から
namespaceで囲む - Play モード時の参照は
Start()内で取得し直す - ゴール判定などシンプルな判定は位置比較でも十分安定する
- Claude Code はエラー解決の相棒だが「提案を試す→結果を見る」サイクルで使う