今回はRPGゲーム等でよく使われるメッセージ表示機能を作りたいと思います。
誰かと話した時に、その会話の内容を表示する部分です。
大まかな仕様として、表示したい会話の内容を一定秒数後に一文字づつ表示していく。
全部の会話文字列の間に分割する文字列を入れておき、1回で表示するメッセージを指定出来るようにする。
また、文字を表示している間にマウスクリックをしたら、1回で表示出来る文字数分を一気に表示するクイック表示機能をつける。
表示したい会話の内容がなくなった場合、マウスクリックでメッセージ画面ごと消す。
こんな感じの仕様にします。
2019/07/02に全面的に機能を簡略化し、より使いやすくしました。
メッセージ表示用のUIを作成
まずはメッセージを表示する場所を作成します。
ヒエラルキーにCreateからUI→Canvasを作り、名前をMessageUIとします。
MessageUIの子要素にUI→Panelを作り、その子要素にUI→Text、UI→Imageを作成します。
上のような感じの階層を作成します。
大枠がPanelで、それより少し小さくTextを作成します。Imageは右下に配置します。
Imageはクリックを促すアイコンになるのでインスペクタのSource ImageでSpriteを設定してください。
TextをPanelより少し小さくしたのは、同じ大きさにすると枠ギリギリに表示されてしまうので見づらくなる為です。
Textのインスペクタで設定を上のようにします。
Textは親のPanelのサイズに合わせて大きさを変え、Panelの枠から10ピクセル内側になります。
Alignmentは左上を指定し、左上から会話が表示されるようにします。
Horizontal OverflowはWrapにし、横幅を超える文字があった場合自動で次の行に表示します。
Vertical OverflowはTruncateにします。Truncateにすると縦幅を超える文字があった場合表示しません。
Overflowに設定すると、指定した文字は枠を超えて表示されるようになります。
ここの設定は自由に設定してください。
Best Fitにチェックを入れると枠に応じて制限内で文字の大きさが自動で変更されます。
制限値はBest Fitにチェックを入れると、Min SizeとMax Sizeの指定が出来ます。
しかし、メッセージは統一した文字の大きさで表示したいので、今回はチェックを入れません。
メッセージ表示スクリプトMessageを作成
メッセージを表示するスクリプトMessageを作成します。
MessageスクリプトはMessageUIゲームオブジェクトに設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | using UnityEngine; using System.Collections; using UnityEngine.UI; using System.Text.RegularExpressions; public class Message : MonoBehaviour { // メッセージUI private Text messageText; // 表示するメッセージ [SerializeField] [TextArea(1, 20)] private string allMessage = "今回はRPGでよく使われるメッセージ表示機能を作りたいと思います。\n" + "メッセージが表示されるスピードの調節も可能であり、改行にも対応します。\n" + "改善の余地がかなりありますが、 最低限の機能は備えていると思われます。\n" + "ぜひ活用してみてください。\n<>" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ<>" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ<>" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ" + "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ"; // 使用する分割文字列 [SerializeField] private string splitString = "<>"; // 分割したメッセージ private string[] splitMessage; // 分割したメッセージの何番目か private int messageNum; // テキストスピード [SerializeField] private float textSpeed = 0.05f; // 経過時間 private float elapsedTime = 0f; // 今見ている文字番号 private int nowTextNum = 0; // マウスクリックを促すアイコン private Image clickIcon; // クリックアイコンの点滅秒数 [SerializeField] private float clickFlashTime = 0.2f; // 1回分のメッセージを表示したかどうか private bool isOneMessage = false; // メッセージをすべて表示したかどうか private bool isEndMessage = false; void Start() { clickIcon = transform.Find("Panel/Image").GetComponent<Image>(); clickIcon.enabled = false; messageText = GetComponentInChildren<Text>(); messageText.text = ""; SetMessage(allMessage); } void Update() { // メッセージが終わっているか、メッセージがない場合はこれ以降何もしない if (isEndMessage || allMessage == null) { return; } // 1回に表示するメッセージを表示していない if (!isOneMessage) { // テキスト表示時間を経過したらメッセージを追加 if (elapsedTime >= textSpeed) { messageText.text += splitMessage[messageNum][nowTextNum]; nowTextNum++; elapsedTime = 0f; // メッセージを全部表示、または行数が最大数表示された if (nowTextNum >= splitMessage[messageNum].Length) { isOneMessage = true; } } elapsedTime += Time.deltaTime; // メッセージ表示中にマウスの左ボタンを押したら一括表示 if (Input.GetMouseButtonDown(0)) { // ここまでに表示しているテキストに残りのメッセージを足す messageText.text += splitMessage[messageNum].Substring(nowTextNum); isOneMessage = true; } // 1回に表示するメッセージを表示した } else { elapsedTime += Time.deltaTime; // クリックアイコンを点滅する時間を超えた時、反転させる if (elapsedTime >= clickFlashTime) { clickIcon.enabled = !clickIcon.enabled; elapsedTime = 0f; } // マウスクリックされたら次の文字表示処理 if (Input.GetMouseButtonDown(0)) { nowTextNum = 0; messageNum++; messageText.text = ""; clickIcon.enabled = false; elapsedTime = 0f; isOneMessage = false; // メッセージが全部表示されていたらゲームオブジェクト自体の削除 if (messageNum >= splitMessage.Length) { isEndMessage = true; transform.GetChild(0).gameObject.SetActive(false); } } } } // 新しいメッセージを設定 void SetMessage(string message) { this.allMessage = message; // 分割文字列で一回に表示するメッセージを分割する splitMessage = Regex.Split(allMessage, @"\s*" + splitString + @"\s*", RegexOptions.IgnorePatternWhitespace); nowTextNum = 0; messageNum = 0; messageText.text = ""; isOneMessage = false; isEndMessage = false; } // 他のスクリプトから新しいメッセージを設定しUIをアクティブにする public void SetMessagePanel(string message) { SetMessage(message); transform.GetChild(0).gameObject.SetActive(true); } } |
messageTextはメッセージを表示するUIのTextを後で設定します。
allMessageは村人と話した時に会話する全内容を設定します。
\nで改行、<>はデフォルトの1回に表示する会話の分割に使う文字列です。
[TextArea(1, 20)]アトリビュートはインスペクタで最低1行で表示を20行にし、それより多い分はスクロールして見れるようにします。
allMessageはインスペクタでも設定出来ます。
splitStringは全会話内容を1回で表示するメッセージに分割する時の分割文字列を設定します。デフォルトでは<>にしています。
splitMessageは全会話内容を1回に表示するメッセージ事に分割して配列にして入れます。
messageNumはsplitMessageの今何番目のメッセージかを表しています。
textSpeedはテキストを表示する速さ
elapsedTimeは次のメッセージを表示するまでの経過時間やアイコンの点滅の経過時間を入れます。
nowTextNumは現在の1回で表示するメッセージの何番目の文字かです。
clickIconはクリックを促すアイコンを指定します。
clickFlashTimeはクリックを促す時に点滅する間隔です。
isOneMessageは1回で表示するメッセージを表示したかどうか
isEnbMessageは全ての会話が終了したかどうかです。
StartメソッドではクリックアイコンImageの取得や会話を表示するTextの取得、SetMessageで会話内容のセットをしています。
Updateメソッドを見ていきます。
会話が終了している時、または会話が設定されていない時はreturnでそれ以降の処理をしません。
isOneMessageで1回のメッセージを表示していない時で経過時間がtextSpeedを越えたら現在見ている1回に表示するメッセージ(splitMessage)の今見ている文字をmessageTextに足します。
現在見ている文字番号が現在見ている1回に表示するメッセージ(splitMessage)の最大値を越えていた時は1回のメッセージは全て表示したのでisOneMessageをtrueにします。
メッセージ表示中にマウスの左ボタンを押したら現在までに表示している文字列にsplitMessage[messageNum].Substring(nowTextNum)で残りの文字列を足して1回に表示するメッセージを全て表示します。
StringクラスのSubstringメソッドは引数で指定した文字の位置以降を求めることが出来ます。
例えば
1 2 3 | Debug.Log("Test".Substring(1)); |
とすればコンソールに est が表示されます。
Substringメソッドは第2引数を渡すことも出来、第1引数で指定した文字位置から第2引数で指定した文字数分を取得することが出来ます。
一回に表示するメッセージ(isOneMessage)がfalseの時はクリックアイコンを点滅させ、マウスの左ボタンが押されたら初期化処理をして次のsplitMessageを表示していきます。
全てのsplitMessageを表示しきったらisEndMessageをtrueにし、子要素のパネルを非アクティブにします。
SetMessageメソッドは全ての会話文を引数で受け取り、それを1回で表示するメッセージに分割して配列にします。
Regex.Splitを使ってallMessageをパターンで分割し配列にしています。
分割文字列はsplitStringの文字と前後に\s*を付けて『空白文字列splitString空白文字列』というパターンを分割文字列としています。
SetMessageメソッドが呼ばれる時は別の人と話す時など会話が一新される時なので、初期化処理をしています。
SetMessagePanelは外部のスクリプトから再びメッセージを表示する時に使用します。
今回はサンプルなのでヒエラルキー上に空のゲームオブジェクトを作成し、名前をActiveMessagePanelにし、これにActiveMessagePanelスクリプトを取り付け、ここから新しいメッセージを設定出来るようにしてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using UnityEngine; using System.Collections; public class ActiveMessagePanel : MonoBehaviour { // MessageUIに設定されているMessageスクリプトを設定 [SerializeField] private Message messageScript; // 表示させるメッセージ private string message = "かかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかかか" + "ききききききききききききききききききききききききききききききききききききききききききききききききききききききき\n" + "くくくくく\n" + "けけけけけけけけけけけけ"; // Update is called once per frame void Update () { if (Input.GetButtonDown ("Fire2")) { messageScript.SetMessagePanel (message); } } } |
ActiveMessagePanelスクリプトはマウスの右ボタンを押したらMessageスクリプトのSetMessagePanelにメッセージを設定して、呼び出します。
今回はマウスの右ボタンを押してメッセージを設定していますが、普通ならば村人と会話を開始するというイベントのタイミングでMessageスクリプトのSetMessagePanelを呼び出せばいいと思います。
上ではMessage2という名前になっていますが、これはわたくしの都合上そうなっているだけです。
Unityの実行ボタンを押して確認してみましょう。
上のようになりました。
メッセージ機能の問題点
以前作成した機能だと1回に表示するメッセージの分割が難しかったり、処理が少し複雑になっていたので、
それらを解消しました。
少し注意が必要なのが、1回に表示するメッセージ用の分割文字列を入れないとUIの設定上文字が見えなくなったりします。
allMessageには区切りの良い所で区切り文字列を入れてください。
区切り文字列はデフォルトで<>としていますが適宜変更してください。
またallMessageをインスペクタでも設定出来るようにしましたが、コンポーネントの歯車のResetを選択した時はスクリプトで記述したallMessageの内容が上書きされます。
なのでスクリプト中で正しい会話内容を指定しておいた方がいいです。
逆にスクリプト中のフィールド等の設定値はコンポーネントの歯車のResetでインスペクタでも反映されます。