メインコンテンツまでスキップ

UI_2_画面外インジケーター2

このプログラムは、Unityでターゲットインジケーター(矢印など)を画面上に表示するためのものです。

ターゲットが画面外に出た場合、その方向に矢印を表示して、プレイヤーがターゲットの位置を把握できるようにします。


1. クラスとフィールドの定義

[RequireComponent(typeof(RectTransform))]
public class TargetIndicator : MonoBehaviour
{
[SerializeField] private Transform target = default;
[SerializeField] private Image arrow = default;

private Camera mainCamera;
private RectTransform rectTransform;
  • [RequireComponent(typeof(RectTransform))]
    これは、TargetIndicatorスクリプトがRectTransformコンポーネントを必須としていることを示しています。
    つまり、TargetIndicatorスクリプトをこのGameObjectに追加すると、自動的にRectTransformも必要になるということです。

  • target
    ターゲットとなるオブジェクト(例えばプレイヤーが追跡する敵やオブジェクト)のTransformを指定します。これがインジケーターが追う対象となります。

  • arrow
    矢印画像(Image)を指定します。画面外に出たターゲットを指し示す矢印を表示するために使用されます。

  • mainCamera
    mainCameraは、ゲームのカメラオブジェクトを保持します。このカメラを使用して、ターゲットが画面内にいるか外にいるかを判断します。

  • rectTransform
    現在のオブジェクト(このスクリプトがアタッチされているオブジェクト)のRectTransformを保持します。
    画面上でのUIオブジェクトの位置やサイズを扱うために必要です。


2. Startメソッド

private void Start() {
mainCamera = Camera.main;
rectTransform = GetComponent<RectTransform>();
}
  • mainCamera = Camera.main;
    シーン内のメインカメラ(通常はMain Cameraタグが付けられているカメラ)を取得します。ターゲットのワールド座標を画面座標に変換するために必要です。

  • rectTransform = GetComponent<RectTransform>();
    このGameObjectのRectTransformコンポーネントを取得します。UI要素の位置を設定するために使用されます。


3. LateUpdateメソッド

private void LateUpdate() {
float canvasScale = transform.root.localScale.z;
var center = 0.5f * new Vector3(Screen.width, Screen.height);
  • canvasScale = transform.root.localScale.z;
    キャンバスのスケールを取得します。これにより、UIがスケール変更を受けている場合に対応できます。

  • center = 0.5f * new Vector3(Screen.width, Screen.height);
    画面の中央座標を計算します。Screen.widthScreen.heightを使用して、画面の中心を計算します。


4. ターゲットの画面位置の計算

var pos = mainCamera.WorldToScreenPoint(target.position) - center;
if (pos.z < 0f) {
pos.x = -pos.x;
pos.y = -pos.y;

if (Mathf.Approximately(pos.y, 0f)) {
pos.y = -center.y;
}
}
  • mainCamera.WorldToScreenPoint(target.position)
    ターゲットのワールド座標を画面座標に変換します。これにより、ターゲットが画面内のどこに位置するかが分かります。

  • pos.z < 0f の判定
    画面内にターゲットが存在する場合、pos.zは正の値になりますが、ターゲットがカメラの後ろ(画面外)にある場合、pos.zは負の値になります。負の値の場合、ターゲットは画面の反対側にあるため、その座標を反転させます。

  • Mathf.Approximately(pos.y, 0f)
    y座標がほぼ0の場合(水平軸上)、ターゲットが中央に近いため、y座標を中央のy座標に設定します。


5. スクリーン外での矢印の表示

var halfSize = 0.5f * canvasScale * rectTransform.sizeDelta;
float d = Mathf.Max(
Mathf.Abs(pos.x / (center.x - halfSize.x)),
Mathf.Abs(pos.y / (center.y - halfSize.y))
);

bool isOffscreen = (pos.z < 0f || d > 1f);
if (isOffscreen) {
pos.x /= d;
pos.y /= d;
}
rectTransform.anchoredPosition = pos / canvasScale;
  • halfSize = 0.5f * canvasScale * rectTransform.sizeDelta;
    UIオブジェクト(矢印)のサイズの半分を取得し、それをキャンバスのスケールで補正します。

  • d = Mathf.Max(...)
    ターゲットが画面外に出ている場合、その方向に矢印を描画するためのスケーリング係数dを計算します。dは、画面外に出た分だけのスケール補正を行います。

  • isOffscreen = (pos.z < 0f || d > 1f);
    ターゲットが画面外にあるかどうかを判断します。pos.z < 0fはターゲットがカメラの後ろにあることを意味し、d > 1fはターゲットが画面外に広がっていることを意味します。

  • pos.x /= d; pos.y /= d;
    ターゲットが画面外に出ている場合、その位置を矢印が画面端に表示できるようにスケーリングします。

  • rectTransform.anchoredPosition = pos / canvasScale;
    最後に、計算した位置をRectTransformanchoredPositionに設定します。この位置がUIキャンバス上の矢印の位置となります。


6. 矢印の表示と回転

arrow.enabled = isOffscreen;
if (isOffscreen) {
arrow.rectTransform.eulerAngles = new Vector3(
0f, 0f,
Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg
);
}
  • arrow.enabled = isOffscreen;
    ターゲットが画面外に出ている場合、矢印を表示します。isOffscreenfalseの場合、矢印は非表示になります。

  • arrow.rectTransform.eulerAngles = new Vector3(...)
    矢印の向きを計算して回転させます。Mathf.Atan2(pos.y, pos.x)を使って、矢印がターゲットの方向を指すようにします。この角度を度数法に変換して、矢印の回転を設定します。


まとめ

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(RectTransform))]
public class TargetIndicator : MonoBehaviour
{
[SerializeField] private Transform target = default;
[SerializeField] private Image arrow = default;

private Camera mainCamera;
private RectTransform rectTransform;

private void Start() {
mainCamera = Camera.main;
rectTransform = GetComponent<RectTransform>();
}

private void LateUpdate() {
float canvasScale = transform.root.localScale.z;
var center = 0.5f * new Vector3(Screen.width, Screen.height);

var pos = mainCamera.WorldToScreenPoint(target.position) - center;
if (pos.z < 0f) {
pos.x = -pos.x;
pos.y = -pos.y;


if (Mathf.Approximately(pos.y, 0f)) {
pos.y = -center.y;
}
}

var halfSize = 0.5f * canvasScale * rectTransform.sizeDelta;
float d = Mathf.Max(
Mathf.Abs(pos.x / (center.x - halfSize.x)),
Mathf.Abs(pos.y / (center.y - halfSize.y))
);

bool isOffscreen = (pos.z < 0f || d > 1f);
if (isOffscreen) {
pos.x /= d;
pos.y /= d;
}
rectTransform.anchoredPosition = pos / canvasScale;

arrow.enabled = isOffscreen;
if (isOffscreen) {
arrow.rectTransform.eulerAngles = new Vector3(
0f, 0f,
Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg
);
}
}
}