本文へスキップ
Unity基礎

UnityのScriptableObject入門|初期HPを共有して、残りHPは個別に持つ

Unity 6で共通の初期HPをScriptableObjectに保存し、2体の残りHPを別々に管理する例です。アセットとコンポーネントの違い、参照の登録、設定値のコピーを図と完成コードで確認します。

公開日: 2026-10-27#Unity#CSharp#ScriptableObject#データ設計#初心者
ガイドキャラクター
Unity 6で共通の初期HPをScriptableObjectに保存し、2体の残りHPを別々に管理する例です。アセットとコンポーネントの違い、参照の登録、設定値のコピーを図と完成コードで確認します。
学習者キャラクター
準備するものと、動作確認で見る結果を先に確認してから進めます。
UnityのScriptableObject入門|初期HPを共有して、残りHPは個別に持つの手順と判断条件を示す説明図
仕組みを説明する模式図です。エディタのスクリーンショットではありません。小さい画面では横にスクロールできます。設定値と手順は本文で確認してください。

この記事で作るもの

2つのボタンで「敵A」「敵B」に10ずつダメージを与える、Unity 6・uGUI・TextMeshProの小さな実験を作ります。画面には各敵のHPを表示します。敵の3Dモデルや戦闘処理は使いません。

共通の初期HPは1つのCommonEnemySettings.assetへ置き、残りHPは各GameObjectのスクリプトへ置きます。Aを攻撃しても、BのHPや共通アセットの値が変わらないことがゴールです。

ScriptableObjectは、GameObjectから独立したデータを扱える型です。今回作る設定データはProject内のアセットになり、GameObjectへコンポーネントとして付けるものではありません。ScriptableObjectの公式資料

1. 初期HPの設定アセットを作る

Project内へEnemySettings.csを作り、次のコードに置き換えます。ファイル名とクラス名を一致させます。

using UnityEngine;

[CreateAssetMenu(fileName = "EnemySettings", menuName = "Learning/Enemy Settings")]
public sealed class EnemySettings : ScriptableObject
{
    [SerializeField, Min(1)] private int maxHealth = 100;

    public int MaxHealth => Mathf.Max(1, maxHealth);
}
  1. コンパイルが終わり、Consoleにエラーがないことを確認します。
  2. Project内を右クリックしてCreate > Learning > Enemy Settingsを選びます。
  3. 作成されたアセットをCommonEnemySettingsと名付けます。
  4. アセットのInspectorでMax Healthを100にします。

CreateAssetMenuは、設定アセットを作る項目をCreateメニューへ追加します。.csはデータの型の定義、作成した.assetは実際の設定データです。後で参照欄へ入れるのはCommonEnemySettingsのアセットです。CreateAssetMenuの公式資料

2. 2体とUIを準備する

  1. Hierarchyに空のGameObjectを2つ作り、EnemyAとEnemyBにします。
  2. UI > Button - TextMeshProでボタンを2つ作ります。TMP Essentialsの読み込みを求められたら読み込みます。
  3. CanvasをScreen Space - Overlayにし、CanvasのGraphic RaycasterとEventSystemを残します。使用している入力設定に対応したUI入力モジュールを使います。
  4. Canvasの子へUI > Text - TextMeshProを2つ作り、HealthLabelAとHealthLabelBにします。ボタン内の文字とは別のオブジェクトです。
  5. 4つのUIを重ならない位置へ置きます。例として、すべて中央アンカー、サイズ(320, 48)で次のPos Yを使えます。
UIPos X / Pos Y表示文字
HealthLabelA0 / 120HP: 100 / 100
DamageButtonA0 / 60Aに10ダメージ
HealthLabelB0 / -40HP: 100 / 100
DamageButtonB0 / -100Bに10ダメージ

ボタンを作ったときの名前をDamageButtonA、DamageButtonBに変えます。それぞれの子のTMPテキストへ表示文字を設定します。両ButtonのOn Click()は空にします。次のコードがクリックを登録します。

EnemyA                         ← EnemyHealthDemo
EnemyB                         ← EnemyHealthDemo
Canvas
  HealthLabelA                 ← TextMeshProUGUI
  DamageButtonA                ← Button
    Text (TMP)
  HealthLabelB                 ← TextMeshProUGUI
  DamageButtonB                ← Button
    Text (TMP)
