406 lines
14 KiB
C#
406 lines
14 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
namespace AibisDream
|
|
{
|
|
/// <summary>
|
|
/// 画板组件,支持鼠标拖动绘制(UI Space)
|
|
/// 需要挂载在带有RectTransform和RawImage的GameObject上
|
|
/// </summary>
|
|
[RequireComponent(typeof(RectTransform))]
|
|
[RequireComponent(typeof(RawImage))]
|
|
public class SignArea : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
|
|
{
|
|
[Header("画板设置")]
|
|
[SerializeField] private int textureWidth = 512;
|
|
[SerializeField] private int textureHeight = 512;
|
|
[SerializeField] private Color32 backgroundColor = Color.clear;
|
|
|
|
[Header("画笔设置")]
|
|
[SerializeField] private Color drawColor = Color.black;
|
|
[SerializeField] private int brushSize = 5;
|
|
[SerializeField] private bool smoothDrawing = true; // 是否平滑绘制(连接两点之间的所有点)
|
|
|
|
[Header("长度目标设置")]
|
|
[SerializeField] private float targetLength = 100f; // 目标绘画长度(像素)
|
|
|
|
public event Action<float> onLengthTargetExceeded; // 绘画长度超过目标事件(参数:当前长度)
|
|
|
|
private Texture2D drawTexture;
|
|
private RawImage drawImage;
|
|
private RectTransform rectTransform;
|
|
|
|
private Vector2 lastDrawPoint;
|
|
private bool isDrawing = false;
|
|
private float currentStrokeLength = 0f; // 当前单次绘画的线段长度
|
|
private float totalLength = 0f; // 累计所有笔画的总长度
|
|
private bool hasTriggeredTargetEvent = false; // 是否已触发超过目标事件
|
|
private bool isLocked = true; // 锁定标记,锁定状态下无法绘画
|
|
|
|
private void Awake()
|
|
{
|
|
drawImage = GetComponent<RawImage>();
|
|
rectTransform = GetComponent<RectTransform>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 初始化画布纹理
|
|
/// </summary>
|
|
public void Init()
|
|
{
|
|
// 创建可读写的纹理
|
|
drawTexture = new Texture2D(textureWidth, textureHeight, TextureFormat.RGBA32, false);
|
|
drawTexture.filterMode = FilterMode.Point; // 使用 Point 过滤模式实现像素化效果
|
|
|
|
// 填充背景色
|
|
Color32[] pixels = new Color32[textureWidth * textureHeight];
|
|
for (int i = 0; i < pixels.Length; i++)
|
|
{
|
|
pixels[i] = backgroundColor;
|
|
}
|
|
drawTexture.SetPixels32(pixels);
|
|
drawTexture.Apply();
|
|
|
|
// 设置到RawImage
|
|
drawImage.texture = drawTexture;
|
|
|
|
// 重置累计总长度和事件标志
|
|
totalLength = 0f;
|
|
hasTriggeredTargetEvent = false;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Clear();
|
|
|
|
if (drawTexture != null)
|
|
{
|
|
Destroy(drawTexture);
|
|
}
|
|
drawTexture = null;
|
|
lastDrawPoint = Vector2.zero;
|
|
isDrawing = false;
|
|
onLengthTargetExceeded = null;
|
|
// Dispose 后锁定
|
|
isLocked = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 将屏幕坐标转换为纹理坐标
|
|
/// </summary>
|
|
private bool ScreenToTextureCoord(Vector2 screenPos, out Vector2 textureCoord)
|
|
{
|
|
textureCoord = Vector2.zero;
|
|
|
|
// 将屏幕坐标转换为UI本地坐标
|
|
if (UIManager.Instance.ScreenPointToLocalPointInRectangle(rectTransform, screenPos, out Vector2 localPoint))
|
|
{
|
|
// 检查是否在RectTransform范围内
|
|
Rect rect = rectTransform.rect;
|
|
if (!rect.Contains(localPoint))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// 转换为纹理坐标 (0,0) 到 (textureWidth, textureHeight)
|
|
// localPoint的范围是 (-rect.width/2, -rect.height/2) 到 (rect.width/2, rect.height/2)
|
|
float normalizedX = (localPoint.x + rect.width / 2) / rect.width;
|
|
float normalizedY = (localPoint.y + rect.height / 2) / rect.height;
|
|
|
|
textureCoord.x = normalizedX * textureWidth;
|
|
textureCoord.y = normalizedY * textureHeight;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 在指定位置绘制(方形像素笔刷)
|
|
/// </summary>
|
|
private void DrawAtPoint(Vector2 point)
|
|
{
|
|
// 检查纹理是否已初始化
|
|
if (drawTexture == null)
|
|
{
|
|
Debug.LogWarning("SignArea: drawTexture 未初始化,请先调用 Init() 方法");
|
|
return;
|
|
}
|
|
|
|
int centerX = Mathf.RoundToInt(point.x);
|
|
int centerY = Mathf.RoundToInt(point.y);
|
|
|
|
// 绘制方形笔刷(像素化,无柔边效果)
|
|
for (int x = -brushSize; x <= brushSize; x++)
|
|
{
|
|
for (int y = -brushSize; y <= brushSize; y++)
|
|
{
|
|
int px = centerX + x;
|
|
int py = centerY + y;
|
|
|
|
// 检查边界
|
|
if (px >= 0 && px < textureWidth && py >= 0 && py < textureHeight)
|
|
{
|
|
// 像素化绘制:直接使用画笔颜色,不进行透明度渐变
|
|
Color existingColor = drawTexture.GetPixel(px, py);
|
|
// Alpha 混合公式: result = src * srcAlpha + dst * (1 - srcAlpha)
|
|
Color blendedColor = new Color(
|
|
Mathf.Lerp(existingColor.r, drawColor.r, drawColor.a),
|
|
Mathf.Lerp(existingColor.g, drawColor.g, drawColor.a),
|
|
Mathf.Lerp(existingColor.b, drawColor.b, drawColor.a),
|
|
Mathf.Min(1f, existingColor.a + drawColor.a * (1f - existingColor.a))
|
|
);
|
|
drawTexture.SetPixel(px, py, blendedColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 在两点之间绘制线条(平滑绘制)
|
|
/// </summary>
|
|
private void DrawLine(Vector2 start, Vector2 end)
|
|
{
|
|
float distance = Vector2.Distance(start, end);
|
|
int steps = Mathf.CeilToInt(distance);
|
|
|
|
for (int i = 0; i <= steps; i++)
|
|
{
|
|
float t = steps > 0 ? (float)i / steps : 0;
|
|
Vector2 point = Vector2.Lerp(start, end, t);
|
|
DrawAtPoint(point);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 应用绘制更改到纹理
|
|
/// </summary>
|
|
private void ApplyDrawing()
|
|
{
|
|
if (drawTexture != null)
|
|
{
|
|
drawTexture.Apply();
|
|
// 确保RawImage显示更新
|
|
if (drawImage != null && drawImage.texture != drawTexture)
|
|
{
|
|
drawImage.texture = drawTexture;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清除画布
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
if (drawTexture == null)
|
|
{
|
|
Debug.LogWarning("SignArea: drawTexture 未初始化,无法清除画布");
|
|
return;
|
|
}
|
|
|
|
Color32[] pixels = new Color32[textureWidth * textureHeight];
|
|
for (int i = 0; i < pixels.Length; i++)
|
|
{
|
|
pixels[i] = backgroundColor;
|
|
}
|
|
drawTexture.SetPixels32(pixels);
|
|
drawTexture.Apply();
|
|
|
|
// 重置累计总长度和事件标志
|
|
totalLength = 0f;
|
|
hasTriggeredTargetEvent = false;
|
|
onLengthTargetExceeded = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置画笔颜色
|
|
/// </summary>
|
|
public void SetDrawColor(Color color)
|
|
{
|
|
drawColor = color;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置画笔大小
|
|
/// </summary>
|
|
public void SetBrushSize(int size)
|
|
{
|
|
brushSize = Mathf.Max(1, size);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置背景颜色
|
|
/// </summary>
|
|
public void SetBackgroundColor(Color color)
|
|
{
|
|
backgroundColor = color;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置目标绘画长度
|
|
/// </summary>
|
|
public void SetTargetLength(float length)
|
|
{
|
|
targetLength = Mathf.Max(0f, length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取累计所有笔画的总长度
|
|
/// </summary>
|
|
public float GetCurrentStrokeLength()
|
|
{
|
|
return totalLength;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置锁定状态
|
|
/// </summary>
|
|
/// <param name="locked">true 为锁定(无法绘画),false 为解锁(可以绘画)</param>
|
|
public void SetLocked(bool locked)
|
|
{
|
|
isLocked = locked;
|
|
// 如果锁定,停止当前绘画
|
|
if (locked && isDrawing)
|
|
{
|
|
isDrawing = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取当前锁定状态
|
|
/// </summary>
|
|
public bool IsLocked()
|
|
{
|
|
return isLocked;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存画布为PNG图片
|
|
/// </summary>
|
|
public byte[] SaveToPNG()
|
|
{
|
|
if (drawTexture == null)
|
|
{
|
|
Debug.LogWarning("SignArea: drawTexture 未初始化,无法保存图片");
|
|
return null;
|
|
}
|
|
return drawTexture.EncodeToPNG();
|
|
}
|
|
|
|
// EventSystem接口实现
|
|
public void OnPointerDown(PointerEventData eventData)
|
|
{
|
|
if (eventData.button != PointerEventData.InputButton.Left)
|
|
return;
|
|
|
|
// 检查是否锁定
|
|
if (isLocked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 检查纹理是否已初始化
|
|
if (drawTexture == null)
|
|
{
|
|
Debug.LogWarning("SignArea: drawTexture 未初始化,请先调用 Init() 方法");
|
|
return;
|
|
}
|
|
|
|
if (ScreenToTextureCoord(eventData.position, out Vector2 textureCoord))
|
|
{
|
|
isDrawing = true;
|
|
// 重置当前绘画会话的累计长度
|
|
currentStrokeLength = 0f;
|
|
// 确保第一次点击也能绘制
|
|
lastDrawPoint = textureCoord;
|
|
DrawAtPoint(textureCoord);
|
|
ApplyDrawing();
|
|
|
|
// 强制更新RawImage显示
|
|
if (drawImage != null && drawTexture != null)
|
|
{
|
|
drawImage.texture = drawTexture;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnDrag(PointerEventData eventData)
|
|
{
|
|
if (!isDrawing)
|
|
return;
|
|
|
|
// 检查是否锁定
|
|
if (isLocked)
|
|
{
|
|
isDrawing = false;
|
|
return;
|
|
}
|
|
|
|
if (ScreenToTextureCoord(eventData.position, out Vector2 textureCoord))
|
|
{
|
|
float segmentLength = Vector2.Distance(lastDrawPoint, textureCoord);
|
|
|
|
if (smoothDrawing)
|
|
{
|
|
// 平滑绘制:连接两点之间的所有点
|
|
// 如果两点距离太近,直接绘制当前点(避免DrawLine计算出错)
|
|
if (segmentLength < 0.1f)
|
|
{
|
|
DrawAtPoint(textureCoord);
|
|
}
|
|
else
|
|
{
|
|
DrawLine(lastDrawPoint, textureCoord);
|
|
// 累计线段长度
|
|
currentStrokeLength += segmentLength;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 直接绘制当前点,但仍累计线段长度
|
|
DrawAtPoint(textureCoord);
|
|
// 累计线段长度
|
|
currentStrokeLength += segmentLength;
|
|
}
|
|
|
|
ApplyDrawing();
|
|
lastDrawPoint = textureCoord;
|
|
}
|
|
}
|
|
|
|
public void OnPointerUp(PointerEventData eventData)
|
|
{
|
|
if (isDrawing)
|
|
{
|
|
// 将当前笔画长度累加到总长度
|
|
totalLength += currentStrokeLength;
|
|
|
|
// 检查累计总长度是否超过目标长度,且尚未触发过事件
|
|
if (!hasTriggeredTargetEvent && totalLength > targetLength)
|
|
{
|
|
hasTriggeredTargetEvent = true;
|
|
// 触发超过目标长度事件,传递累计总长度
|
|
onLengthTargetExceeded?.Invoke(totalLength);
|
|
// 触发事件后自动锁定
|
|
isLocked = true;
|
|
}
|
|
}
|
|
isDrawing = false;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
// 清理纹理资源
|
|
if (drawTexture != null)
|
|
{
|
|
Destroy(drawTexture);
|
|
}
|
|
}
|
|
}
|
|
}
|