うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】uGUI で絵を書く

EventSystems を 利用して Texture2D に絵をかけるスクリプト

f:id:hacchi_man:20220117000524g:plain

スクリプト

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
 
[RequireComponent(typeof(RawImage))]
public class DrawTexture : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    [SerializeField]
    private RawImage _rawImage;
 
    [SerializeField]
    private int _radius = 3;
 
    private Texture2D _texture;
    private Vector2 _screenPointMax;
    private Vector2 _screenPointMin;
    private Vector2 _lastScreenPosition;
     
    private const int DIVIDE = 20;
    private Dictionary<Vector2Int, float> _dictionary;

    public event Action<Texture2D> Drawn;

#if UNITY_EDITOR
    private void Reset()
    {
        _rawImage = GetComponent<RawImage>();
    }
#endif

    private void Start()
    {
        var rectTransform = transform as RectTransform;
 
        var sizeDelta = rectTransform.sizeDelta;
        _texture = new Texture2D(
            (int) sizeDelta.x, 
            (int) sizeDelta.y
        );
 
        _texture.wrapMode = TextureWrapMode.Clamp;
        for (var x = 0; x < _texture.width; x++)
        {
            for (var y = 0; y < _texture.height; y++)
            {
                _texture.SetPixel(x, y, Color.white);
            }
        }
 
        _texture.Apply();
        _rawImage.texture = _texture;
         
        var canvas = transform.GetComponentInParent<Canvas>().rootCanvas;
 
        var corners = new Vector3[4];
        rectTransform.GetWorldCorners(corners);
         
        var screenPoints = new Vector3[4];
        for (var i = 0; i < corners.Length; i++)
        {
            var screenPoint = RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, corners[i]);
            screenPoints[i] = screenPoint;
        }
 
        _screenPointMax = new Vector2(
            screenPoints.Max(v => v.x),
            screenPoints.Max(v => v.y)
        );
         
        _screenPointMin = new Vector2(
            screenPoints.Min(v => v.x),
            screenPoints.Min(v => v.y)
        );
 
        _dictionary = new Dictionary<Vector2Int, float>();
        for (var x = -_radius; x <= _radius; x++)
        {
            for (var y = -_radius; y <= _radius; y++)
            {
                var position = new Vector2Int(x, y);
                if (!_dictionary.ContainsKey(position))
                {
                    _dictionary.Add(position, 1f);
                }
            }
        }
    }
 
    private void OnDestroy()
    {
        Destroy(_texture);
    }
 
    private void Draw(Vector2 screenPosition)
    {
        var u = Mathf.InverseLerp(_screenPointMin.x, _screenPointMax.x, screenPosition.x);
        var v = Mathf.InverseLerp(_screenPointMin.y, _screenPointMax.y, screenPosition.y);
        var x = (int)Mathf.Lerp(0, _texture.width - 1, u); 
        var y = (int)Mathf.Lerp(0, _texture.height - 1, v);
        foreach (var pair in _dictionary)
        {
            if (x < 0 || y < 0 || x >= _texture.width || y >= _texture.height)
                continue;
            
            _texture.SetPixel(x + pair.Key.x, y + pair.Key.y, Color.black);
        }
    }
 
    private void CompleteDraw(Vector2 screenPosition)
    {
        // 20分割で補間
        for (var i = 0; i <= DIVIDE; i++)
        {
            var lerpScreenPosition = Vector2.Lerp(_lastScreenPosition, screenPosition, i / (float)DIVIDE);
            Draw(lerpScreenPosition);
        }
    }
 
    void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
    {
        Draw(eventData.position);
        _texture.Apply();
        _lastScreenPosition = eventData.position;
    }
 
    void IDragHandler.OnDrag(PointerEventData eventData)
    {
        CompleteDraw(eventData.position);
        _texture.Apply();
        _lastScreenPosition = eventData.position;
    }
 
    void IEndDragHandler.OnEndDrag(PointerEventData eventData)
    {
        Drawn?.Invoke(_texture);
    }
}