EventSystem

Project内: CommonEnemySettings.asset

3. 残りHPを各スクリプトに持たせる

EnemyHealthDemo.csを作り、次のコードを保存します。EnemyAとEnemyBの両方へ、このスクリプトを付けます。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public sealed class EnemyHealthDemo : MonoBehaviour
{
    [SerializeField] private EnemySettings settings;
    [SerializeField] private TMP_Text healthLabel;
    [SerializeField] private Button damageButton;

    private int maxHealth;
    private int currentHealth;

    private void OnEnable()
    {
        if (settings == null || healthLabel == null || damageButton == null)
        {
            Debug.LogError("設定アセット、HP表示、Buttonを登録してください。", this);
            enabled = false;
            return;
        }

        maxHealth = settings.MaxHealth;
        currentHealth = maxHealth;
        damageButton.onClick.AddListener(TakeTenDamage);
        RefreshLabel();
    }

    private void TakeTenDamage()
    {
        currentHealth = Mathf.Max(0, currentHealth - 10);
        RefreshLabel();
    }

    private void RefreshLabel()
    {
        healthLabel.text = $"HP: {currentHealth} / {maxHealth}";
    }

    private void OnDisable()
    {
        if (damageButton != null)
        {
            damageButton.onClick.RemoveListener(TakeTenDamage);
        }
    }
}

Inspectorの登録は次のようにします。同じアセットを参照し、UIの参照は各敵に分けます。

コンポーネントSettingsHealth LabelDamage Button
EnemyACommonEnemySettingsHealthLabelADamageButtonA
EnemyBCommonEnemySettingsHealthLabelBDamageButtonB

有効化されたときにsettings.MaxHealthを読み取り、各コンポーネントのmaxHealthとcurrentHealthへコピーします。ダメージで減らすのは各自のcurrentHealthだけです。この例では、コンポーネントを無効から有効へ戻した場合もHPを初期化します。OnEnableの公式資料

動作確認

操作期待する結果
PlayするAもBもHP: 100 / 100
AのButtonを1回押すAは90 / 100、Bは100 / 100
BのButtonを2回押すAは90 / 100のまま、Bは80 / 100
Aを10回以上攻撃するAのHPは0で止まり、負の数にならない
実行中にCommonEnemySettingsを見るMax Healthは100のまま
Playを終了し、アセットを150へ変えて再びPlayするAもBも150 / 150から始まる

Play中にアセットを編集する手順は、この確認には含めません。Editor上ではアセットの編集が残る場合があるため、共通設定を試しに変更するときはPlayを終了してから行います。実行中にアセットを変えても、このコードがコピー済みのHPを自動で更新することはありません。

うまく動かないとき

  • Createメニューに項目がない:Consoleのコンパイルエラー、ファイル名、CreateAssetMenuの記述を確認します。
  • Settingsへスクリプトを入れられない:.csではなく、作成したCommonEnemySettingsアセットを登録します。
  • AのボタンでBの表示が変わる:2体のHealth LabelとDamage Buttonの参照が入れ替わっていないか確認します。
  • クリック1回で20減る:On Click()へ追加登録していないか、同じButtonを両コンポーネントへ渡していないか確認します。
  • 参照を直しても反応しない:参照不足でコンポーネントが無効になった場合は、有効にし直します。

まとめ

共通の初期値はScriptableObject、変化する残りHPは各EnemyHealthDemoへ持たせます。1つの設定を共有しながら、AとBを独立して変化させられます。この例はセーブ機能を実装していません。終了後のHPを復元したい場合は、セーブデータ設計の基本へ進めます。

学習者キャラクター
今回の内容で覚えておくポイントを確認したいです。
ガイドキャラクター
共通の初期値はScriptableObject、変化する残りHPは各EnemyHealthDemoへ持たせます。1つの設定を共有しながら、AとBを独立して変化させられます。この例はセーブ機能を実装していません。終了後のHPを復元したい場合は、セーブデータ設計の基本へ進めます。
この記事のまとめを案内するガイドキャラクター

この記事のまとめ

Unity 6で共通の初期HPをScriptableObjectに保存し、2体の残りHPを別々に管理する例です。アセットとコンポーネントの違い、参照の登録、設定値のコピーを図と完成コードで確認します。