侧边栏壁纸
博主头像
LittleAO的学习小站 博主等级

在知识的沙漠寻找绿洲

  • 累计撰写 125 篇文章
  • 累计创建 27 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Unity实现刮刮乐效果

LittleAO
2026-02-27 / 0 评论 / 0 点赞 / 1 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。
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();
            }
        }
    }
}

0

评论区