Unity アイテム管理システム チュートリアル

このチュートリアルでは、以下の内容を実装します。

  • ScriptableObject によるアイテムデータの管理
  • ファクトリ/レジストリパターンを用いた重み付きランダム生成
  • 共通の落下処理を持つ抽象クラス(ItemBase)と、個別効果を定義するインターフェース(IItemEffect)
  • 各アイテム(AppleItem、BombItem)の個別効果実装と ScoreManager を用いたスコア更新
  • 衝突処理によりアイテム効果を呼び出す BasketController

ステップ 1: プロジェクトのセットアップ

  1. Unity で新規プロジェクトを作成します。(AppleCatchPlus)
  2. シーンに適当な背景、カメラ、ライトを配置し、必要に応じて UI 用の TextMeshPro コンポーネント(スコア表示用やタイマー用)を用意します。
  3. ゲーム内にアイテムを受け取る「Basket」などのオブジェクトを用意し、後述の BasketController をアタッチします。

ステップ 2: 各コードファイルの作成

以下の各コードファイルを作成し、対応するスクリプトとしてプロジェクトに追加します。
すべて Allman スタイル(開き括弧「{」が行頭)です。

2-1. 共通インターフェース

IItemEffect.cs

using UnityEngine;

public interface IItemEffect
{
    // 衝突時にアイテム固有の効果を適用する
    void ApplyEffect();
    // 衝突時に再生するサウンドを返す
    AudioClip GetAudioClip();
}

2-2. アイテム定義(ScriptableObject)

ItemData.cs
このスクリプトは、エディタからアイテムの情報(Prefab、出現重み、個別落下速度など)を設定するために使用します。

using UnityEngine;

[CreateAssetMenu(menuName = "Item/ItemData")]
public class ItemData : ScriptableObject
{
    public string itemName;       // アイテム名
    public GameObject prefab;     // 出現用のPrefab
    public float spawnWeight;     // 出現確率に用いる重み
    public float dropSpeed;       // 個別の落下速度
}

【Tips】
Assets フォルダ内で右クリック → Create → Item → ItemData を選び、各アイテムごとに ScriptableObject を作成します。
ここで、Apple 用、Bomb 用のデータを設定してください。


2-3. 共通処理・個別効果のための抽象基底クラス

ItemBase.cs
各アイテムの共通の落下処理などを実装し、IItemEffect を通して個別効果を後から定義します。

using UnityEngine;

public abstract class ItemBase : MonoBehaviour, IItemEffect
{
    protected float dropSpeed = -0.03f;

    // アイテムの落下速度を設定するメソッド
    public void SetDropSpeed(float speed)
    {
        dropSpeed = speed;
    }

    // 共通の落下処理
    protected virtual void Update()
    {
        transform.Translate(0, dropSpeed, 0);
        if (transform.position.y < -1.0f)
        {
            Destroy(gameObject);
        }
    }

    // IItemEffect インターフェースの実装は各サブクラスで定義
    public abstract void ApplyEffect();
    public abstract AudioClip GetAudioClip();
}

2-4. アイテム生成のファクトリ/レジストリ

ItemFactory.cs
登録された ItemData リストから、重み付きランダム抽出でアイテムを生成し、各アイテムの落下速度を適用します。

using UnityEngine;
using System.Collections.Generic;

public static class ItemFactory
{
    // エディタや初期化処理で登録する、すべての ItemData のリスト
    public static List<ItemData> itemDataList = new List<ItemData>();

    // 重み付きランダム抽出によりアイテムを生成
    public static GameObject CreateRandomItem()
    {
        float totalWeight = 0;
        foreach (ItemData data in itemDataList)
        {
            totalWeight += data.spawnWeight;
        }

        float randomPoint = Random.Range(0, totalWeight);
        float current = 0;
        foreach (ItemData data in itemDataList)
        {
            current += data.spawnWeight;
            if (randomPoint <= current)
            {
                GameObject instance = GameObject.Instantiate(data.prefab);
                // ItemBase を持つ場合、ScriptableObject で設定された dropSpeed を反映
                ItemBase itemBase = instance.GetComponent<ItemBase>();
                if (itemBase != null)
                {
                    itemBase.SetDropSpeed(data.dropSpeed);
                }
                return instance;
            }
        }
        return null;
    }
}

2-5. アイテム生成コンポーネント

ItemGenerator.cs
一定の間隔でアイテムを生成し、出現位置をランダムに設定します。

using UnityEngine;

public class ItemGenerator : MonoBehaviour
{
    float span = 1.0f;   // 生成間隔
    float delta = 0;

    // 生成間隔のパラメータを外部から変更可能に
    public void SetParameter(float span)
    {
        this.span = span;
    }

    void Update()
    {
        delta += Time.deltaTime;
        if (delta > span)
        {
            delta = 0;
            GameObject item = ItemFactory.CreateRandomItem();
            if (item != null)
            {
                // 出現位置をランダムに設定
                float x = Random.Range(-1, 2);
                float z = Random.Range(-1, 2);
                item.transform.position = new Vector3(x, 4, z);
            }
        }
    }
}

2-6. 各アイテムの個別実装例

AppleItem.cs
Apple の効果としてスコアに 100 加算します。

using UnityEngine;

public class AppleItem : ItemBase
{
    public AudioClip appleSE;

    public override void ApplyEffect()
    {
        // Apple の効果:スコアに 100 加算
        ScoreManager.Instance.AddScore(100);
    }

    public override AudioClip GetAudioClip()
    {
        return appleSE;
    }
}

BombItem.cs
Bomb の効果としてスコアを 2 で割ります。

using UnityEngine;

public class BombItem : ItemBase
{
    public AudioClip bombSE;

    public override void ApplyEffect()
    {
        // Bomb の効果:スコアを 2 で割る
        ScoreManager.Instance.DivideScore(2);
    }

    public override AudioClip GetAudioClip()
    {
        return bombSE;
    }
}

2-7. スコア管理クラス

ScoreManager.cs
スコアの更新・表示を担当するシングルトンパターンのクラスです。

using UnityEngine;
using TMPro;

public class ScoreManager : MonoBehaviour
{
    public static ScoreManager Instance { get; private set; }
    private int score = 0;
    public TextMeshProUGUI scoreText; // スコア表示用の TextMeshProUGUI

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(this.gameObject);
            return;
        }
        Instance = this;
    }

    public void AddScore(int value)
    {
        score += value;
        UpdateScoreText();
    }

    public void DivideScore(int divisor)
    {
        score /= divisor;
        UpdateScoreText();
    }

    private void UpdateScoreText()
    {
        if (scoreText != null)
        {
            scoreText.text = score.ToString() + " point";
        }
    }
}

