ScriptableObject入門(Unity6で学ぶ)

キャラクターが玉を投げるサンプルでデータ管理を体験しよう

はじめに

この記事は、手に持ったアイテムを投げる」サンプルを通して、親子オブジェクトの操作や AddForce・linearVelocity を使った挙動を学んだ後の継続として作成されています。

今回はそのサンプルを発展させ、投げる玉の“性質”を ScriptableObject で管理する練習をしてみましょう。

見た目や操作は同じでも、データの扱い方を変えるだけで、設計の柔軟さがぐっと広がります。


ScriptableObjectとは

ScriptableObject は、スクリプトから分離されたデータ入れ物です。

シーン上に配置する必要がなく、アセットとして値を保存・共有できます。

これを使うと、たとえば

  • 投げる玉のスピード
  • 重力を使うかどうか
  • 消えるまでの時間といった情報をスクリプトとは別に管理できるようになります。

つまり、「動作ロジック」と「データ」を分離することで、コードを書き換えずに挙動を切り替えることができるようになります。


シーン構成

今回のサンプルは、Prefabを使わずに「最初からシーンに配置された玉」を使います。

構成は次のようになります。

Player(キャラクター)
 └─ Bullet(投げるオブジェクト、子として配置)
  • Player は投げるスクリプトを持ちます。
  • Bullet は Rigidbody2D と Collider2D を持つ単純なオブジェクトです。
  • Bullet は最初は Player の手元にあり、Spaceキーで投げられます。

1. 投げるデータを定義する(ScriptableObject)

まずは、投げる玉のデータをアセットとしてまとめるクラスを作ります。

  1. Project ウィンドウで右クリック
  2. Create → Scripting → ScriptableObject Script
  3. 「ThrowData」の名前でスクリプタブルオブジェクトのスクリプトを作成
using UnityEngine;
using UnityEngine.InputSystem; // Input System(簡易)

public class ThrowItem2D_Simple : MonoBehaviour
{
    [SerializeField] Transform item;   // 手に持っているアイテム(子)
    [SerializeField] float throwForce = 5f; // AddForce 用
    [SerializeField] float linearSpeed = 5f; // linearVelocity 用
    [SerializeField] bool useGravity = true; // 投げ方切替

    void Update()
    {
        if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame)
        {
            Throw();
        }
    }

    void Throw()
    {
        if (item == null) return;

        // 親から切り離し(ワールド座標維持)
        item.SetParent(null, true);

        var rb = item.GetComponent<Rigidbody2D>();
        if (rb != null)
        {
            if (useGravity)
            {
                // ① 重力あり → 放物線
                rb.gravityScale = 1f;
                rb.AddForce(transform.right * throwForce, ForceMode2D.Impulse);
            }
            else
            {
                // ② 重力なし → 等速直進(Unity6推奨=linearVelocity)
                rb.gravityScale = 0f;
                rb.linearVelocity = transform.right * linearSpeed;
            }
        }

        // 後処理
        Destroy(item.gameObject, 2f);
        item = null;
    }
}
using UnityEngine;

[CreateAssetMenu(fileName = "ThrowData", menuName = "Sample/Throw Data")]
public class ThrowData : ScriptableObject
{
    [Header("投げる力(AddForce 用)")]
    [Min(0f)] public float throwForce = 5f;   // useGravity=true で使用、false のとき未使用

    [Header("直進速度(linearVelocity 用)")]
    [Min(0f)] public float linearSpeed = 5f;  // useGravity=false で使用、true のとき未使用

    [Header("重力を使うか(true=AddForce, false=linearVelocity)")]
    public bool useGravity = true;

    [Header("破棄までの秒数")]
    [Min(0f)] public float lifeTime = 2f;
}

作成手順

  1. Project ウィンドウで右クリック
  2. Create → Sample → Throw Data
  3. 「NormalBullet」などの名前でアセットを作成
  4. throwForce、useGravity、lifeTime の値を自由に設定

使い分けルール

  • useGravity = true → throwForce を使う(linearSpeed は未使用)
  • useGravity = false → linearSpeed を使う(throwForce は未使用)

例(ThrowData アセット)

ファイル名throwForcelinearSpeeduseGravitylifeTime挙動
NormalBullet50ON2標準的な放物線(AddForce使用)
FastBullet010OFF1直進してすぐ消える(linearVelocity使用)
HeavyBullet30ON5ゆっくり落ちる放物線

