メニュー

Unityでノベルゲームを作ろう!(第2回:文章表示編)

  • URLをコピーしました!

お疲れ様です、poppyです。本日もこちらのブログを見に来てくださりありがとうございます。
今回からUnityでアセットを使わずにノベルゲームを作るところをお見せいたします。
第2回は「GameManagerの設定・文章表示」をやっていきたいと思います。

制作環境

制作環境は第1回と同様です。また、変数についても第1回のものを使い回すのでご注意ください。

  • バージョン:2022.3.24f1 (日本語化パッチ適用済み)
  • テンプレート:Universal 2D
  • スクリプト:Visual Studio 2022 (Windows)

今回は「宴」などのアセットは用いませんのでご了承ください。(多分使ったほうが早くできるとは思うけど)

免責事項

当ブログの記載内容や情報の信頼性については可能な限り十分注意をしておりますが、その完全性、正確性、妥当性及び公正性について保証するものではありません。
情報の誤りや不適切な表現があった場合には予告なしに記事の編集・削除を行うこともございます。あくまでもご自身の判断にてご覧頂くようにお願い致します。
当ブログの記載内容によって被った損害・損失については一切の責任を負いかねます。ご了承ください。

お詫び

あくまでも実装当時のコードを掲載しております。現在はバグなどを潰すために修正を行っているため本記事とは異なるコードになっておりますが、
こちらに反映する予定はございません。大変申し訳ありません。あくまでも参考程度に見ていただければ幸いです。

また、今回もこちらのサイトを大いに参考にさせてもらっています。第3回くらいからは自力+ChatGPTを使ったものになりますのでお楽しみに?

今回の目標

第2回では以下のことができるようにします。
・テキスト及びスクリプトを書き込むCSVファイルを作成し、読み込む
・スクリプトファイルを作成する
・文章が1文字ずつ表示され、クリックを押すと全文字表示されるようになる

CSVファイルの作成

本ゲームではテキスト及びスクリプト(画像表示や移動、フェードイン・フェードアウトなどの命令)をCSVファイルで管理することにします。適当にExcelファイルを開き、このように1列目は開けて2列目に名前、3列目に本文を書くようにします(1列目を開けたのは命令か文章かを判別できるようにするため)

そして「名前をつけて保存」のときにファイルの種類をCSV UTF-8(コンマ区切り)にすることを推奨します。文字コードはできる限りUTF-8に統一します。名前はtext_script.csvとします。

そしてAssetsを右クリックして「作成」→フォルダーから新規フォルダー(名前は「Texts」)を作成し、その中に先ほどのtext_script.csvをドラッグ&ドロップします。

スクリプトの作成(+文字化け対処)

続いてC#スクリプトを作成しますが、現段階ではなぜかUTF-8ではなくShift-JISと判断され文字化けが発生するので、対処する必要があります。
ここでは「.editorconfig」を用いて対処する方法をご紹介します。
まずテキストファイルで.editorconfig.txtを作り以下を記入します。

root = true
[*.cs]
 charset = utf-8-bom

Unityプロジェクト(Assetsで右クリック→エクスプローラーで表示で出る)下に、拡張子の.txtを消した「.editorconfig」として配置すれば、UTF-8で認識されて文字化けしないはずです。
改めて、スクリプトを作成します。Assetsを右クリックして「作成」→「フォルダー」で新規フォルダー(名前は「Scripts」)を作成し、Scripts内で右クリックして「作成」→「C# スクリプト」でC#スクリプトを3つ作ります。名前と役割はそれぞれ以下になります。
・GameManager:ゲーム全体の管理を行う
・MainTextController:画面に映す文章の管理を行う
・UserScriptManager:シナリオや命令の管理を行う

関係性についてはGameManagerがトップにあって、その下部組織にMainTextControllerとUserScriptManagerがある感じです。これからもスクリプトをいくつか作っていくことになりますが、基本的にはGameManagerの下につき、他のスクリプトとやり取りする場合もGameManagerを仲介する形になります。

スクリプトの編集

それではスクリプトを編集していきます。解説事項はコメントとして書いてありますので参考にしてください。

GameManager.cs

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

namespace NovelGame
{
  public class GameManager : MonoBehaviour
  {
    // 別のクラスからGameManagerの変数などを使えるようにするためのもの。(変更はできない)
    public static GameManager Instance { get; private set; }

    //下部組織のスクリプト
    public UserScriptManager userScriptManager;
    public MainTextController mainTextController;

    // ユーザスクリプトの、今の行の数値。クリック(タップ)のたびに1ずつ増える。
    [System.NonSerialized] public int lineNumber;

    void Awake()
    {
      // これで、別のクラスからGameManagerの変数などを使えるようになる。
      Instance = this;

      lineNumber = 0;
    }
  }
}

MainTextController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

namespace NovelGame
{
  public class MainTextController : MonoBehaviour
  {
    [SerializeField] TextMeshProUGUI _mainTextObject;
    [SerializeField] TextMeshProUGUI _nameTextObject;

    // Start is called before the first frame update
    void Start()
    {

      // 最初の行のテキストを表示
      DisplayText();
    }

    // Update is called once per frame
    void Update()
    {
      
      if (Input.GetMouseButtonUp(0))
      {
          GoToTheNextLine();
          DisplayText();
        
      }
    }
    // 次の行へ移動
    public void GoToTheNextLine()
    {
        GameManager.Instance.lineNumber++;
    }


    // テキストを表示
    public void DisplayText()
    {
      //現在の文章をuserScriptManagerのGetCurrentSentenceで引っ張ってくる
      string sentence = GameManager.Instance.userScriptManager.GetCurrentSentence();
      //カンマ区切りで名前と本文をわける
      string[] words = sentence.Split(',');
      
      //1番が名前、2番が本文
      string namesentence = words[1];
      string textsentence = words[2];
      //それぞれテキストオブジェクトに代入する
      _mainTextObject.text = textsentence;
      _nameTextObject.text = namesentence;
    }
  }
}

UserScriptManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

namespace NovelGame
{
  // テキストファイルから文章を読み込み、ステートメントの実行を管理するクラス
  public class UserScriptManager : MonoBehaviour
  {
    [SerializeField] TextAsset _textFile; // テキストファイルを格納するための変数

    List<string> _sentences = new List<string>(); // 読み込んだ文章を格納するリスト
    public bool isWaiting = false; // ウェイト中かどうかのフラグ
    // スクリプトの初期化時に実行されるメソッド
    void Awake()
    {
      // テキストファイルから文章を一行ずつ読み込んでリストに格納する
      StringReader reader = new StringReader(_textFile.text);
      while (reader.Peek() != -1)
      {
        string line = reader.ReadLine();
        _sentences.Add(line);
      }
    }

    // 現在の文章を取得するメソッド
    public string GetCurrentSentence()
    {
      return _sentences[GameManager.Instance.lineNumber];
    }
  }
}

ヒエラルキー・アタッチ・インスペクターの設定

次にヒエラルキー上で右クリックし「空のオブジェクトを作成」で2つ空のオブジェクトを作成し、名前をそれぞれ「GameManager」「UserScriptManager」とします。
そしてスクリプトの方のGameManager.csをオブジェクトの方のGameManagerにドラッグ&ドロップすることでアタッチすることができます。
UserScriptManager.csも同様に同名のオブジェクトにドラッグ&ドロップしてアタッチしてください。
MainTextController.csはMessageWindow下のMainTextオブジェクトにアタッチしてください。
次にインスペクターの設定を行います。
GameManagerのインスペクターには「User Script Manager」と「Main Text Controller」がありますが、それぞれ「シーン」から「UserScriptManager」「MainText(Main Text Controller)」を入れてください。
上記の操作は基本的に新しいManager/Controller系のスクリプトを作ったら行う操作なのでこれを今後は「スクリプト実効化」という名称で使っていきたいと思います。「スクリプト実効化」と呼んだ場合はこちらのことだと思ってください。

UserScriptManagerのインスペクターには「Text File」がありますので、Assets→Texts下にあるtext_scriptをいれます。

最後にMainTextのインスペクター(Main Text Controller)には「Main Text Object」と「Name Text Object」がありますので、それぞれ「MainText」と「NameText」を入れてください。
これで再生ボタンを押せば、最初の1文が画面に表示され、クリックすると次の文が表示されるようになるはずです。

とりあえずこれで文章が表示されるようになりました。とはいえまだ画像がないので真っ暗ですし、1文字ずつ表示もしてくれないのでノベルゲームっぽさはまだありません。
画像については第3回で取り上げるとして、今回最後に1文字ずつ表示を行って終わりにしたいと思います。

1文字ずつ表示&クリックしたら全文表示

文章を1文字ずつ表示し、クリックしたら全文が表示できるようにスクリプトを調整します。

MainTextController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

namespace NovelGame
{
  public class MainTextController : MonoBehaviour
  {
    [SerializeField] TextMeshProUGUI _mainTextObject;
    [SerializeField] TextMeshProUGUI _nameTextObject;
    int _displayedSentenceLength;
    int _sentenceLength;
    float _time;
    float _feedTime;


    // Start is called before the first frame update
    void Start()
    {
      _time = 0f;
      _feedTime = 0.05f;
      DisplayText();
    }

    // Update is called once per frame
    void Update()
    {
      // 文章を1文字ずつ表示する
      _time += Time.deltaTime;
      if (_time >= _feedTime)
      {
        _time -= _feedTime;
        if (!CanGoToTheNextLine())
        {
          _displayedSentenceLength++;
          _mainTextObject.maxVisibleCharacters = _displayedSentenceLength;
        }
      }

      // クリックされたとき、次の行へ移動
      if (Input.GetMouseButtonUp(0))
      {
        if (CanGoToTheNextLine())
        {
          GoToTheNextLine();
          DisplayText();
        }
        else
        {
          _displayedSentenceLength = _sentenceLength;
        }
      }
    }

    // その行の、すべての文字が表示されていなければ、まだ次の行へ進むことはできない
    public bool CanGoToTheNextLine()
    {
      string sentence = GameManager.Instance.userScriptManager.GetCurrentSentence();
      string[] words = sentence.Split(',');
      string textsentence = words[2];
      _sentenceLength = textsentence.Length;
      return (_displayedSentenceLength > textsentence.Length);
    }

    // 次の行へ移動
    public void GoToTheNextLine()
    {

      _displayedSentenceLength = 0;
      _time = 0f;
      _mainTextObject.maxVisibleCharacters = 0;
      GameManager.Instance.lineNumber++;
     
    }


    // テキストを表示
    public void DisplayText()
    {

      //現在の文章をuserScriptManagerのGetCurrentSentenceで引っ張ってくる
      string sentence = GameManager.Instance.userScriptManager.GetCurrentSentence();
      //カンマ区切りで名前と本文をわける
      string[] words = sentence.Split(',');
      
      //1番が名前、2番が本文
      string namesentence = words[1];
      string textsentence = words[2];
      //それぞれテキストオブジェクトに代入する
      _mainTextObject.text = textsentence;
      _nameTextObject.text = namesentence;

    }
  }
}

終わりに

ということで今回はノベルゲーム開発の第2回ということで文章表示とGameManagerがメインとなりました。第3回からは文章以外の命令を打っていき、画像表示などをやっていきたいと思っています。
それではまた!

シェアよろしくお願いします!
  • URLをコピーしました!