Unity Fundamentals について
Unity のコアな開発パターンを学び、実践する開発者向けのスキルです。MonoBehaviour のライフサイクル、シリアライズ、コンポーネント設計、プレハブのワークフローを扱い、関連するリファレンスも提供します。
主な機能
- 01MonoBehaviour のライフサイクル指針
- 02シリアライズと Inspector の実践
- 03コンポーネントの検索と連携パターン
- 04プレハブの作成とバリアント運用
- 05トピック別の詳細リファレンス
活用シーン
- 01適切なライフサイクルコールバックの選択
- 02Inspector でのプライベートフィールド編集
- 03コンポーネント依存関係の設計
- 04再利用可能なプレハブバリアントの作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cryptorabea/ claude_unity_dev_plugin --skill unity-fundamentalsインストール後、Claude Code で/unity-fundamentalsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/serialization-guide.mdprefab-workflows.mdlifecycle-detailed.mdcomponent-patterns.md詳細
Unity の基礎
Unity のコアシステム、MonoBehaviour のライフサイクル、シリアル化、コンポーネントアーキテクチャ、プレハブのワークフローに関する包括的なガイダンスです。
概要
Unity 開発の中心となるのは、MonoBehaviour コンポーネント、Component パターン、そしてコールバックメソッドの特定のライフサイクルです。これらの基礎を理解することは、正しくパフォーマンスに優れた Unity コードを書くうえで不可欠です。このスキルでは、以下を扱います。
- MonoBehaviour のライフサイクルメソッドと適切な使い方
- シリアル化システムと Inspector の連携
- コンポーネントベースのアーキテクチャと GetComponent のパターン
- プレハブのワークフローとベストプラクティス
MonoBehaviour のライフサイクル
Unity は、決められた順序で MonoBehaviour スクリプトの特定のメソッドを呼び出します。誤ったメソッドを使うと、バグ、null 参照、パフォーマンスの低下につながります。
初期化メソッド
Awake()
スクリプトのインスタンスが読み込まれるときに、Start() が呼び出される前に呼び出されます。このオブジェクトの状態を初期化するために使います。
private void Awake()
{
// Cache component references on THIS GameObject
rigidbody = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
// Initialize private state
currentHealth = maxHealth;
inventory = new List<Item>();
// DON'T reference other objects - they may not be initialized yet
}
Awake() の用途:
- コンポーネント参照のキャッシュ
- プライベートフィールドの初期化
- 内部状態の設定
- シングルトンやマネージャーの作成
Awake() の用途として避けるもの:
- 他の GameObject の参照(代わりに Start を使います)
- 他のスクリプトの準備完了に依存する処理
Start()
すべての Awake() 呼び出しが完了した後、最初の Update() の前に呼び出されます。他のオブジェクトを参照するセットアップに使います。
private void Start()
{
// Safe to reference other GameObjects - they're initialized
playerTransform = GameObject.FindWithTag("Player").transform;
gameManager = FindObjectOfType<GameManager>();
// Register with managers or systems
GameManager.Instance.RegisterEnemy(this);
// Perform initialization that depends on other components
SetupWeapon(gameManager.GetStartingWeapon());
}
Start() の用途:
- 他の GameObject の検索と参照
- 他のコンポーネントの初期化メソッドの呼び出し
- マネージャーやシステムへの登録
- シーン全体の初期化完了に依存するセットアップ
一般的なパターン:
private Camera mainCamera; // Cache in Awake
private Transform target; // Find in Start
private void Awake()
{
mainCamera = Camera.main; // Cache expensive lookup
}
private void Start()
{
target = GameObject.FindWithTag("Target").transform; // Find after scene loads
}
OnEnable() / OnDisable()
GameObject がアクティブ/非アクティブになったときに呼び出されます。イベントの登録/解除に使います。
private void OnEnable()
{
// Subscribe to events
GameEvents.OnPlayerDied += HandlePlayerDeath;
InputManager.OnJumpPressed += HandleJump;
// Re-enable functionality
StartCoroutine(SpawnEnemies());
}
private void OnDisable()
{
// Unsubscribe from events (prevents memory leaks!)
GameEvents.OnPlayerDied -= HandlePlayerDeath;
InputManager.OnJumpPressed -= HandleJump;
// Clean up temporary state
StopAllCoroutines();
}
重要: メモリリークを防ぐため、必ず OnDisable() で登録を解除してください。
Update メソッド
Update()
毎フレーム呼び出されます。パフォーマンスに大きく影響するため、使用は控えめにします。
private void Update()
{
// Input handling
if (Input.GetKeyDown(KeyCode.Space))
Jump();
// Frame-dependent logic
UpdateUI();
// AVOID expensive operations here
// DON'T call GetComponent every frame
// DON'T use Find methods
}
可能な限り Update() の使用を避けてください。 イベント駆動型のアプローチを優先します。
FixedUpdate()
固定タイムステップ(デフォルトでは 50 FPS)で呼び出されます。物理演算の処理のみに使います。
private void FixedUpdate()
{
// Physics operations
rigidbody.AddForce(moveDirection * moveSpeed);
// Physics-based movement
rigidbody.MovePosition(transform.position + velocity * Time.fixedDeltaTime);
// DON'T handle input here (use Update)
// DON'T update UI here (use Update or LateUpdate)
}
ルール: Rigidbody や物理演算に関わる処理には FixedUpdate() を使います。それ以外は Update() またはイベント駆動型のアプローチを使います。
LateUpdate()
すべての Update() 呼び出しの後に呼び出されます。カメラの追従や最終調整に使います。
private void LateUpdate()
{
// Camera follow (after player moved in Update)
transform.position = target.position + offset;
// Final position adjustments
ClampToBounds();
// UI updates that depend on world state
UpdateHealthBar();
}
破棄メソッド
OnDestroy()
GameObject が破棄されるときに呼び出されます。最終的なクリーンアップに使います。
private void OnDestroy()
{
// Unsubscribe from static events
GameEvents.OnPlayerDied -= HandlePlayerDeath;
// Release resources
if (texture != null)
Destroy(texture);
// Notify other systems
GameManager.Instance.UnregisterEnemy(this);
}
ライフサイクルの順序の概要
- Awake() - 自分自身を初期化
- OnEnable() - イベントに登録
- Start() - 他のオブジェクトを参照し、最終セットアップを実行
- FixedUpdate() - 物理演算(50 FPS)
- Update() - 毎フレームのロジック(可変 FPS)
- LateUpdate() - カメラ、最終調整
- OnDisable() - イベントの登録を解除
- OnDestroy() - 最終的なクリーンアップ
シリアル化システム
Unity のシリアル化システムは、Inspector に表示される内容とデータの保持方法を制御します。
シリアル化フィールド
プライベートフィールドを Inspector で編集できるようにします。
[SerializeField] private int maxHealth = 100;
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private GameObject prefab;
メリット:
- カプセル化を維持(プライベートアクセス)
- Inspector で編集可能
- プレハブ/シーンデータの保持
ベストプラクティス: public フィールドよりも [SerializeField] private を優先します。
Header と Tooltip
Inspector のセクションを整理します。
[Header("Movement Settings")]
[Tooltip("Maximum movement speed in units per second")]
[SerializeField] private float moveSpeed = 5f;
[Tooltip("Rotation speed in degrees per second")]
[SerializeField] private float rotationSpeed = 180f;
[Header("Combat Settings")]
[SerializeField] private int attackDamage = 25;
シリアル化のルール
Unity がシリアル化するもの:
- パブリックフィールド([HideInInspector] が指定されている場合を除く)
- [SerializeField] が指定されたプライベートフィールド
- 対応する型:プリミティブ型、Unity オブジェクト、構造体、配列、List
Unity がシリアル化しないもの:
- プロパティ
- [SerializeField] が指定されていないプライベートフィールド
- Dictionary
- インターフェース
- 静的フィールド
プロパティとシリアル化フィールド
// DON'T expose internal state directly
public int health; // Bad: public field
// DO use properties for controlled access
[SerializeField] private int health = 100;
public int Health
{
get => health;
set
{
health = Mathf.Clamp(value, 0, maxHealth);
OnHealthChanged?.Invoke(health);
}
}
コンポーネントアーキテクチャ
Unity は Component パターンを使用します。機能は複数のコンポーネントから構成されます。
GetComponent のパターン
繰り返し検索するのを避けるため、Awake() で参照をキャッシュします。
// ❌ BAD - Repeated GetComponent calls
private void Update()
{
GetComponent<Rigidbody>().velocity = Vector3.forward; // Expensive!
}
// ✅ GOOD - Cache once
private Rigidbody rb;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
private void Update()
{
rb.velocity = Vector3.forward; // Fast
}
コンポーネントのバリエーション
// GetComponent<T>() - On this GameObject
rb = GetComponent<Rigidbody>();
// GetComponentInChildren<T>() - This GameObject and children
animator = GetComponentInChildren<Animator>();
// GetComponentInParent<T>() - This GameObject and parents
canvas = GetComponentInParent<Canvas>();
// GetComponents<T>() - Multiple components
Collider[] colliders = GetComponents<Collider>();
パフォーマンス: すべての GetComponent の結果をキャッシュしてください。Update/FixedUpdate 内では呼び出さないでください。
必須コンポーネント
コンポーネントの依存関係を宣言します。
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Collider))]
public class PlayerMovement : MonoBehaviour
{
private Rigidbody rb;
private void Awake()
{
rb = GetComponent<Rigidbody>(); // Guaranteed to exist
}
}
コンポーネント間の通信
直接参照(インスペクター):
[SerializeField] private HealthDisplay healthDisplay;
private void TakeDamage(int amount)
{
health -= amount;
healthDisplay.UpdateHealth(health); // Direct call
}
GetComponent(実行時):
private void OnTriggerEnter(Collider other)
{
var health = other.GetComponent<Health>();
if (health != null)
health.TakeDamage(10);
}
イベント(疎結合):
public event Action<int> OnHealthChanged;
private void TakeDamage(int amount)
{
health -= amount;
OnHealthChanged?.Invoke(health); // Any subscribers get notified
}
プレハブのワークフロー
プレハブは再利用可能な GameObject のテンプレートです。プレハブのワークフローを理解すると、よくある問題を防げます。
プレハブの作成
Hierarchy から GameObject を Project ウィンドウにドラッグします。Hierarchy の青いテキストは、プレハブのインスタンスであることを示します。
プレハブのインスタンス
プレハブインスタンスへの変更:
- オーバーライド - このインスタンスのみに変更を適用します(太字の青色)
- 適用 - 変更をプレハブアセットに反映します(すべてのインスタンスに影響します)
- 元に戻す - インスタンスの変更を破棄し、プレハブと一致させます
プレハブのバリアント
ベースとなるプレハブのバリエーションを作成します。
Base Prefab: Enemy
├── Variant: FastEnemy (increased speed)
├── Variant: TankEnemy (increased health)
└── Variant: FlyingEnemy (added flight)
ベースプレハブへの変更はバリアントにも反映されます。
ネストされたプレハブ
プレハブには他のプレハブを含めることができます。
Car Prefab
├── Wheel Prefab (x4)
├── Engine Prefab
└── Door Prefab (x4)
ネストされたプレハブは個別に編集できます。
プログラムからのプレハブの使用
[SerializeField] private GameObject enemyPrefab;
[SerializeField] private Transform spawnPoint;
private void SpawnEnemy()
{
// Instantiate prefab
GameObject enemy = Instantiate(enemyPrefab, spawnPoint.position, Quaternion.identity);
// Configure instance
enemy.GetComponent<Enemy>().SetTarget(player);
// Parent to container (optional)
enemy.transform.SetParent(enemyContainer);
}
プレハブのベストプラクティス
- 実行時に生成されるものにはプレハブを使用する(敵、弾丸、UI パネルなど)
- プレハブを複製する代わりに、プレハブのバリアントを作成する
- 変更を慎重に適用する - すべてのインスタンスに影響します
- プレハブを整理されたフォルダーに保管する - Prefabs/Characters、Prefabs/UI など
- すべてのインスタンスに変更を適用する前に、プレハブの変更をテストする
よくあるパターン
シングルトンマネージャー
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
オブジェクトの初期化
public class Enemy : MonoBehaviour
{
[SerializeField] private int health = 100;
private Rigidbody rb;
private Transform target;
// 1. Cache components on self
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
// 2. Find external references
private void Start()
{
target = GameObject.FindWithTag("Player").transform;
}
// 3. Subscribe to events
private void OnEnable()
{
GameEvents.OnWaveComplete += HandleWaveComplete;
}
// 4. Unsubscribe from events
private void OnDisable()
{
GameEvents.OnWaveComplete -= HandleWaveComplete;
}
}
追加リソース
参考ファイル
特定のトピックに関する詳しいガイダンス:
references/lifecycle-detailed.md- ライフサイクルメソッドの完全なリファレンスreferences/serialization-guide.md- 高度なシリアライズパターンreferences/component-patterns.md- コンポーネントアーキテクチャのベストプラクティスreferences/prefab-workflows.md- プレハブの包括的な使用ガイド
クイックリファレンス
ライフサイクルの順序: Awake → OnEnable → Start → FixedUpdate → Update → LateUpdate → OnDisable → OnDestroy
シリアライズ: public よりも [SerializeField] private を使用
コンポーネント: Awake() でキャッシュし、Update() 内で GetComponent を呼び出さない
プレハブ: 再利用可能なオブジェクトに使用し、変更を適用する前にテストする
これらの基本原則に従って、パフォーマンスに優れ、保守しやすく、バグのない Unity プロジェクトを構築しましょう。