Unityで線を引きたいときは、LineRendererを使うのが簡単なのですが、テクスチャの張り方を変えたり、線の太さを動的に変えたりと、アレンジした線を引こうとすると、とたんに難しくなります。
ということで、この記事ではLineRendererを使わずに自力で線を引く方法を紹介します。
記事の内容は次のとおりです。
線を引くアルゴリズム
基本的には自分で線を描画するためのポリゴンを作って、そこにテクスチャを貼っていく感じです。ポリゴンの形状は線の流れに沿って柔軟に変化させます。
Unityで自作のポリゴンを作る方法はこちらの記事で紹介しているので、合わせて参考にしてください。
nn-hokuson.hatenablog.com
線に沿ってポリゴンを生成するアルゴリズムは次のような流れになります。まず、最初にタッチされた点に2点ぶんの頂点を生成します。
タッチの座標が移動したら、前タッチ座標から今タッチ座標までのベクトルを求めて、そのベクトルを90度回した点と-90度回した点に新しい頂点を2つ生成します。
前回と今回の頂点をあわせて4頂点できるので、これで三角形ポリゴンを2つ描画します。あとはこれを繰り返し、タッチの移動に従って四角形のポリゴンを生成していきます。
ポリゴンを動的に生成する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);
this.uvs.Add(new Vector2(xoffset, 0));
this.uvs.Add(new Vector2(xoffset, 1));
xoffset += (top - prev).magnitude / 6.0f;
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);
this.vertices.Add(tp);
this.vertices.Add(tp);
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をドラッグ&ドロップします。
また、描画にはMesh FilterコンポーネントとMesh Rendererコンポーネントが必要になるため、インスペクタのAdd Componentからこれらのコンポーネントを追加して下さい。
マテリアルをアタッチする
線を描画するために使用するテクスチャを指定します。プロジェクトウィンドウで右クリックしてCreate→Materialを選択し、インスペクタからMaterialのシェーダを「Unlit/Transparent」に変更したうえで、テクスチャをセットしてください。
最後にPenのスクリプトにいま作成したテクスチャをセットします。ヒエラルキーウインドウでPenを選択し、インスペクタの「mat」の欄にマテリアルをドラッグ&ドロップしてください。
線を描いてみる
Unityを実行して画面上をドラッグしてみてください。ドラッグした軌跡に従って線が描けると思います。
テクスチャの伸び方を変えたい場合はPen.csのuspeedを変更してください。これによりテクスチャのu方向へのサンプリング速度が変化します。
また、線の太さを変えたい場合はpenSizeのパラメータを変更してください。このpenSizeをペンの移動に応じて変更することで、動的に太さが変化する線を書くことが出来ます。