おもちゃラボ

Unityで遊びを作ってます

【Unity】LineRendererでは引けない線を引く

Unityで線を引きたいときは、LineRendererを使うのが簡単なのですが、テクスチャの張り方を変えたり、線の太さを動的に変えたりと、アレンジした線を引こうとすると、とたんに難しくなります。

ということで、この記事ではLineRendererを使わずに自力で線を引く方法を紹介します。

f:id:nn_hokuson:20180227213645g:plain:w560

記事の内容は次のとおりです。

線を引くアルゴリズム

基本的には自分で線を描画するためのポリゴンを作って、そこにテクスチャを貼っていく感じです。ポリゴンの形状は線の流れに沿って柔軟に変化させます。

f:id:nn_hokuson:20180227203043j:plain:w500

Unityで自作のポリゴンを作る方法はこちらの記事で紹介しているので、合わせて参考にしてください。

nn-hokuson.hatenablog.com

線に沿ってポリゴンを生成するアルゴリズムは次のような流れになります。まず、最初にタッチされた点に2点ぶんの頂点を生成します。

f:id:nn_hokuson:20180227203626p:plain:w200

タッチの座標が移動したら、前タッチ座標から今タッチ座標までのベクトルを求めて、そのベクトルを90度回した点と-90度回した点に新しい頂点を2つ生成します。

f:id:nn_hokuson:20180227204246p:plain:w400

前回と今回の頂点をあわせて4頂点できるので、これで三角形ポリゴンを2つ描画します。あとはこれを繰り返し、タッチの移動に従って四角形のポリゴンを生成していきます。

f:id:nn_hokuson:20180227204531p:plain:w420

ポリゴンを動的に生成するLineスクリプトを作る

プロジェクトウィンドウで右クリックし、Create→C# Scriptを選択、「Line.cs」というスクリプトを作り、次のプログラムを入力してください。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Line : MonoBehaviour
{
    public Material _mat;

    List<Vector3> points = new List<Vector3>();
    List<Vector3> vertices = new List<Vector3>();
    List<Vector2> uvs = new List<Vector2>();
    List<int> tris = new List<int>();

    Mesh mesh;
    int offset = 0;
    float xoffset = 0;
    float penSize = 0.6f;          // 筆の太さ
    float uScrollSpeed = 0.18f;  // テクスチャの伸びる速度

    void CreateMesh(float size)
    {
        Vector2 prev = this.points[this.points.Count - 2];
        Vector2 top = this.points[this.points.Count - 1];
        Vector2 dir = (top - prev).normalized;

        Vector2 plus90  = top + new Vector2(-dir.y, dir.x) * size;
        Vector2 minus90 = top + new Vector2(dir.y, -dir.x) * size;

        // 頂点を追加
        this.vertices.Add(minus90);
        this.vertices.Add(plus90);

        // UVを追加
        this.uvs.Add(new Vector2(xoffset, 0));
        this.uvs.Add(new Vector2(xoffset, 1));
        xoffset += (top - prev).magnitude / 6.0f;////uScrollSpeed; 

        // インデックスを追加
        this.tris.Add(offset);
        this.tris.Add(offset + 1);
        this.tris.Add(offset + 2);
        this.tris.Add(offset + 1);
        this.tris.Add(offset + 3);
        this.tris.Add(offset + 2);

        offset += 2;

        mesh.vertices = this.vertices.ToArray();
        mesh.uv = this.uvs.ToArray();
        mesh.triangles = this.tris.ToArray();

        GetComponent<MeshFilter>().sharedMesh = mesh;
        GetComponent<MeshRenderer>().material = _mat;
    }

    public void PenDown(Vector3 tp)
    {
        // 開始点を保存
        this.points.Add(tp);

        // 頂点を2つ生成
        this.vertices.Add(tp);
        this.vertices.Add(tp);

        // uv座標を設定
        this.uvs.Add(new Vector2(0, 1f));
        this.uvs.Add(new Vector2(0, 0));
        this.offset = 0;

        // メッシュ生成
        this.mesh = new Mesh();
    }

    public void PenMove(Vector3 tp, float size)
    {
        this.points.Add(tp);

        CreateMesh(size);
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            this.points.Clear();
            this.vertices.Clear();
            this.uvs.Clear();
            this.tris.Clear();

            Vector3 tp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            PenDown(tp);
        }
        else if (Input.GetMouseButton(0))
        {
            Vector3 tp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            PenMove(tp, this.penSize);
        }
    }
}

このプログラムでは次の3つのListを使っています。

  • タッチが移動した座標を保存するpoints
  • ポリゴンの頂点座標を保存するvertices
  • ポリゴンを描画するためのインデックスを保存するindex

CreateMeshメソッドでは現在タッチされている座標をもとに追加すべきポリゴンの頂点座標を2つと、UV座標を2つ、インデックス6つ(三角形のポリゴンを2個描画するため)計算し、リストに追加しています。

CreateMeshメソッド中の次のコードでは、現在のタッチ座標と1フレーム前のタッチ座標から移動ベクトルdirを計算しています。dirはnormalizedで正規化されていることに注意して下さい。

        Vector2 prev = this.points[this.points.Count - 2];
        Vector2 top = this.points[this.points.Count - 1];
        Vector2 dir = (top - prev).normalized;

次のスクリプトでは回転行列を使って、現在のタッチ座標(top)を中心に、移動ベクトル(dir)を+90度回した点の座標と-90度回した点の座標を計算しています。

        Vector2 plus90  = top + new Vector2(-dir.y, dir.x) * size;
        Vector2 minus90 = top + new Vector2(dir.y, -dir.x) * size;

ポリゴンの生成プログラムについて詳しくは次の記事を合わせて参照ください。

nn-hokuson.hatenablog.com

このプログラムでは、1本の線しか描画出来ないプログラムになっていますが、Meshを動的に生成することで、簡単に複数の線を同時に引けるようになります。

Lineスクリプトをアタッチする

プログラムができたら、Pen.csをアタッチするためのGameObjectを作ります。ヒエラルキーウインドウからCreate→Empty Objectを選択して名前をLineに変更します。生成したPenオブジェクトにLine.csをドラッグ&ドロップします。

f:id:nn_hokuson:20180227211410j:plain

また、描画にはMesh FilterコンポーネントとMesh Rendererコンポーネントが必要になるため、インスペクタのAdd Componentからこれらのコンポーネントを追加して下さい。

f:id:nn_hokuson:20180227211603p:plain:w300

マテリアルをアタッチする

線を描画するために使用するテクスチャを指定します。プロジェクトウィンドウで右クリックしてCreate→Materialを選択し、インスペクタからMaterialのシェーダを「Unlit/Transparent」に変更したうえで、テクスチャをセットしてください。

f:id:nn_hokuson:20180227212225p:plain:w300

最後にPenのスクリプトにいま作成したテクスチャをセットします。ヒエラルキーウインドウでPenを選択し、インスペクタの「mat」の欄にマテリアルをドラッグ&ドロップしてください。

f:id:nn_hokuson:20180227212541j:plain

線を描いてみる

Unityを実行して画面上をドラッグしてみてください。ドラッグした軌跡に従って線が描けると思います。

f:id:nn_hokuson:20180227213645g:plain:w560

テクスチャの伸び方を変えたい場合はPen.csのuspeedを変更してください。これによりテクスチャのu方向へのサンプリング速度が変化します。

また、線の太さを変えたい場合はpenSizeのパラメータを変更してください。このpenSizeをペンの移動に応じて変更することで、動的に太さが変化する線を書くことが出来ます。

f:id:nn_hokuson:20180227214211j:plain:w300