9-プレイヤーUIプレハブ
このセクションでは、Player UIシステムを作成する方法を説明します。プレイヤーの名前と現在の体力を表示する必要があります。
また、プレイヤーを追従するためにUIの位置を管理する必要があります。
このセクションはネットワーキングとは関係がありませんが、ネットワーキングに関するいくつかの高度な機能を提供し、また開発時の制約について説明するため重要な設計パターンをいくつか記載しています。
必要がないのでUIはネットワーク化されません。トラフィックを使用せずに進める方法は、他にたくさんあります。
これは常に追及すべきことです。機能をネットワーク化せずにすむのは良いことです。
次に考えるべきことは、ネットワーク化された各プレイヤーにどのようにUIを用意するかです。
専用のPlayerUIスクリプトを使用したUIプレハブがあります。
PlayerManagerスクリプトはこのUIプレハブの参照を保持し、PlayerManagerが開始されるとこのUIプレハブをインスタンス化し、そのプレイヤーを追従するようプレハブに伝えます。
UIプレハブの作成
- UI Canvasを持つ任意のシーンを開きます。
- キャンバスにスライダーUIGameObjectを追加して
Player UI
と名付けます。 - Rect Transformの垂直アンカーをMiddleに、水平アンカーをCenterに設定します。
- RectTransformの幅を80、高さを15に設定します。
Background
の子を選択し、Imageコンポーネントの色をRedに設定します。- 「Fill Area/Fill」の子を選択し、Image色をGreenに設定します。
- Text UI GameObjectを
Player UI
の子として追加し、Player Name Text
と名付けます。 - 階層から、AssetのPrefab Folderに
Player UI
をドラッグします。これでプレハブができました。 - 今後は不要なため、シーン内のインスタンスを削除します。
PlayerUIスクリプトの基本
新しいC#スクリプトを作成し、それを
PlayerUI
と名付けます。基本的なスクリプトの構造は以下の通りです。これに応じて
PlayerUI
スクリプトを編集し、保存します:C#
using UnityEngine; using UnityEngine.UI; using System.Collections; namespace Com.MyCompany.MyGame { public class PlayerUI: MonoBehaviour { #region Public Properties [Tooltip("UI Text to display Player's Name")] public Text PlayerNameText; [Tooltip("UI Slider to display Player's Health")] public Slider PlayerHealthSlider; #endregion #region Private Properties #endregion #region MonoBehaviour Messages #endregion #region Public Methods #endregion } }
PlayerUI
スクリプトを保存します。
それでは、プレハブ自体を作成しましょう。
1.プレハブPlayerUI
にPlayerUI
スクリプトを追加します。
2. パブリックフィールドPlayerNameTextに子のGameObject「Player Name Text」をドラッグアンドドロップします。
3. パブリックフィールドPlayerHealthSliderにSlider Componentをドラッグアンドドロップします。
インスタンス化とプレイヤーとの結合
PlayerUIをプレイヤーと結合
'PlayerUI'スクリプトは、それがどのプレイヤーを代表しているかを認識する必要があります。その理由の1つは、その体力と名前を表示できるようにするためです。この結合を可能にするためにパブリックメソッドを作成してみましょう。
スクリプト
PlayerUI
を開きます。「Private Properties」リージョンにプライベートプロパティを追加します。
C#
PlayerManager _target;
定期的に体力を調べるため、計画的に考える必要があります。効率を良くするためにPlayerManagerの参照をキャッシュすると有効です。
このパブリックメソッドを「Public Methods」リージョンに追加します。
C#
public void SetTarget(PlayerManager target) { if (target == null) { Debug.LogError("<Color=Red><a>Missing</a></Color> PlayMakerManager target for PlayerUI.SetTarget.",this); return; } // Cache references for efficiency _target = target; if (PlayerNameText != null) { PlayerNameText.text = _target.photonView.owner.name; } }
このメソッドをMonoBehaviour Messages Regionに追加してください。
C#
void Update() { // Reflect the Player Health if (PlayerHealthSlider != null) { PlayerHealthSlider.value = _target.Health; } }
PlayerUI
スクリプトを保存します。
これにより、対象プレイヤーの名前と体力を示すUIを設定できました。
インスタンス化
プレイヤープレハブをインスタンス化するたびに、このプレハブをインスタンス化する必要があると分かりました。
初期化中にPlayerManagerの中からインスタンス化をおこなうのが最良の方法です。
スクリプト
PlayerManager
を開きます。参照を保持するため、以下のようにPlayer UI参照にパブリックフィールドを追加します:
C#
[Tooltip("The Player's UI GameObject Prefab")] public GameObject PlayerUiPrefab;
Start()
メソッド内に以下のコードを追加します。C#
if (PlayerUiPrefab!=null) { GameObject _uiGo = Instantiate(PlayerUiPrefab) as GameObject; _uiGo.SendMessage ("SetTarget", this, SendMessageOptions.RequireReceiver); } else { Debug.LogWarning("<Color=Red><a>Missing</a></Color> PlayerUiPrefab reference on player Prefab.", this); }
PlayerManager
スクリプトを保存します。
これらはすべてUnityの標準的なコーディングです。ただし、先ほど作成したインスタンスにメッセージを送信している点に留意してください。
受信者が必要です。つまり、 反応するコンポーネントをSetTarget
が見つけらなかった場合に通知されます。
もう一つの方法は、インスタンスからPlayerUIコンポーネントを取得し、SetTarget
を直接呼び出す方法です。
コンポーネントを直接使用することは一般的に推奨されますが、様々な方法で同じ目的を達成できることを知っておくと便利です。
しかし、これでは不十分でプレイヤーを削除する必要があります。シーン上にorphelinのUIインスタンスが分散するのを避けるため、割り当てられた対象が無くなったことを確認し次第、UIインスタンスを破棄する必要があります。
PlayerUI
スクリプトを開きます。Update()
機能を追加します。C#
// Destroy itself if the target is null, It's a fail-safe when Photon is destroying Instances of a Player over the network if (_target == null) { Destroy(this.gameObject); return; }
PlayerUI
スクリプトを保存します。
このコードは簡単ながら非常に便利です。
Photonがネットワーク化されたインスタンスを削除する方法を理由として、ターゲット参照がnullで発見された場合にUIインスタンスが自身を破棄した方が簡単です。
そうすることにより潜在的な問題の多くを回避し、非常に安全です。どんな理由でターゲットが欠損している場合でも、関連するUIは自動的に自身を破棄するので非常に便利で迅速です。
ただし、ここで注意が必要です。新しいレベルが読み込まれると、UIは破棄されますがプレイヤーはとどまります。このためレベルが読み込まれたとわかったら、プレイヤーをインスタンス化する必要があります。以下をおこなってみましょう。
スクリプト
PlayerManager
を開きます。CalledOnLevelWasLoaded()メソッド内に以下のコードを追加します。
C#
GameObject _uiGo = Instantiate(this.PlayerUiPrefab) as GameObject; _uiGo.SendMessage("SetTarget", this, SendMessageOptions.RequireReceiver);
PlayerManager
スクリプトを保存します。
この処理をおこなうにはより複雑かつ強力な方法が存在し、UIはシングルトンから作成することも可能です。しかし、ルームに参加したり、ルームから退出したりする他のプレイヤーも同様にUIを処理する必要があるため、処理がすぐに複雑化してしまいます。私達がおこなう実装では、UIプレハブのインスタンス化による重複で、明快な処理になっています。簡潔に実行するため、インスタンス化をおこなうプライベートメソッドを作成して、"SetTarget"メッセージを様々な場所から送信し、コードを複製することなくメソッドを呼ぶことが可能です。
UIキャンバスへのペアレンティング
"SetTarget"
Unity UIシステムの重要な制約の1つは、UI要素はすべてCanvas GameObject内に配置しなければならないことです。そのため、PlayerUIプレハブがインスタンス化される時に処理をする必要があります。これは、PlayerUIのインスタンス化の際に行います。
PlayerUI
スクリプトを開きます。このメソッドを「MonoBehaviour Messages」リージョン内に追加します。
C#
void Awake() { this.GetComponent<Transform>().SetParent (GameObject.Find("Canvas").GetComponent<Transform>()); }
PlayerUI
スクリプトを保存します。
なぜこのように力ずくの方法でキャンバスを見つけるのでしょうか?それは、シーンが読み込まれたりアンロードされる際はプレハブも同様に読み込まれたりアンロードされ、キャンバスは毎回異なるものになるためです。
複雑なコード構造を避けるため、最も簡単な方法を使用します。
「Find」は遅いので、使用は推奨されません。
より複雑な処理を実装するのはこのチュートリアルの範囲外ですが、Unityやスクリプトに慣れたら試してみてください。読み込みやアンロードを考慮に入れた、キャンバス要素参照のより良い管理方法をコーディングできます。
対象プレイヤーの追従
これは非常に興味深い点です。Player UIにPlayerターゲットを追従させる必要があります。
これを実現するには、いくつかの小さな問題を解決する必要があります。
- UIは2D要素で、プレイヤーは3D要素です。この場合、どのようにして位置を一致させられるでしょうか?
*UIがプレイヤーよりわずかに上に位置することは好ましくありません。プレイヤーの位置から画面上でオフセットするには、どうすればよいでしょうか?
これらを解決するには、以下を参照してください:
PlayerUI
スクリプトを開きます「Public Properties」リージョン内にこのパブリックプロパティを追加します。
C#
[Tooltip("Pixel offset from the player target")] public Vector3 ScreenOffset = new Vector3(0f, 30f, 0f);
「Private Properties Messages」内にこれらの3つのプライベートプロパティを追加します。
C#
float _characterControllerHeight = 0f; Transform _targetTransform; Vector3 _targetPosition;
_target
が設定されたら、SetTarget()
メソッドに以下のコードを追加します:C#
_targetTransform = _target.transform; CharacterController _characterController = _target.GetComponent<CharacterController>(); // Get data from the Player that won't change during the lifetime of this Component if (_characterController != null) { _characterControllerHeight = _characterController.height; }
このパブリックメソッドを「Public Methods」リージョン内に追加します:
C#
void LateUpdate() { // #Critical // Follow the Target GameObject on screen. if (_targetTransform!=null) { _targetPosition = _targetTransform.position; _targetPosition.y += _characterControllerHeight; this.transform.position = Camera.main.WorldToScreenPoint (_targetPosition) + ScreenOffset; } }
PlayerUI
スクリプトを保存します。
2Dの位置と3Dの位置を合わせる際のポイントは、カメラのWorldToScreenPoint
機能を使用することです。ゲーム内には1つしかないので、Unityシーンのデフォルト設定であるメインカメラにアクセスします。
オフセットをいくつかのステップで設定しました。最初に対象の実際の位置を取得して_characterControllerHeight
を追加し、プレイヤーのトップの画面位置を推定した後、最後に画面オフセットを追加します。