Unity技術資料: 単一キャンバスで複数の3DオブジェクトにステータスUIを表示する方法

本資料では、Unityを使用して単一のキャンバス(Canvas)上に複数の3Dオブジェクトの頭上にステータスUI(例:名前ラベル、ヘルスバー)を表示する方法について詳細に解説します。効率的なUI管理とパフォーマンス最適化を考慮し、スクリーン座標への変換を活用した実装手法を紹介します。

はじめに

Unityにおいて、複数の3Dオブジェクトに対して個別のステータスUIを表示する場合、各オブジェクトごとにキャンバスを配置するとパフォーマンスに影響を与える可能性があります。そこで、単一のキャンバスを使用して効率的にUIを管理する方法を紹介します。


前提条件

  • Unity 2019.4以降
  • 基本的なUnityの操作知識
  • C#プログラミングの基礎知識

実装手順

ステップ1: キャンバスの設定

  1. Canvasの作成
    • Hierarchyビューで右クリック → UICanvasを選択して新しいCanvasを作成します。
    • 作成したCanvasの名前を「StatusCanvas」など分かりやすい名前に変更します。
  2. CanvasのRender Mode設定
    • StatusCanvasを選択し、InspectorビューCanvasコンポーネントのRender Modeを以下のいずれかに設定します。
      • Screen Space – Overlay: UIが常にスクリーン上にオーバーレイ表示されます。
      • Screen Space – Camera: 特定のカメラに対してUIをレンダリングします。この場合、Render Cameraを指定します。
  3. Canvas Scalerの設定
    • CanvasCanvas Scalerコンポーネントを確認し、UI Scale ModeScale With Screen Sizeに設定します。
    • Reference Resolutionを適切な値(例:1920×1080)に設定し、Screen Match ModeMatch Width Or Heightに設定します。

ステップ2: UIプレハブの作成

  1. UI要素の作成
    • StatusCanvasの子として、表示したいステータスのUI要素を作成します。例として、名前表示用のTextとヘルスバー用のSliderを作成します。
    • UI要素の配置やデザインを調整し、見栄えを整えます。
  2. プレハブ化
    • 作成したUI要素(例:名前表示用のTextとヘルスバー用のSliderを含む親オブジェクト)を選択し、Projectビューにドラッグ&ドロップしてプレハブ化します。例として「StatusUI.prefab」と命名します。

ステップ3: スクリプトの作成

スクリプトの新規作成

Projectビューで右クリック → CreateC# Scriptを選択し、「StatusDisplay.cs」と命名します。

スクリプトの編集

  • 以下のコードをStatusDisplay.csに記述します。

using UnityEngine;
using UnityEngine.UI;

public class StatusDisplay : MonoBehaviour
{
    [Header("UI設定")]
    public Camera mainCamera;          // メインカメラ
    public Canvas canvas;              // ステータスを表示するキャンバス
    public GameObject statusPrefab;    // ステータスUIのプレハブ
    public Vector3 offset = new Vector3(0, 2, 0); // UIのオフセット位置

    private GameObject statusUIInstance;
    private RectTransform canvasRectTransform;

    void Start()
    {
        // カメラの設定
        if (mainCamera == null)
            mainCamera = Camera.main;

        // キャンバスの設定
        if (canvas == null)
            canvas = FindObjectOfType<Canvas>();

        // ステータスUIのインスタンスを生成
        statusUIInstance = Instantiate(statusPrefab, canvas.transform);
        canvasRectTransform = canvas.GetComponent<RectTransform>();

        // 初期設定(例:名前の設定)
        Text nameText = statusUIInstance.transform.Find("NameText").GetComponent<Text>();
        nameText.text = gameObject.name;
    }

    void Update()
    {
        if (statusUIInstance != null)
        {
            // オブジェクトの位置にオフセットを加算
            Vector3 worldPosition = transform.position + offset;

            // ワールド座標をスクリーン座標に変換
            Vector3 screenPosition = mainCamera.WorldToScreenPoint(worldPosition);

            // スクリーン座標をキャンバスのローカル座標に変換
            Vector2 anchoredPosition;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                canvasRectTransform,
                screenPosition,
                canvas.renderMode == RenderMode.ScreenSpaceCamera ? mainCamera : null,
                out anchoredPosition
            );

            // UIの位置を更新
            RectTransform statusRect = statusUIInstance.GetComponent<RectTransform>();
            statusRect.anchoredPosition = anchoredPosition;

            // オブジェクトがカメラの前にあるか確認し、UIの表示/非表示を切り替え
            bool isVisible = screenPosition.z > 0;
            statusUIInstance.SetActive(isVisible);
        }
    }

    void OnDestroy()
    {
        // オブジェクトが破壊された際にUIも破壊
        if (statusUIInstance != null)
        {
            Destroy(statusUIInstance);
        }
    }
}
  1. コードの解説
    • 変数説明
      • mainCamera: スクリーン座標への変換に使用するカメラ。指定がない場合はメインカメラを自動取得。
      • canvas: UIを配置するキャンバス。指定がない場合はシーン内のCanvasを自動取得。
      • statusPrefab: ステータス表示用のUIプレハブ。
      • offset: UIをオブジェクトの頭上に表示するための位置オフセット。
    • Startメソッド
      • 必要なコンポーネント(カメラ、キャンバス)を取得。
      • ステータスUIのインスタンスを生成し、キャンバスの子として配置。
      • UI内のTextコンポーネントにオブジェクト名を設定。
    • Updateメソッド
      • オブジェクトの位置にオフセットを加算し、ワールド座標を取得。
      • WorldToScreenPointを使用してワールド座標をスクリーン座標に変換。
      • ScreenPointToLocalPointInRectangleを使用してスクリーン座標をキャンバスのローカル座標に変換。
      • UIのRectTransformanchoredPositionを更新して位置を設定。
      • オブジェクトがカメラの前にあるか確認し、UIの表示/非表示を切り替え。
    • OnDestroyメソッド
      • オブジェクトが破壊された際に対応するUIも破壊することでメモリリークを防止。

