【Unity】ペイントアプリを作る

Unityで簡単なお絵かきをできるペイントアプリを作ってみました。
Event Triggerを用いてタッチ座標を取得し、Texture2Dを生成することで線を描画しました。

#環境
* Unity 2019.4.10f1

#実装
まず、Canvasを追加し、子オブジェクトとしてUIのRaw Imageを追加。
Canvas の Render Mode は「Screen Space – Overlay」
Raw Imageは画像のように設定し、画面いっぱいに広がるようにした。

![スクリーンショット 2021-02-08 0.04.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654729/8a75a508-af75-734c-f102-30b9fe060958.png)

---
次にCreate Emptyを追加し、名前は「PaintController」にした。
そして、以下のスクリプトをアタッチした。

＊ 背景を白色にするコードを追加しました。（2021/10/21）

```PaintController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class PaintController : MonoBehaviour
 {
	[SerializeField]
	private RawImage m_image = null;

	private Texture2D m_texture = null;

    [SerializeField]
    private int m_width = 4;
 
    [SerializeField]
    private int m_height = 4;
 
    private Vector2 m_prePos;
    private Vector2 m_TouchPos;
    
    private float m_clickTime, m_preClickTime;

	public void OnDrag( BaseEventData arg ) //線を描画
    {
        PointerEventData _event = arg as PointerEventData; //タッチの情報取得
        
        // 押されているときの処理
		m_TouchPos = _event.position; //現在のポインタの座標
		m_clickTime = _event.clickTime; //最後にクリックイベントが送信された時間を取得

        float disTime = m_clickTime - m_preClickTime; //前回のクリックイベントとの時差

        int width  = m_width;  //ペンの太さ(ピクセル)
        int height = m_height; //ペンの太さ(ピクセル)

        var dir  = m_prePos - m_TouchPos; //直前のタッチ座標との差
        if(disTime > 0.01) dir = new Vector2(0,0); //0.1秒以上間隔があいたらタッチ座標の差を0にする

        var dist = (int)dir.magnitude; //タッチ座標ベクトルの絶対値

        dir = dir.normalized; //正規化
        
        //指定のペンの太さ(ピクセル)で、前回のタッチ座標から今回のタッチ座標まで塗りつぶす
        for(int d = 0; d < dist; ++d)
        {
            var p_pos = m_TouchPos + dir * d; //paint position
            p_pos.y -= height/2.0f;
            p_pos.x -= width/2.0f;
            for ( int h = 0; h < height; ++h )
            {
                int y = (int)(p_pos.y + h);
                if ( y < 0 || y > m_texture.height ) continue; //タッチ座標がテクスチャの外の場合、描画処理を行わない

                for ( int w = 0; w < width; ++w )
                {
                    int x = (int)(p_pos.x + w);
                    if ( x >= 0 && x <= m_texture.width )
                    {
                        m_texture.SetPixel( x, y, Color.black ); //線を描画
                    }
                }
            }
        }
        m_texture.Apply();
        m_prePos = m_TouchPos;
        m_preClickTime = m_clickTime;
	}

    public void OnTap( BaseEventData arg ) //点を描画
    {
        PointerEventData _event = arg as PointerEventData; //タッチの情報取得
        
        // 押されているときの処理
		m_TouchPos = _event.position; //現在のポインタの座標

        int width  = m_width;  //ペンの太さ(ピクセル)
        int height = m_height; //ペンの太さ(ピクセル)

        var p_pos = m_TouchPos; //paint position
        p_pos.y -= height/2.0f;
        p_pos.x -= width/2.0f;

        for ( int h = 0; h < height; ++h )
        {
            int y = (int)(p_pos.y + h);
            if ( y < 0 || y > m_texture.height ) continue; //タッチ座標がテクスチャの外の場合、描画処理を行わない
            for ( int w = 0; w < width; ++w )
            {
                int x = (int)(p_pos.x + w);
                if ( x >= 0 && x <= m_texture.width )
                {
                    m_texture.SetPixel( x, y, Color.black ); //点を描画
                }
            }
        }       
        m_texture.Apply();
    }

    private void Start ()
    {
        var rect = m_image.gameObject.GetComponent<RectTransform>().rect;
        m_texture = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGBA32, false);

        //下の行追加（2021/10/21）
        WhiteTexture((int)rect.width, (int)rect.height);

        m_image.texture = m_texture;
    }

    //下の関数を追加（2021/10/21）
    //テクスチャを白色にする関数
    private void WhiteTexture(int width, int height)
    {
        for(int w = 0; w < width; w++)
        {
            for (int h = 0; h < height; h++)
            {
                m_texture.SetPixel(w, h, Color.white);
            }
        }
        m_texture.Apply();
    }
}
```
そして、下の画像のように設定。

