【Unity 6】ScriptableObject入門|敵の設定データと現在HPを分けて理解しよう

広告

青いパーカーの学習者とモンスターたち、ScriptableObjectの設定データの関係を示した図
敵の共通設定はScriptableObject、個別の状態はMonoBehaviourで管理します。

「敵の種類を増やすたびに、スクリプトを複製しないといけないの?」――そんな疑問から、ゲームの設定データと実行中の状態を分ける設計を学びます。

この演習では、同じ Slime.asset を参照する2体のスライムを用意し、片方だけにダメージを与えます。最大HPは両方とも10のまま、現在HPだけが片方で減ることを、Gameビューで確かめましょう。

学習目標

  1. MonoBehaviour と ScriptableObject の役割を説明できる。
  2. CreateAssetMenu を使って .asset ファイルを作成できる。
  3. 同じ設定データを複数のオブジェクトから参照できる。
  4. 最大HP(設定値)と現在HP(実行時の状態)を区別できる。

事前知識: class、継承、フィールド、[SerializeField]、Unity Inspector、GameObject、Prefab。Unity 6(6000.x)を想定。画面上の文字表示はTextMeshProを使用します。初回導入時にTMP Essentialsのインポートを求められたら、インポートしてください。

1. なぜ設定データを分けるの?(10分)

次のように、敵の設定を MonoBehaviour に直接書く方法を考えましょう。

using UnityEngine;

public class SimpleMonster : MonoBehaviour
{
    [SerializeField] private int maxHp = 10;
    [SerializeField] private int attack = 3;
}

少数の敵だけなら、これでも問題ありません。ただし同じ種類の敵が増えていくと、どのオブジェクトに同じ設定を使うか、変更をどこで管理するかが気になるようになります。

ScriptableObjectを使うと、設定を .asset ファイルとして作り、複数のGameObjectから同じデータを参照できます。ScriptableObject はGameObjectにコンポーネントとして追加するのではなく、独立したアセットとして扱います。

注意:「ScriptableObjectを使えば必ず速くなる」「MonoBehaviourに値を持ってはいけない」という意味ではありません。小さなゲームでは直接設定した方が簡単な場合もあります。今回は共有する設定データを整理する使い方を学びます。

2. モンスター設定を作ろう(15分)

Projectウィンドウに次のフォルダーを作ります。

Assets/
  Scripts/
    MonsterData.cs
    MonsterActor.cs
  MonsterData/
    Slime.asset
    Goblin.asset   ← 余裕があれば演習で作成

Assets/Scripts/MonsterData.cs を作成し、次のコードを入力します。

using UnityEngine;

[CreateAssetMenu(fileName = "NewMonsterData", menuName = "Game/Monster Data")]
public class MonsterData : ScriptableObject
{
    [SerializeField] private string monsterName = "モンスター";
    [SerializeField, Min(1)] private int maxHp = 10;
    [SerializeField, Min(0)] private int attack = 3;

    public string MonsterName => monsterName;
    public int MaxHp => maxHp;
    public int Attack => attack;
}

ここで押さえるポイント

  • : ScriptableObject:設定データの型を定義する。
  • [CreateAssetMenu(...)]:Projectウィンドウの右クリック→ Create > Game > Monster Data に項目を追加する。
  • [SerializeField]:private フィールドをInspectorに表示・保存する。
  • MonsterName、MaxHp、Attack:外部から設定値を読み取るためのプロパティ。今回は外部から直接代入できない。
  • [Min]:Inspectorでの値の入力補助。ゲームルールの完全な検証の代わりではありません。

アセットの作成手順

  1. Unityに戻り、Consoleにコンパイルエラーがないか確認します。
  2. Projectウィンドウで Assets/MonsterData を右クリックします。
  3. Create > Game > Monster Data をクリックします。
  4. 作成されたファイルを Slime.asset に変更します。
  5. Slime.asset を選択し、Inspectorに次の値を入れます。
項目値
Monster Nameスライム
Max Hp10
Attack3

.cs は「データの型」、.asset は「実際の設定データ」です。C#のクラス定義とオブジェクトの関係を思い出しましょう。

3. 同じ設定を使う2体を作ろう(20分)

次に Assets/Scripts/MonsterActor.cs を作成します。

using TMPro;
using UnityEngine;

public class MonsterActor : MonoBehaviour
{
    [SerializeField] private MonsterData monsterData;
    [SerializeField] private TMP_Text statusText;

    private int currentHp;

    private void Start()
    {
        if (monsterData == null || statusText == null)
        {
            Debug.LogError("MonsterData または StatusText が未設定です。", this);
            enabled = false;
            return;
        }

        currentHp = monsterData.MaxHp;
        RefreshView();
    }

    // UI Button の On Click() から呼び出す
    public void TakeDamage()
    {
        if (monsterData == null || statusText == null || !enabled)
        {
            return;
        }

        currentHp = Mathf.Max(0, currentHp - 3);
        RefreshView();
    }

    private void RefreshView()
    {
        statusText.text =
            $"{monsterData.MonsterName}\n" +
            $"現在HP:{currentHp} / 最大HP:{monsterData.MaxHp}\n" +
            $"攻撃力:{monsterData.Attack}";
    }
}

Unityでの画面づくり

