【実践記事・ADV編④】話題を解放する・証拠を入手する——フラグ管理と証拠システムの実装

初心者向け実践開発

対応バージョン:Unity 6.3 LTS(6000.3系)/WebGL出力 この記事はシリーズ全11回の第4回です。第3回では会話システムと話題カードシステムを実装しました。今回はゲームの進行を管理する核心部分を作ります。 【PR】本記事にはアフィリエイトリンクを含みます。

前回までで「セリフを表示する」「話題カードを選ぶ」という基本動作が動く状態になりました。しかしこのままでは、すべての話題カードが最初から表示されてしまいます。

アドベンチャーゲームらしい「ある情報を聞いたら次の話題が解放される」「証拠を入手したら追及コマンドが使えるようになる」という進行管理の仕組みを今回実装します。

目次


STEP3:FlagManager(フラグ管理・話題解放)

フラグとは何か

「フラグ」はゲームの進行状態を管理するための仕組みです。「この会話を聞いた」「この証拠を入手した」という事実を、文字列のIDとして記録しておきます。

たとえば沙耶との「あの夜のこと」という話題を最後まで聞いたとき、talked_saya_nightというフラグを立てます。FlagManagerはこのフラグが立ったことを検知し、「月凪の過去について」という新しい話題カードのIsUnlockedをtrueに変更します。これで次に話題カードパネルを開いたとき、新しい話題が増えているという状態になります。

FlagManagerを作る

Scripts/System/フォルダに「FlagManager」という名前でMonoBehaviour Scriptを作成します。

using UnityEngine;
using System.Collections.Generic;

public class FlagManager : MonoBehaviour
{
    public static FlagManager Instance;

    private HashSet<string> activeFlags = new HashSet<string>();

    void Awake()
    {
        Instance = this;
    }

    public void SetFlag(string flagID)
    {
        if (!activeFlags.Contains(flagID))
        {
            activeFlags.Add(flagID);
            Debug.Log("Flag set: " + flagID);
            OnFlagChanged(flagID);
        }
    }

    public bool HasFlag(string flagID)
    {
        return activeFlags.Contains(flagID);
    }

    void OnFlagChanged(string flagID)
    {
        UnlockTopicsByFlag(flagID);
    }

    void UnlockTopicsByFlag(string flagID)
    {
        TopicData[] allTopics = Resources.LoadAll<TopicData>("Topics");
        foreach (TopicData topic in allTopics)
        {
            if (topic.unlockFlagID == flagID)
            {
                topic.isUnlocked = true;
                Debug.Log("Topic unlocked: " + topic.topicTitle);
            }
        }
    }
}

設計のポイント

フラグの保存にHashSet<string>を使っています。List<string>でも動きますが、HashSetは「すでに含まれているか」の確認が高速で、重複して同じフラグが登録されることもありません。フラグの数が増えても動作が遅くなりにくい設計です。

Resources.LoadAll<TopicData>("Topics")はAssets/Resources/Topicsフォルダ内にある全TopicDataを動的に読み込みます。これにより、新しい話題カードを追加するときにスクリプトを書き換える必要がなく、Topicsフォルダにファイルを置くだけで自動的に管理対象になります。

Resourcesフォルダの準備

Resources.LoadAll<T>()を使うには、読み込みたいアセットがAssets/Resourcesフォルダの中になければなりません。

🔧 詳しい手順

  1. ProjectウィンドウのAssetsフォルダで右クリック→「Create」→「Folder」で「Resources」フォルダを作成する
  2. Resourcesフォルダ内に「Topics」フォルダを作成する
  3. 第3回で作成したTopicDataのファイル(Topic_Saya_Nightなど)を、Data/Dialogues/TopicsフォルダからResources/Topicsフォルダに移動する(ProjectウィンドウでドラッグすればOK)

⚠️ 実際にハマったミス:Resourcesフォルダのタイポ 開発中に「Resources」を「Resoueces」(rを1つ抜かし)とタイポしたまま進めてしまいました。フラグを立ててもResources.LoadAll<TopicData>("Topics")が何も見つけられず、話題カードが解放されないという不具合が出ました。エラーも警告も出ないので気づくのが遅れました。Resourcesは「Re-sour-ces」と読むと覚えやすいです。フォルダを作ったら必ず綴りを確認してください。

