こんにちわ、Unityエンジニアのオオバです。

マウスを動かして絵を描くアプリ
Unityで作る場合どうすればよいでしょうか。

  1. マウス座標をワールド座標に変換
  2. ワールド座標にメッシュを生成
  3. マウスが動くごとにメッシュをつないでいく

こんな感じでしょうか。
難しそうですよね。

本記事では、ペイントアプリまではいきませんが、
マウスを追いかけるメッシュを生成する方法を紹介します。

スクリプトでメッシュを作る方法を何度か記事にしてきました。

毎フレームスクリーン座標を元にメッシュを作る

結論を簡単にまとめます。

このまとめだけではよくわからないので
詳細解説に入ります。

【Unity】ペイントアプリのようなメッシュの作り方_0

今回作るものはマウスを動かすと
こんな感じでメッシュが作られます。

この記事の内容

マウス座標をワールド座標に変換

【Unity】ペイントアプリのようなメッシュの作り方_1

Cameraコンポーネントの
ScreenToWorldPointメソッドを使います。
マウス座標を渡すとでワールド座標に変換する超便利関数です。

コチラの記事で詳しく解説していますので
読んでみてください。

頂点の座標を決める

こちらの図のようにスクリーン座標からワールド座標に変換した
マウス座標をもとに各頂点の座標を決めていきます。

【Unity】ペイントアプリのようなメッシュの作り方_2

数字は頂点の番号です。
つまりインデックス配列です。

このようなイメージでメッシュを描画していきます。

クリック開始直後は4頂点で四角形を描画

【Unity】ペイントアプリのようなメッシュの作り方_3

💻ソースコード : 描画初回フレームのみの処理
// _widthは筆の幅、posはワールド座標に変換したマウス座標  
var pt0 = new Vector3(pos.x - _width * 0.5f, pos.y, 0);  
var pt1 = new Vector3(pos.x + _width * 0.5f, pos.y, 0);  
var pt2 = new Vector3(pos.x - _width * 0.5f, pos.y, 0);  
var pt3 = new Vector3(pos.x + _width * 0.5f, pos.y, 0);  
_vertices.Add(pt0);  
_vertices.Add(pt1);  
_vertices.Add(pt2);  
_vertices.Add(pt3);  

初回フレームは4頂点で四角形メッシュを作ります。

2フレーム以降は2頂点ずつ追加して四角形を描画

💻ソースコード : 描画2フレーム以降の処理
var pt2 = new Vector3(pos.x - _width * 0.5f, pos.y, 0);  
var pt3 = new Vector3(pos.x + _width * 0.5f, pos.y, 0);  
_vertices.Add(pt2);  
_vertices.Add(pt3);  

2フレーム目以降は、
このように2頂点ずつ追加していきます。

インデックス配列の順を決める

0, 1, 3  
0, 3, 2  

頂点インデックスは上のような順序にしています。

次のコードで実現しています。

_indices.Add(_offsetIndex);  
_indices.Add(_offsetIndex + 1);  
_indices.Add(_offsetIndex + 3);  
_indices.Add(_offsetIndex);  
_indices.Add(_offsetIndex + 3);  
_indices.Add(_offsetIndex + 2);  

_offsetIndexは初回の0,1,3,2四角形を描画した後は、
頂点インデックス番号は2番から始まってほしいので、
そのオフセットを行う変数です。

_offsetIndex += 2;  

四角形を描画し終えた番号に2を加算すると
インデックスがつながります。

両面描画にする

デフォルトシェーダでは、
片面描画になります。

そこでCull Off設定して、
両面描画にしましょう。

まとめ : ペイントのようなメッシュの作り方

【Unity】ペイントアプリのようなメッシュの作り方_4

記事の内容を簡単にまとめます。

  1. マウス座標をワールド座標に変換
  2. ワールド座標にメッシュを生成
  3. 初回描画は4頂点描画
  4. 2回目以降は2頂点ずつ追加して描画

