見出し画像

【Unity】続・ノベルゲームの雛形

前回の記事はこちら
前回はサウンドノベルに必要な最低限の実装をしました。
ですが正直あれだけでは公開できるほどのゲームは作れないでしょう。ということで、メインのローグライクを後回しにして、より発展的なノベルゲームの実装方法についてを先に投稿します。

今回実装するもの

・バックログ
・パラメーター代入&表示&比較&演算処理

一応前回の続きから行なっていくので、前回を読んでいない方は先にそちらからお読み下さい。
なお、今回も前回と同じく外部ファイルはテキストファイルのみ使用します。

バックログの実装

手始めにバックログの実装をしようかと思います。
まずは外形から作成します。プレハブのGameManagerをダブルクリックして開いて下さい。そうしたら、「UI」-「Scroll View」を作成し、名前を「BackLogScrollView」にします。そしてあとは自由に設定しましょう。
参考までに、筆者はこんな感じにしました。

スクリーンショット 2020-11-14 5.20.35

ヒエラルキータブはこんな感じです。テキストの「BackLogText」をContent内に追加し、ScrollBar Horizontalを削除しました。

スクリーンショット 2020-11-14 5.42.34

後は設定です。載せていないものは特に値を変えていません。

スクリーンショット 2020-11-14 4.51.16

スクリーンショット 2020-11-14 5.23.46

スクリーンショット 2020-11-14 5.24.16

それではコードを書いていきます。今回はマウスのスクロールを検知したらバックログが表示されるようにします。
GameManagerクラスを変更して下さい。

// スクリプトの最初に追記
using System;
using UnityEngine.EventSystems;

// パラメーターを追加
[SerializeField]
private ScrollRect backLog;

// メソッドを追加
/**
* スクロールした時の処理
*/
private void MouseScroll()
{
   if (backLog.gameObject.activeSelf && Input.mouseScrollDelta.y > 0 && backLog.verticalNormalizedPosition <= 0)
   {
       backLog.gameObject.SetActive(false);
   }
   if (!backLog.gameObject.activeSelf && Input.mouseScrollDelta.y < 0)
   {
       backLog.verticalNormalizedPosition = 0;
       backLog.gameObject.SetActive(true);
       EventSystem.current.SetSelectedGameObject(backLog.verticalScrollbar.gameObject);
   }
}

// メソッドを変更
private void Update()
{
   if (Input.GetMouseButtonDown(0)) OnClick();
   if (Input.GetMouseButtonDown(1)) OnClickRight();
   MouseScroll();
}

private void ReadLine(string text)
{
   if (text[0].Equals(SEPARATE_COMMAND))
   {
       ReadCommand(text);
       if (_selectButtonList.Count > 0) return;
       if (_waitTime > 0)
       {
           StartCoroutine(WaitForCommand());
           return;
       }
       ShowNextPage();
       return;
   }
   string[] ts = text.Split(SEPARATE_MAIN_START);
   string name = ts[0];
   string main = ts[1].Remove(ts[1].LastIndexOf(SEPARATE_MAIN_END));
   nameText.text = name;
   if (name.Equals("")) nameText.transform.parent.gameObject.SetActive(false);
   else nameText.transform.parent.gameObject.SetActive(true);
   mainText.text = "";
   _charQueue = SeparateString(main);
   StartCoroutine(ShowChars(captionSpeed));
   // 以下1行(2行)を追記
   backLog.content.GetComponentInChildren<Text>().text +=
       text + Environment.NewLine + Environment.NewLine;
}

実行してみます。上にスクロールするとバックログが表示され、下にスクロールすると閉じたと思います。

スクリーンショット 2020-11-14 6.25.04

ただ、これもウィンドウ非表示と同じ問題を抱えています。クリックすると反応してしまうことと、勝手に先に進んでしまうことです。この二つを何とかしましょう。
GameManagerクラスを変更して下さい。

// パラメーターを追記
private bool isStop { get{ return !mainText.transform.parent.gameObject.activeSelf || backLog.gameObject.activeSelf; } }

// メソッドを追加
/**
* 全てのアニメーションを一時停止・再開する
*/
private void StopAllAnimation(bool isStop)
{
   StopAnimation(isStop, foregroundImage);
   foreach (Image image in _charaImageList)
       StopAnimation(isStop, image);
   foreach (Button button in _selectButtonList)
       StopAnimation(isStop, button.image);
}

/**
* アニメーションを一時停止・再開する
*/
private void StopAnimation(bool isStop, Image image)
{
   Animator animator = image.GetComponent<Animator>();
   if (isStop) animator.speed = 0;
   else animator.speed = 1;
}

// メソッドを変更
private void OnClick()
{
   if (isStop) return;
   if (_charQueue.Count > 0) OutputAllChar();
   else
   {
       if (_selectButtonList.Count > 0) return;
       if (!ShowNextPage())
           EditorApplication.isPlaying = false;
   }
}

