ScriptableObject入門(Unity6で学ぶ)
キャラクターが玉を投げるサンプルでデータ管理を体験しよう
はじめに
この記事は、「手に持ったアイテムを投げる」サンプルを通して、親子オブジェクトの操作や AddForce・linearVelocity を使った挙動を学んだ後の継続として作成されています。
今回はそのサンプルを発展させ、投げる玉の“性質”を ScriptableObject で管理する練習をしてみましょう。
見た目や操作は同じでも、データの扱い方を変えるだけで、設計の柔軟さがぐっと広がります。
ScriptableObjectとは
ScriptableObject は、スクリプトから分離されたデータ入れ物です。
シーン上に配置する必要がなく、アセットとして値を保存・共有できます。
これを使うと、たとえば
- 投げる玉のスピード
- 重力を使うかどうか
- 消えるまでの時間といった情報をスクリプトとは別に管理できるようになります。
つまり、「動作ロジック」と「データ」を分離することで、コードを書き換えずに挙動を切り替えることができるようになります。
シーン構成
今回のサンプルは、Prefabを使わずに「最初からシーンに配置された玉」を使います。
構成は次のようになります。
Player(キャラクター)
└─ Bullet(投げるオブジェクト、子として配置)
- Player は投げるスクリプトを持ちます。
- Bullet は Rigidbody2D と Collider2D を持つ単純なオブジェクトです。
- Bullet は最初は Player の手元にあり、Spaceキーで投げられます。
1. 投げるデータを定義する(ScriptableObject)
まずは、投げる玉のデータをアセットとしてまとめるクラスを作ります。
- Project ウィンドウで右クリック
- Create → Scripting → ScriptableObject Script
- 「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;
}
作成手順
- Project ウィンドウで右クリック
- Create → Sample → Throw Data
- 「NormalBullet」などの名前でアセットを作成
- throwForce、useGravity、lifeTime の値を自由に設定

使い分けルール
- useGravity = true → throwForce を使う(linearSpeed は未使用)
- useGravity = false → linearSpeed を使う(throwForce は未使用)
例(ThrowData アセット)
| ファイル名 | throwForce | linearSpeed | useGravity | lifeTime | 挙動 |
|---|---|---|---|---|---|
| NormalBullet | 5 | 0 | ON | 2 | 標準的な放物線(AddForce使用) |
| FastBullet | 0 | 10 | OFF | 1 | 直進してすぐ消える(linearVelocity使用) |
| HeavyBullet | 3 | 0 | ON | 5 | ゆっくり落ちる放物線 |
補足:未使用側を 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 を切り替えるだけで
さまざまな投げ方をテストできます。
| アセット名 | Force | Gravity | Lifetime | 挙動 |
|---|---|---|---|---|
| NormalBullet | 5 | ON | 2 | 標準的な放物線 |
| FastBullet | 10 | OFF | 1 | 直進してすぐ消える |
| HeavyBullet | 3 | ON | 5 | ゆっくり落ちる |
コードを一切変更せずに「投げ方」を変えられるのが ScriptableObject の強みです。
4.UML図
クラス構造図(ScriptableObject+子オブジェクト投げ)

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

5. 注意点と補足
実行中の値変更に注意
ScriptableObject はアセットとして保存されるため、Play中に値を変更するとそのまま保存されます。
練習時はコピーを作って使うと安全です。
Rigidbody の扱い
投げる前に isKinematic = true にしておき、投げる瞬間に Dynamic に変更しても構いません。
これで「待機 → 発射」の流れが明確になります。
まとめ
| 学んだこと | 内容 |
|---|---|
| ScriptableObjectとは | データをアセットとして扱う仕組み |
| 利用効果 | コードとデータを分離できる |
| 子オブジェクトを投げる構成 | シンプルで理解しやすい |
| データ差し替え | コードを書き換えずに挙動変更可能 |
ScriptableObject は、データ設計の第一歩として最適な練習テーマです。
今回のように「投げる動き」に適用することで、動きと設定を分ける考え方が自然に身につきます。
Tips
Prefabを使わず、シーン内のオブジェクトを直接操作する方法は、スクリプトの流れを目で追って学びたい初学者に最適です。
実際に「親から外す → Rigidbodyで飛ぶ」という流れを視覚的に確認できるため、コードの理解やデバッグ練習に向いています。
一方で、実際のゲーム開発ではPrefab化してInstantiateする運用が一般的です。
複数の弾を同時に出したり、敵ごとに種類を切り替えたりする際にはPrefabを使う方が効率的です。
したがって、
- 学習・可視化の段階では「シーン内のオブジェクト操作」
- 実践・拡張段階では「Prefab生成+ScriptableObject参照」
と段階的に移行していくのが理想です。
ScriptableObjectを導入しておけば、Prefab運用に移行してもデータ部分はそのまま再利用できます。



ディスカッション
コメント一覧
まだ、コメントがありません