こんな感じです。

初心者には難しかったと思います。

説明もまだまだ難しい気がしています。
再度リライトしてより分かりやすい記事にしていこうと思います。

最後にソースコード全体の共有

以下のコードをGameObjectにAddComponentすると
マウスを追いかけるペイント風の挙動になると思います。

using System.Collections.Generic;  
using UnityEngine;  

[RequireComponent(typeof(MeshFilter))]  
[RequireComponent(typeof(MeshRenderer))]  
public class DynamicBrushMesh : MonoBehaviour  
{
    struct VertexData  
    {
        public Vector3[] vertices;  
        public int[] indices;  
    }

    private MeshFilter _filter;  
    private MeshFilter Filter => _filter ? _filter : _filter = GetComponent<MeshFilter>();  

    private List<Vector3> _vertices = new List<Vector3>();  
    private List<int> _indices = new List<int>();  
    // 筆の太さ  
    [SerializeField] private float _radius = 0.1f;  

    private bool _isInit;  
    private int _offsetIndex = 0;  
    private bool _isMouseDown;  
    private Mesh _mesh;  
    private Vector3 _mousePos;  

    void Update()  
    {
        _isMouseDown = Input.GetMouseButton(0);  
        if (_isMouseDown)  
        {
            _mousePos = Input.mousePosition;  
            _mousePos.z = Mathf.Abs(Camera.main.transform.position.z);  
            var pos = Camera.main.ScreenToWorldPoint(_mousePos);  
            Draw(pos);  
        }
    }

    public void Draw(Vector3 pos)  
    {
        var data = CreateVertex(pos);  
        if (_mesh == null) _mesh = new Mesh();  

        _mesh.vertices = data.vertices;  
        _mesh.SetIndices(data.indices, MeshTopology.Triangles, 0);  
        _mesh.RecalculateNormals();  
        Filter.mesh = _mesh;  
    }


    VertexData CreateVertex(Vector3 pos)  
    {
        if (_isInit == false)  
        {
            // 初回処理  
            _isInit = true;  
            var pt0 = new Vector3(pos.x - _radius * 0.5f, pos.y, 0);  
            var pt1 = new Vector3(pos.x + _radius * 0.5f, pos.y, 0);  
            var pt2 = new Vector3(pos.x - _radius * 0.5f, pos.y, 0);  
            var pt3 = new Vector3(pos.x + _radius * 0.5f, pos.y, 0);  
            _vertices.Add(pt0);  
            _vertices.Add(pt1);  
            _vertices.Add(pt2);  
            _vertices.Add(pt3);  
        }
        else  
        {
            // 2回目以降  
            var pt2 = new Vector3(pos.x - _radius * 0.5f, pos.y, 0);  
            var pt3 = new Vector3(pos.x + _radius * 0.5f, pos.y, 0);  
            _vertices.Add(pt2);  
            _vertices.Add(pt3);  
        }

        _indices.Add(_offsetIndex);  
        _indices.Add(_offsetIndex + 1);  
        _indices.Add(_offsetIndex + 3);  
        _indices.Add(_offsetIndex);  
        _indices.Add(_offsetIndex + 3);  
        _indices.Add(_offsetIndex + 2);  

        // 変数更新処理  
        _offsetIndex += 2;  

        return new VertexData()  
        {
            vertices = _vertices.ToArray(),  
            indices = _indices.ToArray()  
        };  
    }
}

何かの参考になれば幸いです。


フォローすると UIデザイナー力の上がるTwitter やってます!
今日から使えるテクニックを発信中。
ぜひフォローしてみてください!
👉フォローはこちら!

最後まで読んでいただきありがとうございました!
すばらしいUnityライフをお過ごしください。

期間限定 最大95%オフセール
効率UPメガバンドル開催中!最大95%オフ!!!
期間 : 11月1日午後15時59分まで
オススメ記事
検証環境