概要
重要な建造物はミニマップやレーダー上で範囲外になっても端っこに表示させ続けるゲームは数多く存在します、多分。
今回はそんなミニマップをUnity上で作ってみようと思います。
ちなみに完成するとこんな感じになります。
今回は範囲外であることがわかるように半透明で表示させています。
下準備
1. とりあえず動くキャラクターと適当にオブジェクトを配置する
- とりあえず地面と動かせるものを準備する。困ったら豆腐
- 地面は
Ground
、動かせるものはPlayer
とかわかりやすいようにしておく
- 地面は
- 後はミニマップに表示するためのオブジェクトも適当に配置
- 名前は
Object
とか適当に
- 名前は
- ついでにカメラもそれっぽく追尾させる
2. ミニマップのアイコンを用意する
それっぽいアイコンを作ります。
面倒くさかったらProjectからCreate->Sprites
を使ってもいいです。
色はSpriteRenderer
で変えることもできるのでとりあえず白のみの画像で。
3. ミニマップのフレームとマスク画像を用意する
今回は円形で作るので円形のフレームとマスク画像を準備します。即席で作ったので使いたい方はどうぞ。
ミニマップを表示する
まずはシンプルな円形ミニマップを作ります。
1. ミニマップ用のカメラを作成する
- 空のゲームオブジェクト
Minimap
を作る -
Minimap
の子になるようにミニマップ用のカメラMinimap Camera
を追加- Rotationを(90, 0, 0)にしてカメラを真下に向かせる
- プレイヤーを中心にカメラを動かすスクリプトを作って
Minimap Camera
にアタッチする- InspectorからプレイヤーのTransformを設定するのを忘れずに
- ProjectionをOrthographicにしてSizeを適宜調整する
public class Minimap : MonoBehaviour {
[SerializeField] private Player player;
void Update () {
var pos = player.transform.position;
pos.y = transform.position.y;
transform.position = pos;
}
}
Minimap Camera
のカメラプレビューでプレイヤーを追尾していることを確認します。
2. ミニマップを円形で表示する
ミニマップカメラで映しているものを画面上に表示します。
- ProjectからRender Textureを作成、ファイル名は
MinimapRender
にする - Minimap Cameraを選択して、CameraのTarget Textureに作成した
MinimapRender
を設定する
これでRender TextureにMinimap Cameraの映しているものを表示することができます。
次にミニマップを円形にして表示します。
- フレーム用のImageを作成する
- 名前は
Minimap Frame
にしてSource Imageにフレーム画像を設定する
- 名前は
-
Minimap Frame
の子にマスク画像のImageを作成する- 名前は
Minimap Mask
にしてSource Imageにマスク画像を設定する - 設定したらMaskをアタッチして
Show Mask Graphic
のチェックを外す
- 名前は
-
Minimap Mask
の子にRaw Imageを作成する- 名前は'Minimap Render'にしてTextureに
MinimapRender
を設定する
- 名前は'Minimap Render'にしてTextureに
- Rect TransformのWidthとHeightは適宜調整する
3. ミニマップにアイコンを表示する
現在のミニマップは上から撮ったものをそのまま映しているだけです。そのため、地面とアイコンのみを表示します。
- 地面用とアイコン用のレイヤーを追加する
- それぞれ
Ground
,Minimap
にする
- それぞれ
-
Main Camera
のCulling MaskからMinimap
を除外する -
Minimap Camera
のCullin MaskをGround
とMinimap
にする
これでメインカメラはMinimap
以外、ミニマップカメラはGround
とMinimap
のレイヤーのみを映すことができます。
アイコンのレイヤーをMinimap
に設定するとミニマップ上にアイコンを表示することができます。
早速プレイヤーのアイコンをミニマップに表示させてみます。
- ヒエラルキーの
Player
にミニマップ用のアイコンをD&Dする- 名前は
MinimapIcon
とかにリネームする
- 名前は
- Rotationを(90, 0, 0)にする
- レイヤーを
Minimap
にする
これでミニマップ上にプレイヤーを示すアイコンを表示することができます。
Object
に対しても同じことを行い、オブジェクトのアイコンも表示させてみるとこんな感じ。
ミニマップ範囲外のオブジェクトを半透明で表示する
アイコン表示を制御するためのスクリプトを作成してObject
のMinimapIcon
にアタッチします。
minimapCameraはInspector上から設定していますが、ミニマップカメラにタグつけてGameObject.FindGameObjectWithTag
を使ってもいいと思います。
表示範囲は何も考えずにカメラのorthographicSize
に設定しておきます(orthographicSize
はカメラ縦幅の半分の大きさ)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(SpriteRenderer))]
public class MinimapIcon : MonoBehaviour {
[SerializeField] private Camera minimapCamera; // ミニマップ用カメラ
[SerializeField] private Transform iconTarget; // アイコンに対応するオブジェクト(建造物等)
[SerializeField] private float rangeRadiusOffset = 1.0f; // 表示範囲のオフセット
// 必要なコンポーネント
private SpriteRenderer spriteRenderer;
private float minimapRangeRadius; // ミニマップの表示範囲
private float defaultPosY; // アイコンのデフォルトY座標
const float normalAlpha = 1.0f; // 範囲内のアルファ値
const float outRangeAlpha = 0.5f; // 範囲外のアルファ値
private void Start () {
minimapRangeRadius = minimapCamera.orthographicSize;
spriteRenderer = gameObject.GetComponent<SpriteRenderer>();
defaultPosY = transform.position.y;
}
private void Update () {
}
}
1.オブジェクトがミニマップ範囲内か確認する
ミニマップ用カメラとオブジェクトの距離を求めて、その距離がミニマップ範囲内かチェックします。
今回はVector3.Distanceで距離を求めます。平面上の距離を求める必要があるので、Y座標は統一しておきます。
private bool CheckInsideMap() {
var cameraPos = minimapCamera.transform.position;
var targetPos = iconTarget.position;
// 直線距離で判定するため、yは0扱いにする
cameraPos.y = targetPos.y = 0;
return Vector3.Distance(cameraPos, targetPos) <= minimapRangeRadius - rangeRadiusOffset;
}
2.ミニマップ範囲内のオブジェクトを表示
ミニマップ範囲内の場合はSpriteRenderer
のアルファ値を1にしてそのまま表示すればOKです。
private void Update () {
DispIcon();
}
private void DispIcon() {
// アイコンを表示する座標
var iconPos = new Vector3(iconTarget.position.x, defaultPosY, iconTarget.position.z);
// ミニマップ範囲内の場合はそのまま表示する
if (CheckInsideMap()) {
spriteRenderer.color = new Color(spriteRenderer.color.r, spriteRenderer.color.g, spriteRenderer.color.b, normalAlpha);
transform.position = iconPos;
return;
}
}
3.ミニマップ範囲外のオブジェクトを表示
ミニマップ端にアイコンを表示するためにベクトルを計算する必要がありますが、Vector3.ClampMagnitudeを使うことで簡単に求めることができます。
このAPIはベクトルと最大距離を指定すると最大距離までに制限したベクトルを返してくれるすごいやつです。これを紹介するためにこの記事を書いてると言っても過言ではない。
方向ベクトルをVector3.ClampMagnitude
で制限することでミニマップ端までのベクトルを簡単に求めることができます。
半透明にするにはSpriteRenderer
のアルファ値を0.5くらいにすればOKです。
// アイコンを半透明にする
spriteRenderer.color = new Color(spriteRenderer.color.r, spriteRenderer.color.g, spriteRenderer.color.b, outRangeAlpha);
// カメラとアイコンの位置から方向ベクトルを求める
var centerPos = new Vector3(minimapCamera.transform.position.x, defaultPosY, minimapCamera.transform.position.z);
var offset = iconPos - centerPos;
// 指定距離で制限した方向ベクトルを求めてアイコン位置を設定する
transform.position = centerPos + Vector3.ClampMagnitude(offset, minimapRangeRadius - rangeRadiusOffset);
4.コード全文
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(SpriteRenderer))]
public class MinimapIcon : MonoBehaviour {
[SerializeField] private Camera minimapCamera; // ミニマップ用カメラ
[SerializeField] private Transform iconTarget; // アイコンに対応するオブジェクト(建造物等)
[SerializeField] private float rangeRadiusOffset = 1.0f; // 表示範囲のオフセット
// 必要なコンポーネント
private SpriteRenderer spriteRenderer;
private float minimapRangeRadius; // ミニマップの表示範囲
private float defaultPosY; // アイコンのデフォルトY座標
const float normalAlpha = 1.0f; // 範囲内のアルファ値
const float outRangeAlpha = 0.5f; // 範囲外のアルファ値
private void Start () {
minimapRangeRadius = minimapCamera.orthographicSize;
spriteRenderer = gameObject.GetComponent<SpriteRenderer>();
defaultPosY = transform.position.y;
}
private void Update () {
DispIcon();
}
/// <summary>
/// アイコン表示を更新する
/// </summary>
private void DispIcon() {
// アイコンを表示する座標
var iconPos = new Vector3(iconTarget.position.x, defaultPosY, iconTarget.position.z);
// ミニマップ範囲内の場合はそのまま表示する
if (CheckInsideMap()) {
spriteRenderer.color = new Color(spriteRenderer.color.r, spriteRenderer.color.g, spriteRenderer.color.b, normalAlpha);
transform.position = iconPos;
return;
}
// マップ範囲外の場合、ミニマップ端までのベクトルを求めて半透明で表示する
spriteRenderer.color = new Color(spriteRenderer.color.r, spriteRenderer.color.g, spriteRenderer.color.b, outRangeAlpha);
var centerPos = new Vector3(minimapCamera.transform.position.x, defaultPosY, minimapCamera.transform.position.z);
var offset = iconPos - centerPos;
transform.position = centerPos + Vector3.ClampMagnitude(offset, minimapRangeRadius - rangeRadiusOffset);
}
/// <summary>
/// オブジェクトがミニマップ範囲内にあるか確認する
/// </summary>
/// <returns>ミニマップ範囲内の場合、trueを返す</returns>
private bool CheckInsideMap() {
var cameraPos = minimapCamera.transform.position;
var targetPos = iconTarget.position;
// 直線距離で判定するため、yは0扱いにする
cameraPos.y = targetPos.y = 0;
return Vector3.Distance(cameraPos, targetPos) <= minimapRangeRadius - rangeRadiusOffset;
}
}
5.動作確認
スクリプトのInspectorからminimapCamera
, iconTarget
, Range Radius Offset
を設定しつつ、Object
のアイコン表示が期待通りに動作すればOKです。今回はRange Radius Offset
を3.5にしてみました。
おしまい
というわけで割とお手軽に作ることができました。
作り始めたときは「画面端へのベクトル計算とかどうするねん」って思っていたのですが、Vector3.ClampMagnitude
を使えば面倒な計算とかしなくても実装することができました。
すごく簡単に作ったので「ここおかしくね?」みたいなのあったらぜひコメントください。