private void MouseScroll()
{
   if (backLog.gameObject.activeSelf && Input.mouseScrollDelta.y > 0 && backLog.verticalNormalizedPosition <= 0)
   {
       StopAllAnimation(false);
       backLog.gameObject.SetActive(false);
   }
   if (!backLog.gameObject.activeSelf && Input.mouseScrollDelta.y < 0)
   {
       StopAllAnimation(true);
       backLog.verticalNormalizedPosition = 0;
       backLog.gameObject.SetActive(true);
       EventSystem.current.SetSelectedGameObject(backLog.verticalScrollbar.gameObject);
   }
}

private IEnumerator ShowChars(float wait)
{
   while (true)
   {
       if (!isStop)
       {
           if (!OutputChar()) break;
       }
       yield return new WaitForSeconds(wait);
   }
   yield break;
}

private IEnumerator WaitForCommand()
{
   float time = 0;
   while (time < _waitTime)
   {
       if (!isStop) time += Time.deltaTime;
       yield return null;
   } 
   _waitTime = 0;
   ShowNextPage();
   yield break;
}

実行してみます。バックログ表示中はテキストが進まなくなったと思います。ついでにバックログ表示中やウィンドウ非表示時は待機時間経過とアニメーションも止まるようにしました。
これに伴い、プレハブのSelectButtonにもアニメーターコンポーネントを追加する必要があります。CharacterImageからコピーして持ってくるといいでしょう。
個人的に後もう一つ気になる箇所があります。それは名前がない時にもかぎかっこが表示される点です。簡単なので直してしまいましょう。
GameManagerクラスを変更して下さい。

// メソッドを変更private void ReadLine(string text)
{
   if (text[0].Equals(SEPARATE_COMMAND))
   {
       ReadCommand(text);
       if (_selectButtonList.Count > 0) return;
       if (_waitTime > 0)
       {
           StartCoroutine(WaitForCommand());
           return;
       }
       ShowNextPage();
       return;
   }
   string[] ts = text.Split(SEPARATE_MAIN_START);
   string name = ts[0];
   string main = ts[1].Remove(ts[1].LastIndexOf(SEPARATE_MAIN_END));
   nameText.text = name;
   if (name.Equals(""))
   {
       nameText.transform.parent.gameObject.SetActive(false);
       backLog.content.GetComponentInChildren<Text>().text += main;
   }
   else
   {
       nameText.transform.parent.gameObject.SetActive(true);
       backLog.content.GetComponentInChildren<Text>().text += text;
   }
   mainText.text = "";
   _charQueue = SeparateString(main);
   StartCoroutine(ShowChars(captionSpeed));
   backLog.content.GetComponentInChildren<Text>().text +=
       Environment.NewLine + Environment.NewLine;
}

スクリーンショット 2020-11-14 22.42.59

バックログにのみ表示する文字列

選択肢で何を選んだのかを記録に残したり、こっそりメッセージを隠したりするなど、バックログにのみ表示できる文字列があるといいですね。という訳でそんなコマンドを作成しましょう。
GameManagerクラスを変更して下さい。

// パラメーターを追加
private const string COMMAND_BACK_LOG = "backlog";

// メソッドを追加
/**
* バックログに記述する
*/
private void WriteBackLog(string parameter)
{
   parameter = parameter.Substring(parameter.IndexOf('"') + 1, parameter.LastIndexOf('"') - parameter.IndexOf('"') - 1);
   backLog.content.GetComponentInChildren<Text>().text += parameter + Environment.NewLine + Environment.NewLine;
}

// メソッドを変更
private void ReadCommand(string cmdLine)
{
   cmdLine = cmdLine.Remove(0, 1);
   Queue<string> cmdQueue = SeparateString(cmdLine, SEPARATE_COMMAND);
   foreach (string cmd in cmdQueue)
   {
       string[] cmds = cmd.Split(COMMAND_SEPARATE_PARAM);
       if (cmds[0].Contains(COMMAND_BACKGROUND))
           SetBackgroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_FOREGROUND))
           SetForegroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_CHARACTER_IMAGE))
           SetCharacterImage(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_JUMP))
           JumpTo(cmds[1]);
       if (cmds[0].Contains(COMMAND_SELECT))
           SetSelectButton(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_WAIT_TIME))
           SetWaitTime(cmds[1]);
       if (cmds[0].Contains(COMMAND_BGM))
           SetBackgroundMusic(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_SE))
           SetSoundEffect(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_CHANGE_SCENE))
           ChangeNextScene(cmds[1]);
       if (cmds[0].Contains(COMMAND_BACK_LOG))
           WriteBackLog(cmds[1]);
   }
}

テキストファイルのどこかに以下を記述します。

!backlog="隠しメッセージです"

