【Unity】オブジェクトの頭上に情報を表示する(基本)
様々な方法がありますが、今回は、UI(TMPro)オブジェクトにスクリプトをアタッチすることで、3Dオブジェクトの頭上に情報を表示する基本的な方法について解説します。
考え方
3DオブジェクトのプレイヤーとUIオブジェクトは異なる座標系を使用しているため、位置を適切に変換してUIオブジェクトをプレイヤーの頭上に表示する必要があります。これにより、UIオブジェクトが常にプレイヤーの頭上に表示されるように見せることができます。
サンプルシーン構成
以下の構成でプロジェクトを作成します。
- プロジェクト名:LifeGaugeSample
- プレイヤーオブジェクト:プレイヤーのモデルやキャラクター
- ライフゲージオブジェクト:プレイヤーの頭上に常に表示されるUIオブジェクト(例:テキストや画像)
プレイヤーオブジェクト
プレイヤーオブジェクトは、シーン内で動くキャラクターやモデルです。このオブジェクトの頭上にUIが常に表示されるように設定します。
ライフゲージオブジェクト
ライフゲージオブジェクトには、テキストUI(TMPro)を使用します。このオブジェクトをUIのローカル座標系で移動させ、常にプレイヤーの頭上に留まるようにします。テキスト以外のUI要素(画像やボタンなど)でも同様の手法が適用できます。
スクリプト
ライフゲージオブジェクトにアタッチするスクリプトを作成します。このスクリプトでは、以下の2つのアウトレットをインスペクターから接続します。
- UIテキストの親オブジェクト(Canvas)
- 追従するプレイヤーオブジェクト(Cubeなど)
注意点:
- それぞれの型は
GameObject
型ではなく、具体的なコンポーネント型(RectTransform
やTransform
)である必要があります。これにより、ゲームオブジェクトにアタッチされているコンポーネントのインスタンスを取得できます。
以下は、スクリプトのサンプルです。
using UnityEngine;
// プレイヤーのライフゲージを表示するクラス
public class LifeGauge : MonoBehaviour
{
// 親のRectTransformを指定します。
[SerializeField]
private RectTransform parentRectTransform;
// プレイヤーのTransformを指定します。
[SerializeField]
private Transform playerTransform;
// フレームごとに呼び出されるUpdateメソッド
void Update()
{
// プレイヤーの位置をスクリーン座標に変換します。
Vector3 screenPoint = Camera.main.WorldToScreenPoint(playerTransform.position);
// スクリーン座標をローカル座標に変換します。
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRectTransform, screenPoint, null, out Vector2 localPoint);
// ゲージの位置をプレイヤーの頭上に設定します(Y座標を50ピクセル上に設定)。
transform.localPosition = localPoint + new Vector2(0, 50);
}
}
コード解説
RectTransform の役割
RectTransform
は、UnityのUIシステムで使用されるコンポーネントで、UI要素(テキスト、画像、ボタンなど)の位置、サイズ、回転、スケールを制御します。ライフゲージの親オブジェクト(Canvas)のRectTransform
を参照することで、UI内での正確な位置調整が可能です。
[SerializeField]
private RectTransform parentRectTransform;
parentRectTransform
はプライベートな変数で、[SerializeField]
属性によりインスペクターから設定可能です。この変数はライフゲージを配置する親のRectTransform
を参照します。
ワールド座標からスクリーン座標への変換
プレイヤーの位置を3D空間のワールド座標から2Dスクリーン座標に変換します。これにより、UI上で正確な位置に表示することが可能です。
Vector3 screenPoint = Camera.main.WorldToScreenPoint(playerTransform.position);
Camera.main
を使用して、プレイヤーの位置をスクリーン座標に変換し、その結果をscreenPoint
に格納します。
ワールド座標
3D空間を指す座標
スクリーン座標
UI(Canvas)での2次元座標
左下が原点とした位置を示します
プレイヤーとスクリーン座標
Camera.mainを使用して、playerTransformの位置をワールド座標からスクリーン座標に変換し、その座標をscreenPointに格納します。これにより、キャラクターの位置情報を画面上の座標に変換できます。
スクリーン座標から親(Canvas)の子オブジェクト(Text(TMP))としてのローカル座標系に変換
ライフゲージのText(TMP)が子オブジェクトなので、続いてローカル座標系に変換します
screenPointを parentRectTransform のローカル座標系に変換します。結果は localPoint に格納されます。これにより、親RectTransform内での位置を取得できます。
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRectTransform, screenPoint, null, out Vector2 localPoint);
RectTransformUtility.ScreenPointToLocalPointInRectangle メソッドは、UnityのUIシステムで使用される、スクリーン座標から指定された親要素のローカル座標系への変換を行うための便利なメソッドです。このメソッドはUI要素の操作やイベント処理の際に非常に役立ちます。
以下に、このメソッドの各引数と役割を説明します:
- parentRectTransform: これは、ローカル座標系に変換するための親要素の RectTransform コンポーネントを指定するパラメータです。スクリーン座標からローカル座標に変換する際、この親要素の座標系を基準として変換が行われます。
- screenPoint: screenPoint は、スクリーン上での座標を指定するパラメータです。通常、マウスの位置やタッチイベントの位置など、デバイスの画面上での座標を指定します。
canvas
(null): canvas パラメータは、特定の Canvas グループに関連する場合に指定されます。通常、これをnullに設定し、screenPoint と parentRectTransform の間の変換を行います。ただし、特定の Canvas グループに関連付けられている場合、それを指定できます。- localPoint: localPoint パラメータは、メソッドの出力として使用されます。メソッドが成功した場合、
localPoint
に変換されたローカル座標が格納されます。この座標は、指定された parentRectTransform 内での位置を示します。
このメソッドは、スクリーン上の座標(screenPoint)を、指定した親要素のローカル座標系に変換するのに使用されます。ローカル座標系の原点はCanvasの中心地点です。例えば、マウスカーソルの位置を取得し、それをUI要素のローカル座標系内で使用したい場合に便利です。このメソッドを使用することで、UI要素の位置に関する計算を簡略化し、正確な位置情報を得ることができます。
アンカーを中心とした時の座標系のイメージがローカル座標系のイメージになります
実際は、アンカーの位置を変えてもローカル座標系の原点には反映されません
UnityでUI要素(例えば、ライフゲージ)を3Dオブジェクト(プレイヤー)の頭上に表示する際、RectTransformUtility.ScreenPointToLocalPointInRectangle
メソッドを使用してスクリーン座標をUIのローカル座標系に変換します。このときのローカル座標系の中心位置(原点)がどこに設定されているかは、UIの配置や設定において重要なポイントです。
ローカル座標系の原点とは?
ローカル座標系の原点は、特定のRectTransform
コンポーネントにおける基準点のことを指します。この原点は、主に以下の2つのプロパティによって決定されます。
- アンカー(Anchor)
- ピボット(Pivot)
ピボット(Pivot)の役割
RectTransform
のローカル座標系における原点は、ピボットポイントによって決定されます。ピボットは、UI要素の回転やスケーリングの基準点として機能するだけでなく、ローカル座標系の原点としても使用されます。
- デフォルト設定: 通常、
RectTransform
のピボットは(0.5, 0.5)
に設定されており、これはUI要素の中心を意味します。 - カスタマイズ: ピボットを変更することで、ローカル座標系の原点をUI要素の異なる位置(例えば、左下や右上)に移動させることができます。
この場合の原点位置
今回のスクリプトおよび設定において、以下のように考えることができます。
- 親
RectTransform
(Canvas)のピボット:- 通常、Canvasの
RectTransform
のピボットは(0.5, 0.5)
であり、Canvasの中心がローカル座標系の原点となります。 - これにより、
RectTransformUtility.ScreenPointToLocalPointInRectangle
メソッドで変換されたlocalPoint
は、Canvasの中心を基準とした相対位置となります。
- 通常、Canvasの
- 子
RectTransform
(ライフゲージ)のローカル座標:- ライフゲージの
RectTransform
のピボットも通常は(0.5, 0.5)
です。 - スクリプト内で設定される
transform.localPosition
は、親Canvasのローカル座標系における位置であり、ピボットを基準とした相対位置になります。
- ライフゲージの
図解による説明
以下に、ローカル座標系の原点位置を視覚的に説明します。
Canvas(親 RectTransform)
+---------------------------+
| |
| (0,0) | ← ピボット(中心)がローカル座標系の原点
| + |
| |
| ライフゲージ |
| |
+---------------------------+
- Canvasの中心が
(0,0)
の原点となります。 RectTransformUtility.ScreenPointToLocalPointInRectangle
によって取得されたlocalPoint
は、この原点を基準に計算されます。- スクリプトでは、
localPoint
にオフセット(例えば、new Vector2(0, 50)
)を加えることで、ライフゲージがプレイヤーの頭上に適切に配置されます。
ピボットの変更がローカル座標系に与える影響
ピボットを変更すると、ローカル座標系の原点も移動します。例えば、ピボットを左下に設定すると、ローカル座標系の原点はCanvasの左下に移動します。この設定により、localPoint
の基準が変わるため、ライフゲージの表示位置も相対的に変動します。
例: ピボットを左下に設定
Canvas(親 RectTransform)
+---------------------------+
| |
| (0, Canvas.height) | ← ピボットが左下に移動
| + |
| |
| ライフゲージ |
| |
+---------------------------+
この場合、localPoint
は左下を基準として計算されるため、スクリプト内でのオフセット調整もそれに合わせて行う必要があります。
まとめ
- ローカル座標系の原点は、
RectTransform
のピボットポイントに基づいて決定されます。 - 通常、Canvasのピボットは中央に設定されており、これがローカル座標系の原点となります。
- ピボットを変更することで、ローカル座標系の原点も変更され、UI要素の配置やスクリプト内での位置調整に影響を与えます。
- スクリプトを使用してUI要素をプレイヤーの頭上に表示する際は、親Canvasのピボット設定を確認し、それに基づいて位置調整を行うことが重要です。
これにより、UI要素が期待通りに表示され、プレイヤーの動きに合わせて正確に追従するようになります。
ライフゲージの位置設定
取得したローカル座標にオフセットを加算して、ライフゲージをプレイヤーの頭上に表示します。
transform.localPosition = localPoint + new Vector2(0, 50);
ここでは、Y軸方向に50ピクセルのオフセットを加えています。この値を調整することで、ライフゲージの表示位置を微調整できます。
このスクリプトは、ゲームオブジェクトにアタッチされた際に、指定された親RectTransform内でキャラクターの位置に合わせてライフゲージを表示するのに役立ちます。
ここでのコードでは以下の行が使用されています:
transform.localPosition = localPoint + new Vector2(0, 50);
これに対して、「なぜ RectTransform.localPosition
ではなく transform.localPosition
を使用しているのか」ですが、具体的には、このスクリプトがアタッチされているオブジェクトがCanvas下のTextMeshPro子オブジェクトであることを踏まえています。
1. Transform
と RectTransform
の関係
まず、Transform
と RectTransform
の関係を明確にしましょう。
Transform
:- Unityのすべてのゲームオブジェクトは
Transform
コンポーネントを持っています。 - オブジェクトの位置、回転、スケールを管理します。
- Unityのすべてのゲームオブジェクトは
RectTransform
:Transform
のサブクラスであり、主にUI要素(Canvasの子オブジェクトなど)に使用されます。- UIのレイアウトやアンカー、ピボットなどの追加機能を提供します。
重要な点は、RectTransform
は Transform
を継承しているということです。つまり、RectTransform
は Transform
のすべてのプロパティとメソッドを持ちながら、さらにUI特有の機能を追加しています。
2. なぜ transform.localPosition
で問題ないのか
RectTransform
が Transform
を継承しているため、transform.localPosition
を使用しても機能的には問題ありません。具体的には以下のようになります。
// RectTransformがTransformを継承しているため、localPositionは使用可能
transform.localPosition = localPoint + new Vector2(0, 50);
この場合、transform
は実際には RectTransform
コンポーネントへの参照となります。したがって、localPosition
を設定することで、UI要素の位置を変更することができます。
3. RectTransform
を明示的に使用する利点
技術的には transform.localPosition
でも動作しますが、コードの可読性と意図の明確化の観点から、RectTransform
を明示的に使用することが推奨されます。以下にその理由を示します。
- 可読性の向上:
- 他の開発者がコードを読んだ際に、このオブジェクトがUI要素であることが明確になります。
- UI特有のプロパティへのアクセス:
RectTransform
にはanchoredPosition
やsizeDelta
など、UIレイアウトに特化したプロパティが存在します。これらを使用する場合、RectTransform
を明示的に扱う方が自然です。
- 一貫性:
- プロジェクト全体で
RectTransform
を明示的に使用することで、一貫したコードスタイルを維持できます。
- プロジェクト全体で
具体例:
以下のように RectTransform
をキャッシュして使用する方法が考えられます。
using UnityEngine;
public class LifeGauge : MonoBehaviour
{
[SerializeField]
RectTransform parentRectTransform;
[SerializeField]
Transform playerTransform;
// RectTransformをキャッシュする
RectTransform rectTransform;
void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
void Update()
{
Vector3 screenPoint = Camera.main.WorldToScreenPoint(playerTransform.position);
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRectTransform, screenPoint, null, out Vector2 localPoint);
Debug.Log("screenPoint: " + screenPoint);
Debug.Log("localPoint: " + localPoint);
// RectTransformを使用
rectTransform.localPosition = localPoint + new Vector2(0, 50);
}
}
4. anchoredPosition
の使用を検討する
UI要素の位置を設定する際には、localPosition
よりも anchoredPosition
を使用する方が適切な場合があります。anchoredPosition
はアンカーに基づいた位置を設定するため、レイアウトの柔軟性が向上します。
rectTransform.anchoredPosition = localPoint + new Vector2(0, 50);
まとめ
transform.localPosition
は技術的に問題なく使用可能:RectTransform
はTransform
を継承しているため、transform.localPosition
を使用しても機能します。
RectTransform
を明示的に使用することを推奨:- コードの可読性と意図の明確化のために、
RectTransform
を使用する方が望ましいです。 - UI特有のプロパティ(例:
anchoredPosition
)を使用する際にも便利です。
- コードの可読性と意図の明確化のために、
- 実装例の改善:
RectTransform
をキャッシュし、anchoredPosition
を使用することで、より柔軟で明確なコードになります。
元のコードで transform.localPosition
を使用している理由は、おそらく単純に Transform
がベースクラスであり、機能的には問題がないためです。しかし、将来的な拡張性や可読性を考慮すると、RectTransform
を明示的に使用する方がベストプラクティスといえます。
発展(インスペクターで頭上位置の調整ができるようにする)
次のようにリファクタリングすることで、プレイヤーと表示されるUIとの位置関係の調整をインスペクターから調整できるようになります
using UnityEngine;
public class LifeGauge : MonoBehaviour
{
[SerializeField] private RectTransform parentRectTransform;
[SerializeField] private Transform playerTransform;
[SerializeField] private Vector2 offset = new Vector2(0, 50);
private void Update()
{
UpdateLifeGaugePosition();
}
private void UpdateLifeGaugePosition()
{
Vector3 screenPoint = Camera.main.WorldToScreenPoint(playerTransform.position);
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRectTransform, screenPoint, null, out Vector2 localPoint);
transform.localPosition = localPoint + offset;
}
}
変更点の説明
- オフセットフィールドの導入csharpコードをコピーする
[SerializeField] private Vector2 offset = new Vector2(0, 50);
offset
フィールドを追加し、UIの表示位置をインスペクターから調整できるようにしました。これにより、表示位置の微調整が容易になります。 - 位置更新処理の分離csharpコードをコピーする
private void UpdateLifeGaugePosition() { // 位置更新の処理 }
位置更新のコードをUpdateLifeGaugePosition
メソッドに切り出し、Update
メソッドをシンプルに保ちました。これにより、コードの可読性と保守性が向上します。
利点
保守性の向上:将来的な変更や拡張が容易になります。保守性が向上しました。また、オフセットの調整が簡単になり、コードの変更が効率的に行えるようになります。
柔軟な調整:インスペクターからオフセット値を変更できるため、ライフゲージの表示位置を簡単に調整できます。
コードの整理:位置更新処理をメソッドに分離することで、コードが整理され、理解しやすくなります。
ディスカッション
コメント一覧
まだ、コメントがありません