Unity アイテム管理システムとリアルタイム難易度・出現率調整チュートリアル(最終版)
このチュートリアルでは、以下の機能を実装します。
- ScriptableObject によるアイテムデータ管理
各アイテムの Prefab、出現重み、個別落下速度、そしてアイテムタイプ(Enum)をエディタ上で管理します。 - DifficultySettings によるパラメータ管理
ゲーム進行に合わせた生成間隔、出現率、時間の閾値などを DifficultySettings アセットで管理し、GameDirector で適用します。 - 抽象基底クラスと IItemEffect インターフェースによる共通処理と個別効果の分離
共通の落下処理は ItemBase に集約し、各アイテムは IItemEffect 経由で独自の効果(スコア更新など)を実装します。 - ファクトリ/レジストリパターンを用いた重み付きランダム生成
ItemFactory が登録された ItemData リストから、spawnWeight による重み付きランダム抽出でアイテムを生成します。 - ScoreManager によるスコア管理
各アイテムの効果は ScoreManager 経由でスコア更新されます。 - Enum によるアイテムタイプ管理
アイテム識別には文字列リテラルではなく Enum(ItemType)を使用することで、保守性と安全性を向上させます。 - BasketController による衝突処理
衝突時にアイテムの効果を呼び出し、サウンドを再生します。
以下、各コードファイルとシーン設定の手順を示します。
すべて Allman スタイル(開き括弧「{」が行頭)で記述されています。
- 1. 1. プロジェクトのセットアップ
- 2. 2. 各コードファイルの作成
- 2.1. 2-1. 共通インターフェース
- 2.2. 2-2. アイテムタイプ Enum の定義
- 2.3. 2-3. アイテム定義(ScriptableObject)
- 2.4. 2-4. DifficultySettings(難易度パラメータ管理)
- 2.5. 2-5. 共通処理・個別効果のための抽象基底クラス
- 2.6. 2-6. アイテム生成のファクトリ/レジストリ
- 2.7. 2-7. アイテムデータ登録用マネージャー
- 2.8. 2-8. アイテム生成コンポーネント
- 2.9. 2-9. 各アイテムの個別実装例
- 2.10. 2-10. スコア管理クラス
- 2.11. 2-11. ゲーム進行管理&難易度調整
- 2.12. 2-12. バスケット(衝突処理)
- 3. 3. シーンへの配置と設定
- 4. まとめ
1. プロジェクトのセットアップ
- Unity で新規プロジェクトを作成します。
- シーンにカメラ、ライト、UI 用の TextMeshPro コンポーネントを配置します。
- タイマー表示用の UI オブジェクトは「Time」という名前にしてください。
- スコア表示用の UI オブジェクトは ScoreManager に設定します。
- ゲーム内でアイテムを受け取る「Basket」オブジェクトを作成し、後述の BasketController をアタッチします。
2. 各コードファイルの作成
2-1. 共通インターフェース
IItemEffect.cs
using UnityEngine;
public interface IItemEffect
{
// 衝突時にアイテム固有の効果を適用する
void ApplyEffect();
// 衝突時に再生するサウンドを返す
AudioClip GetAudioClip();
}
2-2. アイテムタイプ Enum の定義
ItemType.cs
public enum ItemType
{
Apple,
Bomb
}
2-3. アイテム定義(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; // 個別の落下速度
public ItemType itemType; // Enum によるアイテムタイプ
}
【Tips】
Assets 内で右クリック → Create → Item → ItemData を選び、Apple 用、Bomb 用などのアセットを作成してください。
2-4. DifficultySettings(難易度パラメータ管理)
DifficultySettings.cs
ゲーム進行に合わせた生成間隔、出現率、時間の閾値などをまとめた ScriptableObject です。
using UnityEngine;
[CreateAssetMenu(menuName = "Settings/DifficultySettings")]
public class DifficultySettings : ScriptableObject
{
public float initialTime = 30.0f;
// 各ステージへの移行閾値(秒)
// Stage1: time >= stage2Threshold
// Stage2: stage3Threshold <= time < stage2Threshold
// Stage3: stage4Threshold <= time < stage3Threshold
// Stage4: time < stage4Threshold
public float stage2Threshold = 23f;
public float stage3Threshold = 12f;
public float stage4Threshold = 4f;
// 各ステージの生成間隔
public float intervalStage1 = 1.0f; // 23~30 秒
public float intervalStage2 = 0.8f; // 12~23 秒
public float intervalStage3 = 0.5f; // 4~12 秒
public float intervalStage4 = 0.3f; // 0~4 秒
public float gameOverInterval = 10000.0f; // ゲームオーバー時
// 各ステージの出現率設定(spawnWeight)
public float bombWeightStage1 = 2f;
public float appleWeightStage1 = 8f;
public float bombWeightStage2 = 4f;
public float appleWeightStage2 = 6f;
public float bombWeightStage3 = 6f;
public float appleWeightStage3 = 4f;
public float bombWeightStage4 = 0f;
public float appleWeightStage4 = 1f;
}
2-5. 共通処理・個別効果のための抽象基底クラス
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-6. アイテム生成のファクトリ/レジストリ
ItemFactory.cs
登録された ItemData リストから、重み付きランダム抽出でアイテムを生成します。
using UnityEngine;
using System.Collections.Generic;
public static class ItemFactory
{
// ItemDataManager により登録される 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 を持つ場合、設定された dropSpeed を反映
ItemBase itemBase = instance.GetComponent<ItemBase>();
if (itemBase != null)
{
itemBase.SetDropSpeed(data.dropSpeed);
}
return instance;
}
}
return null;
}
}
2-7. アイテムデータ登録用マネージャー
以下のいずれかの方法で ItemData を登録します。
方法 1: Inspector から手動登録
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);
}
}
【設定手順】
シーン内に空の GameObject を作成し、「ItemDataManager」と名付け、上記スクリプトをアタッチ。
Inspector で各 ItemData アセットをドラッグ&ドロップして登録。
方法 2: Resources フォルダから自動読み込み
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);
}
}
【設定手順】
プロジェクトに「Resources/ItemData」フォルダを作成し、各 ItemData アセットを配置。
シーン内に空の GameObject を作成し、「ItemDataManager」と名付け、上記スクリプトをアタッチ。
2-8. アイテム生成コンポーネント
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-9. 各アイテムの個別実装例
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-10. スコア管理クラス
ScoreManager.cs
スコア更新・表示を行うシングルトンパターンのクラスです。
using UnityEngine;
using TMPro;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager Instance { get; private set; }
private int score = 0;
public TextMeshProUGUI scoreText; // スコア表示用 UI
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 に対応する TextMeshProUGUI を設定します。
2-11. ゲーム進行管理&難易度調整
GameDirector.cs
DifficultySettings を参照して、生成間隔および出現率(spawnWeight)を動的に調整します。
時間の閾値も DifficultySettings によりパラメータ化しています。
using UnityEngine;
using TMPro;
public class GameDirector : MonoBehaviour
{
public DifficultySettings difficultySettings; // Inspector で設定
GameObject timerText;
float time;
GameObject generator;
void Start()
{
time = difficultySettings.initialTime;
timerText = GameObject.Find("Time");
generator = GameObject.Find("ItemGenerator");
}
void Update()
{
time -= Time.deltaTime;
if (time < 0)
{
time = 0;
// ゲームオーバー時はアイテム生成を停止
generator.GetComponent<ItemGenerator>().SetParameter(difficultySettings.gameOverInterval);
}
else if (time < difficultySettings.stage4Threshold)
{
generator.GetComponent<ItemGenerator>().SetParameter(difficultySettings.intervalStage4);
AdjustSpawnRates(difficultySettings.bombWeightStage4, difficultySettings.appleWeightStage4);
}
else if (time < difficultySettings.stage3Threshold)
{
generator.GetComponent<ItemGenerator>().SetParameter(difficultySettings.intervalStage3);
AdjustSpawnRates(difficultySettings.bombWeightStage3, difficultySettings.appleWeightStage3);
}
else if (time < difficultySettings.stage2Threshold)
{
generator.GetComponent<ItemGenerator>().SetParameter(difficultySettings.intervalStage2);
AdjustSpawnRates(difficultySettings.bombWeightStage2, difficultySettings.appleWeightStage2);
}
else // time >= stage2Threshold
{
generator.GetComponent<ItemGenerator>().SetParameter(difficultySettings.intervalStage1);
AdjustSpawnRates(difficultySettings.bombWeightStage1, difficultySettings.appleWeightStage1);
}
timerText.GetComponent<TextMeshProUGUI>().text = time.ToString("F1");
}
// 出現率(spawnWeight)の調整メソッド(Enum を使用)
void AdjustSpawnRates(float bombWeight, float appleWeight)
{
foreach (var data in ItemFactory.itemDataList)
{
if (data.itemType == ItemType.Bomb)
{
data.spawnWeight = bombWeight;
}
else if (data.itemType == ItemType.Apple)
{
data.spawnWeight = appleWeight;
}
}
}
}
【設定】
シーン上に「Time」という名前の UI オブジェクト(TextMeshProUGUI)を作成し、タイマー表示用として利用してください。
Inspector で DifficultySettings アセットをドラッグ&ドロップして割り当てます。
2-12. バスケット(衝突処理)
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. シーンへの配置と設定
- ItemDataManager
- シーン内に空の GameObject を作成し、名前を「ItemDataManager」とします。
- 上記
ItemDataManager.cs
をアタッチし、Inspector から各 ItemData アセットを登録するか、Resources 配置の方法で設定します。
- ItemGenerator
- 空の GameObject を作成し、名前を「ItemGenerator」として
ItemGenerator.cs
をアタッチします。
- 空の GameObject を作成し、名前を「ItemGenerator」として
- GameDirector
- 空の GameObject を作成し、名前を「GameDirector」として
GameDirector.cs
をアタッチします。 - Inspector で DifficultySettings アセットを割り当てます。
- 空の GameObject を作成し、名前を「GameDirector」として
- ScoreManager
- シーン内に ScoreManager 用の GameObject を作成し、
ScoreManager.cs
をアタッチします。 - Inspector で
scoreText
に対応する TextMeshProUGUI コンポーネントを設定します。
- シーン内に ScoreManager 用の GameObject を作成し、
- BasketController
- バスケット用の GameObject に
BasketController.cs
と AudioSource、Collider(Trigger)を設定します。
- バスケット用の GameObject に
- UI の設定
- 「Time」という名前の UI オブジェクト(TextMeshProUGUI)を作成し、GameDirector によりタイマー表示に利用。
- ScoreManager によるスコア表示用 UI も設定してください。
まとめ
- データ駆動設計
ScriptableObject(ItemData、DifficultySettings)を利用して、各アイテムや難易度パラメータをエディタ上で管理し、柔軟な拡張性を実現しました。 - 共通処理と個別効果の分離
ItemBase
に共通の落下処理を実装し、AppleItem
やBombItem
はIItemEffect
経由で独自の効果(ScoreManager 経由のスコア更新)を実装しています。 - ファクトリ/レジストリパターン
ItemFactory
が登録された ItemData リストから重み付きランダム抽出でアイテムを生成し、各アイテムに個別の落下速度を反映します。 - リアルタイム難易度調整
GameDirector
が DifficultySettings を参照して、生成間隔および出現率(spawnWeight)を時間経過に合わせて動的に変更します。
さらに、時間の閾値も DifficultySettings によりパラメータ化され、ハードコーディングを排除しています。 - Enum によるアイテムタイプ管理
アイテムの識別に文字列リテラルではなく Enum(ItemType)を使用することで、保守性と安全性を向上させています。 - 衝突処理
BasketController
がアイテムとの衝突時に効果を呼び出し、サウンドを再生します。
このチュートリアルに沿って実装すれば、柔軟にパラメータ調整が可能な、拡張性・保守性に優れたアイテム管理システムが構築できます。
ディスカッション
コメント一覧
まだ、コメントがありません