ここでは複雑な画像素材を使いません。UIだけで2体の独立した状態を確認します。

  1. Hierarchyで Canvas を作ります。Canvasがなければ自動生成されることもあります。
  2. Canvasの下に Text – TextMeshPro を2つ作成し、名前を SlimeTextA と SlimeTextB にします。文字が見えるよう左右に配置し、RectTransformの幅・高さを適宜調整します。
  3. Canvasの下に Button – TextMeshPro を2つ作成します。左右に配置し、表示文字をそれぞれ「Aにダメージ」「Bにダメージ」にします。
  4. Hierarchyに空のGameObject SlimeA、SlimeB を2つ作り、両方に MonsterActor コンポーネントを追加します。
  5. SlimeA の Monster Data に Slime.asset、Status Text に SlimeTextA を割り当てます。SlimeB にも同じ Slime.asset と SlimeTextB を割り当てます。
  6. 「Aにダメージ」Buttonの On Click() で + を押し、SlimeA をドラッグし、MonsterActor > TakeDamage() を選択します。「Bにダメージ」も同様に SlimeB を指定します。
  7. Playを押してGameビューの文字を確認します。文字が表示されない場合は、TextMeshProのフォントアセットが日本語の文字を含むかも確認してください。環境によっては日本語対応フォントの準備が必要です。

予想してから実行しよう: 最初はどちらも 10 / 10。Aのボタンだけを押すと、BのHPはどうなるでしょうか?

操作スライムAスライムB
開始時10 / 1010 / 10
Aを1回押す7 / 1010 / 10
Aをもう1回押す4 / 1010 / 10
Bを1回押す4 / 107 / 10

結果の読み方: AとBは同じ Slime.asset にある最大HPを参照しています。一方、currentHp は各 MonsterActor コンポーネントが持つ別々のフィールドです。だから、片方へのダメージはもう片方の現在HPに影響しません。

4. 自分で考える演習(10分)

演習A:ゴブリンを追加する

Assets/MonsterData で Create > Game > Monster Data から Goblin.asset を作りましょう。

項目値
Monster Nameゴブリン
Max Hp30
Attack8

SlimeB の Monster Data だけを Goblin.asset に変更してPlayします。C#のコードを変更せず、Bがゴブリンになり、最大HPが30になることを確認します。

演習B:共有してはいけない状態を考える

次のコードを MonsterData に書いて、ダメージのたびに変更したら何が問題でしょうか?

// これは今回の設計では採用しない例
// monsterData に currentHp があり、そこへ書き込むと仮定
// monsterData.currentHp -= 3;

考える観点: 同じアセットを参照する2体が、状態まで共有してしまう可能性があります。さらにエディター上のPlay中にScriptableObjectアセットを変更すると、テスト後に意図しない値が残ることがあります。設定値は参照し、実行時の状態は個々のMonoBehaviourが持つという分け方を基本にしましょう。

補足:ScriptableObjectそのものに処理(メソッド)を書くこともできます。「ScriptableObjectはメソッドを持てない」という説明は誤りです。今回は入門として設定データの管理に用途を絞っています。

5. 振り返り(5分)

種類役割今回の例
MonoBehaviourGameObjectに付けて、実行中のふるまい・状態を扱うMonsterActor と currentHp
ScriptableObjectGameObjectから独立した共有設定データを扱うMonsterData
.asset ファイル設定データの実体Slime.asset、Goblin.asset
PrefabGameObjectとコンポーネント構成を再利用するひな形SlimeA をPrefab化する場合

確認問題(3問)

Q1. 同じ Slime.asset を参照するスライムが2体います。Aの currentHp を3減らしたら、Bの currentHp も減りますか?

Q2. MonsterData.cs と Slime.asset の違いを説明してください。

Q3. 敵の「最大HP」と「現在HP」は、それぞれどこで管理するのが適切ですか?

解答例を表示 1. 減らない。`currentHp` はそれぞれの `MonsterActor` が持つ。 2. `.cs` はデータの型を定義し、`.asset` はその型に基づく設定値を保存する。 3. 最大HPは `MonsterData` の設定値、現在HPは各 `MonsterActor` のフィールドで管理する。

よくあるトラブル

症状確認すること
Game/Monster Data がメニューにないMonsterData.cs のコンパイルエラー、ファイル名・クラス名、CreateAssetMenu を確認
Gameビューに文字が出ないTMP Essentials、テキスト参照、Canvas配置、フォントの日本語対応を確認
ボタンを押しても変化しないButtonの On Click() の対象GameObjectと TakeDamage() を確認
エラーが出るMonster Data、Status Text の参照が両方セットされているか確認
Bの種類を変えたのにAも変わるSlimeA と SlimeB の Monster Data の割り当てを個別に確認

発展:Prefabやチーム開発とのつながり

この演習の SlimeA をPrefabにすれば、同じ構成の敵を増やせます。PrefabはGameObjectのひな形、ScriptableObjectは設定データの共有先です。この2つは競合する機能ではなく、一緒に利用できます。

GitHubで共同作業する場合は、MonsterData.cs に加え、Slime.asset や Goblin.asset および対応する .meta ファイルをコミット対象に含めます。プロジェクトで運用中の「Git操作時はUnityを閉じる」ルールにも従いましょう。

次に読む記事

参考資料(公式)

訪問数 3 回, 今日の訪問数 3回

広告

C#

Posted by hidepon