using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// 刮刮乐效果:底板 Image 在下,蒙层 RawImage 在上,点击滑动刮开显露底板。
/// 脚本需挂在 RawImage 同一 GameObject 上,且 RawImage 的 Raycast Target 需勾选。
/// </summary>
[RequireComponent(typeof(RawImage))]
public class ScratchCard : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
{
[SerializeField] RawImage mMaskImage;
[SerializeField] int mBrushSize = 50;
[SerializeField] [Range(0f, 1f)] float mRevealRate = 0.9f;
Texture2D mMaskTex;
int mWidth;
int mHeight;
int mTotalPixels;
int mScratchedPixels;
Vector2 mLastPos;
Camera mCam;
public System.Action OnScratchStart;
public System.Action OnScratchComplete;
void Awake()
{
// 默认值赋值
if (mMaskImage == null)
mMaskImage = GetComponent<RawImage>();
// 获取相机
var canvas = mMaskImage.canvas;
mCam = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
// 获取纹理
var src = mMaskImage.texture;
// 获取纹理宽高,创建新纹理作为蒙层
int w = src.width;
int h = src.height;
mMaskTex = new Texture2D(w, h, TextureFormat.ARGB32, false);
// 如果纹理可读,则将纹理像素复制到新纹理
var tex2d = src as Texture2D;
if (tex2d != null && tex2d.isReadable)
{
mMaskTex.SetPixels(tex2d.GetPixels());
}
else
{
Debug.LogError("纹理不可读,请将纹理的Read/Write Enabled勾选");
}
// 应用新纹理
mMaskTex.Apply();
mMaskImage.texture = mMaskTex;
mWidth = mMaskTex.width;
mHeight = mMaskTex.height;
mTotalPixels = mWidth * mHeight;
}
// 处理点击
public void OnPointerDown(PointerEventData eventData)
{
mLastPos = eventData.position;
ScratchAt(eventData.position);
}
// 处理拖拽
public void OnDrag(PointerEventData eventData)
{
ScratchAt(eventData.position);
float dist = Vector2.Distance(eventData.position, mLastPos);
if (dist > 5f)
{
int steps = Mathf.CeilToInt(dist / 10f);
for (int i = 1; i < steps; i++)
{
var t = (float)i / steps;
ScratchAt(Vector2.Lerp(mLastPos, eventData.position, t));
}
}
mLastPos = eventData.position;
}
public void OnPointerUp(PointerEventData eventData) { }
void ScratchAt(Vector2 screenPos)
{
if (mMaskTex == null) return;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
mMaskImage.rectTransform, screenPos, mCam, out var localPos))
return;
var rect = mMaskImage.rectTransform.rect;
if (rect.width <= 0 || rect.height <= 0) return;
// 计算纹理坐标
float u = (localPos.x - rect.xMin) / rect.width;
float v = (localPos.y - rect.yMin) / rect.height;
if (u < 0 || u > 1 || v < 0 || v > 1) return;
// 计算像素坐标
int px = Mathf.Clamp((int)(u * mWidth), 0, mWidth - 1);
int py = Mathf.Clamp((int)(v * mHeight), 0, mHeight - 1);
int r = Mathf.Max(1, mBrushSize / 2);
bool scratched = false;
for (int dy = -r; dy <= r; dy++)
{
for (int dx = -r; dx <= r; dx++)
{
if (dx * dx + dy * dy > r * r) continue; // 如果距离大于半径,则跳过
int x = px + dx;
int y = py + dy;
if (x < 0 || x >= mWidth || y < 0 || y >= mHeight) continue; // 如果像素坐标超出范围,则跳过
var c = mMaskTex.GetPixel(x, y);
if (c.a > 0.01f) // 如果像素透明度大于0.01,则设置为透明
{
c.a = 0f;
mMaskTex.SetPixel(x, y, c);
mScratchedPixels++;
scratched = true;
}
}
}
if (scratched)
{
mMaskTex.Apply();
if (mScratchedPixels == 1)
OnScratchStart?.Invoke();
if ((float)mScratchedPixels / mTotalPixels >= mRevealRate)
{
mMaskImage.gameObject.SetActive(false);
OnScratchComplete?.Invoke();
}
}
}
}
评论区