9 - プレイヤーUIプレハブ

このセクションでは、プレイヤーUIシステムを作成する方法を説明します。 プレイヤー名と現在の体力を表示する必要があります。 UIの位置も管理してプレイヤーを追従するようにします。

このセクションはネットワーキングとは関係がありませんが、ネットワーキングに関するいくつかの高度な機能を提供し、また開発時の制約について説明するため重要な設計パターンをいくつか記載しています。

UIは単純に必要がないのでネットワーキング化されませんが、トラフィックを使用せずに進める方法は他にいくつもあります。 これは常に追及すべきことです。機能をネットワーク化せずにすむのは良いことです。

次に考えるべきことは、ネットワーク化された各プレイヤーにどのようにUIを用意するかです。

専用のPlayerUIスクリプトを使用したUIプレハブがあります。 PlayerManagerスクリプトはこのUIプレハブの参照を保持し、PlayerManagerが開始されるとこのUIプレハブをインスタンス化し、プレハブに担当のプレイヤーを追従するように指示します。

Contents

UIプレハブの作成

  1. UI Canvasのある任意のシーンを開きます。
  2. キャンバスにスライダーUI GameObjectを追加してPlayer UIと名付けます。
  3. Rect Transform の垂直アンカーをMiddleに、水平アンカーをCenterに設定します。
  4. RectTransformの幅を80に、高さを15に設定します。
  5. Backgroundの子を選択し、Imageコンポーネントの色をRedにします。
  6. 「Fill Area/Fill」の子を選択し、Imageの色をgreenにします。
  7. Text UI GameObjectをPlayer UIの子として追加し、Player Name Textと名付けます。
  8. 階層からAssetのPrefab folderにPlayer UIをドラッグします。これでプレハブができました。
  9. 今後は不要なため、シーン内のインスタンスを削除します。

Back To Top

PlayerUIスクリプトの基本

  1. 新しいC#スクリプトを作成し、PlayerUIと名付けます。
  2. 以下がスクリプトの基本の構成です。これに応じて編集し、PlayerUIスクリプトを保存します。

            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
    
    
            }
        }
  3. PlayerUIスクリプトを保存します。

ではプレハブ自体を作成してみましょう。

  1. プレハブPlayerUIPlayerUIスクリプトを追加します。
  2. パブリックフィールドPlayerNameTextに子のGameObjectである「Player Name Text」をドラッグアンドドロップします。
  3. パブリックフィールドPlayerHealthSliderにSlider Componentをドラッグアンドドロップします。

Back To Top

インスタンス化とプレイヤーとの結合

PlayerUIをプレイヤーと結合する

PlayerUIスクリプトは、何よりもまず、体力とプレイヤー名を表示するため、どのプレイヤーを代表しているのか把握していなければなりません。 ではパブリックメソッドを作成してこの結合を可能にしてみましょう。

  1. スクリプトPlayerUIを開きます。
  2. 「プライベートフィールド」リージョンでプライベートプロパティを追加します。  

        PlayerManager target;

    ここで頭に入れておくべきことは、体力については定期的に調べるため、効率的に動作させるため参照をキャッシュしておくのが便利だということです。

  3. 「Public Methods」リージョンにこのパブリックメソッドを追加します。

        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;
        }
    }
  4. MonoBehaviour CallBacksリージョンにこのメソッドを追加します。

        void Update()
    {
        // Reflect the Player Health
        if (playerHealthSlider != null)
        {
            playerHealthSlider.value = target.Health;
        }
    }
  5. PlayerUIスクリプトを保存します。

これで、UIが対象のプレイヤー名と体力を表示するようになります。

Back To Top

インスタンス化

プレイヤープレハブをインスタンス化するたびに、このプレハブをインスタンスする必要があることがわかりました。 初期化中にPlayerManagerで行うのがインスタンス化の最良の方法です。

  1. スクリプトPlayerManagerを開きます。
  2. 参照を保持するため、以下のようにパブリックフィールドをPlayerUI参照に追加します。

        [Tooltip("The Player's UI GameObject Prefab")]
    [SerializeField]
    private GameObject playerUiPrefab;
  3. Start()メソッドにこのコードを追加します。

        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);
    }
  4. PlayerManagerスクリプトを保存します。

これらは全てUnityの標準コードですが、メッセージの送信先が作成したばかりのインスタンスである点に留意してください。 SetTargetがそれに反応するコンポーネントを見つけられない場合、警告がでて通知されるよう、受信者が必要です。 もう一つの方法としては、PlayerUIコンポーネントをインスタンスから取得し、直接SetTargetを呼び出します。 一般的に、直接コンポーネント使用することを推奨しますが、他にも取得する方法があることを知っておくのも良いことです。

