
射程円を描く
Line RendererでPlayerの足元を中心にした射程円を地面へ描きます。
照準を動かす
Raycastでマウスが指すGroundの位置へTarget Markerを動かします。
届く場所だけ発動
距離がcastRange以内なら緑にし、左クリックでCastAtを呼びます。
導入
スキル射程と発動確定を迷わせない戦闘UI設計ガイドでは、ボタンで数字を変えるのではなく、地面へ射程円を描き、マウス位置が届くかを色で示してから発動を確定します。完成イメージの円、照準、発動ラインを順番に作ります。
これから作るもの
- 3Dシーンに Player GameObject と Plane を置きます。Plane の Layer を
Groundにします。 - Player の子に空の GameObject を作り、
RangeLineと名付けて Line Renderer を追加します。幅は 0.08、Material は発光色にします。 - Cylinder を薄くして
TargetMarkerと名付け、Player とは別の GameObject にします。届く場所は緑、届かない場所は赤で表示します。 AbilityRangeIndicatorスクリプトを新規作成し、Player に付けます。Inspector で Range Line と Target Marker をドラッグします。
ステップ1: 射程円を地面へ描く
Line Renderer は点を順番につなぐ部品です。円周上の点を64個作ると、Playerの足元を中心にした射程円になります。
using UnityEngine;
public class AbilityRangeIndicator : MonoBehaviour
{
[SerializeField] private LineRenderer rangeLine; // 地面に描く射程円
[SerializeField] private Transform targetMarker; // マウス位置を示すマーカー
[SerializeField] private float castRange = 8f; // スキルが届く最大距離
[SerializeField] private int circleSegments = 64; // 円をなめらかに見せる頂点数
private Camera mainCamera;
private Vector3 casterPosition;
private void Awake()
{
mainCamera = Camera.main;
casterPosition = transform.position;
DrawRangeCircle();
}
private void DrawRangeCircle()
{
rangeLine.positionCount = circleSegments + 1;
for (var i = 0; i <= circleSegments; i++)
{
var angle = i * Mathf.PI * 2f / circleSegments;
var offset = new Vector3(Mathf.Cos(angle), 0.02f, Mathf.Sin(angle)) * castRange;
rangeLine.SetPosition(i, casterPosition + offset);
}
}
}
ステップ2: マウスが指す地面を見つける
画面のマウス位置から地面へ見えない線を飛ばします。線が Plane に当たった場所を照準の位置にします。これは懐中電灯で地面を照らすイメージです。
private bool TryMoveMarkerToMouse(out bool isInRange)
{
isInRange = false;
var ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if (!Physics.Raycast(ray, out var hit, 200f, LayerMask.GetMask("Ground"))) return false;
var flatTarget = hit.point;
flatTarget.y = casterPosition.y + 0.04f;
targetMarker.position = flatTarget;
var distance = Vector3.Distance(casterPosition, flatTarget);
isInRange = distance <= castRange;
return true;
}
ステップ3: 射程内かどうかを色で伝える
Playerから照準までの距離が castRange 以下なら緑、外なら赤にします。数字を読まなくても、発動できる場所がすぐ分かります。
private void UpdateMarkerColor(bool isInRange)
{
var renderer = targetMarker.GetComponent<Renderer>();
renderer.material.color = isInRange
? new Color(0.25f, 1f, 0.7f, 0.9f) // 届く場所は緑
: new Color(1f, 0.25f, 0.25f, 0.9f); // 届かない場所は赤
}
ステップ4: 左クリックで発動を確定する
Update では毎フレーム照準を動かし、左クリックした瞬間だけ CastAt を呼びます。射程外では発動しないため、完成イメージの『ここには届かない』状態も確認できます。
private void Update()
{
if (!TryMoveMarkerToMouse(out var isInRange)) return;
UpdateMarkerColor(isInRange);
if (Input.GetMouseButtonDown(0) && isInRange)
{
CastAt(targetMarker.position);
}
}
完成版
以下を AbilityRangeIndicator.cs として保存します。Planeに Ground Layer が付いていること、Range LineとTarget MarkerがInspectorへ入っていることを確認してからPlayしてください。
using UnityEngine;
public class AbilityRangeIndicator : MonoBehaviour
{
[SerializeField] private LineRenderer rangeLine; // 地面に描く射程円
[SerializeField] private Transform targetMarker; // マウス位置を示すマーカー
[SerializeField] private float castRange = 8f; // スキルが届く最大距離
[SerializeField] private int circleSegments = 64; // 円をなめらかに見せる頂点数
private Camera mainCamera;
private Vector3 casterPosition;
private void Awake()
{
mainCamera = Camera.main;
casterPosition = transform.position;
DrawRangeCircle();
}
private void DrawRangeCircle()
{
rangeLine.positionCount = circleSegments + 1;
for (var i = 0; i <= circleSegments; i++)
{
var angle = i * Mathf.PI * 2f / circleSegments;
var offset = new Vector3(Mathf.Cos(angle), 0.02f, Mathf.Sin(angle)) * castRange;
rangeLine.SetPosition(i, casterPosition + offset);
}
}
}
private void Update()
{
if (!TryMoveMarkerToMouse(out var isInRange)) return;
UpdateMarkerColor(isInRange);
if (Input.GetMouseButtonDown(0) && isInRange)
{
CastAt(targetMarker.position);
}
}
private bool TryMoveMarkerToMouse(out bool isInRange)
{
isInRange = false;
var ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if (!Physics.Raycast(ray, out var hit, 200f, LayerMask.GetMask("Ground"))) return false;
var flatTarget = hit.point;
flatTarget.y = casterPosition.y + 0.04f;
targetMarker.position = flatTarget;
var distance = Vector3.Distance(casterPosition, flatTarget);
isInRange = distance <= castRange;
return true;
}
private void UpdateMarkerColor(bool isInRange)
{
var renderer = targetMarker.GetComponent<Renderer>();
renderer.material.color = isInRange
? new Color(0.25f, 1f, 0.7f, 0.9f) // 届く場所は緑
: new Color(1f, 0.25f, 0.25f, 0.9f); // 届かない場所は赤
}
private void CastAt(Vector3 point)
{
Debug.DrawLine(casterPosition, point, Color.cyan, 0.5f);
Debug.Log($"スキルを発動: {point}");
}
}
動作確認
- Play を押します。Playerの足元に水色の円が出ます。
- マウスを円の内側へ動かします。照準が緑になります。
- 円の外側へ動かします。照準が赤になります。
- 緑の照準で左クリックします。Consoleに発動位置が出て、Debug LineでPlayerから照準へ線が引かれます。
まとめ
射程UIは、射程円を描く、地面の位置を取る、距離で色を変える、クリックで発動する、の4つで作れます。次は CastAt の中でPrefabを生成すれば、実際の弾や範囲攻撃へ広げられます。
この記事のまとめ
射程円、予測ライン、確定前の注意表示を使って、攻撃や回復スキルの失敗感を減らすための設計を整理します。
大きな違いを生むのは、派手な性能よりも 毎日触れる環境を無理なく整えること です。見やすい画面と使いやすい道具がそろうと、学習や次の一歩を決めやすくなります。