実行してみます。上記を記述した箇所に文章が到達すると、バックログに隠しメッセージが表示されます。

スクリーンショット 2020-11-14 23.35.02

使用方法は以下の通りです。

バックログにのみ文字列を表示
!backlog="《文字列》"

ゲーム中パラメーターの宣言・代入・表示

テキスト途中でフラグを立てられたり、数値や文字列を保存できるといいですね。という訳でここからはパラメーター処理について書いていきます。
まずはゲーム中にパラメーターを宣言、代入できるようにしましょう。
GameManagerクラスを変更して下さい。

// パラメーターを追加
private const string COMMAND_PARAM = "param";
private const string COMMAND_WRITE = "_write";
private Dictionary<string, object> _params = new Dictionary<string, object>();

// メソッドを追加
/**
* ゲーム中パラメーターの設定
*/
private void SetParameterForGame(string name, string cmd, string parameter)
{
   cmd = cmd.Replace(COMMAND_PARAM, "").Replace(" ", "");
   name = name.Substring(name.IndexOf('"') + 1, name.LastIndexOf('"') - name.IndexOf('"') - 1);
   parameter = parameter.Substring(parameter.IndexOf('"') + 1, parameter.LastIndexOf('"') - parameter.IndexOf('"') - 1);
   switch (cmd)
   {
       case COMMAND_WRITE:
           _params[name] = parameter;
           break;
   }
}

// メソッドを変更
private void ReadCommand(string cmdLine)
{
   cmdLine = cmdLine.Remove(0, 1);
   Queue<string> cmdQueue = SeparateString(cmdLine, SEPARATE_COMMAND);
   foreach (string cmd in cmdQueue)
   {
       string[] cmds = cmd.Split(COMMAND_SEPARATE_PARAM);
       if (cmds[0].Contains(COMMAND_BACKGROUND))
           SetBackgroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_FOREGROUND))
           SetForegroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_CHARACTER_IMAGE))
           SetCharacterImage(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_JUMP))
           JumpTo(cmds[1]);
       if (cmds[0].Contains(COMMAND_SELECT))
           SetSelectButton(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_WAIT_TIME))
           SetWaitTime(cmds[1]);
       if (cmds[0].Contains(COMMAND_BGM))
           SetBackgroundMusic(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_SE))
           SetSoundEffect(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_CHANGE_SCENE))
           ChangeNextScene(cmds[1]);
       if (cmds[0].Contains(COMMAND_BACK_LOG))
           WriteBackLog(cmds[1]);
       if (cmds[0].Contains(COMMAND_PARAM))
           SetParameterForGame(cmds[1], cmds[0], cmds[2]);
   }
}

そしてこれをテキストファイルのSTARTラベルの最初に追記して下さい。

!param_write="playername"="みにに"

さて、これで代入はできましたが、これだけではパラメーターを表示することはできません。表示できなければ意味がないので、更に変更します。
GameManagerクラスを変更して下さい。

// パラメーターを追加
private const char OUTPUT_PARAM = '"';

// メソッドを追加
/**
* ゲーム中パラメーターの置き換え
*/
private string ReplaceParameterForGame(string line)
{
   string[] lines = line.Split(OUTPUT_PARAM);
   for (int i = 1; i < lines.Length; i += 2)
       lines[i] = _params[lines[i]].ToString();
   return String.Join("", lines);
}

// メソッドを変更
private void ReadLine(string text)
{
   if (text[0].Equals(SEPARATE_COMMAND))
   {
       ReadCommand(text);
       if (_selectButtonList.Count > 0) return;
       if (_waitTime > 0)
       {
           StartCoroutine(WaitForCommand());
           return;
       }
       ShowNextPage();
       return;
   }
   text = ReplaceParameterForGame(text);
   string[] ts = text.Split(SEPARATE_MAIN_START);
   string name = ts[0];
   string main = ts[1].Remove(ts[1].LastIndexOf(SEPARATE_MAIN_END));

   nameText.text = name;
   if (name.Equals(""))
   {
       nameText.transform.parent.gameObject.SetActive(false);
       backLog.content.GetComponentInChildren<Text>().text += main;
   }
   else
   {
       nameText.transform.parent.gameObject.SetActive(true);
       backLog.content.GetComponentInChildren<Text>().text += text;
   }
   mainText.text = "";
   _charQueue = SeparateString(main);
   StartCoroutine(ShowChars(captionSpeed));
   backLog.content.GetComponentInChildren<Text>().text +=
       Environment.NewLine + Environment.NewLine;
}

それができたら、以下をテキストファイルのどこかに書きましょう。

&"playername"さん「"playername"と言います」

実行してみます。以下のように表示されればOKです。

スクリーンショット 2020-11-16 3.23.50

いろいろ値を変えてみて、試してみて下さい。

パラメーターの演算処理