【設定】
ScoreManager をシーン内の空の GameObject にアタッチし、Inspector で scoreText に UI の TextMeshProUGUI コンポーネントを割り当てます。


2-8. ゲーム進行管理

GameDirector.cs
タイマーなど、ゲーム進行に関する管理を行います。

using UnityEngine;
using TMPro;

public class GameDirector : MonoBehaviour
{
    GameObject timerText;
    float time = 30.0f;
    GameObject generator;

    void Start()
    {
        timerText = GameObject.Find("Time");
        generator = GameObject.Find("ItemGenerator");
    }

    void Update()
    {
        time -= Time.deltaTime;
        if (time < 0)
        {
            time = 0;
        }
        timerText.GetComponent<TextMeshProUGUI>().text = time.ToString("F1");
    }
}

【設定】
シーン上に “Time" という名前の UI オブジェクトを作成して、タイマー表示用に利用します。


2-9. バスケット(衝突処理)

BasketController.cs
アイテムと衝突した際に、アイテムの効果を呼び出し、サウンドを再生します。

using UnityEngine;

public class BasketController : MonoBehaviour
{
    public AudioSource aud;
    GameObject director;

    void Start()
    {
        Application.targetFrameRate = 60;
        aud = GetComponent<AudioSource>();
        director = GameObject.Find("GameDirector");
    }

    void OnTriggerEnter(Collider other)
    {
        // IItemEffect を実装しているか確認し、あれば効果を適用
        IItemEffect effect = other.gameObject.GetComponent<IItemEffect>();
        if (effect != null)
        {
            aud.PlayOneShot(effect.GetAudioClip());
            effect.ApplyEffect();
        }
        Destroy(other.gameObject);
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                float x = Mathf.RoundToInt(hit.point.x);
                float z = Mathf.RoundToInt(hit.point.z);
                transform.position = new Vector3(x, 0, z);
            }
        }
    }
}

