LoginSignup
11
7

More than 3 years have passed since last update.

【Unity】範囲外のオブジェクトを端っこで表示させ続けるミニマップ

Last updated at Posted at 2019-07-24

概要

重要な建造物はミニマップやレーダー上で範囲外になっても端っこに表示させ続けるゲームは数多く存在します、多分。
今回はそんなミニマップをUnity上で作ってみようと思います。

ちなみに完成するとこんな感じになります。
今回は範囲外であることがわかるように半透明で表示させています。
05_minimapComplete.gif

下準備

1. とりあえず動くキャラクターと適当にオブジェクトを配置する

  • とりあえず地面と動かせるものを準備する。困ったら豆腐
    • 地面はGround、動かせるものはPlayerとかわかりやすいようにしておく
  • 後はミニマップに表示するためのオブジェクトも適当に配置
    • 名前はObjectとか適当に
  • ついでにカメラもそれっぽく追尾させる

01_moveBox.gif

2. ミニマップのアイコンを用意する

それっぽいアイコンを作ります。
面倒くさかったらProjectからCreate->Spritesを使ってもいいです。
色はSpriteRendererで変えることもできるのでとりあえず白のみの画像で。
minimap_icon.png

3. ミニマップのフレームとマスク画像を用意する

今回は円形で作るので円形のフレームとマスク画像を準備します。即席で作ったので使いたい方はどうぞ。
minimap_frame.png minimap_mask.png

ミニマップを表示する

まずはシンプルな円形ミニマップを作ります。

1. ミニマップ用のカメラを作成する

  • 空のゲームオブジェクトMinimapを作る
  • Minimapの子になるようにミニマップ用のカメラMinimap Cameraを追加
    • Rotationを(90, 0, 0)にしてカメラを真下に向かせる
  • プレイヤーを中心にカメラを動かすスクリプトを作ってMinimap Cameraにアタッチする
    • InspectorからプレイヤーのTransformを設定するのを忘れずに
  • ProjectionをOrthographicにしてSizeを適宜調整する 01_minimapcamera2.png
MinimapCamera.cs
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のカメラプレビューでプレイヤーを追尾していることを確認します。
02_minimapCamera.gif

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を設定する
  • Rect TransformのWidthとHeightは適宜調整する

ヒエラルキーはこんな感じに
02_minimapdisp.png

ミニマップが円形で表示されてることを確認します。
03_minimap.gif

3. ミニマップにアイコンを表示する

現在のミニマップは上から撮ったものをそのまま映しているだけです。そのため、地面とアイコンのみを表示します。

  • 地面用とアイコン用のレイヤーを追加する
    • それぞれGround, Minimapにする
  • Main CameraのCulling MaskからMinimapを除外する
  • Minimap CameraのCullin MaskをGroundMinimapにする

これでメインカメラはMinimap以外、ミニマップカメラはGroundMinimapのレイヤーのみを映すことができます。
アイコンのレイヤーをMinimapに設定するとミニマップ上にアイコンを表示することができます。

早速プレイヤーのアイコンをミニマップに表示させてみます。

  • ヒエラルキーのPlayerにミニマップ用のアイコンをD&Dする
    • 名前はMinimapIconとかにリネームする
  • Rotationを(90, 0, 0)にする
  • レイヤーをMinimapにする

03_minimapicon.png

これでミニマップ上にプレイヤーを示すアイコンを表示することができます。
Objectに対しても同じことを行い、オブジェクトのアイコンも表示させてみるとこんな感じ。
04_minimapIcon.gif

ミニマップ範囲外のオブジェクトを半透明で表示する

アイコン表示を制御するためのスクリプトを作成してObjectMinimapIconにアタッチします。
minimapCameraはInspector上から設定していますが、ミニマップカメラにタグつけてGameObject.FindGameObjectWithTagを使ってもいいと思います。
表示範囲は何も考えずにカメラのorthographicSizeに設定しておきます(orthographicSizeはカメラ縦幅の半分の大きさ)。

MinimapIcon.cs
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座標は統一しておきます。

MinimapIcon.cs
    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です。

MinimapIcon.cs
    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です。

MinimapIcon.cs
        // アイコンを半透明にする
        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.コード全文

MinimapIcon.cs
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にしてみました。
05_minimapComplete.gif

おしまい

というわけで割とお手軽に作ることができました。
作り始めたときは「画面端へのベクトル計算とかどうするねん」って思っていたのですが、Vector3.ClampMagnitudeを使えば面倒な計算とかしなくても実装することができました。

すごく簡単に作ったので「ここおかしくね?」みたいなのあったらぜひコメントください。

参考

11
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
7