次は値の演算処理です。
GameManagerクラスを変更して下さい。

// スクリプトの最初に以下を追記
using System.Data;

// パラメーターを追加
private const string COMMAND_CALC = "_calc";
private DataTable _dt = new DataTable();

// メソッドを追加
/**
* ゲーム中パラメーターの演算処理
*/
private object CalcParameterForGame(string parameter)
{
   parameter = ReplaceParameterForGame(parameter.Replace(" ", ""));
   object result = __dt.Compute(parameter, "");
   return result;
}

// メソッドを変更
private void SetParameterForGame(string name, string cmd, string parameter)
{
   cmd = cmd.Replace(COMMAND_PARAM, "").Replace(" ", "");
   name = name.Substring(name.IndexOf('"') + 1, name.LastIndexOf('"') - name.IndexOf('"') - 1);
   parameter = parameter.Substring(parameter.IndexOf('"') + 1, parameter.LastIndexOf('"') - parameter.IndexOf('"') - 1);
   switch (cmd)
   {
       case COMMAND_WRITE:
           _params[name] = parameter;
           break;
       case COMMAND_CALC:
           _params[name] = CalcParameterForGame(parameter);
           break;
   }
}

これをテキストファイルのどこかに書きましょう。

!param_write="a"="3"
!param_calc="result"="1+2+"a""

&「計算結果は"result"です」

下記のように表示されればOKです。

スクリーンショット 2020-11-16 14.39.02

1+2+3で6になりました。ちゃんと計算されています。

パラメーターの比較処理

次は比較処理についてです。普通に代入する分には上記を使用すればいいのですが、「=」が間に挟まると少々厄介なことになるので修正します。
GameManagerクラスを変更して下さい。

private void ReadCommand(string cmdLine)
{
   cmdLine = cmdLine.Remove(0, 1);
   Queue<string> cmdQueue = SeparateString(cmdLine, SEPARATE_COMMAND);
   foreach (string cmd in cmdQueue)
   {
       // 以下一行を変更
       string[] cmds = cmd.Split((COMMAND_SEPARATE_PARAM+"").ToCharArray(), count:3);
       if (cmds[0].Contains(COMMAND_BACKGROUND))
           SetBackgroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_FOREGROUND))
           SetForegroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_CHARACTER_IMAGE))
           SetCharacterImage(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_JUMP))
           JumpTo(cmds[1]);
       if (cmds[0].Contains(COMMAND_SELECT))
           SetSelectButton(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_WAIT_TIME))
           SetWaitTime(cmds[1]);
       if (cmds[0].Contains(COMMAND_BGM))
           SetBackgroundMusic(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_SE))
           SetSoundEffect(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_CHANGE_SCENE))
           ChangeNextScene(cmds[1]);
       if (cmds[0].Contains(COMMAND_BACK_LOG))
           WriteBackLog(cmds[1]);
       if (cmds[0].Contains(COMMAND_PARAM))
           SetParameterForGame(cmds[1], cmds[0], cmds[2]);
   }
}

テキストファイルの先程の式を以下に書き換えて下さい。

!param_calc="result"="1+2="a""

このように表示されたらOKです。

スクリーンショット 2020-11-16 15.19.11

さて、代入と表示だけ行うのであればこれでいいのですが、折角比較が扱えるのですから分岐にも活かせるといいですよね。という訳で実装します。
GameManagerクラスを変更して下さい。

// パラメーターを追加
private const string COMMAND_BRANCH = "branch";

// メソッドを追加
/**
* 比較式がtrueだったら対応するラベルまでジャンプする
*/
private void CompareJumpTo(string method, string parameter)
{
   method = method.Substring(method.IndexOf('"') + 1, method.LastIndexOf('"') - method.IndexOf('"') - 1);
   if ((bool)CalcParameterForGame(method)) JumpTo(parameter);
}

// メソッドを変更
private void ReadCommand(string cmdLine)
{
   cmdLine = cmdLine.Remove(0, 1);
   Queue<string> cmdQueue = SeparateString(cmdLine, SEPARATE_COMMAND);
   foreach (string cmd in cmdQueue)
   {
       string[] cmds = cmd.Split((COMMAND_SEPARATE_PARAM+"").ToCharArray(), count:3);
       if (cmds[0].Contains(COMMAND_BACKGROUND))
           SetBackgroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_FOREGROUND))
           SetForegroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_CHARACTER_IMAGE))
           SetCharacterImage(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_JUMP))
           JumpTo(cmds[1]);
       if (cmds[0].Contains(COMMAND_SELECT))
           SetSelectButton(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_WAIT_TIME))
           SetWaitTime(cmds[1]);
       if (cmds[0].Contains(COMMAND_BGM))
           SetBackgroundMusic(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_SE))
           SetSoundEffect(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_CHANGE_SCENE))
           ChangeNextScene(cmds[1]);
       if (cmds[0].Contains(COMMAND_BACK_LOG))
           WriteBackLog(cmds[1]);
       if (cmds[0].Contains(COMMAND_PARAM))
           SetParameterForGame(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_BRANCH))
           CompareJumpTo(cmds[1], cmds[2]);
   }
}

