logo

UNITYで扇形を作ってみた

投稿日2021-06-28

更新日2024-05-17

Picture of the logo
目次(タップして移動)

扇形のギズモとは?

demo

視界や索敵範囲などを表すために扇形のギズモを用いることがよくありますが、自分の知る限りUnityの標準機能で直接扇形のオブジェクトを作り出す機能が見当たらなかったので自作しました。

スクリプトで動的にメッシュを作り出す

inspector

完成したスクリプトの実行例。このようにインスペクターからも操作できるようになっている。スクリプトでの変形、Dotweenなどでアニメーションさせることも可能。

メッシュとポリゴンと三角形

sankaku

全てのポリゴンは三角形(三つの頂点)からなり、それが集まって形になったものをメッシュと呼ぶ。

新規オブジェクトを作成しコンポーネントとしてアタッチさせる

public GameObject CreateGizmo(GameObject parent, Vector3 loc, Vector3 rot, Material mat)
        {
            GameObject g = Instantiate(new GameObject(), loc + parent.transform.position, Quaternion.Euler(rot)) as GameObject;
            g.transform.parent = parent.transform;
            g.AddComponent<MeshRenderer>();
            g.AddComponent<MeshFilter>();
            g.GetComponent<MeshRenderer>().material = mat;
            return g;
        }

雛形のプレハブを作っても良かったのですが、空オブジェクトを作り各コンポーネントをアタッチする方法をとりました。引数で親オブジェクトを渡し、その下に各オブジェクトを作成し、必要なコンポーネントをアタッチし、作成した子オブジェクトを戻り値として返します。今後はこの子オブジェクトがギズモになります。

三角ポリゴンの配列からメッシュを作成する

    int i = 0;
    vertices.Add(new Vector3(0, 0, 0)); //ここが扇の中心軸になる頂点
    for (float d = -angle / 2f; d <= angle / 2f; d++)
    {
        x = Mathf.Sin(d * Mathf.Deg2Rad) * range;
        y = Mathf.Cos(d * Mathf.Deg2Rad) * range;
        vertices.Add(new Vector3(x, 0, y));
        triangles.AddRange(new int[] { 0, i + 1, i + 2 });
        i++;
    }

forループが-angle/2からプラスのangle/2までなのは、左右に角度の半分づつであるため。角度は整数とは限らないのでfloat型にしている。頂点の座標は三角関数で求める。角度にはラジアン法を使う必要があるので、いちいち変換しなくてはならない。(ここでも計算で重くなりそうだ・・・)

verticesは頂点のリスト型配列。中心軸が0番で、時計回りに1から順に頂点のVector3座標が代入される。

trianglesは各三角ポリゴンの頂点を表している。前述のverticesの添え字番号が入る。1番目の三角ポリゴンの頂点は0,1,2、2番目のポリゴンは0,2,3、3番目のポリゴンは0,3,4がそれぞれ頂点となる。3つの頂点の配列をリストにして、実質メッシュの設計図としている。

ソースコードの全体像

FanGizmo.cs
using System.Collections.Generic;
using UnityEngine;

namespace FanGizmos
{
    /// <summary>
    /// 扇形のギズモを作る
    /// </summary>
    public class FanGizmo : MonoBehaviour
    {
        /// <summary>
        /// 扇形のギズモを作る
        /// </summary>
        /// <param name="parent">ギズモの親オブジェクト</param>
        /// <param name="loc">親オブジェクトとの相対位置</param>
        /// <param name="rot">親オブジェクトとの相対角度</param>
        /// <param name="mat">ギズモに割り当てるマテリアル</param>
        /// <returns>instantiateしたギズモオブジェクト</returns>
        public GameObject CreateGizmo(GameObject parent, Vector3 loc, Vector3 rot, Material mat)
        {
            GameObject g = Instantiate(new GameObject(), loc + parent.transform.position, Quaternion.Euler(rot)) as GameObject;
            g.transform.parent = parent.transform;
            g.AddComponent<MeshRenderer>();
            g.AddComponent<MeshFilter>();
            g.GetComponent<MeshRenderer>().material = mat;
            return g;
        }
        /// <summary>
        /// ギズモを指定した角度、長さに変形する。処理は重いのでUpdate非推奨。
        /// </summary>
        /// <param name="g">ギズモになるgameobjectそのものrefで渡す</param>
        /// <param name="parent">ギズモの親オブジェクト</param>
        /// <param name="angle">ギズモの角度</param>
        /// <param name="range">ギズモの長さ</param>
        public void RefreshGizmo(ref GameObject g, GameObject parent, float angle, float range)
        {
            var mesh = new Mesh();
            List<Vector3> vertices = new List<Vector3>();
            List<int> triangles = new List<int>();
            float x, y;
            int i = 0;
            vertices.Add(new Vector3(0, 0, 0));

            for (float d = -angle / 2f; d <= angle / 2f; d++)
            {
                x = Mathf.Sin(d * Mathf.Deg2Rad) * range;
                y = Mathf.Cos(d * Mathf.Deg2Rad) * range;
                vertices.Add(new Vector3(x, 0, y));
                triangles.AddRange(new int[] { 0, i + 1, i + 2 });
                i++;
            }
            triangles.RemoveRange(triangles.Count - 3, 3);
            mesh.SetVertices(vertices);
            mesh.SetTriangles(triangles, 0);

            mesh.RecalculateNormals();
            g.GetComponent<MeshFilter>().sharedMesh = mesh;

        }
    }
}

RefleshGizmoメソッドの中でGetComponentを使用しているのでUpdate()などの毎フレーム呼ばれるメソッドに組み込むのはおすすめ出来ません。コンポーネントを事前にキャッシュする事も出来ますが、汎用性を優先して今回はメソッド内にGetComponentを組み込みました。

test.cs
using UnityEngine;

public class test : MonoBehaviour
{
    [Header("ギズモの長さ")]
    [SerializeField, Range(0, 100)] private float _sight_range;
    [Header("ギズモの角度")]
    [SerializeField, Range(0, 360)] private float _sight_angle;
    [Header("ギズモに割り当てるマテリアル")]
    [SerializeField] private Material mat;
    private GameObject _gizmo;
    private FanGizmos.FanGizmo _fanGizmo;

    // Start is called before the first frame update
    void Start()
    {
        _fanGizmo = new FanGizmos.FanGizmo();
        _gizmo = _fanGizmo.CreateGizmo(this.gameObject, Vector3.zero, Vector3.zero, mat);
    }

    // Update is called once per frame
    void Update()
    {
        _fanGizmo.RefreshGizmo(ref _gizmo, this.gameObject, _sight_angle, _sight_range);
    }
}

テスト用のスクリプトを用意してみました。Update()内での使用は勧めないと言っておきながら使ってますけど。。。ギズモを持たせたい親オブジェクトにtest.csをアタッチして下さい。FanGizmo.csはアタッチしなくていいです。

inspector

test.csをCubeオブジェクトにアタッチしてみた例

最後に

正直、これはブログにするか迷いました。需要があるかは不明ですが、自身の勉強のため投稿してみました。何かのお役に立てれば幸いです。






このサイトをシェアする