TopicDataにフラグ解放条件を設定する

第3回で作ったTopicData.csに、フラグ解放のための項目をすでに追加してあります。

unlockFlagID    // このIDのフラグが立つと解放される

たとえば「月凪の過去について」という話題カードの場合、Inspectorで以下のように設定します。

Topic ID:saya_past
Topic Title:月凪の過去について
Is Unlocked:チェックを外す(最初は非表示)
Unlock Flag ID:talked_saya_night

これで「あの夜のこと」を最後まで聞いてtalked_saya_nightフラグが立ったとき、FlagManagerが自動的に「月凪の過去について」のIsUnlockedをtrueに変えてくれます。

DialogueDataにフラグ設定を追加する

会話が終わったときにフラグを立てる設定は、DialogueDataのInspectorで行います。

Set Flag On Complete:talked_saya_night

この設定をした会話を最後まで読むと、DialogueManagerのEndDialogue()からFlagManager.Instance.SetFlag("talked_saya_night")が呼ばれ、フラグが立ちます。

🔧 詳しい手順(テストする)

  1. GameManagerにAdd ComponentでFlagManagerを追加する
  2. Char_SayaのTopicsに新しい話題カード(Topic_Saya_Past)を追加し、Is Unlockedのチェックを外す
  3. Topic_Saya_PastのUnlock Flag IDに「talked_saya_night」と入力する
  4. Ch2_Saya_Night(あの夜のことの会話データ)のSet Flag On Completeに「talked_saya_night」と入力する
  5. 再生して「あの夜のこと」を最後まで読む
  6. ConsoleにFlag set: talked_saya_night・Topic unlocked: 月凪の過去についてと表示されて、話題カードパネルに新しいカードが増えれば成功

⚠️ 「Flag set」は出るが「Topic unlocked」が出ない場合 Resources.LoadAll<TopicData>("Topics")がTopicDataを見つけられていません。以下を確認してください。

  • Resourcesフォルダの綴りが正確か(大文字小文字・スペルミス)
  • Topic_Saya_PastがResources/Topicsフォルダの中にあるか
  • Topic_Saya_PastのUnlock Flag IDに正確に「talked_saya_night」と入力されているか
あの夜のことを読み終えたあと、月凪の過去についてが新しく追加された話題カードパネル

STEP4:EvidenceSystem(証拠入手・通知UI)

EvidenceDataを作る

Scripts/System/フォルダに「EvidenceData」という名前でMonoBehaviour Scriptを作成し、クラスをScriptableObjectに書き換えます。

using UnityEngine;

[CreateAssetMenu(fileName = "EvidenceData", menuName = "MysteryADV/EvidenceData")]
public class EvidenceData : ScriptableObject
{
    public string evidenceID;
    public string evidenceName;
    [TextArea(2, 4)]
    public string description;
}

EvidenceManagerを作る

Scripts/System/フォルダに「EvidenceManager」という名前でMonoBehaviour Scriptを作成します。

using UnityEngine;
using System.Collections.Generic;

public class EvidenceManager : MonoBehaviour
{
    public static EvidenceManager Instance;

    private List<EvidenceData> collectedEvidence = new List<EvidenceData>();

    void Awake()
    {
        Instance = this;
    }

    public void CollectEvidence(EvidenceData evidence)
    {
        if (!collectedEvidence.Contains(evidence))
        {
            collectedEvidence.Add(evidence);
            Debug.Log("Evidence collected: " + evidence.evidenceName);
            FlagManager.Instance.SetFlag("evidence_" + evidence.evidenceID);
            EvidenceNotifier.Instance.ShowNotification(evidence.evidenceName);
        }
    }

    public bool HasEvidence(string evidenceID)
    {
        return collectedEvidence.Exists(e => e.evidenceID == evidenceID);
    }

    public List<EvidenceData> GetAllEvidence()
    {
        return collectedEvidence;
    }
}

証拠を入手したとき、FlagManager.Instance.SetFlag("evidence_" + evidence.evidenceID)で自動的にフラグも立てています。たとえば動画(evidenceID: “video”)を入手するとevidence_videoというフラグが立ちます。このフラグを使って、追及コマンドの解放などの条件判定ができます。