Scenarioファイルを以下のように変更して下さい。

#START&
!param_write="playername"="みにに"
!foreground_color="0,0,0"
!foreground_anim="fadein,0,2,%0,0,0,0%%"
!wait="2"
!background_sprite="background_sprite1"
!charaimg_sprite="polygon"="background_sprite2"
!charaimg_size="polygon"="500, 500, 1"
!charaimg_rotate="polygon"="30,30,0"
!bgm_sound="bgm1"!bgm_loop="true"
!bgm_volume="0"
!se_sound="sesample"="se1"
!se_priority="sesample"="150"
!se_play="sesample"=""

&みにに「Hello,World!」

&!charaimg_active="polygon"="true"
!charaimg_anim="polygon"="anim,0,1,EaseInOut%255,255,255,0%1000,1000,0%1000,500,0"
!bgm_play=""!bgm_fade="5,1"

&みにに「これはテキスト表示のサンプルです」

&!wait="5"
&!charaimg_active="polygon"="true"
!background_sprite="background_sprite2"
!background_color="255,0,255"
!charaimg_anim="polygon"="anim,,,Replay"
!wait="5"
&名無し「こんにちは!」
&!select_text="NEXT1"="こんにちは"
!select_text="NEXT2"="こんばんは"
!select_text="NEXT3"="おはようございます"

#END&
!charaimg_delete="polygon"=""
!bgm_mute="false"
&「ポリゴンを削除しました」
&!foreground_anim="fadeout,0,2,%0,0,0%%"
!wait="2"&
!branch=""time"<17"="NEXTSCENE"

#NEXTSCENE&
!scene="NextScene"

#NEXT1&
!se_play="sesample"=""
!param_write="time"="12"
&「こんにちはを選んだ」
&!jump_to="END"

#NEXT2&
!bgm_fade="5,0"
!param_write="time"="19"
&「こんばんはを選んだ」
&!jump_to="END"

#NEXT3&
!param_write="time"="9"
&「おはようございますを選んだ」
&!jump_to="END"

timeに値を代入し、それが17未満であれば次のシーンに移るようにしています。
実行してみます。「こんばんは」を選ぶと次のシーンには移らなくなったはずです。
ということで使い方のまとめは以下です。

ゲーム中パラメーターの代入
「!param_write="《パラメーター名》"="《値》"」
ゲーム中パラメーターの指定
「"《パラメーター名》"」
ゲーム中パラメーターの計算
「!param_calc="《パラメーター名》"="《計算式》"」
条件付き分岐
「!branch="《条件式》"="《ジャンプ先ラベル名》"」

なお、計算式で文字列の結合はできません。また条件式では、「等価(==)」や「かつ(&&)」、「または(||)」の代わりに「=」や「AND」、「OR」を使用して下さい。

文字列の結合・パラメーターにパラメーターを代入

上記まとめでできないことをできるようにしましょう。
GameManagerクラスを変更して下さい。

private void SetParameterForGame(string name, string cmd, string parameter)
{
   cmd = cmd.Replace(COMMAND_PARAM, "").Replace(" ", "");
   name = name.Substring(name.IndexOf('"') + 1, name.LastIndexOf('"') - name.IndexOf('"') - 1);
   parameter = parameter.Substring(parameter.IndexOf('"') + 1, parameter.LastIndexOf('"') - parameter.IndexOf('"') - 1);
   switch (cmd)
   {
       case COMMAND_WRITE:
           // 以下1行を変更
           _params[name] = ReplaceParameterForGame(parameter);
           break;
       case COMMAND_CALC:
           _params[name] = CalcParameterForGame(parameter);
           break;
   }
}

Scenarioファイルを以下のように変更して下さい。

#START&
!param_write="playername"="みにに"
!foreground_color="0,0,0"
!foreground_anim="fadein,0,2,%0,0,0,0%%"
!wait="2"
!background_sprite="background_sprite1"
!charaimg_sprite="polygon"="background_sprite2"
!charaimg_size="polygon"="500, 500, 1"
!charaimg_rotate="polygon"="30,30,0"
!bgm_sound="bgm1"!bgm_loop="true"
!bgm_volume="0"
!se_sound="sesample"="se1"
!se_priority="sesample"="150"
!se_play="sesample"=""

&みにに「Hello,World!」

&!charaimg_active="polygon"="true"
!charaimg_anim="polygon"="anim,0,1,EaseInOut%255,255,255,0%1000,1000,0%1000,500,0"
!bgm_play=""!bgm_fade="5,1"