ステップ4: スクリプトの適用

  1. プレハブの準備
    • 作成したStatusUI.prefabには、以下のUI要素を含めます。
      • NameText: オブジェクト名を表示するTextコンポーネント。
      • HealthBar: ヘルスを表示するSliderコンポーネントなど。
  2. 3Dオブジェクトへのスクリプト適用
    • Hierarchyビューでステータスを表示したい3Dオブジェクトを選択します。
    • Inspectorビューで「Add Component」をクリックし、StatusDisplayスクリプトを追加します。
    • スクリプトのインスペクターで以下を設定します。
      • Status PrefabStatusUI.prefabをドラッグ&ドロップ。
      • Offset: 必要に応じて調整(例:(0, 2, 0)など)。
  3. 複数オブジェクトへの適用
    • 複数の3Dオブジェクトに対して同様にStatusDisplayスクリプトを追加することで、各オブジェクトの頭上にステータスUIが表示されます。

ステップ5: パフォーマンスの最適化

大量のオブジェクトに対してステータスUIを表示する場合、パフォーマンスの低下が懸念されます。以下の最適化手法を検討します。

  1. プールシステムの導入
    • UI要素の動的生成と破棄を避け、事前に一定数のUIインスタンスをプールして再利用します。これにより、ガベージコレクションの発生を抑制し、パフォーマンスを向上させます。
  2. UIの更新頻度の調整
    • 全てのUIを毎フレーム更新するのではなく、一定間隔(例:0.1秒ごと)で更新するようにします。Updateメソッドではなく、Coroutineを使用する方法も有効です。
  3. カリングの活用
    • カメラの視野外にあるオブジェクトのUIは非表示にすることで、不要な描画を削減します。Renderer.isVisibleプロパティを活用して、オブジェクトの可視状態を判定します。
  4. UI要素の軽量化
    • 必要最低限のUI要素のみを表示し、過剰なエフェクトやアニメーションを避けることで、描画負荷を軽減します。

補足: ワールドスペースキャンバスの代替案

上記の方法はスクリーンスペースキャンバスを使用していますが、特定のケースではワールドスペースキャンバスを使用する方が適している場合があります。ワールドスペースキャンバスは3D空間内にUIを配置するため、カメラとの連動が自然になります。

ワールドスペースキャンバスの設定手順

  • キャンバスの設定変更
    • StatusCanvasを選択し、CanvasコンポーネントのRender ModeWorld Spaceに変更します。
    • RectTransformのサイズを適切に調整し、UIが適切なスケールで表示されるようにします。
  • UIプレハブの作成
    • ワールドスペースキャンバス用のUIプレハブを作成します。通常、テキストや画像は3Dオブジェクトの子として配置します。
    • StatusUI.prefabをワールドスペースキャンバスに適したサイズと配置に調整します。
  • スクリプトの調整
    • ワールドスペースキャンバスを使用する場合、UIの位置は3D空間内で直接管理します。スクリーン座標への変換は不要です。StatusDisplay.csUpdateメソッドを以下のように変更します。
void Update()
{
    if (statusUIInstance != null)
    {
        // UIの位置をオブジェクトの頭上に設定
        Vector3 worldPosition = transform.position + offset;
        statusUIInstance.transform.position = worldPosition;

        // オブジェクトがカメラの前にあるか確認
        Vector3 toCamera = mainCamera.transform.position - transform.position;
        bool isVisible = Vector3.Dot(transform.forward, toCamera) > 0;
        statusUIInstance.SetActive(isVisible);
    }
}

ワールドスペースキャンバスでは、UIが3D空間内に存在するため、直接位置を設定するだけで表示位置が反映されます。


まとめ

本資料では、Unityにおいて単一のキャンバスを使用して複数の3Dオブジェクトの頭上にステータスUIを表示する方法について解説しました。スクリーンスペースキャンバスとワールドスペースキャンバスの両方の手法を紹介し、各々の利点と実装手順を説明しました。また、大量のオブジェクトに対応する際のパフォーマンス最適化手法についても触れました。

効率的なUI管理とパフォーマンスの維持を両立させるために、プロジェクトの要件に応じて最適な手法を選択してください。

UI,Unity

Posted by hidepon