ゲームで何らかのイベントが発生した時に、それを見逃すとゲームの進行に支障をもたらすことがあります。
そんなわけで、今回はUnityのゲーム内で発生したイベント等のログを確認出来る機能を作成していきたいと思います。
今回の機能を作成すると、以下のような感じのものが出来ます。
上の例ではログを上から表示していますが、今回はログを下から追加するパターンも作成しています。
二つのパターンで作成
今回作成するログ表示機能は二通りの表示方法で作成したいと思います。
二通りと言っても、新しいログを(チャットのように)上から表示していくのか、下から追加していくかの違いです。
どちらにするかでUIの設定やスクリプトが少し変わってきます。
上から表示していくログ表示機能の作成
まずは上からログを表示していく機能を作成していきます。
UIの作成
ログを表示するUIを作成します。
ログを表示するUIはScrollViewを使って作ります。
ヒエラルキー上で右クリックからUI→Scroll Viewを選択します。
Scroll Viewを作成すると、スクロールバーを使ってスクロールをすることが出来る領域のUIが作成されます。
Canvasを選択し、名前をLogSystem1とし、インスペクタでCanvas ScalerのUI Scale ModeをScale With Screen Sizeに変更します。
Scale With Screen Sizeにするとスクリーンサイズ(ゲーム画面サイズ)に応じてUIが拡大・縮小します。
参照するReference Resolution(参照解像度)は800×600であるとしてそのままにしておきます(解像度に応じて変更してください)。
次にヒエラルキーのLogSystem1の子にあるContentを選択し、右クリックからUI→Textを選択します。
このTextにログのテキストを表示していくことになります。
今回のUIではTextを使いますが、TextMeshProを用いてももちろんいいです。
UIの設定
ログ表示用のUIが出来たので、後は設定をしていきます。
まずはLogSystem1の子のScroll Viewを選択しログ表示UIをゲーム画面の左下に表示するようにします。
インスペクタのRect TransformコンポーネントのAnchor Presetsのアイコンを押します。
押したら、Shift+Altキーを押しながら左下のアイコンを押します。
これでUI全体がゲーム画面の左下に移動します。
次にAnchorがbottom leftとなっているのを確認し、Widthを300、Heightを200とします。
ImageのColorを押し、黒色にし、A(透明度)を0にして透けないようにします。
さらにScroll RectコンポーネントのContentにLogSystem1の子のTextをドラッグ&ドロップして設定します。
Contentはスクロールバーでスクロールする対象を設定する必要があり、今回の場合はログを表示したTextのサイズに応じてスクロール出来るようにしています。
次にMovement TypeをClampedにします。
これはスクロールする対象(今回の場合はText)をマウスドラッグした場合等に表示領域の範囲内に制限して動かすようにする為の設定です。
Unrestrictedに設定すると表示領域に関係なく制限を超えて動いてしまいます。
次にScroll Sensitivityを50にします。
これはマウスのスクロールボタンでスクロールする場合のスクロール感度で、値を大きくするとスクロール量が大きくなります。
次にHorizontal ScrollbarのVisibilityをAuto Hideにし横にスクロールする必要がない場合はスクロールバーは自動で隠れるようにします。
Auto Hideと似たものでAuto Hide And Expand Viewportというものもありますが、こちらの場合はスクロールバーが非表示になった場合にビューポート(表示領域)をスクロールバーを隠した領域に拡大するようになります。
次にVeritical ScrollbarのVisibilityをPermanentにし、縦にスクロールする必要がなくても常にスクロールバーを表示するようにします。
次にヒエラルキーでViewportを選択します。
Viewportは表示領域なので、ログのテキストを表示する領域に合わせてサイズを調整します。
初期設定だとAnchorがstretch stretchとなっており、Scroll View全体のサイズに合わせて拡大・縮小するようになっています。
ですがこのままだとテキストがこのScroll Viewの範囲ギリギリに表示されてしまうので、少し見づらいです。
そこでViewportのサイズをScroll Viewよりも全体的に小さくするようにし、少し隙間があるようにします。
シーンビューでViewportの四隅の△のアイコンをShiftキーを押しながら押し、Scroll Viewより全体的に小さくなるように移動させます。
Viewportの四隅を移動させたことによってRect TransformのRight等に数値が入ったら0を入力します。
次にViewportの子のContentを選択し、インスペクタのRect TransformでAnchor PresetsからShift+Altキーを押しながらstretch stretchを選択し、数値を0にします。
次にContentの子のTextを選択し、Anchor PresetsからShift+Altキーを押しながらtop stretchを選択します。
これでTextはContentの上部から始まり、横はContentのサイズに合わせて拡大・縮小します。
次にTextコンポーネントのFont Sizeを18、Rich Textにチェックを入れ、Colorを白色にします。
Font Sizeは自由に設定してください(UIの調整が必要になります)。
Rich Textにチェックを入れるとテキストにタグを使用することが出来ます。
使用出来るタグはUnityマニュアルを参照してください。
Colorはデフォルトのテキストの色を設定します。
次にTextのインスペクタの下のAdd Componentを押し、検索窓でcontentと入力し、出てきたContent Size Fitterコンポーネントを取り付けます。
Content Size Fitterはレイアウト要素のサイズを自動でコントロールしてくれます。
今回の場合はログ表示のテキストサイズはどんどん変わるので、それに合わせてテキストの領域をContent Size Fitterで自動で変更するようにします。
ただし、変更するのは縦の長さだけで、横の長さは変えないので、Veritical FitだけPreferred Sizeにし、自動で最適化します。
これでログを表示するUIが出来ました。
UIはログを表示するようにしてから微調整が必要ですが、とりあえず次に行きましょう。
ログ表示スクリプトの作成
ログを表示するUIが出来たので次はログを表示する為のスクリプトを作成していきます。
ログを全て表示したり、ログの種類を分けてその種類のログだけを出力出来るようにします。
ひとつのログの全リストにログのテキストとログの種類を保持することも出来ますが、ログの種類によってログの全リストからデータを抽出するようにし、ログの数を制限してログが一定数たまった時に削除すると、今はイベントログだけを表示するようにしているのに、タイマーのログが全ログリストに追加されていき、イベントログがどんどん削除されてしまうということが起きて制御が面倒になります。
そこで今回は「全てのログリスト」、「タイマーログリスト」、「イベントログリスト」とログのリストを分け、表示するログの設定によって表示するリスト自体を変えるようにしました。
ただし、リスト事に保存するログ数を設定するようにしたので、設定の仕方によっては全ログリストに載っていないのに、イベントログリストにはデータがあるということもあります。(^_^;)
新しくLogSystemという名前のスクリプトを作成し、LogSystem1ゲームオブジェクトに取り付けます。
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 | using System; using System.Collections; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; public class LogSystem : MonoBehaviour { // 表示するログの種類の列挙型 public enum LogType { All, Time, Event } // ログ出力先テキスト [SerializeField] private Text logText; // 全データ private List<string> allLogs; // 時間のデータ private List<string> timerLogs; // イベントのデータ private List<string> eventLogs; // 現在表示するログの種類 [SerializeField] private LogType logTypeToDisplay = LogType.All; // ログを保存する数 [SerializeField] private int allLogDataNum = 10; [SerializeField] private int timerLogDataNum = 10; [SerializeField] private int eventLogDataNum = 10; // 縦のスクロールバー [SerializeField] private Scrollbar verticalScrollbar; private StringBuilder logTextStringBuilder; // Start is called before the first frame update void Start() { allLogs = new List<string>(); timerLogs = new List<string>(); eventLogs = new List<string>(); logTextStringBuilder = new StringBuilder(); } // ログテキストの追加 public void AddLogText(string logText, LogType logType) { // ログテキストの追加 allLogs.Add(logText); if(logType == LogType.Event) { eventLogs.Add(logText); } else if(logType == LogType.Time) { timerLogs.Add(logText); } // ログの最大保存数を超えたら古いログを削除 if(allLogs.Count > allLogDataNum) { allLogs.RemoveRange(0, allLogs.Count - allLogDataNum); } if(timerLogs.Count > timerLogDataNum) { timerLogs.RemoveRange(0, timerLogs.Count - timerLogDataNum); } if (eventLogs.Count > eventLogDataNum) { eventLogs.RemoveRange(0, eventLogs.Count - eventLogDataNum); } // ログテキストの表示 if (logTypeToDisplay == LogType.All || logTypeToDisplay == logType) { ViewLogText(); } } // 上からログを追加するバージョン // ログテキストの表示 public void ViewLogText() { logTextStringBuilder.Clear(); List<string> selectedLogs = new List<string>(); // ログタイプによって表示するログを変える if (logTypeToDisplay == LogType.All) { selectedLogs = allLogs; } else if (logTypeToDisplay == LogType.Event) { selectedLogs = eventLogs; } else if (logTypeToDisplay == LogType.Time) { selectedLogs = timerLogs; } foreach (var log in selectedLogs) { logTextStringBuilder.Insert(0, log + Environment.NewLine); } logText.text = logTextStringBuilder.ToString().TrimEnd(); UpdateScrollBar(); } // スクロールバーの位置の更新 public void UpdateScrollBar() { verticalScrollbar.value = 1f; } } |
列挙型でLogTypeを作成しています。
今回の場合は全ログ、タイマーログ、イベントログの3つのログの種類を作りました。
logTextはログテキストを表示するUIのTextをインスペクタで設定します。
各ログのstring型のリストを作成しました。
logTypeToDisplayは今表示するリストを何にするかの設定で、インスペクタで変更出来るようにしています。
各ログリストの最大保存数をリスト事に作成します。
今回の場合は各リストは10個までログを保存できるようにしています。
verticalScrollbarは横のスクロールバーをインスペクタで設定します。
リストに保存したログを連結する時にログの数が多いと処理が遅くなるので、StringBuilderを使って連結処理を速くします。
Startメソッドでは各ログリストとlogTextStringBuilderのインスタンスを生成しています。
AddLogTextメソッドはログを追加するメソッドで、引数でログテキストとログの種類を受け取ります。
全てのログリストには必ずテキストを保存します。
その後ログの種類によってログテキストを追加するリストを選択します。
各ログリストには最大保存数を設定したので、もし最大保存数を超えたログが保存されたら超過分のリストをRemoveRangeメソッドで削除しています。
通常であればRemoveAtメソッドの引数に0を指定して一番最初の(一番古い)ログだけを削除すればいいんですが、ゲーム中に保存できるログ数を変える事も想定してRemoveRangeメソッドにしました。
ログが追加されたら表示するログの設定が全ログリスト、または表示するログの設定と引数で受け取ったログの種類が一致する場合だけViewLogTextメソッドを呼んでログテキストの表示をします。
ViewLogTextメソッドでは最初にlogTextStringBuilderをクリアしています。
次に表示するログを選定する為にlogTypeToDisplayの値を比較し、新しく作ったselectedLogsリストに入れています。
foreachを使ってselectedLogsのリストの要素分を繰り返し、logTextStringBuilderのInsertメソッドを使って先頭にログテキストに改行コードを追加したものを挿入します。
Environment.NewLineを使うとOSのよって違う改行コードを指定する必要がなく、OSに合わせて改行コードに変換してくれます。
最後にlogTextStringBuilder.ToString()で文字列に変換し、TrimEndメソッドで最後の改行を削除し、それをテキストに入れています。
次にUpdateScrollBarメソッドを呼び出しています。
UpdateScrollBarメソッドでは新しいログが追加された時にスクロールバーの値を変更して、そのログにフォーカスします。
今回の場合は上からログが追加されていくので、縦のスクロールバーの値を1にして、Textの一番上が見える状態にスクロールするようにしています。
LogSystem1ゲームオブジェクトのインスペクタのLogSystemで設定をします。
LogTextにはLogSystem1の子のTextをドラッグ&ドロップし、Vertical ScrollbarにはLogSystem1の子のScrollbar Verticalゲームオブジェクトをドラッグ&ドロップします。
これでログを表示するスクリプトが出来ましたが、実際のイベント等を発生し、LogSystemのAddLogTextメソッドを使って実際にログを記録させないといけません。
そこで移動するキャラクターを配置したり、イベントを発生させたり、タイマーのログを記録したりといったことをして、実際にログを記録していこうと思います。
キャラクターの作成と床の作成
まずはキャラクターの作成ですが、これはCharacterControllerコンポーネント等を使って実際に床の上を歩くようなキャラクターを配置してください。
キャラクターの移動スクリプトやAnimatorController等の作成や設定等は各自作成してください。
その他の設定として、キャラクターのゲームオブジェクトのインスペクタのTagにPlayerを設定してください。
またキャラクターの名前を取得出来るスクリプトを作成し、キャラクターに取り付けます。
1 2 3 4 5 6 7 8 9 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerStatus : MonoBehaviour { public string PlayerName { get; set; } = "かめくめちゃん"; } |
PlayerNameというプロパティを持つだけのスクリプトです。
キャラクターの作成と設定が終わったら床のゲームオブジェクトも配置してください。
イベント発生エリアの作成
次にイベントが発生するエリアを作成します。
ヒエラルキーで右クリックから3D Object→Cubeを選択し、名前をEventArea1とします。
Transformで値を変更し、Box ColliderのIs Triggerにチェックを入れて物理的に衝突せず、検知エリアとして使用します。
Assetsフォルダ内で右クリックし、Create→Materialを選択し、名前をEventArea1とします。
EventArea1マテリアルのRendering ModeをTransparentにし、透明の設定が反映されるようにします。
Albedoの横の色の部分を押し、赤色にし、Aを150程度にし少し透けるようにします。
先ほど作ったEventArea1ゲームオブジェクトにドラッグ&ドロップをして設定します。
EventArea1ゲームオブジェクトを選択した状態でCtrl+Dキーを押して複製し名前をEventArea2とし、Transformの変更と、EventArea2マテリアルを作成して設定します。
今回の場合はEventArea2マテリアルの色は黄色にしました。
イベント発生スクリプトの作成
イベントエリアが二つ出来たので、あとはキャラクターがこのエリアに侵入した時と出ていった時にログを記録出来るようにします。
新しくEventAreaScriptというスクリプトを作成し、EventArea1ゲームオブジェクトとEventArea2ゲームオブジェクトの取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class EventAreaScript : MonoBehaviour { // ログシステム [SerializeField] private LogSystem logSystem; // エリアの名前 [SerializeField] private string areaName; private void OnTriggerEnter(Collider other) { if(other.CompareTag("Player")) { logSystem.AddLogText("<color=green>" + other.GetComponent<PlayerStatus>().PlayerName + "</color>" + "が<color=blue>" + areaName + "</color>" + "に侵入しました。", LogSystem.LogType.Event); } } private void OnTriggerExit(Collider other) { if (other.CompareTag("Player")) { logSystem.AddLogText("<color=green>" + other.GetComponent<PlayerStatus>().PlayerName + "</color>" + "が<color=blue>" + areaName + "</color>" + "から出ました。", LogSystem.LogType.Event); } } } |
logSystemはインスペクタで設定します。
areaNameはこのイベントエリアの名前をインスペクタで設定します。
OnTriggerEnterメソッドはこのエリアのコライダに他のコライダが入ってきた時に呼ばれるので、引数で受け取ったCollider型のotherのタグを調べ、Playerが設定されていればキャラクターが侵入してきたので、logSystemのAddLogTextメソッドを使って表示するテキストとログの種類を渡して呼び出します。
テキストにはキャラクターに取り付けたPlayerStatusスクリプトからキャラクターの名前を取得してタグを使って緑色にし、自身のエリアの名前を付けています。
またログの種類はイベントエリアに侵入したのでLogSystem.LogType.Eventを渡しています。
OnTriggerExitメソッドは自身のコライダから他のコライダが出ていった時に呼ばれるので、そのコライダのタグがPlayerであればキャラクターが出ていったので、この時も同じようにキャラクター名とエリア名をログテキストとして渡し、ログの種類もイベントにしています。
スクリプトが出来たらEventArea1ゲームオブジェクトとEventArea2ゲームオブジェクトに取り付けて、それぞれ設定します。
LogSystemにはヒエラルキーのLogSystem1ゲームオブジェクトをドラッグ&ドロップして設定し、EventArea1ゲームオブジェクトのEventAreaScriptのAreaNameにはRedArea、EventArea2ゲームオブジェクトのEventAreaScriptのAreaNameにはYellowAreaを入力します。
これでイベントエリアが出来ました。
現時点でイベントエリアに侵入したり出て行ったりを行うとログが表示されるのを確認出来ます。
ログが追加されると縦のスクロールバーも変化し、表示出来ないほどログが多くなるとスクロールバーを使ったり、マウスのスクロールで表示領域をスクロール出来ます。
スクロールバーが変化するのはScroll ViewゲームオブジェクトのScroll RectコンポーネントのContentにTextを設定し、そのTextはContent Size Fitterコンポーネントによって自動でサイズを変化させている為です。
Textのサイズを固定したものにするとスクロールバーは変化しません。
タイマーイベントの作成
イベントエリアを作成したことでログが記録されるのを確認出来ました。
次はタイマーのログを記録出来るようにします。
ヒエラルキー上で右クリックからCreate Emptyを選択し、空のゲームオブジェクトを作成します。
名前をTimeLogとします。
新しくTimeLogという名前のスクリプトを作成し、TimeLogゲームオブジェクトに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class TimeLog : MonoBehaviour { [SerializeField] private LogSystem logSystem; private int count; // Start is called before the first frame update void Start() { StartCoroutine(Count()); } IEnumerator Count() { while (true) { yield return new WaitForSeconds(1f); count++; logSystem.AddLogText("<color=red>" + count.ToString() + "</color>秒経過", LogSystem.LogType.Time); } } } |
logSystemはインスペクタでLogSystem1ゲームオブジェクトに取り付けたLogSystemを設定します。
countは経過秒数を入れるフィールドです。
Startメソッドではコルーチンを使ってCountメソッドを呼び出しています。
Countメソッドでは1秒待機した後にcountを1増やし、その後にそのカウントした値をログのテキストとし、ログの種類をLogSystem.LogType.Time(タイマーログ)としています。
TimeLogゲームオブジェクトのTimeLogコンポーネントのLogSystemにはLogSystem1ゲームオブジェクトをドラッグ&ドロップして設定します。
実際に試すと以下のような感じになります。
これで上からログを表示していくログ機能が出来ました。
下から追加していくログ表示機能の作成
ログを上から表示していくログ機能が出来たので、次はログを下から追加するパターンを作成していきます。
ログの表示機能の基本は上から表示するのとほとんど同じなので、違う部分だけを解説していきます。
まずはヒエラルキーでLogSystem1ゲームオブジェクトを選択し、Ctrl+Dキーを押して複製して名前をLogSystem2とします。
LogSystem2の子のTextゲームオブジェクトを選択し、インスペクタのAnchor PresetsからShift+Altキーを押しながらbottom stretchを選択します。
これでこのTextのテキストは下側に寄せられます。
次にLogSystemスクリプトのViewLogTextメソッドとUpdateScrollbarメソッドを変更します。
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 | // 下からログを追加するバージョン public void ViewLogText() { logTextStringBuilder.Clear(); List<string> selectedLogs = new List<string>(); if (logTypeToDisplay == LogType.All) { selectedLogs = allLogs; } else if (logTypeToDisplay == LogType.Event) { selectedLogs = eventLogs; } else if (logTypeToDisplay == LogType.Time) { selectedLogs = timerLogs; } foreach (var log in selectedLogs) { logTextStringBuilder.Append(Environment.NewLine + log); } logText.text = logTextStringBuilder.ToString().TrimStart(); UpdateScrollBar(); } public void UpdateScrollBar() { verticalScrollbar.value = 0f; } |
下からログを追加する場合はStringBuilderのAppendメソッドを使って「改行コード+ログテキスト」という形で追加します。
下からログを追加するので、連結したログテキストの先頭の改行コードが不要となる為、TrimStartメソッドを使って削除したものをUIのテキストに入れています。
最後にEventArea1ゲームオブジェクト、EventArea2ゲームオブジェクトのEventAreaScriptのLogSystemにLogSystem2ゲームオブジェクトをドラッグ&ドロップをして設定し、TimeLogゲームオブジェクトのTimeLogのLogSystemにもLogSystem2ゲームオブジェクトをドラッグ&ドロップして設定します。
これで上からログを表示する機能から下からログを追加する機能への変換が出来ました。
終わりに
今回はログの種類によって表示を切り替えられるようにする為に3つのリストを作りました。
そもそもログの種類で分ける必要がない場合は一つのリストを使うだけで出来ます。
今回は作成していませんが、ログ表示を切り替えられるようにする場合はUIにログの種類のボタンを用意して、ボタンが押されたらlogTypeToDisplayの値を変更し、ViewLogTextメソッドを呼ぶ処理が必要になります(ログを更新する為)。
その他やるべきこととして、ログの表示で一部だけ見えてしまうことがあるので、調整をする必要があります。
上のように下からログを追加していくパターンではテキストのサイズがViewportの範囲ときっちりあっていない為、「3秒経過」の上の部分が切れてしまっています。
こういった場合はUnity実行時にViewportゲームオブジェクトの四隅の△を調整し、テキストが綺麗に表示されるようにし、ViewportのインスペクタのRect Transformの右の3つの丸の部分を押して、Copy→Componentをした後にUnityの実行をやめ、再度Rect Transformの右の3つの丸を押しPaste→Component Valuesを選択し、実行時にコピーした値をペーストします。
上からログを表示する場合は下側が切れる可能性があるので、そちらを調整する必要があります。
実行時にコンポーネントの値をコピーし、実行後にペーストするやり方については以下の記事を参照してください。