Unityで会話システムを作ってみた③ScriptableObjectで会話データ管理など

unity scriptableobject 3Dゲームの作り方
※記事内に広告が含まれています。

バーチャル3Dクリエイター神部まゆみです(*^_^*)

この記事はUnityで会話システムを作ってみた記事の続きです。

前回の記事はこちら。

●PRスペース●
■PR■

現在、TPSシステム系アセットが50%オフで買える Unityパブリッシャーセール をやってるみたいです(2025/09/12 23:59まで)。

Unity パブリッシャーセール 50%オフ

クーポンコード FANTACODESTUDIOS を使うと、↓のロープスイングシステムがタダで貰えます(決済時にクーポンコード入力適用で0円に)。

unity ロープスイングシステム

無料アセットだけでも貰っておくと良いかも。

〇まゆみマート|BOOTH

BOOTHでVRoidテクスチャやVRChat向けオブジェクトなどを販売しています。いいねしてくれると励みになります(*^-^*)

やりたいこと:ScriptableObjectとやらで会話データを管理しやすくしたい

なんかChatGPT氏がこれを使うと良いと言っていたので使ってみます。

unity scriptableobject

ChatGPT氏に聞いてみる

↓はChatGPTのコピペですが、その後結構改良したのですぐ使いたい人はページ下の実装の項目から読んだほうが良いと思います。

unity scriptableobject

神部まゆみ
神部まゆみ

今後村人を増やしていくにあたってセリフの管理などをしやすくしたいのですが、ScriptableObjectとか言うのを使うと良いと聞きました。
使ったことがないので詳しく説明していただけませんか

ChatGPT
ChatGPT

村人の数が増えてくると、セリフの管理が大変になるので、ScriptableObject を使うと便利です。

ScriptableObjectとは?

ScriptableObject は、Unityでデータを管理するためのオブジェクトです。
通常のスクリプトとは違い、シーンに依存せず、アセットとして保存できるのが特徴です。
セリフデータをScriptableObjectにまとめることで、以下のメリットがあります。

メリット

村人ごとにセリフデータを分けて管理できる
スクリプトを編集せずにインスペクターからセリフを変更できる
データをアセットとして保存するので、シーンをまたいでも管理しやすい
他のスクリプトと分離されるため、可読性・メンテナンス性が向上


ScriptableObjectを使った会話システムの作成

① ScriptableObjectの作成

村人ごとにセリフを保存するScriptableObjectを作成します。

1. DialogueData.cs を作成
using UnityEngine;

[CreateAssetMenu(fileName = "NewDialogue", menuName = "Dialogue/DialogueData")]
public class DialogueData : ScriptableObject
{
[TextArea(3, 5)]
public string[] sentences; // 会話のセリフリスト
}

👆 これで、ScriptableObjectのデータテンプレートが作られました。
[CreateAssetMenu] を付けることで、Unityの 「右クリック → Create → Dialogue → DialogueData」 から作成できます。


② セリフデータを作る

  1. Unityの ProjectビューAssets フォルダを開く
  2. 右クリック → CreateDialogueDialogueData を選択
  3. 新しいセリフデータのアセット (NewDialogue) が作成される
  4. インスペクターでセリフを入力(例えば…) plaintextコピーする編集するこんにちは!村へようこそ! よろしくお願いします! この村にはたくさんの人がいますよ。 楽しみです! こんな感じでセリフを設定できます。
unity scriptableobject

これで、どのセリフが村人のものか、プレイヤーのものかを一つのデータ内で指定可能になります!


③ DialogueManager にScriptableObjectを組み込む

次に、会話スクリプトをScriptableObject対応に変更します。

3. DialogueManager.cs の改修
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

public class DialogueManager : MonoBehaviour
{
    public GameObject dialogueCanvas; // 全体の会話ウィンドウ

    public Text villagerDialogueText; // 村人のテキスト
    public GameObject villagerWindow; // 村人のウィンドウ(上側)

    public Text playerDialogueText;   // プレイヤーのテキスト
    public GameObject playerWindow;   // プレイヤーのウィンドウ(下側)

    public DialogueData dialogueData; // 会話データ

    private Queue<DialogueData.DialogueLine> dialogueQueue; // セリフのキュー
    private bool isTalking = false;

    void Start()
    {
        dialogueQueue = new Queue<DialogueData.DialogueLine>();
    }

