using Assets.MyGame.Scripts.Net; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; /// /// GPU 刮刮乐(三层架构版) /// /// 层级结构: /// RawImage (prizeImage) ← 最下层:奖品图 /// Text (tmp_AwardName) ← 中间层:奖品文字 /// RawImage (scratchImage) ← 最上层:涂层遮罩(挂本脚本) /// public class GuaGuaLeGPU : SubUIBase, IPointerDownHandler, IDragHandler { [Header("UI 引用(必填)")] public RawImage scratchImage; // 最上层:涂层遮罩(挂此脚本) public GuaGuaLeItem giftPrefab; // 奖品预制体(用于显示奖品图片) public Transform giftPrefabParent; public Sprite sprite_Gold; public Sprite sprite_Diamond; public Button btn_Get; [Header("材质(模板)")] public Material maskMat; // 使用 ScratchMask.shader(涂层遮罩) public Material eraseMat; // 使用 ScratchErase.shader(擦除) [Header("涂层颜色")] public Color coatColor = new Color(0.7f, 0.7f, 0.7f, 1f); [Header("笔刷")] [Range(0.01f, 0.3f)] public float brushSize = 0.06f; [Header("自动揭示")] [Range(0f, 1f)] public float revealThreshold = 0.6f; public bool autoReveal = true; [Header("事件")] public UnityEngine.Events.UnityEvent onRevealed; // ---- 运行时 ---- private RenderTexture maskRT; private Material instanceMaskMat; // 涂层遮罩材质实例 private Material instanceEraseMat; // 擦除材质实例 private int rtWidth = 512; private int rtHeight = 512; private string currentAward; private bool isRevealed; private bool isScratching; private Vector2 lastUV; private bool hasLastUV; private Texture2D progressReadTex; private int dragCount; protected override void OnShow(object args = null) { base.OnShow(args); ResetCard(); } private void Start() { if (scratchImage == null) { Debug.LogError("[GPU] scratchImage 为空!"); return; } if (maskMat == null) { Debug.LogError("[GPU] maskMat 为空!"); return; } if (eraseMat == null) { Debug.LogError("[GPU] eraseMat 为空!"); return; } InitCard(); } private void InitCard() { maskRT = new RenderTexture(rtWidth, rtHeight, 0, RenderTextureFormat.ARGB32) { filterMode = FilterMode.Bilinear }; maskRT.Create(); ClearRT(maskRT, Color.white); instanceMaskMat = new Material(maskMat); instanceMaskMat.SetTexture("_MaskTex", maskRT); instanceMaskMat.SetColor("_CoatColor", coatColor); scratchImage.material = instanceMaskMat; scratchImage.texture = null; instanceEraseMat = new Material(eraseMat); instanceEraseMat.SetFloat("_BrushSize", brushSize); progressReadTex = new Texture2D(rtWidth, rtHeight, TextureFormat.ARGB32, false); ResetState(); FetchAward(); } private void ResetState() { isRevealed = false; isScratching = false; hasLastUV = false; dragCount = 0; } // ================================================================ // API 抽奖 // ================================================================ private void FetchAward() { APIRequestList.ScratchCardDraw(result => { if (result == null || giftPrefab == null) return; currentAward = result.prizeName ?? "谢谢参与"; // 清除旧的奖励物品 for (int i = giftPrefabParent.childCount - 1; i >= 0; i--) Destroy(giftPrefabParent.GetChild(i).gameObject); var item = Instantiate(giftPrefab, giftPrefabParent); switch (result.prizeType) { case 1: // 金币 → 本地 sprite item.Init(sprite_Gold, result.count); break; case 2: // 钻石 → 本地 sprite item.Init(sprite_Diamond, result.count); break; default: // 道具 → 网络下载图片 string imgUrl =APIManager.baseURL+ result.baseProduct?.imageUrl; if (!string.IsNullOrEmpty(imgUrl)) item.Init(imgUrl, result.count); break; } // 抽奖时刮刮卡已被扣减,立即刷新用户数据(触发 GameMainPanel_FishBase.RefreshGGKCount) string userId = GameManager.Instance?.user?.id; if (!string.IsNullOrEmpty(userId)) APIRequestList.GetUser(userId, a => GameManager.Instance.UpdataUserData(a)); }); } // ================================================================ // 公开方法 // ================================================================ public void ResetCard() { btn_Get.gameObject.SetActive(false); if (maskRT == null) return; ClearRT(maskRT, Color.white); ResetState(); if (instanceMaskMat != null) instanceMaskMat.SetColor("_CoatColor", coatColor); FetchAward(); } public void RevealAll() { btn_Get.gameObject.SetActive(true); if (maskRT == null || isRevealed) return; isRevealed = true; ClearRT(maskRT, Color.black); // 抽奖后刷新用户数据(金币/钻石变化) string userId = GameManager.Instance?.user?.id; if (!string.IsNullOrEmpty(userId)) APIRequestList.GetUser(userId, a => GameManager.Instance.UpdataUserData(a)); onRevealed?.Invoke(); } // ================================================================ // 输入处理 // ================================================================ public void OnPointerDown(PointerEventData e) { if (isRevealed) return; isScratching = true; if (UVFromPointer(e, out var uv)) { ScratchAt(uv); lastUV = uv; hasLastUV = true; } TryAutoReveal(); } public void OnDrag(PointerEventData e) { if (isRevealed || !isScratching) return; if (!UVFromPointer(e, out var uv)) return; if (hasLastUV) { float dist = Vector2.Distance(uv, lastUV); int steps = Mathf.Max(1, Mathf.CeilToInt(dist / (brushSize * 0.4f))); for (int i = 1; i <= steps; i++) { float t = (float)i / steps; var p = Vector2.Lerp(lastUV, uv, t); ScratchAt(p); } } else { ScratchAt(uv); } lastUV = uv; hasLastUV = true; if (++dragCount % 10 == 0) TryAutoReveal(); } private void OnDisable() { isScratching = false; hasLastUV = false; } // ================================================================ // 核心擦除(GPU) // ================================================================ private void ScratchAt(Vector2 uv) { instanceEraseMat.SetVector("_BrushPos", new Vector4(uv.x, uv.y, 0, 0)); var temp = RenderTexture.GetTemporary(rtWidth, rtHeight, 0, RenderTextureFormat.ARGB32); Graphics.Blit(maskRT, temp, instanceEraseMat); Graphics.Blit(temp, maskRT); RenderTexture.ReleaseTemporary(temp); } // ================================================================ // 进度检测 // ================================================================ private void TryAutoReveal() { if (isRevealed || !autoReveal || revealThreshold <= 0f) return; if (CalcRevealProgress() >= revealThreshold) RevealAll(); } public float CalcRevealProgress() { if (maskRT == null || progressReadTex == null) return 0f; RenderTexture prev = RenderTexture.active; RenderTexture.active = maskRT; progressReadTex.ReadPixels(new Rect(0, 0, rtWidth, rtHeight), 0, 0, false); progressReadTex.Apply(); RenderTexture.active = prev; Color32[] pixels = progressReadTex.GetPixels32(); int total = 0, cleared = 0; int step = Mathf.Max(1, pixels.Length / 2048); for (int i = 0; i < pixels.Length; i += step) { total++; if (pixels[i].r < 50) cleared++; } return total == 0 ? 0f : (float)cleared / total; } // ================================================================ // 工具 // ================================================================ private static void ClearRT(RenderTexture rt, Color color) { RenderTexture prev = RenderTexture.active; RenderTexture.active = rt; GL.Clear(false, true, color); RenderTexture.active = prev; } private bool UVFromPointer(PointerEventData e, out Vector2 uv) { uv = Vector2.zero; var rt = scratchImage.rectTransform; if (!RectTransformUtility.ScreenPointToLocalPointInRectangle( rt, e.position, e.pressEventCamera, out var localPos)) return false; var rect = rt.rect; if (rect.width <= 0f || rect.height <= 0f) return false; uv = new Vector2( (localPos.x - rect.x) / rect.width, (localPos.y - rect.y) / rect.height ); return uv.x >= 0f && uv.x <= 1f && uv.y >= 0f && uv.y <= 1f; } public string CurrentAward => currentAward; public bool IsRevealed => isRevealed; private void OnDestroy() { if (maskRT != null) { maskRT.Release(); Destroy(maskRT); } if (instanceMaskMat != null) Destroy(instanceMaskMat); if (instanceEraseMat != null) Destroy(instanceEraseMat); if (progressReadTex != null) Destroy(progressReadTex); } }