入手通知UIを作る

Scripts/UI/フォルダに「EvidenceNotifier」という名前でMonoBehaviour Scriptを作成します。

using UnityEngine;
using TMPro;
using System.Collections;

public class EvidenceNotifier : MonoBehaviour
{
    public static EvidenceNotifier Instance;

    public GameObject notificationPanel;
    public TMP_Text notificationText;

    void Awake()
    {
        Instance = this;
        notificationPanel.SetActive(false);
    }

    public void ShowNotification(string evidenceName)
    {
        notificationText.text = "証拠を入手:" + evidenceName;
        notificationPanel.SetActive(true);
        StartCoroutine(HideAfterDelay(3f));
    }

    IEnumerator HideAfterDelay(float delay)
    {
        yield return new WaitForSeconds(delay);
        notificationPanel.SetActive(false);
    }
}

StartCoroutineIEnumeratorを使うことで、「3秒後に非表示にする」という時間のかかる処理を、他の処理を止めずに非同期で実行できます。

🔧 詳しい手順(通知UIを作る)

  1. Hierarchyで右クリック→「UI (Canvas)」→「Panel」を選択し、名前を「NotificationPanel」にする
  2. Rect TransformをShift+Altを押しながら右上に設定し、Width:400・Height:80・Pos X:-20・Pos Y:-20に設定する
  3. NotificationPanel内に「Text – TextMeshPro」を追加し、名前を「NotificationText」にする。アンカーをStretch(四方)に設定し、Font Size:20・Color:Whiteにする
  4. NotificationPanelのImageのColorを濃い紺色(Hex: 323250FF)に変更する
  5. NotificationPanelのチェックを外して最初は非表示にする
  6. GameManagerにAdd ComponentでEvidenceManagerとEvidenceNotifierを追加する
  7. EvidenceNotifierのInspectorのNotification Panel欄にNotificationPanel、Notification Text欄にNotificationTextをドラッグ&ドロップして接続する

証拠データを作る

🔧 詳しい手順

  1. Resourcesフォルダ内に「Evidences」フォルダを作成する
  2. Evidencesフォルダ内で右クリック→「MysteryADV」→「EvidenceData」を選択し、名前を「Evidence_Video」にする
  3. Inspectorに以下を入力する
    • Evidence ID:video
    • Evidence Name:請求書の動画
    • Description:木下健太が月凪で撮影した動画。請求書の内訳と、田中浩二が宗一郎と話している場面が映っている。
  4. 証拠を入手させたい会話データ(DialogueData)のGrant Evidence On Complete欄にEvidence_Videoをドラッグ&ドロップする

完成して振り返って

⚠️ ハマったポイント

  • Resourcesフォルダの名前は特に注意が必要。タイポしてもエラーが出ないので気づきにくい。フォルダを作ったら即確認する
  • HashSet<string>はフラグ管理に適している。List<string>でもよいが、重複チェックが簡潔に書ける
  • 証拠入手時にフラグも自動で立てる設計にすると、後から「この証拠を持っているか確認する」という条件が書きやすくなる
  • CoroutineIEnumerator)は「n秒後に何かする」という時間のかかる処理に使える。今回の通知UIの自動消去に使ったが、ウェイト演出・フェードアウトなど応用範囲が広い
  • Debug.Logでフラグや証拠の入手をログに出しておくと、「フラグが立っているのか立っていないのか」の確認が格段にしやすくなる。実装が完了したら削除してよい

次回予告

第5回では、シーン遷移とエンディング分岐を実装し、WebGL出力でブラウザで動かすところまで進めます。「証拠を揃えて容疑者を追及したら、ゲームが終わる」という大詰めの部分です。


あわせて読みたいおすすめ書籍【PR】

今回実装したフラグ管理・ScriptableObject・Coroutineについてさらに深く学びたい方には、以下の書籍もおすすめです。本セクションにはアフィリエイトリンクを含みます。

※このセクションのリンクはアフィリエイトリンクであり、商品が購入された場合、サイト運営者に紹介料が支払われることがあります【PR】。


この記事を作成するにあたって参考にしたサイト

コメント

タイトルとURLをコピーしました