    public void StartDialogue(DialogueData newDialogueData)
    {
        if (isTalking) return;

        isTalking = true;
        Time.timeScale = 0; // ゲームを一時停止

        dialogueCanvas.SetActive(true);
        dialogueQueue.Clear();

        dialogueData = newDialogueData;

        foreach (DialogueData.DialogueLine line in dialogueData.dialogueLines)
        {
            dialogueQueue.Enqueue(line);
        }

        DisplayNextSentence();
    }

    public void DisplayNextSentence()
    {
        if (dialogueQueue.Count == 0)
        {
            EndDialogue();
            return;
        }

        // 次のセリフを取得
        DialogueData.DialogueLine currentLine = dialogueQueue.Dequeue();

        // 誰が話しているのか判定
        if (currentLine.speaker == "Villager")
        {
            villagerWindow.SetActive(true);
            playerWindow.SetActive(false);
            villagerDialogueText.text = currentLine.sentence;
        }
        else if (currentLine.speaker == "Player")
        {
            villagerWindow.SetActive(false);
            playerWindow.SetActive(true);
            playerDialogueText.text = currentLine.sentence;
        }
    }

    public void EndDialogue()
    {
        dialogueCanvas.SetActive(false);
        Time.timeScale = 1; // ゲーム再開
        isTalking = false;
    }

    void Update()
    {
        if (isTalking && Input.GetKeyDown(KeyCode.Return))
        {
            DisplayNextSentence();
        }
    }
}

④ ScriptableObjectを会話システムに適用

  1. DialogueManager をアタッチしているオブジェクトを選択
  2. インスペクターで Dialogue Data に作成した NewDialogue をセット
  3. プレイして StartDialogue() を実行
    • dialogueData に設定したセリフが表示される! 🎉

村人ごとにセリフを切り替える

村人が増えてきたら、それぞれの村人に異なる DialogueData を設定できます。

csharpコピーする編集するpublic void StartDialogue(DialogueData newDialogueData)
{
    if (isTalking) return;

    isTalking = true;
    Time.timeScale = 0;

    dialogueCanvas.SetActive(true);
    sentences.Clear();

    dialogueData = newDialogueData; // 新しいセリフデータを設定

    foreach (string sentence in dialogueData.sentences)
    {
        sentences.Enqueue(sentence);
    }

    DisplayNextSentence();
}

そして、村人のオブジェクトに DialogueData をセットして、
StartDialogue(villager1Dialogue);
のように呼び出せば、村人ごとに違う会話が可能になります!🎉


まとめ

ScriptableObjectを使うと、セリフデータをアセットとして管理できる
村人ごとに異なるセリフを簡単に設定できる
スクリプトを編集せずに、インスペクターからセリフを変更できる
スクリプトとデータを分けることで、管理しやすくなる


次のステップ🚀 もし 会話の選択肢イベント連動 を作りたくなったら、
DialogueData次の会話やイベントを指定する機能 を追加すると良いですね! 😆

試してみて、わからないことがあれば気軽に聞いてください!👍

ああ、クラスとか構造体みたいな感じで、事前に設計しとけば村人ごとにインスタンス化してセリフを格納しておけるってことかな?

まぁちょっと言われたとおりにやってみます。

実際に実装してみる

上のChatGPT氏の説明からちょっと改良したので、こちらの手順通りやったほうが良いです。

DialogueData.cs を作成

これは新規で作成します。

using UnityEngine;
using System.Collections.Generic;

[CreateAssetMenu(fileName = "NewDialogueData", menuName = "Dialogue/DialogueData")]
public class DialogueData : ScriptableObject
{
    public List<DialogueEntry> sentences; // `string` → `DialogueEntry` に変更!
}

とりあえずコピペして保存すればOK。オブジェクトにアタッチしなくていいです。

新しいダイアログを作成するとインスペクターで編集できるようになる

これを作ると作成(create)メニューにDialogueとかいうのが追加されてましたΣ(゚Д゚)

unity scriptableobject

↓作成したファイルはこんな感じ。Is Villagerにチェックを入れると上側のウィンドウのテキストに表示されます。チェックを入れない場合はプレイヤーなので下側に表示される。

unity scriptableobject

上側っていうのは↓こんな感じね。今のところ村人を上のウィンドウに表示している。

unity 会話システム

画像も指定できるようにしようとしたけど、だいぶコードが変わってしまったのでまた次回やろうかな(^_^;)

DialogueEntry.cs を作成する

これDialogueData.csとは別に作れって言われたけど、一緒にできないかな?まぁとりあえず今回はこれを作ります。