ステップ 3: シーンへの配置と設定

  1. ItemGenerator
    • 空の GameObject を作成し、名前を “ItemGenerator" に設定。
    • 上記の ItemGenerator.cs をアタッチします。
  2. GameDirector
    • 同様に空の GameObject を作成し、"GameDirector" として ItemDirector.cs をアタッチします。
  3. ScoreManager
    • シーン内に ScoreManager 用の空オブジェクトを作成し、ScoreManager.cs をアタッチ。
    • Inspector で scoreText に UI の TextMeshProUGUI コンポーネントを設定します。
  4. BasketController
    • バスケット用の GameObject に BasketController.cs と AudioSource をアタッチ。
    • 衝突判定用に Collider(Trigger)を設定してください。
  5. アイテムの登録
    • 各アイテム用の ScriptableObject(ItemData)を作成し、Prefab、spawnWeight、dropSpeed を設定。
    • 起動時やエディタ上の初期化スクリプトで、ItemFactory.itemDataList にこれらのデータを追加するか、専用の Manager で登録します。

以下は、起動時に ItemFactory.itemDataList に ScriptableObject の ItemData を自動登録するための具体例です。以下の 2 つの方法を紹介します。


方法 1: Inspector から手動で登録する方法

専用の Manager スクリプトを作成し、Inspector で ItemData のリストを設定します。
この Manager は Awake() でそのリストを ItemFactory.itemDataList に代入します。

ItemDataManager.cs

using UnityEngine;
using System.Collections.Generic;

public class ItemDataManager : MonoBehaviour
{
    // Inspector で登録する ItemData のリスト
    public List<ItemData> registeredItemDatas = new List<ItemData>();

    void Awake()
    {
        // 登録されたリストを ItemFactory のリストに設定
        ItemFactory.itemDataList = new List<ItemData>(registeredItemDatas);
    }
}

【設定手順】

  1. シーン内に空の GameObject を作成し、名前を「ItemDataManager」などとします。
  2. この GameObject に上記の ItemDataManager.cs をアタッチします。
  3. Inspector で、registeredItemDatas に Apple 用や Bomb 用などの ItemData アセットをドラッグ&ドロップして登録します。

方法 2: Resources フォルダを利用して自動読み込みする方法

ItemData アセットを「Resources/ItemData」フォルダに配置し、起動時に Resources.LoadAll() を使ってすべての ItemData を読み込み、ItemFactory.itemDataList に登録します。

ItemDataManager.cs

using UnityEngine;
using System.Collections.Generic;

public class ItemDataManager : MonoBehaviour
{
    void Awake()
    {
        // Resources/ItemData フォルダ内のすべての ItemData アセットを読み込み
        ItemData[] itemDatas = Resources.LoadAll<ItemData>("ItemData");
        ItemFactory.itemDataList = new List<ItemData>(itemDatas);
    }
}

【設定手順】

  1. プロジェクト内に「Resources」フォルダを作成し、その中に「ItemData」フォルダを作成します。
  2. Apple 用、Bomb 用などの ItemData アセットを「Resources/ItemData」フォルダに配置します。
  3. シーン内に空の GameObject を作成し、名前を「ItemDataManager」などとします。
  4. 上記の ItemDataManager.cs をアタッチします。

どちらの方法も、起動時に ItemFactory.itemDataList に必要な ItemData を登録するための方法です。
Inspector で手動登録する方法は、柔軟にリストを管理できるためデバッグや変更が簡単です。
Resources フォルダを使う方法は、アセットが自動的に読み込まれるので、追加の作業が少なくなります。


まとめ

  • データ駆動設計
    ScriptableObject を使い、各アイテムの情報(Prefab、出現重み、個別落下速度)をエディタ上で管理できます。
  • 共通処理と個別効果の分離
    ItemBase に共通処理を実装し、AppleItem や BombItem で個別効果を定義します。
    各効果は ScoreManager を介してスコア更新され、GameDirector には影響しません。
  • ファクトリ/レジストリパターン
    ItemFactory を用いて、重み付きランダム抽出によりアイテムを生成し、ScriptableObject の情報を各アイテムに反映します。
  • シーンへの適用
    各コンポーネントをシーン上に配置し、UI やオブジェクトの設定を行うことで、実際にゲーム内で機能するシステムとなります。

このチュートリアルを参考に、プロジェクトに合わせた拡張や調整を行ってください。
新たなアイテムを追加する場合は、対応する ScriptableObject を作成し、Prefab に適切なスクリプト(ItemBase を継承したもの)をアタッチするだけで済むようになっています。