497 lines
23 KiB
C#
497 lines
23 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using DG.Tweening;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.AddressableAssets;
|
||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||
using UnityEngine.UI;
|
||
|
||
/// <summary>
|
||
/// XJMH Boss 出场播报(把 lua 的 EnemyAppearEffect:BossAppear 搬到 C#)。
|
||
///
|
||
/// ── 原版流程(lua)──
|
||
/// Boss OnCreate(= C# 的 MonsterControl_BossBase.Init)→ CreateAppearTip():
|
||
/// GC.EffectMgr.CreateEffect("EnemyAppearEffect", "PW_Effect_Boss_Appear",
|
||
/// {prefab = self.enemyId, isBoss = true});
|
||
/// 各 Boss 再自己补出场语音(TextScorpion / TextShark / TextDragon ...),
|
||
/// 而 102/112/113/114/115 这几个没有专属类,走基类 Enemy:CreateAppearTip → 播 AppearTip。
|
||
/// 播报本体(pw_effect/PW_Effect.txt → EnemyAppearEffect:BossAppear):
|
||
/// Image.sprite = config.appearTip.image ← 专属播报图(handbook/tsfj_*.png)
|
||
/// Mul.text = config.appearTip.mul ← "Boss" / "大Boss" / "特殊奖励" / "最高奖励xxxx倍"
|
||
/// Name.text = config.appearTip.name ← "xxx入场"
|
||
/// MotionNode: (-822,100) →(-568,100) 0.2s EOutSine → 显示粒子 Effect
|
||
/// → 停 2.5s → 回 (-822,100) 0.2s EInSine → 销毁
|
||
///
|
||
/// ── 本项目三个坑(都在这段逻辑里处理掉)──
|
||
/// 1. 预制体 PW_Effect_Boss_Appear 的 Mul / Name 在原工程挂的是自定义 Text 组件,该脚本没导入本
|
||
/// 项目(历史提交 f93ed7bbc 已把这两个组件删掉)→ 这里运行时补 TextMeshProUGUI。
|
||
/// ⚠️ IL2CPP 铁律:运行时 AddComponent 的文本必须显式设 font,否则不显示。
|
||
/// 2. 预制体根节点自带 (-822,100) 的 anchoredPosition,而 lua 只动 MotionNode →
|
||
/// 生成后必须先把根节点归零,否则位移叠加会跑到屏幕外看不见。
|
||
/// 3. 播报图按「专属播报图 → 图鉴图」的顺序尝试加载:部分 Boss 美术还没出专属播报图。
|
||
/// 4. 整条播报是纯展示层,所有 Graphic 都要 raycastTarget=false,否则会挡住打鱼点击。
|
||
/// </summary>
|
||
public class BossAppearTipXJMH : MonoBehaviour
|
||
{
|
||
// ==================== 预制体节点名(Art/XJMH/pw/pw_prefab/game/effect/PW_Effect_Boss_Appear.prefab)====================
|
||
private const string NodeMotion = "MotionNode"; // 参与滑入/滑出的节点
|
||
private const string NodeImage = "Image"; // 播报图
|
||
private const string NodeFrame = "Frame"; // 底框(lua 不动,保留预制体自带)
|
||
private const string NodeName = "Name"; // "xxx入场"
|
||
private const string NodeMul = "Mul"; // "Boss"/"大Boss"/"特殊奖励"
|
||
private const string NodeEffect = "Effect"; // 粒子(滑入结束才显示)
|
||
|
||
// ==================== 动画参数(lua EnemyAppearEffect:BossAppear 原值,画布按 1136x640 设计)====================
|
||
// lua: motionNode.localPosition = (-822,100) → localMoveTo(-568,100,0.2,EOutSine)
|
||
// → delay 2.5 → localMoveTo(-822,100,0.2,EInSine) → 销毁
|
||
// 注意:-568 正好是 1136 宽画布的左边缘(-1136/2),也就是"播报贴屏幕左边、滑出后完全移出屏幕"。
|
||
private const float LuaCanvasWidth = 1136f;
|
||
private const float LuaStartX = -822f;
|
||
private const float LuaEndX = -568f;
|
||
private const float LuaY = 100f;
|
||
private const float TipSlideInTime = 0.2f;
|
||
private const float TipHoldTime = 2.5f; // 停留
|
||
private const float TipSlideOutTime = 0.2f;
|
||
|
||
/// <summary>true=按实际画布宽度等比换算(终点贴左边缘,和 lua 的相对位置一致);
|
||
/// false=直接用 lua 原值。位置不对时可用 TipStartXOverride / TipEndXOverride 手调。</summary>
|
||
public static bool AdaptToCanvasWidth = true;
|
||
/// <summary>手动覆盖起点 X(AdaptToCanvasWidth=false 时才用)</summary>
|
||
public static float TipStartXOverride = LuaStartX;
|
||
/// <summary>手动覆盖终点 X(AdaptToCanvasWidth=false 时才用)</summary>
|
||
public static float TipEndXOverride = LuaEndX;
|
||
|
||
// ==================== 文字样式(取自被删掉的原始自定义 Text 组件)====================
|
||
private static readonly Color TipTextColor = new Color(1f, 0.7764706f, 0f, 1f); // 金色
|
||
private const float TipMulFontSize = 23f;
|
||
private const float TipNameFontSize = 27f;
|
||
|
||
// ==================== 播报图目录 ====================
|
||
private const string HandbookDir = "Assets/Art/XJMH/pw/pw_sprite/handbook/";
|
||
|
||
// ==================== 运行状态 ====================
|
||
private RectTransform _motion;
|
||
private Image _image;
|
||
private TextMeshProUGUI _nameText;
|
||
private TextMeshProUGUI _mulText;
|
||
private Transform _effect;
|
||
|
||
private Sequence _seq;
|
||
private AsyncOperationHandle<Sprite> _spriteHandle;
|
||
private bool _hasSpriteHandle;
|
||
|
||
/// <summary>当前是否有播报在播(同一时刻只留一条,避免 Boss 连刷时叠图)</summary>
|
||
public static bool IsPlaying { get; private set; }
|
||
|
||
// ================================================================
|
||
// 配置表(对齐 lua pw_config/PW_Config.txt → PW_Config.Enemy[].appearTip)
|
||
// ================================================================
|
||
|
||
/// <summary>单个 Boss 的播报配置</summary>
|
||
[Serializable]
|
||
public class AppearTipData
|
||
{
|
||
/// <summary>项目鱼表编号(tbfishresources.json 的 fishNumber,= C# MonsterContral.fishTypeID)。
|
||
/// 0 表示只按 bossName 匹配(该 Boss 还没进鱼表)</summary>
|
||
public int fishTypeId;
|
||
/// <summary>中文名(= 预制体 MonsterControl_BossBase.bossName,也等于 lua PW_Config.Enemy[].name)</summary>
|
||
public string bossName;
|
||
/// <summary>lua type(PW_Config.Enemy[].type),仅注释用</summary>
|
||
public int luaType;
|
||
/// <summary>Mul 文本</summary>
|
||
public string mul;
|
||
/// <summary>Name 文本</summary>
|
||
public string title;
|
||
/// <summary>播报图地址,按顺序尝试(专用播报图 → 图鉴图兜底)</summary>
|
||
public string[] imageAddresses;
|
||
/// <summary>出场语音(可空:Boss 自己在 OnEnable 已经播过的不重复播)</summary>
|
||
public string soundName;
|
||
}
|
||
|
||
private static readonly AppearTipData[] Tips =
|
||
{
|
||
// ── 真 Boss:lua 有专属类,出场语音由各自的 *_BOSS.cs OnEnable 负责(这里不重复播)──
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12026, bossName = "爆裂金鲨", luaType = 1000, mul = "Boss", title = "爆裂金鲨入场",
|
||
imageAddresses = new[] { HandbookDir + "tsfj_njs.png", HandbookDir + "爆裂金鲨.png" },
|
||
},
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12028, bossName = "机械蝎", luaType = 3000, mul = "大Boss", title = "机械蝎入场",
|
||
// ⚠️ 专属播报图 tsfj_xiez.png 项目里还没有 → 自动回退图鉴图 机械蝎.png
|
||
imageAddresses = new[] { HandbookDir + "tsfj_xiez.png", HandbookDir + "机械蝎.png" },
|
||
},
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12029, bossName = "星际巡航", luaType = 4000, mul = "大Boss", title = "星际巡航入场",
|
||
imageAddresses = new[] { HandbookDir + "星际巡航.png", HandbookDir + "tsfj_xjxh.png" },
|
||
},
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12030, bossName = "破坏球", luaType = 5000, mul = "Boss", title = "破坏球入场",
|
||
imageAddresses = new[] { HandbookDir + "破坏球.png", HandbookDir + "tsfj_phq.png" },
|
||
},
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12031, bossName = "幸运轮", luaType = 6000, mul = "Boss", title = "幸运轮",
|
||
// 幸运轮.png 本身就是专属播报图(与 tsfj_xyl.png 同一张)
|
||
imageAddresses = new[] { HandbookDir + "幸运轮.png", HandbookDir + "tsfj_xyl.png" },
|
||
},
|
||
|
||
// ── 特殊玩法 / 免费道具:lua 无专属类 → 走基类,出场音是 AppearTip ──
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12034, bossName = "烈焰风暴", luaType = 115, mul = "特殊奖励", title = "烈焰风暴入场",
|
||
imageAddresses = new[] { HandbookDir + "烈焰风暴.png" }, soundName = "PW_AppearTip",
|
||
},
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12022, bossName = "幸运飞碟", luaType = 114, mul = "最高奖励1500倍", title = "幸运飞碟入场",
|
||
imageAddresses = new[] { HandbookDir + "幸运飞碟.png" }, soundName = "PW_AppearTip",
|
||
},
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12023, bossName = "小丑扑克", luaType = 113, mul = "最高奖励1600倍", title = "小丑扑克入场",
|
||
imageAddresses = new[] { HandbookDir + "小丑扑克.png" }, soundName = "PW_AppearTip",
|
||
},
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12024, bossName = "黑洞飞船", luaType = 112, mul = "特殊奖励", title = "黑洞飞船入场",
|
||
imageAddresses = new[] { HandbookDir + "黑洞飞船.png" }, soundName = "PW_AppearTip",
|
||
},
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 12025, bossName = "弹药箱", luaType = 102, mul = "特殊奖励", title = "弹药箱入场",
|
||
imageAddresses = new[] { HandbookDir + "弹药箱.png" }, soundName = "PW_AppearTip",
|
||
},
|
||
|
||
// ── 还没进鱼表 / 项目里没有 Boss 预制体,仅按中文名兜底(保留配置,接上即生效)──
|
||
new AppearTipData
|
||
{
|
||
fishTypeId = 0, bossName = "女王龙", luaType = 2000, mul = "大Boss", title = "女王龙入场",
|
||
imageAddresses = new[] { HandbookDir + "tsfj_nfl.png", HandbookDir + "女王龙.png" },
|
||
},
|
||
};
|
||
|
||
private static Dictionary<int, AppearTipData> _byFishId;
|
||
private static Dictionary<string, AppearTipData> _byNameOnly;
|
||
|
||
private static void EnsureIndex()
|
||
{
|
||
if (_byFishId != null) return;
|
||
|
||
_byFishId = new Dictionary<int, AppearTipData>();
|
||
// _byNameOnly 只收 fishTypeId<=0 的纯名字行:避免某个预制体的 bossName 被误填
|
||
// (例如 7001 存钱罐的 bossName 被填成"小丑扑克")时,在按名字兜底这条路上
|
||
// 命中 12023(小丑扑克)的行 —— 有编号的行只走编号匹配。
|
||
_byNameOnly = new Dictionary<string, AppearTipData>();
|
||
|
||
foreach (var t in Tips)
|
||
{
|
||
if (t.fishTypeId > 0)
|
||
{
|
||
if (!_byFishId.ContainsKey(t.fishTypeId)) _byFishId.Add(t.fishTypeId, t);
|
||
}
|
||
else if (!string.IsNullOrEmpty(t.bossName))
|
||
{
|
||
if (!_byNameOnly.ContainsKey(t.bossName)) _byNameOnly.Add(t.bossName, t);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>查配置:先按项目鱼表编号精确命中,再按中文名兜底</summary>
|
||
public static bool TryGetData(int fishTypeId, string bossName, out AppearTipData data)
|
||
{
|
||
EnsureIndex();
|
||
|
||
if (fishTypeId > 0 && _byFishId.TryGetValue(fishTypeId, out data)) return true;
|
||
if (!string.IsNullOrEmpty(bossName) && _byNameOnly.TryGetValue(bossName, out data)) return true;
|
||
|
||
data = null;
|
||
return false;
|
||
}
|
||
|
||
/// <summary>该 Boss 是否有出场播报配置(供调用方决定要不要退回旧播报)</summary>
|
||
public static bool HasData(int fishTypeId, string bossName) => TryGetData(fishTypeId, bossName, out _);
|
||
|
||
// ================================================================
|
||
// 对外入口
|
||
// ================================================================
|
||
|
||
/// <summary>
|
||
/// 播放一次出场播报。
|
||
/// </summary>
|
||
/// <param name="prefab">播报预制体(PW_Effect_Boss_Appear,取自面板的 boardCast_Boss 字段)</param>
|
||
/// <param name="parent">UI 父节点(子面板层,随 UI 卸载自动清理)</param>
|
||
/// <param name="fishTypeId">C# MonsterContral.fishTypeID(项目鱼表编号)</param>
|
||
/// <param name="bossName">Boss 中文名(MonsterControl_BossBase.bossName)</param>
|
||
/// <param name="font">文本字体(可空 → TMP_Settings.defaultFontAsset = SourceHanSans SDF)</param>
|
||
/// <returns>
|
||
/// true = 本系统已接管(无论实际有没有播:没配播报的 Boss、或上一条还在播都会返回 true,
|
||
/// 这样调用方就不会再退回旧播报导致两条叠着显示);
|
||
/// false = 面板上的预制体不是播报版式(缺 MotionNode),调用方可以退回旧播报。
|
||
/// </returns>
|
||
public static bool TryPlay(GameObject prefab, Transform parent, int fishTypeId, string bossName,
|
||
TMP_FontAsset font = null)
|
||
{
|
||
if (prefab == null || parent == null || !Application.isPlaying) return false;
|
||
|
||
// 版式判定:播报预制体必须有 MotionNode;否则说明面板上挂的还是旧播报预制体
|
||
if (prefab.transform.Find(NodeMotion) == null)
|
||
{
|
||
Debug.LogWarning($"[BossAppearTip] 预制体 {prefab.name} 里没有 {NodeMotion} 节点," +
|
||
"不是 PW_Effect_Boss_Appear 版式,本次不播(可用旧播报兜底)");
|
||
return false;
|
||
}
|
||
|
||
if (!TryGetData(fishTypeId, bossName, out var data))
|
||
{
|
||
// 没配播报的 Boss(如存钱罐 7001 / 机甲雷霆 Z_4030、或 bossName 填错的预制体)
|
||
// 直接不出场播报,绝不退回旧播报——否则会拿 bossName 去乱套图鉴图
|
||
Debug.Log($"[BossAppearTip] 未配置出场播报,跳过:fishTypeId={fishTypeId} bossName={bossName}");
|
||
return true;
|
||
}
|
||
|
||
if (IsPlaying)
|
||
{
|
||
Debug.Log($"[BossAppearTip] 上一条出场播报还没播完,本次跳过:{bossName}");
|
||
return true;
|
||
}
|
||
|
||
var go = Instantiate(prefab, parent);
|
||
go.name = "BossAppearTip_" + (string.IsNullOrEmpty(bossName) ? fishTypeId.ToString() : bossName);
|
||
|
||
var tip = go.GetComponent<BossAppearTipXJMH>();
|
||
if (tip == null) tip = go.AddComponent<BossAppearTipXJMH>();
|
||
tip.Play(data, font);
|
||
return true;
|
||
}
|
||
|
||
// ================================================================
|
||
// 播放流程
|
||
// ================================================================
|
||
|
||
/// <summary>本次播放使用的字体(Play 传入,可空 → 用 TMP 默认字体)</summary>
|
||
private TMP_FontAsset _tipFont;
|
||
|
||
private void Play(AppearTipData data, TMP_FontAsset font)
|
||
{
|
||
IsPlaying = true;
|
||
_tipFont = font; // 供 ResolveNodes → EnsureTmpText 使用
|
||
|
||
// ⭐ 坑2:根节点预制体自带 (-822,100),lua 只动 MotionNode → 先把根归零
|
||
var rootRt = transform as RectTransform;
|
||
if (rootRt != null) rootRt.anchoredPosition = Vector2.zero;
|
||
|
||
ResolveNodes();
|
||
|
||
// ---- 文本(坑1:运行时补 TMP)----
|
||
if (_mulText != null && !string.IsNullOrEmpty(data.mul)) _mulText.text = data.mul;
|
||
if (_nameText != null && !string.IsNullOrEmpty(data.title)) _nameText.text = data.title;
|
||
|
||
// ---- 播报图(坑3:按顺序尝试,第一个加载到就用)----
|
||
LoadSprite(data.imageAddresses);
|
||
|
||
// ---- 粒子:滑入结束才显示(lua: frameEffect:SetActive(false) → 滑入结束 SetActive(true))----
|
||
if (_effect != null) _effect.gameObject.SetActive(false);
|
||
|
||
// ---- 动画(lua EnemyAppearEffect:BossAppear)----
|
||
if (_motion == null)
|
||
{
|
||
Debug.LogWarning($"[BossAppearTip] 找不到 {NodeMotion},播报直接销毁");
|
||
DestroyTip(0f);
|
||
return;
|
||
}
|
||
|
||
_seq?.Kill();
|
||
_seq = DOTween.Sequence();
|
||
ComputeLayout(out float startX, out float endX);
|
||
_motion.anchoredPosition = new Vector2(startX, LuaY);
|
||
_seq.Append(_motion.DOAnchorPos(new Vector2(endX, LuaY), TipSlideInTime).SetEase(Ease.OutSine));
|
||
_seq.AppendCallback(() => { if (_effect != null) _effect.gameObject.SetActive(true); });
|
||
_seq.AppendInterval(TipHoldTime);
|
||
_seq.Append(_motion.DOAnchorPos(new Vector2(startX, LuaY), TipSlideOutTime).SetEase(Ease.InSine));
|
||
_seq.OnComplete(() => DestroyTip(0f));
|
||
|
||
// ---- 出场语音 ----
|
||
if (!string.IsNullOrEmpty(data.soundName))
|
||
AudioManager.Instance.PlayerFXByName(data.soundName);
|
||
|
||
Debug.Log($"[BossAppearTip] 播报 {data.title}({data.mul})fishTypeId={data.fishTypeId} " +
|
||
$"起点/终点=({startX:F0},{LuaY:F0})→({endX:F0},{LuaY:F0})");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 算滑入起止 X。
|
||
/// lua 的 -822/-568 是按 1136x640 画布设计的,-568 正好是画布左边缘;
|
||
/// 本项目画布更宽(UIScene 参考分辨率 1624x750),直接照搬播报会飘在屏幕中间偏左,
|
||
/// 所以默认按「画布宽度 / 1136」等比换算,保证终点始终贴屏幕左边缘。
|
||
/// </summary>
|
||
private void ComputeLayout(out float startX, out float endX)
|
||
{
|
||
if (!AdaptToCanvasWidth)
|
||
{
|
||
startX = TipStartXOverride;
|
||
endX = TipEndXOverride;
|
||
return;
|
||
}
|
||
|
||
float canvasWidth = LuaCanvasWidth;
|
||
|
||
// 从父节点往上找根 Canvas(播报自己的根节点上挂了 Canvas,不能从自己往上找)
|
||
if (transform.parent != null)
|
||
{
|
||
var canvas = transform.parent.GetComponentInParent<Canvas>();
|
||
var root = canvas != null ? canvas.rootCanvas : null;
|
||
var rootRt = root != null ? root.transform as RectTransform : null;
|
||
if (rootRt != null && rootRt.rect.width > 1f) canvasWidth = rootRt.rect.width;
|
||
}
|
||
|
||
float scale = canvasWidth / LuaCanvasWidth;
|
||
endX = -canvasWidth * 0.5f;
|
||
startX = endX - (LuaEndX - LuaStartX) * scale; // 滑入距离 254 等比换算
|
||
}
|
||
|
||
/// <summary>取节点引用;Mul / Name 运行时补 TextMeshProUGUI(原自定义 Text 组件未导入本项目)</summary>
|
||
private void ResolveNodes()
|
||
{
|
||
Transform motion = transform.Find(NodeMotion);
|
||
|
||
if (motion != null)
|
||
{
|
||
// MotionNode 的 anchor 是 (0.5,0.5),anchoredPosition 即 lua 里的 localPosition
|
||
_motion = motion as RectTransform;
|
||
|
||
var imageTf = motion.Find(NodeImage);
|
||
if (imageTf != null)
|
||
{
|
||
_image = imageTf.GetComponent<Image>();
|
||
if (_image != null)
|
||
{
|
||
_image.raycastTarget = false; // 坑4:不挡打鱼点击
|
||
_image.preserveAspect = true; // 各 Boss 播报图比例不同,避免拉伸变形
|
||
|
||
// 预制体自带的示例图是"女王龙"的播报图,先关掉,等目标图加载好再显示,
|
||
// 否则别的 Boss 出场会闪一下女王龙。
|
||
_image.enabled = false;
|
||
}
|
||
}
|
||
|
||
// Frame(底框):lua 不动它(用预制体自带),只关掉射线检测
|
||
var frameTf = motion.Find(NodeFrame);
|
||
var frameImg = frameTf != null ? frameTf.GetComponent<Image>() : null;
|
||
if (frameImg != null) frameImg.raycastTarget = false;
|
||
|
||
_effect = motion.Find(NodeEffect);
|
||
|
||
_nameText = EnsureTmpText(motion, NodeName, TipNameFontSize);
|
||
_mulText = EnsureTmpText(motion, NodeMul, TipMulFontSize);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"[BossAppearTip] 预制体里找不到 {NodeMotion} 节点");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 给节点补 TMP 文本(已有就复用)。
|
||
/// ⚠️ Mul 节点上挂了一个 Canvas(原工程为粒子/文字排序用),保留不动,TMP 直接加在该节点上。
|
||
/// </summary>
|
||
private TextMeshProUGUI EnsureTmpText(Transform parent, string nodeName, float fontSize)
|
||
{
|
||
Transform tf = parent.Find(nodeName);
|
||
if (tf == null)
|
||
{
|
||
Debug.LogWarning($"[BossAppearTip] 预制体里找不到 {nodeName} 节点,文本不显示");
|
||
return null;
|
||
}
|
||
|
||
var tmp = tf.GetComponent<TextMeshProUGUI>();
|
||
if (tmp == null)
|
||
{
|
||
// 兜底:万一节点上有残留的 UGUI Text,先关掉避免叠字
|
||
var legacy = tf.GetComponent<Text>();
|
||
if (legacy != null) legacy.enabled = false;
|
||
tmp = tf.gameObject.AddComponent<TextMeshProUGUI>();
|
||
}
|
||
|
||
// ⭐ 铁律:运行时 AddComponent 的文本必须显式设字体,否则不显示
|
||
tmp.font = _tipFont != null ? _tipFont : TMP_Settings.defaultFontAsset;
|
||
tmp.fontSize = fontSize;
|
||
tmp.fontStyle = FontStyles.Bold; // 原组件 m_FontStyle=2
|
||
tmp.alignment = TextAlignmentOptions.Center; // 原组件 m_Alignment=4
|
||
tmp.color = TipTextColor;
|
||
tmp.raycastTarget = false;
|
||
return tmp;
|
||
}
|
||
|
||
/// <summary>按顺序尝试加载播报图(Addressables 地址),第一个成功就用</summary>
|
||
private async void LoadSprite(string[] addresses)
|
||
{
|
||
if (_image == null || addresses == null || addresses.Length == 0) return;
|
||
|
||
for (int i = 0; i < addresses.Length; i++)
|
||
{
|
||
string address = addresses[i];
|
||
if (string.IsNullOrEmpty(address)) continue;
|
||
|
||
// 地址没注册时 LoadAssetAsync 可能直接抛 InvalidKeyException,所以整段兜起来
|
||
AsyncOperationHandle<Sprite> handle = default;
|
||
Sprite sprite = null;
|
||
try
|
||
{
|
||
handle = Addressables.LoadAssetAsync<Sprite>(address);
|
||
sprite = await handle.Task;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogWarning($"[BossAppearTip] 播报图加载失败,试下一张:{address} → {e.Message}");
|
||
if (handle.IsValid()) Addressables.Release(handle);
|
||
continue;
|
||
}
|
||
|
||
if (this == null || _image == null)
|
||
{
|
||
if (handle.IsValid()) Addressables.Release(handle);
|
||
return;
|
||
}
|
||
|
||
if (handle.Status == AsyncOperationStatus.Succeeded && sprite != null)
|
||
{
|
||
_image.sprite = sprite;
|
||
_image.enabled = true; // 图到位才显示(避免闪预制体自带的示例图)
|
||
_spriteHandle = handle;
|
||
_hasSpriteHandle = true;
|
||
return;
|
||
}
|
||
|
||
if (handle.IsValid()) Addressables.Release(handle);
|
||
Debug.LogWarning($"[BossAppearTip] 播报图不存在,试下一张:{address}");
|
||
}
|
||
|
||
Debug.LogWarning($"[BossAppearTip] 播报图全部加载失败:{string.Join(" / ", addresses)}");
|
||
}
|
||
|
||
private void DestroyTip(float delay)
|
||
{
|
||
if (delay > 0f) DOVirtual.DelayedCall(delay, () => { if (this != null) Destroy(gameObject); });
|
||
else Destroy(gameObject);
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
IsPlaying = false;
|
||
|
||
_seq?.Kill();
|
||
_seq = null;
|
||
|
||
if (_hasSpriteHandle && _spriteHandle.IsValid()) Addressables.Release(_spriteHandle);
|
||
_hasSpriteHandle = false;
|
||
|
||
_motion = null; _image = null;
|
||
_nameText = null; _mulText = null; _effect = null;
|
||
}
|
||
}
|