&みにに「これはテキスト表示のサンプルです」

&!wait="5"
&!charaimg_active="polygon"="true"
!background_sprite="background_sprite2"
!background_color="255,0,255"
!charaimg_anim="polygon"="anim,,,Replay"
!wait="5"
&名無し「こんにちは!」
&!select_text="NEXT1"="こんにちは"
!select_text="NEXT2"="こんばんは"
!select_text="NEXT3"="おはようございます"

#END&
!param_write="text"=""select"を選んだ(時刻:"time"時)"
&「"text"」
&!charaimg_delete="polygon"=""
!bgm_mute="false"
&「ポリゴンを削除しました」
&!foreground_anim="fadeout,0,2,%0,0,0%%"
!wait="2"&
!branch=""time"<17"="NEXTSCENE"

#NEXTSCENE&
!scene="NextScene"

#NEXT1&
!se_play="sesample"=""
!param_write="time"="12"
!param_write="select"="こんにちは"
&!jump_to="END"

#NEXT2&
!bgm_fade="5,0"
!param_write="time"="19"
!param_write="select"="こんばんは"
&!jump_to="END"

#NEXT3&
!param_write="time"="9"
!param_write="select"="おはようございます"
&!jump_to="END"

スクリーンショット 2020-11-16 18.30.00

シーン間で共有するパラメーター

今はシーン間でパラメーターは別々のものを使用しており、シーン移動するとデータが全部削除されます。シーン間で共有できるパラメーターもあるといいですね。という訳で作成します。
GameManagerクラスを変更して下さい。

// パラメーターを追加
private const char OUTPUT_GLOBAL_PARAM = '$';
private const string COMMAND_GLOBAL_PARAM = "globalp";
private static Dictionary<string, object> _globalparams;

// メソッドを変更
private void Init()
{
   if (_globalparams == null)
       _globalparams = new Dictionary<string, object>();
   _text = LoadTextFile(textFile);
   Queue<string> subScenes = SeparateString(_text, SEPARATE_SUBSCENE);
   foreach (string subScene in subScenes)
   {
       if (subScene.Equals("")) continue;
       Queue<string> pages = SeparateString(subScene, SEPARATE_PAGE);
       _subScenes[pages.Dequeue()] = pages;
   }
   _pageQueue = _subScenes.First().Value;
   ShowNextPage();
}

private string ReplaceParameterForGame(string line)
{
   string[] lines = line.Split(OUTPUT_PARAM);
   for (int i = 1; i < lines.Length; i += 2)
   {
       if (lines[i][0] == OUTPUT_GLOBAL_PARAM)
           lines[i] = _globalparams[(lines[i].Remove(0, 1))].ToString();
       else
           lines[i] = _params[lines[i]].ToString();
   }
   return String.Join("", lines);
}

private void SetParameterForGame(string name, string cmd, string parameter, Dictionary<string, object> _params)
{
   cmd = cmd.Replace(" ", "");
   name = name.Substring(name.IndexOf('"') + 1, name.LastIndexOf('"') - name.IndexOf('"') - 1);
   parameter = parameter.Substring(parameter.IndexOf('"') + 1, parameter.LastIndexOf('"') - parameter.IndexOf('"') - 1);
   switch (cmd)
   {
       case COMMAND_WRITE:
           _params[name] = ReplaceParameterForGame(parameter);
           break;
       case COMMAND_CALC:
           _params[name] = CalcParameterForGame(parameter);
           break;
   }
}

private void ReadCommand(string cmdLine)
{
   cmdLine = cmdLine.Remove(0, 1);
   Queue<string> cmdQueue = SeparateString(cmdLine, SEPARATE_COMMAND);
   foreach (string cmd in cmdQueue)
   {
       string[] cmds = cmd.Split((COMMAND_SEPARATE_PARAM+"").ToCharArray(), count:3);
       if (cmds[0].Contains(COMMAND_BACKGROUND))
           SetBackgroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_FOREGROUND))
           SetForegroundImage(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_CHARACTER_IMAGE))
           SetCharacterImage(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_JUMP))
           JumpTo(cmds[1]);
       if (cmds[0].Contains(COMMAND_SELECT))
           SetSelectButton(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_WAIT_TIME))
           SetWaitTime(cmds[1]);
       if (cmds[0].Contains(COMMAND_BGM))
           SetBackgroundMusic(cmds[0], cmds[1]);
       if (cmds[0].Contains(COMMAND_SE))
           SetSoundEffect(cmds[1], cmds[0], cmds[2]);
       if (cmds[0].Contains(COMMAND_CHANGE_SCENE))
           ChangeNextScene(cmds[1]);
       if (cmds[0].Contains(COMMAND_BACK_LOG))
           WriteBackLog(cmds[1]);
       if (cmds[0].Contains(COMMAND_PARAM))
           SetParameterForGame(cmds[1], cmds[0].Replace(COMMAND_PARAM, ""), cmds[2], _params);
       if (cmds[0].Contains(COMMAND_GLOBAL_PARAM))
           SetParameterForGame(cmds[1], cmds[0].Replace(COMMAND_GLOBAL_PARAM, ""), cmds[2], _globalparams);
       if (cmds[0].Contains(COMMAND_BRANCH))
           CompareJumpTo(cmds[1], cmds[2]);
   }
}

