実践ガイド

【実践記事・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フォルダの準備

この例では、読み込みたいアセットをAssets/Resourcesフォルダの中に置きます。ResourcesフォルダはAssets配下の別の場所にも配置できます。

🔧 詳しい手順

  1. ProjectウィンドウのAssetsフォルダで右クリック→「Create」→「Folder」で「Resources」フォルダを作成する
  2. Resourcesフォルダ内に「Topics」フォルダを作成する
  3. 作成済みのTopicDataのファイル(Topic_Saya_Nightなど)をResources/Topicsフォルダに配置する。まだない場合は先にアセットを作成する

⚠️ 実際にハマったミス:Resourcesフォルダのタイポ 開発中に「Resources」を「Resoueces」(2つ目のrをeに置き換えた綴り)とタイポしたまま進めてしまいました。フラグを立てても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

この例ではフラグをメモリ上で保持し、ScriptableObjectの解放状態を変更します。再起動後の進行保存は別途実装し、再開始時の初期化も設計してください。FlagManagerとEvidenceManager、通知先の参照がシーンに設定されていることを確認します。話題パネルが既に開いている場合は開き直して表示を確認してください。これで「あの夜のこと」を最後まで聞いて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を見つけられない、または解放条件のIDが一致していない可能性があります。以下を確認してください。

  • 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);
        StopAllCoroutines(); // このコンポーネントの通知待機を止める
        StartCoroutine(HideAfterDelay(3f));
    }

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

StartCoroutineとIEnumeratorを使うことで、待機中に制御を返し、ゲーム内時間で約3秒後に非表示にできます。WaitForSecondsはTime.timeScaleの影響を受けます。ポーズ中も実時間で待つ場合はWaitForSecondsRealtimeを検討してください。

🔧 詳しい手順(通知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>でもよいが、重複チェックが簡潔に書ける
  • 証拠入手時にフラグも自動で立てる設計にすると、後から「この証拠を持っているか確認する」という条件が書きやすくなる
  • Coroutine(IEnumerator)は「n秒後に何かする」という時間のかかる処理に使える。今回の通知UIの自動消去に使ったが、ウェイト演出・フェードアウトなど応用範囲が広い
  • Debug.Logでフラグや証拠の入手をログに出しておくと、「フラグが立っているのか立っていないのか」の確認が格段にしやすくなる。実装が完了したら削除してよい

次回予告

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


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

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

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


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

← 記事一覧・検索