309 lines
9.2 KiB
C#
309 lines
9.2 KiB
C#
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 GuaGuaLeItem giftPrefab; // 奖品预制体(用于显示奖品图片)
|
||
public Transform giftPrefabParent;
|
||
public Sprite sprite_Gold;
|
||
public Sprite sprite_Diamond;
|
||
|
||
[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 = result.baseProduct?.imageUrl;
|
||
if (!string.IsNullOrEmpty(imgUrl))
|
||
item.Init(imgUrl, result.count);
|
||
break;
|
||
}
|
||
});
|
||
}
|
||
|
||
// ================================================================
|
||
// 公开方法
|
||
// ================================================================
|
||
|
||
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);
|
||
|
||
|
||
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);
|
||
}
|
||
}
|