【Unity2022】スプラインで線を描画する

こじゃらこじゃら

Unity公式のスプラインで線を描く方法を教えてほしいの。

このはこのは

おススメの方法があるので紹介していくね。

Unity 2022.1より使えるようになったビルトインスプラインで線を描画する方法の紹介です。

やり方は一通りではありませんが、本記事では次の2つの方法を紹介します。

スプラインの描画方法
  • Spline Extrudeを用いて描画する
  • Line Rendererスクリプトを用いて描画する

それぞれ特徴が異なるため、必要に応じて使い分けると良いでしょう。

本記事では、このようなスプラインを描画する方法について解説していきます。

動作環境
  • Unity 2022.1.0f1
  • Splines 1.0.1

スポンサーリンク

前提条件

Unity 2022.1以降でUnityプロジェクトが作成され、Splinesパッケージがインストールされているものとします。

また、予めスプラインをシーン上に配置していることとします。

ここまでの手順がわからない方は、以下記事をご覧ください。

Spline Extrudeを用いて線を描画する

Splinesパッケージには、スプラインメッシュを生成するSpline Extrudeコンポーネントが提供されています。

これを用いると、Spline Containerのスプライン情報をもとにメッシュを作成してくれます。

参考:Class SplineExtrude | Splines | 1.0.1

使い方

スプラインオブジェクトにSpline Extrudeコンポーネントを追加します。

すると、以下のようにスプラインメッシュが自動的に作成されます。

また、シーンと同階層にシーン名のフォルダが作られ、その配下にスプラインメッシュのアセットが格納されます。

また、スプラインを編集すると、それに追従するようにメッシュアセットも更新されます。

Spline Extrudeの設定

Spline Extrudeコンポーネントでは、メッシュ生成に関する各種設定ができます。

設定項目は次の通りです。

Spline描画対象のスプライン
Radius線の半径太さ)。
Profile Edges線の側面の数。多いほど綺麗だが頂点数が多くなる。
Segments Per Unit長さ1あたりの分割数。多いほど綺麗だが頂点数が多くなる。
Cap Ends線の切れ目を描画するかどうか。
Range線を描画する範囲。始点と終点を百分率で指定する。
Auto-Regen Geometryスプライン変更時にメッシュを再構築するかどうか。
Update Collidersスプライン変更時にコライダーを更新するかどうか。

参考:Class SplineExtrude | Splines | 1.0.1

xy平面のスプラインを扱う際の注意点

2Dモードでxy平面上にスプラインを配置すると、画像のようにメッシュが捻じれてしまう場合があります。

この場合、奥行きのある配置にすると解消する場合があります。

2Dモードでも線を綺麗に描画させたい場合は、後述するLine Rendererを用いる方法が安全です。

Line Rendererで描画する

スプラインをLine Rendererで描画する手順は以下のような流れになります。

実装の流れ
  • スプラインのパス情報をLine Rendererに反映するスクリプトを実装する
  • Line Rendererを配置・設定する
  • 上のスクリプトをアタッチし適用する

Line Rendererに転送する情報は、線分で近似された曲線になります。 [1]

スクリプトの実装

スプライン情報をLine Rendererに反映するスクリプトの例です。

SplineToLineRenderer.cs
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;

public class SplineToLineRenderer : MonoBehaviour
{
    // 描画対象のスプライン
    [SerializeField] private SplineContainer _splineContainer;

    // Line Renderer
    [SerializeField] private LineRenderer _lineRenderer;

    // セグメントの分割数
    [SerializeField] private int _segments = 30;

    private bool _isDirty;

    private void OnEnable()
    {
        // スプラインの更新時にLineRendererにパスを反映する設定
        _splineContainer.Spline.changed += Rebuild;

        // 初期化時は必ず反映
        Rebuild();
    }

    private void OnDisable()
    {
        _splineContainer.Spline.changed -= Rebuild;
    }

    private void Update()
    {
        // ワールド空間での描画の場合、Transformの更新もチェックしておく
        if (_lineRenderer.useWorldSpace && !_isDirty)
        {
            var splineTransform = _splineContainer.transform;
            _isDirty = splineTransform.hasChanged;
            splineTransform.hasChanged = false;
        }

        if (_isDirty)
            Rebuild();
    }