補足:未使用側を 0 にしておくと混乱しにくいです(将来の運用ルールとしても明快)。


2. プレイヤー側スクリプトで参照

次に、Player側のスクリプトを作成します。

今回は Prefabを使わず、シーン上の子オブジェクトをそのまま投げる構成です。

作成したら、これまでのPlayerにアタッチされているスクリプト(ThrowItem2D_Sample)と差し替えます

using UnityEngine;
using UnityEngine.InputSystem; // Input System(簡易)

public class ThrowItem2D_WithData : MonoBehaviour
{
    [SerializeField] Transform item;       // 手に持っているアイテム(子)
    [SerializeField] ThrowData throwData;  // ScriptableObject(投げる設定データ)

    void Update()
    {
        if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame)
        {
            Throw();
        }
    }

    void Throw()
    {
        if (item == null || throwData == null) return;

        // 親から切り離し(ワールド座標を維持)
        item.SetParent(null, true);

        var rb = item.GetComponent<Rigidbody2D>();
        if (rb != null)
        {
            if (throwData.useGravity)
            {
                // ① 重力あり → 放物線で投げる
                rb.gravityScale = 1f;
                rb.AddForce(transform.right * throwData.throwForce, ForceMode2D.Impulse);
            }
            else
            {
                // ② 重力なし → 等速直進(Unity6推奨:linearVelocity)
                rb.gravityScale = 0f;
                rb.linearVelocity = transform.right * throwData.linearSpeed;
            }
        }

        // 一定時間後に削除
        Destroy(item.gameObject, throwData.lifeTime);

        // 投げ終わり
        item = null;
    }
}

ポイント

項目内容
Instantiateをこのサンプルでは未使用シーンにあらかじめ配置したオブジェクトを使う
SetParent(null)親(Player)から外して独立させる
ScriptableObject参照投げる力・重力・寿命をデータで管理
コード修正なしで挙動変更ThrowDataを差し替えるだけで新しい動きに

3. ScriptableObjectを切り替えて動作確認

同じスクリプトのまま、Inspectorで ThrowData を切り替えるだけで

さまざまな投げ方をテストできます。

アセット名ForceGravityLifetime挙動
NormalBullet5ON2標準的な放物線
FastBullet10OFF1直進してすぐ消える
HeavyBullet3ON5ゆっくり落ちる

コードを一切変更せずに「投げ方」を変えられるのが ScriptableObject の強みです。


4.UML図

クラス構造図(ScriptableObject+子オブジェクト投げ)

投げ処理のシーケンス図(スペースキー → 弾が飛ぶ)

5. 注意点と補足

実行中の値変更に注意

ScriptableObject はアセットとして保存されるため、Play中に値を変更するとそのまま保存されます。

練習時はコピーを作って使うと安全です。

Rigidbody の扱い

投げる前に isKinematic = true にしておき、投げる瞬間に Dynamic に変更しても構いません。

これで「待機 → 発射」の流れが明確になります。


まとめ

学んだこと内容
ScriptableObjectとはデータをアセットとして扱う仕組み
利用効果コードとデータを分離できる
子オブジェクトを投げる構成シンプルで理解しやすい
データ差し替えコードを書き換えずに挙動変更可能

ScriptableObject は、データ設計の第一歩として最適な練習テーマです。

今回のように「投げる動き」に適用することで、動きと設定を分ける考え方が自然に身につきます。


Tips

Prefabを使わず、シーン内のオブジェクトを直接操作する方法は、スクリプトの流れを目で追って学びたい初学者に最適です。

実際に「親から外す → Rigidbodyで飛ぶ」という流れを視覚的に確認できるため、コードの理解やデバッグ練習に向いています。

一方で、実際のゲーム開発ではPrefab化してInstantiateする運用が一般的です。

複数の弾を同時に出したり、敵ごとに種類を切り替えたりする際にはPrefabを使う方が効率的です。

したがって、

  • 学習・可視化の段階では「シーン内のオブジェクト操作」
  • 実践・拡張段階では「Prefab生成+ScriptableObject参照」

と段階的に移行していくのが理想です。

ScriptableObjectを導入しておけば、Prefab運用に移行してもデータ部分はそのまま再利用できます。


参考

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