9 - プレイヤーUIプレハブ
このセクションでは、プレイヤーUIシステムを作成する方法を説明します。
プレイヤー名と現在の体力を表示する必要があります。
UIの位置も管理してプレイヤーを追従するようにします。
このセクションはネットワーキングとは関係がありませんが、
ネットワーキングに関するいくつかの高度な機能を提供し、また開発時の制約について説明するため重要な設計パターンをいくつか記載しています。
UIは単純に必要がないのでネットワーキング化されませんが、トラフィックを使用せずに進める方法は他にいくつもあります。
これは常に追及すべきことです。機能をネットワーク化せずにすむのは良いことです。
次に考えるべきことは、ネットワーク化された各プレイヤーにどのようにUIを用意するかです。
専用のPlayerUI
スクリプトを使用したUIプレハブがあります。
PlayerManager
スクリプトはこのUIプレハブの参照を保持し、PlayerManager
が開始されるとこのUIプレハブをインスタンス化し、プレハブに担当のプレイヤーを追従するように指示します。
UIプレハブの作成
- UI Canvasのある任意のシーンを開きます。
- キャンバスにスライダーUI GameObjectを追加して
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
と名付けます。 CanvasGroup
コンポーネントをPlayer UI
に追加します。- その
CanvasGroup
コンポーネントでInteractable
とBlocks Raycast
プロパティをfalse
に設定します。 Player UI
を階層からアセットのプレハブフォルダーにドラッグします。これでプレハブができました。- 今後は不要なため、シーン内のインスタンスを削除します。
PlayerUIスクリプトの基本
- 新しいC#スクリプトを作成し、
PlayerUI
と名付けます。 - 以下がスクリプトの基本の構成です。これに応じて編集し、
PlayerUI
スクリプトを保存します。
C#
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
namespace Com.MyCompany.MyGame
{
public class PlayerUI : MonoBehaviour
{
#region Private Fields
[Tooltip("UI Text to display Player's Name")]
[SerializeField]
private Text playerNameText;
[Tooltip("UI Slider to display Player's Health")]
[SerializeField]
private Slider playerHealthSlider;
#endregion
#region MonoBehaviour Callbacks
#endregion
#region Public Methods
#endregion
}
}
PlayerUI
スクリプトを保存します。
ではプレハブ自体を作成してみましょう。
- プレハブ
PlayerUI
にPlayerUI
スクリプトを追加します。 - パブリックフィールドPlayerNameTextに子のGameObjectである「Player Name Text」をドラッグアンドドロップします。
- パブリックフィールドPlayerHealthSliderにSlider Componentをドラッグアンドドロップします。
インスタンス化とプレイヤーとの結合
PlayerUIをプレイヤーと結合する
PlayerUI
スクリプトは、何よりもまず、体力とプレイヤー名を表示するため、どのプレイヤーを代表しているのか把握していなければなりません。
その状態と名前を表示できるようにするためです。このバインディングを可能にするパブリックメソッドを作成しましょう。
- スクリプト
PlayerUI
を開きます。 - 「プライベートフィールド」リージョンでプライベートプロパティを追加します。
C#
private 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.NickName;
}
}
MonoBehaviour CallBacksリージョンにこのメソッドを追加します。
C#
void Update() { // Reflect the Player Health if (playerHealthSlider != null) { playerHealthSlider.value = target.Health; } }
PlayerUI
スクリプトを保存します。
これで、UIが対象のプレイヤー名と体力を表示するようになります。
インスタンス化
プレイヤープレハブをインスタンス化するたびに、このプレハブをインスタンスする必要があることがわかりました。
初期化中にPlayerManagerで行うのがインスタンス化の最良の方法です。
スクリプト
PlayerManager
を開きます。参照を保持するため、以下のようにパブリックフィールドをPlayerUI参照に追加します。
C#
[Tooltip("The Player's UI GameObject Prefab")] [SerializeField] private GameObject playerUiPrefab;
Start()
メソッドにこのコードを追加します。C#
if (playerUiPrefab != null) { GameObject _uiGo = Instantiate(playerUiPrefab); _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
を呼び出します。
一般的に、直接コンポーネント使用することを推奨しますが、他にも取得する方法があることを知っておくのも良いことです。
ですが、これだけではまだ十分ではありません。プレイヤーの削除も処理する必要があります。シーンの中にorphanの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
スクリプトを保存します。
このコードは簡単であり、かつ、とても使い勝手のいいものです。対象の参照がnullになった際、Photonのネットワーク化されたインスタンスの削除の方法を理由として、UIインスタンスが自分自身を破棄する方が簡単です。
このやり方をすると、起こり得る多くの問題を避けることができます。そしてとても安全で対象が名¥いあなくなった理由によらず、関連するUIは自動的に自分で破棄するので、速く処理でき便利です。
スクリプト
PlayerManager
を開きます。このコードを
CalledOnLevelWasLoaded()
メソッドに追加します。C#
GameObject _uiGo = Instantiate(this.playerUiPrefab); _uiGo.SendMessage("SetTarget", this, SendMessageOptions.RequireReceiver);
PlayerManager
スクリプトを保存します。
この処理を行うにはより複雑で強力な方法もあり、UIはシングルトンから作成することも可能です。ただし、ルームに入室したり退室したりしている他のプレイヤーも動揺にのUIを処理する必要があるので、たちまち複雑になってしまいます。今回の実装では、UIプレハブのインスタンス化による重複で、明快な処理になっています。簡潔に実行するため、「SetTarget」メッセージをインスタンス化して送信するプライベートメソッドを作成し、コードを複製する代わりに、様々な場所からこのメソッドを呼び出すことも可能です。
UIキャンバスのペアレンティング
Unity UIシステムの重要な制約の1つは、UI要素は全てCanvas GameObjectに配置しなければいけないということです。そのため、PlayerUIプレハブがインスタンス化される時に処理をする必要があります。これは、PlayerUIのインスタンス化の際に行います。
スクリプト
PlayerUI
を開きます。「MonoBehaviour CallBacks」リージョンにこのメソッドを追加します。
C#
void Awake() { this.transform.SetParent(GameObject.Find("Canvas").GetComponent<Transform>(), false); }
PlayerUI
スクリプトを保存します。
なぜこのように力ずくでキャンバスを見つけるのでしょうか?シーンが読み込まれたりアンロードされる際は、プレハブと同様キャンバスも毎回異なるからです。
これ以上複雑なコード構成を避け、一番迅速な方法を使います。ただし「Find」の使用はお勧めしません。このオペレーションは遅いからです。
このようなケースで複雑な処理を実装することは、このチュートリアルの趣旨とは離れますが、Unityやスクリプトに慣れてきたら試してみてください。読み込みやアンロードを考慮に入れた、キャンバス要素参照のより良い管理方法をコーディングでき ます。
対象のプレイヤーを追従する
これは興味深いパートです。Player UIが対象のプレイヤーをスクリーンで追従するようにする必要があります。
以下のような細かい処理が必要です。
- UIは2D要素ですが、プレイヤーは3D要素です。このようなケースで、どのようにして位置を一致させられるでしょうか?
- UIがプレイヤーよりわずかに上に位置することは好ましくありません。プレイヤーの位置から画面上でオフセットするには、どうすればよいでしょうか?
PlayerUI
スクリプトを開きます。「Public Fields」リージョンにこのパブリックプロパティを追加します。
C#
[Tooltip("Pixel offset from the player target")] [SerializeField] private Vector3 screenOffset = new Vector3(0f,30f,0f);
これら2つのプライベートフィールドを「Private Fields」リージョンに追加します。
C#
float characterControllerHeight = 0f;
Transform targetTransform;
Renderer targetRenderer;
CanvasGroup _canvasGroup;
Vector3 targetPosition;
- これを
Awake
メソッド領域の中に追加します。
C#
_canvasGroup = this.GetComponent<CanvasGroup>();
_target
が設定された後、次のコードをSetTarget()
メソッドに追加します。
C#
targetTransform = this.target.GetComponent<Transform>();
targetRenderer = this.target.GetComponent<Renderer>();
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;
}
- 「MonoBehaviour Callbacks」リージョンにこのパブリックメソッドを追加します。
C#
void LateUpdate()
{
// Do not show the UI if we are not visible to the camera, thus avoid potential bugs with seeing the UI, but not the player itself.
if (targetRenderer!=null)
{
this._canvasGroup.alpha = targetRenderer.isVisible ? 1f : 0f;
}
// #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
を追加し、最後にプレイヤーのトップ画面位置を推定した後、最後に画面オフセットを追加します。
前に戻る.
Back to top