🚲

点線って可愛いよねって話

2023/12/21に公開

まず初めに

なぜ!! なぜこのUnity1weekまっさかりの間にアドカレの日程を入れてしまったんだ! バカバカ! メイのバカ!もう知らない!
しかも勘違いして今日だと思ってたら昨日だったし!バカバカ。 これは本当に普通に僕が馬鹿!メイちゃんさっきは関係ないのにバカ呼ばわりしてごめんね。メイちゃん悪くない。

というわけで、この記事は Unityゲーム開発者ギルド Advent Calendar 2023 20日目の記事になります。がっつり遅刻しました。本当にごめんなさい。

それはさておき、点線って良いよね

これを見てください。 

どうですか? キャラクターの周りに有効範囲として線を引いているのですが、実線に比べて、点線の方がなんか

  • おしゃれ
  • かわいい
  • 回すだけで良き
    な感じがしませんか?  そう。点線って良いんですよ。

なにで描画するか

言わずもがな、Unityで線を引く時に使用するアレ LineRenderer です。
実線描画用のComponentですが、よっぽど特殊な例でなければ点線描画もLineRendererで賄えます。 ただ、ちょっとコツが居るのでその紹介をしたいと思います。

テクスチャ画像・Materialの準備

まず、点線にするために大事なのはテクスチャです、

このような、透明と白で出来たTextureを用意します。 これは透明部分と白部分の比率が50:50ですが、この比率を変える事で点線の見た目も変わるのでお好みでどうぞ。

また、使用するテクスチャの ImportSettingsWrap ModeRepeat にしておいてください。

そして、それを使用したMaterialを用意します。 今回は DotMaterial という名前にしました。
シェーダーはBuild-inやURPなど、環境によって若干変わってくるのでよしなに設定してくれればいいのですが、注意点としては

  • 透過(Transparent)系じゃないとテクスチャが透過でも、透過されなくて意味ない
  • LineRenderer の color 指定を適用してくれるShaderが好ましい(なぜか UI/Unlit/Transparent とかだと良さげだったので今回はそれを指定してます。)

LineRendererの設定

適当な空Objectなどに、AddComponent から LineRenderer を指定します。
(確認のために Positions の座標にちょっと大きめの数字を入れると良いです ↓の例では Index:1 に (10,0,10) の座標を入れてあります。 )

(LineRendererって何故か最初Materialが指定されてないからド紫が表示されていっつも一瞬ビビりますよね)

次に Materials の項目に↑で作成したMaterialをセットします。 

これだけで点線描画に!
ならないんだなぁこれが。

コレジャナイ

ちゃんと点線になるように TextureModeTextureScale を設定します。

  • TextureModeStretch のままに
  • TextureScale の X の方に、点線の点の個数を入れます

今回は10にしたので、直線に対してTextureが10回リピートされることになり、点が10個の点線が引かれました。

では、頂点(Positions) を増やして、四角を描いてみます。
頂点を増やす場合は先に Size を増やします(今回は4)

(0,0,0) を左下として、
右下(10,0,0)
右上(10,0,10)
左上(0,0,10)
を追加してみましょう。

最後にまた原点(左下)にもどる (0,0,0) を追加してもよいのですが、LineRendererには Loop という指定があるので、Loop にチェックも入れます。

じゃーん・・・?

あれ、なんか・・・?線が歪んでますよね?
これの対処法は Corner Vertices に1以上の数字を入れる事で解決します。

つぎに、点が少ない!
これは先程 Texture Scale で点の数を10と指定したわけですが、これは頂点増やしても その増えた直線毎に10使われるわけではなく、全体で10個の点になる ため、このようなスカスカの点線になってしまっています。

頂点を増やしたのであれば、それなりに点の数も増やしてあげると良いでしょう


また、線の太さはグラフの(多分最初は1となっている)箇所を一度クリックすると、値が指定できるので、よしなに数字変更をすると理想の点線に近づけることが出来ます。