オブジェクトにアタッチはしなくてよくて作るだけでOK。

[System.Serializable] // ← これをつけると、Inspectorでリスト管理がしやすくなる!
public class DialogueEntry
{
    public string sentence; // セリフの文章
    public bool isVillagerSpeaking; // 村人が話すなら true、プレイヤーなら false

    public DialogueEntry(string sentence, bool isVillagerSpeaking)
    {
        this.sentence = sentence;
        this.isVillagerSpeaking = isVillagerSpeaking;
    }
}

DialogueManager.csを書き換える

前回作ったスクリプトから変わったので書き換えます。

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

public class DialogueManager : MonoBehaviour
{
    public Canvas dialogueCanvas; // 会話用のキャンバス
    public Text villagerText; // 村人のテキストフィールド
    public Text playerText; // プレイヤーのテキストフィールド

    private Queue<DialogueEntry> sentences; // セリフをキューで管理
    private bool isTalking = false;
    private bool isVillagerSpeaking = true; // 交互に話すためのフラグ

    void Start()
    {
        sentences = new Queue<DialogueEntry>();
        dialogueCanvas.gameObject.SetActive(false); // 最初は非表示
    }

    public void StartDialogue(DialogueData dialogueData) // ← public の dialogueData を削除
    {
        if (isTalking) return;

        isTalking = true;
        Time.timeScale = 0; // ゲームを一時停止

        sentences.Clear();

        foreach (var entry in dialogueData.sentences)
        {
            sentences.Enqueue(entry);
        }

        dialogueCanvas.gameObject.SetActive(true);
        DisplayNextSentence();
    }

    public void DisplayNextSentence()
    {
        if (sentences.Count == 0)
        {
            EndDialogue();
            return;
        }

        DialogueEntry entry = sentences.Dequeue();

        if (entry.isVillagerSpeaking)
        {
            villagerText.text = entry.sentence;
            playerText.text = ""; // プレイヤーのテキストを非表示
        }
        else
        {
            playerText.text = entry.sentence;
            villagerText.text = ""; // 村人のテキストを非表示
        }
    }

    public void EndDialogue()
    {
        dialogueCanvas.gameObject.SetActive(false);
        Time.timeScale = 1; // ゲーム再開
        isTalking = false;
    }

    void Update()
    {
        if (isTalking && Input.GetKeyDown(KeyCode.Return)) // Enterキーで次の文章へ
        {
            DisplayNextSentence();
        }
    }
}

NPCInteraction.csを書き換える

こっちもちょこっと変わったので書き換えます。

using UnityEngine;
using UnityEngine.UI;

public class NPCInteraction : MonoBehaviour
{
    public GameObject talkUI; // 「話す(T)」のUIオブジェクト
    public DialogueData dialogueData; // そのNPC専用のセリフデータ

    private DialogueManager dialogueManager;
    private bool canTalk = false; // 会話可能状態のフラグ

    void Start()
    {
        dialogueManager = FindObjectOfType<DialogueManager>(); // DialogueManager を見つけて取得
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player")) // プレイヤーか確認
        {
            talkUI.SetActive(true);
            canTalk = true;
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            talkUI.SetActive(false);
            canTalk = false;
        }
    }

    void Update()
    {
        if (canTalk && Input.GetKeyDown(KeyCode.T))
        {
            if (dialogueManager != null && dialogueData != null)
            {
                dialogueManager.StartDialogue(dialogueData); // ここでエラーが発生しないように修正!
            }
            else
            {
                Debug.LogWarning("DialogueManager または DialogueData が設定されていません!");
            }
        }
    }
}

村人のコンポーネントで確認する

NPCInteractionで会話データを読み出すようにした以外はあまり変わっていないかな。

unity scriptableobject

これで動いた!

前回はスクリプトに直接会話内容を書いていたけど、ちゃんとDialogueDataから会話テキストを読み出せましたね。

会話を書き換えてみる

もう少し長いテキストに書き換えてみます。

unity scriptableobject

おお!ちゃんと反映されましたね。

つづく

結構簡単にできて良かった。

これで会話のデータが管理しやすくなるかな。

次はDialogueのファイルで画像も指定できるようにして、できれば喜怒哀楽の画像も指定するようにしたいけど。

さっきやってみたけど、ちょっと指定項目が多くなりすぎて動かなかった(^_^;)

まぁだいたいできたので、シンプルな感じにしてやってみようと思います。

追記:続きを書きました。

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