Files
5.24BuYu/Assets/MyGame/aa/UI/DefaultPanel/GuaGuaLe/GuaGuaLeGPU.cs
T
2026-06-25 17:21:39 +08:00

302 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Assets.MyGame.Scripts.Net;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// GPU 刮刮乐(三层架构版)
///
/// 层级结构:
/// RawImage (prizeImage) ← 最下层:奖品图
/// Text (tmp_AwardName) ← 中间层:奖品文字
/// RawImage (scratchImage) ← 最上层:涂层遮罩(挂本脚本)
/// </summary>
public class GuaGuaLeGPU : SubUIBase, IPointerDownHandler, IDragHandler
{
[Header("UI 引用(必填)")]
public RawImage scratchImage; // 最上层:涂层遮罩(挂此脚本)
public TextMeshProUGUI tmp_AwardName; // 中间层:奖品文字
public GuaGuaLeItem giftPrefab; // 奖品预制体(用于显示奖品图片)
public Transform giftPrefabParent;
[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) return;
currentAward = result.prizeName ?? "谢谢参与";
if (tmp_AwardName != null)
tmp_AwardName.text = currentAward;
// 清除旧的奖励物品
for (int i = giftPrefabParent.childCount - 1; i >= 0; i--)
Destroy(giftPrefabParent.GetChild(i).gameObject);
// 显示奖品图片(baseProduct.imageUrl)
string imgUrl = result.baseProduct?.imageUrl;
if (!string.IsNullOrEmpty(imgUrl) && giftPrefab != null)
{
var item = Instantiate(giftPrefab, giftPrefabParent);
item.Init(imgUrl, result.count);
}
});
}
// ================================================================
// 公开方法
// ================================================================
public void ResetCard()
{
if (maskRT == null) return;
ClearRT(maskRT, Color.white);
ResetState();
if (instanceMaskMat != null)
instanceMaskMat.SetColor("_CoatColor", coatColor);
FetchAward();
}
public void RevealAll()
{
if (maskRT == null || isRevealed) return;
isRevealed = true;
ClearRT(maskRT, Color.black);
if (tmp_AwardName != null)
tmp_AwardName.text = currentAward;
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);
}
}