この記事では、Unityを使用してゲームタスクシステムを実装し、タスク完了時に複数のイベント(UI更新、効果音再生、データ更新)を通知する方法を紹介します。この例は、デリゲートの「疎結合」と「マルチメソッドバインディング」の特性を示すのに役立ちます。
デリゲートの使用
タスクシステムはタスクの状態を管理し、タスクが完了したときに次の3つのロジックをトリガーします:
- 完了メッセージを表示(UIモジュール)
- 成功効果音を再生(オーディオモジュール)
- タスクの進捗を更新(データモジュール)
タスクシステムはこれらのモジュールを直接参照せずに、デリゲートを通じて間接的に通知します。
ステップ1: デリゲートとタスクシステムの作成
まずデリゲートを定義し、タスクシステムクラスを実装します。タスク完了イベントをデリゲートでトリガーします。
using UnityEngine;
public delegate void TaskCompletionCallback();
public class QuestManager : MonoBehaviour
{
private TaskCompletionCallback onQuestCompleted;
private bool isCompleted = false;
private void Start()
{
onQuestCompleted += UiModule.ShowCompletionMessage;
onQuestCompleted += AudioModule.PlaySuccessSound;
onQuestCompleted += DataModule.UpdateQuestProgress;
}
public void MarkQuestAsCompleted()
{
if (!isCompleted)
{
isCompleted = true;
Debug.Log("クエストマネージャー: クエストが完了しました。通知を開始します...");
onQuestCompleted?.Invoke();
}
}
private void OnDestroy()
{
onQuestCompleted -= UiModule.ShowCompletionMessage;
onQuestCompleted -= AudioModule.PlaySuccessSound;
onQuestCompleted -= DataModule.UpdateQuestProgress;
}
}
ステップ2: 3つのモジュールの実装
各モジュールはデリゲートの要件を満たすメソッド(パラメータなし、戻り値なし)を提供します。タスクシステムの存在を知る必要はありません。
using UnityEngine;
public static class UiModule
{
public static void ShowCompletionMessage()
{
Debug.Log("UIモジュール: クエスト完了ポップアップを表示 → タスクを達成しました!");
}
}
public static class AudioModule
{
public static void PlaySuccessSound()
{
Debug.Log("オーディオモジュール: 成功効果音を再生 → デン!");
}
}
public static class DataModule
{
public static void UpdateQuestProgress()
{
Debug.Log("データモジュール: クエストの進捗を更新 → 100%達成");
}
}
ステップ3: テストロジック
シーンに空のGameObjectを作成し、QuestManagerスクリプトをアタッチします。さらにテストボタンを作成して、クリック時にタスクを完了させます。
using UnityEngine;
using UnityEngine.UI;
public class TestButtonController : MonoBehaviour
{
public QuestManager questManager;
private void Start()
{
GetComponent<Button>().onClick.AddListener(OnButtonClick);
}
private void OnButtonClick()
{
questManager.MarkQuestAsCompleted();
}
}
Actionデリゲート
ActionデリゲートはC#の組み込み機能で、戻り値のないメソッドをカプセル化します。特にイベントコールバックや非同期通知の場面で使用されます。
特徴
- 戻り値なし
- ジェネリック対応(最大16個のパラメータまで)
- 開箱即用(System名前空間に属する)
Unityでの使用例
例1: パラメータなしAction
プレイヤーの死亡イベントをシミュレートし、Actionを使用してUI表示と効果音再生をバインドします。
using UnityEngine;
using System;
public class PlayerCharacter : MonoBehaviour
{
public Action OnPlayerDied;
private void Update()
{
if (Input.GetKeyDown(KeyCode.K))
{
Die();
}
}
private void Die()
{
Debug.Log("プレイヤーが死亡しました");
OnPlayerDied?.Invoke();
}
private void OnDestroy()
{
OnPlayerDied = null;
}
}
public class UiController : MonoBehaviour
{
public PlayerCharacter player;
private void Start()
{
player.OnPlayerDied += DisplayGameOverScreen;
player.OnPlayerDied += PauseGameplay;
}
private void DisplayGameOverScreen()
{
Debug.Log("ゲームオーバー画面を表示");
}
private void PauseGameplay()
{
Time.timeScale = 0;
Debug.Log("ゲームを一時停止");
}
private void OnDestroy()
{
if (player != null)
{
player.OnPlayerDied -= DisplayGameOverScreen;
player.OnPlayerDied -= PauseGameplay;
}
}
}
例2: パラメータありAction
スコア更新イベントをシミュレートし、Action<int>を使用して新しいスコアを渡します。
using UnityEngine;
using System;
public class ScoreKeeper : MonoBehaviour
{
private int score;
public Action<int> OnScoreChanged;
public void IncreaseScore(int points)
{
score += points;
OnScoreChanged?.Invoke(score);
}
}
public class ScoreDisplay : MonoBehaviour
{
public ScoreKeeper scoreKeeper;
private void Start()
{
scoreKeeper.OnScoreChanged += UpdateScoreLabel;
}
private void UpdateScoreLabel(int newScore)
{
Debug.Log($"現在のスコア: {newScore}");
}
private void OnDestroy()
{
if (scoreKeeper != null)
{
scoreKeeper.OnScoreChanged -= UpdateScoreLabel;
}
}
}
例3: 複数のパラメータを持つAction
イベントログ記録イベントをシミュレートし、Action<string, float>を使用してイベント名と所要時間を渡します。
using UnityEngine;
using System;
public class EventLogger : MonoBehaviour
{
public Action OnEventRecorded;
public void LogEvent(string eventName, float duration)
{
OnEventRecorded?.Invoke(eventName, duration);
}
}
public class LogProcessor : MonoBehaviour
{
public EventLogger eventLogger;
private void Start()
{
eventLogger.OnEventRecorded += WriteLogToFile;
eventLogger.OnEventRecorded += PrintLogToConsole;
}
private void WriteLogToFile(string eventName, float duration)
{
Debug.Log($"ファイルにログを保存: {eventName}, 所要時間: {duration}秒");
}
private void PrintLogToConsole(string eventName, float duration)
{
Debug.Log($"コンソールログ: イベント: {eventName}, 所要時間: {duration:F2}秒");
}
private void OnDestroy()
{
if (eventLogger != null)
{
eventLogger.OnEventRecorded -= WriteLogToFile;
eventLogger.OnEventRecorded -= PrintLogToConsole;
}
}
}
UnityEvent
UnityEventはUnityエンジンが提供するイベントシステムで、C#のデリゲートとイベントメカニズムに基づいています。Inspectorパネルからコールバックメソッドを直接バインドできるため、デザイナーやプログラマー以外の人々にも利用できるようになっています。
例: アイテム収集イベント
プレイヤーがアイテムを集めたときに、メッセージ表示、効果音再生、スコア加算の3つの効果をトリガーします。
ステップ1: イベント送信者の作成
このスクリプトは「アイテム」にアタッチされ、プレイヤーがアイテムに触れるとUnityEventをトリガーします。
using UnityEngine;
using UnityEngine.Events;
public class ItemGatherer : MonoBehaviour
{
public UnityEvent<string> onItemGathered;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
string itemName = gameObject.name;
onItemGathered.Invoke(itemName);
gameObject.SetActive(false);
}
}
}
ステップ2: イベント受信者の作成
次の3つのスクリプトは、それぞれ異なるロジックを処理します。
1. メッセージ表示
using UnityEngine;
using TMPro;
public class MessageDisplayer : MonoBehaviour
{
public TextMeshProUGUI messageText;
public void DisplayMessage(string itemName)
{
messageText.text = $"{itemName}を獲得しました!";
messageText.gameObject.SetActive(true);
Invoke(nameof(HideMessage), 2f);
}
private void HideMessage()
{
messageText.gameObject.SetActive(false);
}
}
2. 効果音再生
using UnityEngine;
public class SoundEmitter : MonoBehaviour
{
public AudioClip gatherSound;
private AudioSource audioSource;
private void Awake()
{
audioSource = GetComponent<AudioSource>();
}
public void PlaySound(string itemName)
{
audioSource.PlayOneShot(gatherSound);
}
}
3. スコア加算
using UnityEngine;
using TMPro;
public class ScoreUpdater : MonoBehaviour
{
public TextMeshProUGUI scoreText;
private int score = 0;
public void AddPoints(string itemName)
{
score += 10;
scoreText.text = $"スコア: {score}";
}
}
ステップ3: Inspectorパネルからのイベントバインディング
- 空のGameObjectを作成し、「Item」と名付け、ColliderとRigidbodyを追加し、ItemGathererスクリプトをアタッチします。
- UIテキスト(メッセージ、スコア)、AudioSourceを持つGameObjectを作成し、上記3つの受信者スクリプトをアタッチし、Inspectorで必要なフィールドを設定します。
- Itemを選択し、ItemGathererのonItemGatheredフィールドで3つのエントリを追加します。
主な利点
- 疎結合: ItemGathererは具体的なロジックを知らないで、イベントをトリガーします。
- 柔軟性: 新しい効果を追加する場合、新たな受信者スクリプトを作成し、Inspectorで追加するだけで済みます。
- 視覚化: デザイナーなど非プログラマーもInspectorを使ってイベントを設定できます。
まとめ
どのイベントメカニズムを選択するかは、ビジュアル設定が必要かどうか、コードの柔軟性、プロジェクトの協力環境によって異なります。
- UnityEvent: デザイナーがInspectorでイベントを設定できる場合や、UIボタンクリックやアイテム収集などの視覚的なイベントを関連付ける際に使用します。
- Action/デリゲート: コードレベルでの疎結合が必要な場合や、より高度な柔軟性(動的デリゲート生成、複雑なシグネチャ、LINQとの統合など)が必要な場合に使用します。