    // スプラインからLineRendererにパスを反映する
    public void Rebuild()
    {
        // テンポラリバッファを確保
        var points = new NativeArray<Vector3>(_segments, Allocator.Temp);

        // スプラインの読み取り専用情報
        using var spline = new NativeSpline(
            _splineContainer.Spline,
            _lineRenderer.useWorldSpace
                ? _splineContainer.transform.localToWorldMatrix
                : float4x4.identity
        );

        float total = _segments - 1;

        // セグメント数だけ線分を作成
        for (var i = 0; i < _segments; ++i)
        {
            points[i] = spline.EvaluatePosition(i / total);
        }

        // LineRendererに点情報を反映
        _lineRenderer.positionCount = _segments;
        _lineRenderer.SetPositions(points);

        // バッファを解放
        points.Dispose();

        _isDirty = false;
    }
}

このスクリプトをSplineToLineRenderer.csという名前でUnityプロジェクトに保存します。

Line Rendererの準備

スプラインオブジェクトにLine Rendererコンポーネントを追加します。

そして、マテリアルや太さなど各種設定を行います。本記事ではデフォルトマテリアルを設定するものとします。

スクリプトの適用

前述のスクリプトSplineToLineRenderer.csスプラインオブジェクトにアタッチし、各種設定を行います。

実行結果

Line Rendererでスプラインが描画されるようになりました。

なお、LineRendererの情報等を書き換えた場合のチェックは行っていないため、更新した場合はSplineToLineRenderer.Rebuildメソッドを手動で実行する必要があります。

スクリプトの解説

上記のサンプルスクリプトの処理についても触れておきます。

スプラインの更新検知

スプラインのパス情報の変更などは、Spline.changedイベントから知ることができます。

private void OnEnable()
{
    // スプラインの更新時にLineRendererにパスを反映する設定
    _splineContainer.Spline.changed += Rebuild;

    // 初期化時は必ず反映
    Rebuild();
}

参考:Class Spline | Splines | 1.0.1

サンプルでは、Rebuildメソッドを登録しています。

Transform更新チェック

サンプルでは、スプラインオブジェクトの移動や回転などが変化した際にも描画更新されるように対策しています。

private void Update()
{
    // ワールド空間での描画の場合、Transformの更新もチェックしておく
    if (_lineRenderer.useWorldSpace && !_isDirty)
    {
        var splineTransform = _splineContainer.transform;
        _isDirty = splineTransform.hasChanged;
        splineTransform.hasChanged = false;
    }

    if (_isDirty)
        Rebuild();
}

Line Rendererの点情報がローカル空間指定の場合は不要なのでチェックしないようにしています。

Line Rendererへのパス反映

LineRendererコンポーネントの点情報を配列で渡すため、一時的な作業領域を確保します。

// テンポラリバッファを確保
var points = new NativeArray<Vector3>(_segments, Allocator.Temp);

サンプルでは、NativeArrayコンテナを使ってバッファを確保するようにしています。

参照するスプライン情報は、LineRendererのワールド・ローカル空間それぞれに対応したスプラインの座標を得るために、次のようにNativeSplineオブジェクトを通して参照するようにしています。

// スプラインの読み取り専用情報
using var spline = new NativeSpline(
    _splineContainer.Spline,
    _lineRenderer.useWorldSpace
        ? _splineContainer.transform.localToWorldMatrix
        : float4x4.identity
);

参考:Struct NativeSpline | Splines | 1.0.1

スプライン曲線上の座標をLine Rendererの点情報に反映する処理は以下部分です。

// セグメント数だけ線分を作成
for (var i = 0; i < _segments; ++i)
{
    points[i] = spline.EvaluatePosition(i / total);
}

// LineRendererに点情報を反映
_lineRenderer.positionCount = _segments;
_lineRenderer.SetPositions(points);

最後に、一時的に作成したテンポラリバッファを解放する必要があるため、Disposeメソッドで解放しています。

// バッファを解放
points.Dispose();

さいごに

Unity公式のスプラインを描画する2つの方法を紹介しました。

前者のSpline Extrudeを用いる方法は、ノーコーディングで線の描画を実現でき、スプラインメッシュのアセットを作成して使いまわせるメリットがあります。

後者のLine Rendererを用いる方法は、実装コストはかかるものの柔軟性が高いというメリットがあります。

スクリプトとの連携方法については、以下記事でも解説していますので、興味あればご覧ください。

関連記事

参考サイト

スポンサーリンク