![スクリーンショット 2021-02-08 0.58.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654729/8d448ff5-76c1-6ae7-aa70-6cd702c5c4a9.png)

---
次に、Raw ImageにAdd Componentで「Event Trigger」を追加。
そして、Add New Event Typeで「Drag」と「Pointer Click」を追加。
両方に先程作ったPointControllerオブジェクトをアタッチ。
Functionは、Dragには「OnDrag()」、Pointer Clickには「OnTap()」を指定。

![スクリーンショット 2021-02-08 0.32.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654729/e293fafc-b2c8-8828-155b-edde4312e707.png)

#完成
![スクリーンショット 2021-02-08 1.06.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654729/c2b177ed-f785-e800-dd56-667e46f52df9.png)
このように、マウスで線を描けるようになりました。
タブレットやスマホだと指でなぞった所に描くことができます。

#まとめ
今回は、Unityで簡単なペイントアプリを作ってみました。
まだ線を描けるだけなので、後にブラシの太さ・色の変更、消しゴム、リセットなどの機能を追加したいと思います。

#参考
・ペイントツールの参考 
【Unity】ペイントツールもどきを作ってみた
https://kandycodings.jp/2019/02/10/unidev-paint/

・Event Triggerの参考
【Unity】Event Triggerの種類と用途と使い方【保存版】
https://tech.pjin.jp/blog/2017/09/03/unity_event-trigger/

#追記　-ペイントエリアを一部範囲に限定する方法-
まず、Raw ImageのAnchor Presetを「bottom-left」に設定する。
そして、任意の座標・サイズに変更する。
![スクリーンショット 2021-11-23 17.31.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654729/85c7e5a8-e13a-58d3-9ea4-4833d09e3fe7.png)

PaintController.cs にタッチ座標をテクスチャの座標と合わせるための処理を追加した。

```PaintController.cs
    //省略

    //下１行追加(2021/11/23)
    private Vector2 m_disImagePos;

	public void OnDrag( BaseEventData arg ) //線を描画
    {
        PointerEventData _event = arg as PointerEventData; //タッチの情報取得
        
        // 押されているときの処理
        //以下１行修正(2021/11/23)
        m_TouchPos = _event.position - m_disImagePos; //現在のポインタの座標
        //m_TouchPos = _event.position; //現在のポインタの座標

        //省略

	}

    public void OnTap( BaseEventData arg ) //点を描画
    {
        PointerEventData _event = arg as PointerEventData; //タッチの情報取得
        
        // 押されているときの処理
        //以下１行修正(2021/11/23)
        m_TouchPos = _event.position - m_disImagePos; //現在のポインタの座標
        //m_TouchPos = _event.position; //現在のポインタの座標

        //省略

    }

    private void Start ()
    {
        var rect = m_image.gameObject.GetComponent<RectTransform>().rect;
        m_texture = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGBA32, false);
        //下の1行追加（2021/10/21）
        WhiteTexture((int)rect.width, (int)rect.height);
        m_image.texture = m_texture;

        //以下追加(2021/11/23)
        //ペイントエリアの指定
        var m_imagePos = m_image.gameObject.GetComponent<RectTransform>().anchoredPosition;
        m_disImagePos = new Vector2(m_imagePos.x - rect.width/2, m_imagePos.y - rect.height/2);
    }

    //以下省略
}
```