ですが、これだけではまだ十分ではありません。プレイヤーの削除も処理する必要があります。シーンの中にorphanのUIインスタンスが分散するのを避けるため、割り当てられた対象が無くなったことを確認し次第、UIインスタンスを破棄する必要があります。

  1. PlayerUIスクリプトを開きます。
  2. Update()機能にこれを追加します。

        // 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;
    }
  3. PlayerUIスクリプトを保存します。このコードは簡単であり、かつ、とても使い勝手のいいものです。対象の参照がnullになった際、Photonのネットワーク化されたインスタンスの削除の方法を理由として、UIインスタンスが自分自身を破棄する方が簡単です。 このやり方をすると、起こり得る多くの問題を避けることができます。そしてとても安全で対象が名¥いあなくなった理由によらず、関連するUIは自動的に自分で破棄するので、速く処理でき便利です。

    ただしこのままでは、新しいレベルが読み込まれたとき、UIは破棄されますがプレイヤーはとどまります。このため、レベルの読み込みを把握したらプレイヤーもインスタンス化する必要があります。以下のように処理します。

  4. スクリプトPlayerManagerを開きます。

  5. このコードをCalledOnLevelWasLoaded()メソッドに追加します。

        GameObject _uiGo = Instantiate(this.playerUiPrefab);
    _uiGo.SendMessage("SetTarget", this, SendMessageOptions.RequireReceiver);
  6. PlayerManagerスクリプトを保存します。

この処理を行うにはより複雑で強力な方法もあり、UIはシングルトンから作成することも可能です。ただし、ルームに入室したり退室したりしている他のプレイヤーも動揺にのUIを処理する必要があるので、たちまち複雑になってしまいます。今回の実装では、UIプレハブのインスタンス化による重複で、明快な処理になっています。簡潔に実行するため、「SetTarget」メッセージをインスタンス化して送信するプライベートメソッドを作成し、コードを複製する代わりに、様々な場所からこのメソッドを呼び出すことも可能です。

Back To Top

UIキャンバスのペアレンティング

Unity UIシステムの重要な制約の1つは、UI要素は全てCanvas GameObjectに配置しなければいけないということです。そのため、PlayerUIプレハブがインスタンス化される時に処理をする必要があります。これは、PlayerUIのインスタンス化の際に行います。

  1. スクリプトPlayerUIを開きます。
  2. 「MonoBehaviour CallBacks」リージョンにこのメソッドを追加します。

        void Awake()
    {
        this.transform.SetParent(GameObject.Find("Canvas").GetComponent<Transform>(), false);
    }
  3. PlayerUIスクリプトを保存します。 なぜこのように力ずくでキャンバスを見つけるのでしょうか?シーンが読み込まれたりアンロードされる際は、プレハブと同様キャンバスも毎回異なるからです。これ以上複雑なコード構成を避け、一番迅速な方法を使います。ただし「Find」の使用はお勧めしません。このオペレーションは遅いからです。 このようなケースで複雑な処理を実装することは、このチュートリアルの趣旨とは離れますが、Unityやスクリプトに慣れてきたら試してみてください。読み込みやアンロードを考慮に入れた、キャンバス要素参照のより良い管理方法をコーディングでき  ます。

Back To Top

対象のプレイヤーを追従する

これは興味深いパートです。Player UIが対象のプレイヤーをスクリーンで追従するようにする必要があります。 以下のような細かい処理が必要です。

  • UIは2D要素ですが、プレイヤーは3D要素です。このようなケースで、どのようにして位置を一致させられるでしょうか?
  • UIがプレイヤーよりわずかに上に位置することは好ましくありません。プレーヤーの位置から画面上でオフセットするには、どうすればよいでしょうか?

    1. PlayerUIスクリプトを開きます。
    2. 「Public Fields」リージョンにこのパブリックプロパティを追加します。

          [Tooltip("Pixel offset from the player target")]
      [SerializeField]
      private Vector3 screenOffset = new Vector3(0f,30f,0f);
    3. これら2つのプライベートフィールドを「Private Fields Messagesリージョン」に追加します。

          float characterControllerHeight = 0f;
      Transform targetTransform;
      Vector3 targetPosition;
    4. _targetが設定されたら、SetTarget() メソッドに以下のコードを追加します。

          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;
      }

      プレイヤーはHeightプロパティをもつCharacterControllerに基づいています。プレイヤーの上にあるUI要素を適切にオフセットするために必要な処理です。

    5. このパブリックメソッドを「Public Methods」リージョンに追加します。

          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;
      }
      }
    6. PlayerUIスクリプトを保存します。

2Dの位置と3Dの位置を合わせるためのポイントは、カメラのWorldToScreenPoint機能を使用することです。このゲームでは1つしかないので、Unityシーンのデフォルト設定であるメインカメラにアクセスします。

複数のステップでオフセットを設定しました。最初に対象の実際の位置を取得して、characterControllerHeightを追加し、最後にプレイヤーのトップ画面位置を推定した後、最後に画面オフセットを追加します。

前に戻る.

ドキュメントのトップへ戻る