Scenarioファイルのどこかに以下を追記して下さい。

!globalp_write="globalvalue"="グローバル"

Scenario2ファイルの最後に以下を追記して下さい。

&「"$globalvalue"が持ち越されました」

次のシーンの最後に以下が表示されればOKです。

スクリーンショット 2020-11-16 20.12.34

ということで使い方のまとめは以下です。

シーン間共有パラメーターの代入
「!globalp_write="《パラメーター名》"="《値》"」
ゲーム中パラメーターの指定
「"$《パラメーター名》"」
ゲーム中パラメーターの計算
「!globalp_calc="《パラメーター名》"="《計算式》"」
条件付き分岐
「!globalp="《条件式》"="《ジャンプ先ラベル名》"」

パラメーターをコマンド内でも適用する

パラメーターをコマンド内でも使用できればいいかもしれませんね。ということで実装します。
GameManagerクラスを変更して下さい。

// メソッドを追加
/**
* コマンド中の「""」に挟まれた値を取り出す
*/
private string GetParameterForCommand(string parameter)
{
   parameter = parameter.Substring(parameter.IndexOf('"') + 1, parameter.LastIndexOf('"') - parameter.IndexOf('"') - 1);
   return ReplaceParameterForGame(parameter.Replace(" ", ""));
}

// メソッドを変更
private void ChangeNextScene(string parameter)
{
   parameter = GetParameterForCommand(parameter);
   SceneManager.LoadSceneAsync(parameter);
}

private void JumpTo(string parameter)
{
   parameter = GetParameterForCommand(parameter);
   _pageQueue = _subScenes[parameter];
}

private void CompareJumpTo(string method, string parameter)
{
   if ((bool)CalcParameterForGame(method)) JumpTo(parameter);
}

private void SetWaitTime(string parameter)
{
   parameter = GetParameterForCommand(parameter);
   _waitTime = float.Parse(parameter);
}

private void WriteBackLog(string parameter)
{
   parameter = GetParameterForCommand(parameter);
   backLog.content.GetComponentInChildren<Text>().text += parameter + Environment.NewLine + Environment.NewLine;
}

private void SetCharacterImage(string name, string cmd, string parameter)
{
   cmd = cmd.Replace(COMMAND_CHARACTER_IMAGE, "");
   name = GetParameterForCommand(name);
   Image image = _charaImageList.Find(n => n.name == name);
   if (image == null)
   {
       image = Instantiate(Resources.Load<Image>(prefabsDirectory + CHARACTER_IMAGE_PREFAB), characterImages.transform);
       image.name = name;
       _charaImageList.Add(image);
   }
   SetImage(cmd, parameter, image);
}

private void SetSelectButton(string name, string cmd, string parameter)
{
   cmd = cmd.Replace(COMMAND_SELECT, "");
   name = GetParameterForCommand(name);
   Button button = _selectButtonList.Find(n => n.name == name);
   if (button == null)
   {
       button = Instantiate(Resources.Load<Button>(prefabsDirectory + SELECT_BUTTON_PREFAB), selectButtons.transform);
       button.name = name;
       button.onClick.AddListener(() => SelectButtonOnClick(name));
       _selectButtonList.Add(button);
   }
   SetImage(cmd, parameter, button.image);
}

private void SetImage(string cmd, string parameter, Image image)
{
   cmd = cmd.Replace(" ", "");
   parameter = GetParameterForCommand(parameter);
   switch (cmd)
   {
       case COMMAND_TEXT:
           image.GetComponentInChildren<Text>().text = parameter;
           break;
       case COMMAND_SPRITE:
           image.sprite = LoadSprite(parameter);
           break;
       case COMMAND_COLOR:
           image.color = ParameterToColor(parameter);
           break;
       case COMMAND_SIZE:
           image.GetComponent<RectTransform>().sizeDelta = ParameterToVector3(parameter);
           break;
       case COMMAND_POSITION:
           image.GetComponent<RectTransform>().anchoredPosition = ParameterToVector3(parameter);
           break;
       case COMMAND_ROTATION:
           image.GetComponent<RectTransform>().eulerAngles = ParameterToVector3(parameter);
           break;
       case COMMAND_ACTIVE:
           image.gameObject.SetActive(ParameterToBool(parameter));
           break;
       case COMMAND_DELETE:
           _charaImageList.Remove(image);
           Destroy(image.gameObject);
           break;
       case COMMAND_ANIM:
           ImageSetAnimation(image, parameter);
           break;
   }
}

