Files
aibis-dream/Assets/Scripts/UI/Panel/SignBoard/SignArea.cs
T
2025-11-19 15:08:08 +08:00

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);
}
}
}
}