複雑な線

では、冒頭の例のように 円を描きたい場合 はどうしたら良いでしょうか。
Positions で 頂点を増やしていくのにも限界があるので、Script で LineRenderer をセットアップしてくれる君を用意してしまうのが後々を考えるとお得です。

そんなわけで、ColliderからLineRendererを設定するScriptを用意してみました。

コードを展開
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(LineRenderer)), ExecuteAlways]
public class LineRendererFromCollider : MonoBehaviour
{
    [SerializeField] private Collider _collider;
    [SerializeField] private LineRenderer _lineRenderer;
    [SerializeField] private Color _color = Color.white;
    [SerializeField] private Vector3 _offset = new Vector3(0, 0.1f, 0);
    [SerializeField] private float _dotCnt = 12;

    private bool isDirty;
    private Bounds oldBounds;

    private void Reset()
    {
        _collider = GetComponent<Collider>();
        _lineRenderer = GetComponent<LineRenderer>();
    }

    private void OnValidate()
    {
        isDirty = true;
    }

    private void Update()
    {
        if (Application.isPlaying) return;
        if (_collider.bounds != oldBounds)
        {
            isDirty = true;
        }

        oldBounds = _collider.bounds;
        if (isDirty == false) return;
        isDirty = false;
        this.Apply();
    }

    private void Apply()
    {
        _lineRenderer.startColor = _lineRenderer.endColor = _color;

        var vertices = new List<Vector3>();

        switch (_collider)
        {
            case BoxCollider box:
            {
                var size = box.size;
                var center = box.center;
                vertices.Add(center + _offset + new Vector3(-size.x / 2, 0, -size.z / 2));
                vertices.Add(center + _offset + new Vector3(size.x / 2, 0, -size.z / 2));
                vertices.Add(center + _offset + new Vector3(size.x / 2, 0, size.z / 2));
                vertices.Add(center + _offset + new Vector3(-size.x / 2, 0, size.z / 2));
                _lineRenderer.textureScale = new Vector2(_dotCnt, 1);
                break;
            }
            case SphereCollider sphere:
            {
                var size = sphere.radius;
                var center = sphere.center;
                for (var i = 0; i < 360; i += 15)
                {
                    vertices.Add(center + _offset + new Vector3(Mathf.Cos(i * Mathf.Deg2Rad) * size, 0, Mathf.Sin(i * Mathf.Deg2Rad) * size));
                }

                _lineRenderer.textureScale = new Vector2(_dotCnt, 1);
                break;
            }
            case CapsuleCollider capsule:
            {
                var size = capsule.radius;
                var center = capsule.center;
                for (var i = 0; i < 360; i += 15)
                {
                    vertices.Add(center + _offset + new Vector3(Mathf.Cos(i * Mathf.Deg2Rad) * size, 0, Mathf.Sin(i * Mathf.Deg2Rad) * size));
                }

                _lineRenderer.textureScale = new Vector2(_dotCnt, 1);
                break;
            }
        }

        var points = vertices.ToArray();
        _lineRenderer.numCornerVertices = 4;
        _lineRenderer.useWorldSpace = false;
        _lineRenderer.loop = true;
        _lineRenderer.textureMode = LineTextureMode.Stretch;
        _lineRenderer.positionCount = points.Length;
        _lineRenderer.SetPositions(points);
    }
}

とりあえず BoxColliderSphireCollider CapsuleCollider にだけ対応しています。

Script からは色と点の数、それと 対象とした Collider によっては点線が地面に埋まってしまって見えないことがあるので、offsetを渡せるようになっています。

なお localSapce で線は描画されるので、点線全体を回したり、引き伸ばしたりする場合は 自身のtransform に対してよしなに処理してください。

というわけで

知っておくとちょっとお得なLineRendererによる点線描画のお話でした。
今更だけど点線? 破線? まぁ、いいや。ではでは。

Discussion