private void SetSoundEffect(string name, string cmd, string parameter)
{
   cmd = cmd.Replace(COMMAND_SE, "");
   name = GetParameterForCommand(name);
   AudioSource audio = _seList.Find(n => n.name == name);
   if (audio == null)
   {
       audio = Instantiate(Resources.Load<AudioSource>(prefabsDirectory + SE_AUDIOSOURCE_PREFAB), seAudioSources.transform);
       audio.name = name;
       _seList.Add(audio);
   }
   SetAudioSource(cmd, parameter, audio);
}

private void SetAudioSource(string cmd, string parameter, AudioSource audio)
{
   cmd = cmd.Replace(" ", "");
   parameter = GetParameterForCommand(parameter);
   switch (cmd)
   {
       case COMMAND_PLAY:
           audio.Play();
           break;
       case COMMAND_MUTE:
           audio.mute = ParameterToBool(parameter);
           break;
       case COMMAND_SOUND:
           audio.clip = LoadAudioClip(parameter);
           break;
       case COMMAND_VOLUME:
           audio.volume = float.Parse(parameter);
           break;
       case COMMAND_PRIORITY:
           audio.priority = int.Parse(parameter);
           break;
       case COMMAND_LOOP:
           audio.loop = ParameterToBool(parameter);
           break;
       case COMMAND_FADE:
           FadeSound(audio, parameter);
           break;
       case COMMAND_ACTIVE:
           audio.gameObject.SetActive(ParameterToBool(parameter));
           break;
       case COMMAND_DELETE:
           _seList.Remove(audio);
           Destroy(audio.gameObject);
           break;
   }
}

private void SetParameterForGame(string name, string cmd, string parameter, Dictionary<string, object> _params)
{
   cmd = cmd.Replace(" ", "");
   name = GetParameterForCommand(name);

   switch (cmd)
   {
       case COMMAND_WRITE:
           _params[name] = GetParameterForCommand(parameter);
           break;
       case COMMAND_CALC:
           _params[name] = CalcParameterForGame(parameter);
           break;
   }
}

private object CalcParameterForGame(string parameter)
{
   parameter = GetParameterForCommand(parameter);
   object result = _dt.Compute(parameter, "");
   return result;
}

テストします。
Scenarioファイルを以下のように変更して下さい。

#START&
!param_write="playername"="みにに"
!foreground_color="0,0,0"
!foreground_anim="fadein,0,2,%0,0,0,0%%"
!wait="2"
!background_sprite="background_sprite1"
!charaimg_sprite="polygon"="background_sprite2"
!charaimg_size="polygon"="500, 500, 1"
!charaimg_rotate="polygon"="30,30,0"
!bgm_sound="bgm1"!bgm_loop="true"
!bgm_volume="0"
!se_sound="sesample"="se1"
!se_priority="sesample"="150"
!se_play="sesample"=""
!globalp_write="globalvalue"="グローバル"

&みにに「Hello,World!」

&!charaimg_active="polygon"="true"
!charaimg_anim="polygon"="anim,0,1,EaseInOut%255,255,255,0%1000,1000,0%1000,500,0"
!bgm_play=""!bgm_fade="5,1"

&みにに「これはテキスト表示のサンプルです」

&!wait="5"
&!charaimg_active="polygon"="true"
!background_sprite="background_sprite2"
!background_color="255,0,255"
!charaimg_anim="polygon"="anim,,,Replay"
!wait="5"
&名無し「こんにちは!」
&!param_write="select1"="こんにちは"
!param_write="select2"="こんばんは"
!param_write="select3"="おはようございます"
!select_text="NEXT1"=""select1""
!select_text="NEXT2"=""select2""
!select_text="NEXT3"=""select3""

#END&
!param_write="text"=""select"を選んだ(時刻:"time"時)"
&「"text"」
&!charaimg_delete="polygon"=""
!bgm_mute="false"
&「ポリゴンを削除しました」
&!foreground_anim="fadeout,0,2,%0,0,0%%"
!wait="2"&
!branch=""time"<17"="NEXTSCENE"

#NEXTSCENE&
!scene="NextScene"

#NEXT1&
!se_play="sesample"=""
!param_write="time"="12"
!param_write="select"=""select1""
&!jump_to="END"

#NEXT2&
!bgm_fade="5,0"
!param_write="time"="19"
!param_write="select"=""select2""
&!jump_to="END"

#NEXT3&
!param_write="time"="9"
!param_write="select"=""select3""
&!jump_to="END"

実行すると今までと同じように表示されるはずです。

とりあえずここまでできたら、ほとんどのことができると思います。
(まあまだまだ実装の余地はありそうですが......)
次回は未定です。

この記事が気に入ったらサポートをしてみませんか?