mcpField
U
Unity Fundamentals

Unity Fundamentals

github.com/cryptorabea開発383 回インストール45 週間前に更新
リポジトリ

Unity Fundamentals について

Unity のコアな開発パターンを学び、実践する開発者向けのスキルです。MonoBehaviour のライフサイクル、シリアライズ、コンポーネント設計、プレハブのワークフローを扱い、関連するリファレンスも提供します。

主な機能

  1. 01MonoBehaviour のライフサイクル指針
  2. 02シリアライズと Inspector の実践
  3. 03コンポーネントの検索と連携パターン
  4. 04プレハブの作成とバリアント運用
  5. 05トピック別の詳細リファレンス

活用シーン

  1. 01適切なライフサイクルコールバックの選択
  2. 02Inspector でのプライベートフィールド編集
  3. 03コンポーネント依存関係の設計
  4. 04再利用可能なプレハブバリアントの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/cryptorabea/claude_unity_dev_plugin --skill unity-fundamentals

インストール後、Claude Code で/unity-fundamentalsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
serialization-guide.md
prefab-workflows.md
lifecycle-detailed.md
component-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);
}
ライフサイクルの順序の概要
  1. Awake() - 自分自身を初期化
  2. OnEnable() - イベントに登録
  3. Start() - 他のオブジェクトを参照し、最終セットアップを実行
  4. FixedUpdate() - 物理演算(50 FPS)
  5. Update() - 毎フレームのロジック(可変 FPS)
  6. LateUpdate() - カメラ、最終調整
  7. OnDisable() - イベントの登録を解除
  8. 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);
}
プレハブのベストプラクティス
  1. 実行時に生成されるものにはプレハブを使用する(敵、弾丸、UI パネルなど)
  2. プレハブを複製する代わりに、プレハブのバリアントを作成する
  3. 変更を慎重に適用する - すべてのインスタンスに影響します
  4. プレハブを整理されたフォルダーに保管する - Prefabs/Characters、Prefabs/UI など
  5. すべてのインスタンスに変更を適用する前に、プレハブの変更をテストする

よくあるパターン

シングルトンマネージャー
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 プロジェクトを構築しましょう。

コメント