363 lines
12 KiB
C#
363 lines
12 KiB
C#
using Assets.MyGame.Scripts.Net;
|
||
using Assets.MyGame.Scripts.Net.APIResponses;
|
||
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;
|
||
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;
|
||
/// <summary>本次抽奖结果(领取时按 prizeType 判断是否给游戏内座位加金币)</summary>
|
||
private ScratchCardResult drawResult;
|
||
|
||
private Vector2 lastUV;
|
||
private bool hasLastUV;
|
||
|
||
private Texture2D progressReadTex;
|
||
private int dragCount;
|
||
|
||
/// <summary>
|
||
/// 刮刮卡面板打开期间,挂起 WebSocket 推送(AssetUpdated/BackpackUpdated)触发的用户数据刷新,
|
||
/// 统一在点击"领取"按钮时才刷新用户数据(加金币/钻石/道具)。
|
||
/// </summary>
|
||
public static bool SuspendWsRefresh;
|
||
|
||
/// <summary>
|
||
/// 领取时刮出金币触发(参数 = 金币数):游戏内 GameMainPanel_FishBase 订阅后
|
||
/// 给本地座位加金币并帧同步给房间其他玩家;主界面(无游戏面板)不订阅不触发。
|
||
/// </summary>
|
||
public static event System.Action<long> OnScratchGoldAwarded;
|
||
|
||
public override void Awake()
|
||
{
|
||
base.Awake();
|
||
// 领取按钮:刮完后最后一步点击领取时,才刷新用户数据(金币/钻石/道具/刮刮卡数量)
|
||
if (btn_Get != null)
|
||
btn_Get.onClick.AddListener(OnGetClick);
|
||
}
|
||
|
||
private void OnGetClick()
|
||
{
|
||
SuspendWsRefresh = false;
|
||
// ⭐ 刮出金币:通知游戏内给本地座位加金币并帧同步给房间其他玩家(主界面没订阅不触发)
|
||
if (drawResult != null && drawResult.prizeType == 1 && drawResult.count > 0)
|
||
OnScratchGoldAwarded?.Invoke(drawResult.count);
|
||
drawResult = null;
|
||
string userId = GameManager.Instance?.user?.id;
|
||
if (!string.IsNullOrEmpty(userId))
|
||
APIRequestList.GetUser(userId, a => GameManager.Instance.UpdataUserData(a));
|
||
}
|
||
|
||
public override void Hide()
|
||
{
|
||
SuspendWsRefresh = false;
|
||
base.Hide();
|
||
}
|
||
|
||
protected override void OnShow(object args = null)
|
||
{
|
||
base.OnShow(args);
|
||
// 打开面板即挂起 WebSocket 推送刷新,数据统一在点击"领取"时更新
|
||
SuspendWsRefresh = true;
|
||
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;
|
||
|
||
drawResult = result; // 保存抽奖结果:领取时按 prizeType 判断是否给游戏内座位加金币
|
||
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;
|
||
}
|
||
|
||
// 不在此处刷新用户数据:加金币/钻石/道具统一在最后点击"领取"按钮时进行
|
||
});
|
||
}
|
||
|
||
// ================================================================
|
||
// 公开方法
|
||
// ================================================================
|
||
|
||
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);
|
||
|
||
// 刮开揭示时不刷新用户数据:加金币/钻石/道具统一在最后点击"领取"按钮时进行
|
||
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()
|
||
{
|
||
// 面板销毁时恢复 WebSocket 推送刷新,避免影响其他功能
|
||
SuspendWsRefresh = false;
|
||
if (maskRT != null) { maskRT.Release(); Destroy(maskRT); }
|
||
if (instanceMaskMat != null) Destroy(instanceMaskMat);
|
||
if (instanceEraseMat != null) Destroy(instanceEraseMat);
|
||
if (progressReadTex != null) Destroy(progressReadTex);
|
||
}
|
||
}
|