1966 lines
86 KiB
C#
1966 lines
86 KiB
C#
using Assets.Framework.Tool;
|
||
using Assets.MyGame.Scripts.Net;
|
||
using Assets.MyGame.Scripts.Net.API;
|
||
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
|
||
using Assets.MyGame.Scripts.Net.APIResponses.Enum;
|
||
using DG.Tweening;
|
||
using Spine.Unity;
|
||
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.Linq;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.AddressableAssets;
|
||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||
using UnityEngine.UI;
|
||
|
||
/// <summary>
|
||
/// 星际迷航(XJMH)主面板:只放 XJMH 专属逻辑(炮台切换面板、金币柱、核弹道具)。
|
||
/// 通用逻辑都在父类 GameMainPanel_FishBase(三个游戏共用,不动具体实现)。
|
||
/// XJMH 专属表现移植自原版 Lua:
|
||
/// 切换面板 ← PW_SelectBatteryView(EnterAction / ExitAction / UpdateBattery / SelectBattery)
|
||
/// 金币柱 ← PW_GoldColumnMgr / PW_GoldColumn(见 GoldColumnController)
|
||
/// 道具 ← 原版只开放核弹头:按钮 + 缩放面板(面板同"道具二层"样式,条目按后端核弹数据自动生成)
|
||
/// 设置 ← 按钮 + 缩放面板(覆写父类的宽度展开动画,只影响 XJMH)
|
||
/// </summary>
|
||
public class GameMainPanel_XJMH : GameMainPanel_FishBase
|
||
{
|
||
// ==================== 炮台切换面板(按钮式) ====================
|
||
[Header("炮台切换面板")]
|
||
[Tooltip("长按 img_OpenPT 弹出的切换面板(预制体里嵌的是原版 PW_SelectBatteryView 界面)")]
|
||
public GameObject ChangePTPanel;
|
||
public Button btn_GreenPT; // 对应 xjmhTurretList[0]
|
||
public Button btn_BluePT; // 对应 xjmhTurretList[1]
|
||
public Button btn_GoldPT; // 对应 xjmhTurretList[2]
|
||
public Button btn_VIPPT; // 对应 xjmhTurretList[3]
|
||
|
||
// 按钮与列表索引的映射(顺序固定:Green=0, Blue=1, Gold=2, VIP=3)
|
||
private Button[] _ptButtons;
|
||
|
||
// ⭐ 原版 PW_SelectBatteryView 的三个滑动节点(预制体里就是原版界面结构):
|
||
// mGOContent(内容)从下方滑入滑出;mBtnUserInfo(头像)/ mGOGirl(看板娘)从左侧滑入滑出
|
||
private RectTransform _ptContent;
|
||
private RectTransform _ptUserInfo;
|
||
private RectTransform _ptGirl;
|
||
private Vector2 _ptContentHome;
|
||
private Vector2 _ptUserInfoHome;
|
||
private Vector2 _ptGirlHome;
|
||
|
||
[Header("切换面板动画(原版 EnterAction/ExitAction 参数,可在面板上实时调)")]
|
||
[Tooltip("内容/头像/看板娘的滑动距离(像素),原版 500")]
|
||
public float ptSlideDistance = 500f;
|
||
[Tooltip("滑入/滑出时长(秒),原版 0.3")]
|
||
public float ptSlideTime = 0.3f;
|
||
|
||
// ==================== 金币柱 ====================
|
||
[Tooltip("金币柱控制器:可在预制体上手动挂 GoldColumnController 并拖进来(参数可存盘、Inspector 实时可调);留空则运行时自动挂")]
|
||
public GoldColumnController goldColumnController;
|
||
private GoldColumnController _goldColumn;
|
||
|
||
[Header("金币柱位置(面板上实时调,每次出柱生效)")]
|
||
[Tooltip("首柱相对座位的 X 偏移(以此处为准,控制器组件上的同名字段会被覆盖)")]
|
||
public float goldColumnOffsetX = 150f;
|
||
[Tooltip("相邻金币柱的 X 间距")]
|
||
public float goldColumnDeltaX = 40f;
|
||
[Tooltip("金币柱 Y(相对面板底边,0=贴着底边往上长)")]
|
||
public float goldColumnOffsetY = 0f;
|
||
|
||
// ==================== 核弹列表(按钮 + 缩放面板) ====================
|
||
[Header("核弹列表(按钮 + 缩放面板)")]
|
||
[Tooltip("打开核弹列表的按钮;层级命名不规范没关系,拖你要用的那个按钮本身即可。留空则沿用父类的道具按钮 btn_Prop")]
|
||
public Button btn_NukeList;
|
||
[Tooltip("核弹列表面板:点按钮后缩放弹出,条目自动生成在它下面(做成道具二层同款:Image + HorizontalLayoutGroup + ContentSizeFitter)")]
|
||
public GameObject nukeListPanel;
|
||
[Tooltip("核弹条目模板:不拖则用父类道具二层的模板 tmp_PropTwoItem")]
|
||
public GameObject nukeItemTemplate;
|
||
|
||
// ==================== 设置列表(按钮 + 缩放面板) ====================
|
||
[Header("设置列表(按钮 + 缩放面板)")]
|
||
[Tooltip("打开设置列表的按钮;留空则沿用父类 btn_SettingList(两个按钮都可用)")]
|
||
public Button btn_SettingPopup;
|
||
[Tooltip("设置列表面板:点按钮后缩放弹出;留空则用父类的 SittingListParent")]
|
||
public GameObject settingListPanel;
|
||
|
||
// ==================== 帮助面板(原版 PW_Help:图鉴 / 操作 / 玩法) ====================
|
||
[Header("帮助面板(原版 PW_Help:图鉴/操作/玩法)")]
|
||
[Tooltip("打开帮助面板的按钮(如设置列表里的帮助项);留空则该面板没有入口")]
|
||
public Button btn_HelpPopup;
|
||
[Tooltip("帮助面板:拖 XJMH 预制体里已嵌好的 PW_Help 物体(初始会自动隐藏)")]
|
||
public GameObject helpPanel;
|
||
[Tooltip("图鉴条目模板:拖当前图鉴的 TuJianItem 条目;留空则图鉴页签只切换、不生成条目")]
|
||
public TuJianItem helpTuJianItemTemplate;
|
||
[Tooltip("操作/玩法条目模板:拖 Assets/Art/XJMH/pw/pw_prefab/item/PW_HelpItem2.prefab(内含 _textName / _textDes1~11 / _imgIcon1~5);留空则这两个页签只切换、不生成条目")]
|
||
public GameObject helpItem2Template;
|
||
|
||
// ==================== 锁定设置面板(长按锁定键打开,原版 PW_SetLock) ====================
|
||
[Header("锁定设置面板(长按锁定键打开,原版 PW_SetLock)")]
|
||
[Tooltip("原版 PW_SetLock 面板:拖 Assets/Art/XJMH/pw/pw_prefab/msgbox/PW_SetLock.prefab 的实例(初始自动隐藏);留空则长按锁定退回通用 SuoDing 面板")]
|
||
public GameObject lockSetPanel;
|
||
[Tooltip("锁定条目模板:拖 Assets/Art/XJMH/pw/pw_prefab/item/PW_SetLockItem.prefab;留空则只开关面板、不生成条目")]
|
||
public GameObject lockSetItemTemplate;
|
||
|
||
|
||
[Header("核弹列表动画(照原版 Rocket 逐项弹出)")]
|
||
[Tooltip("相邻条目的弹出/收缩间隔(秒),原版 0.1")]
|
||
public float nukeItemStagger = 0.1f;
|
||
[Tooltip("单个条目单步缩放时长(秒),原版 0.1;展开共 4 步:0→1→压扁→拉长→回正")]
|
||
public float nukeItemPopTime = 0.1f;
|
||
|
||
[Header("设置弹窗缩放时长(照原版 BoxBase)")]
|
||
[Tooltip("设置面板展开时长(秒),原版 0.3")]
|
||
public float popupOpenTime = 0.3f;
|
||
[Tooltip("设置面板收起时长(秒),原版 0.3")]
|
||
public float popupCloseTime = 0.3f;
|
||
|
||
public override void Awake()
|
||
{
|
||
base.Awake();
|
||
InitPTPanel();
|
||
InitNukeProp();
|
||
InitSettingPopup();
|
||
InitHelpPanel();
|
||
InitLockSetPanel();
|
||
}
|
||
|
||
public override void Start()
|
||
{
|
||
base.Start();
|
||
|
||
// ⭐ 关掉锁定开关 → 退出"锁定面板锁定"模式(与父类通用版语义一致):
|
||
// 下次再开锁定开关 = 正常锁定,不受锁定面板勾选约束
|
||
if (toggle_Lock != null)
|
||
{
|
||
toggle_Lock.onValueChanged.AddListener(on =>
|
||
{
|
||
if (!on) _xjLockWhiteOn = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
protected override void OnShow(object args = null)
|
||
{
|
||
base.OnShow(args);
|
||
AudioManager.Instance.ChangeBG(61109);
|
||
}
|
||
|
||
// ==================== 切换面板:初始化 / 显示 / 隐藏 ====================
|
||
|
||
/// <summary>初始化切换面板:绑定按钮点击、抓原版滑动节点、默认隐藏</summary>
|
||
private void InitPTPanel()
|
||
{
|
||
_ptButtons = new Button[] { btn_GreenPT, btn_BluePT, btn_GoldPT, btn_VIPPT };
|
||
|
||
for (int i = 0; i < _ptButtons.Length; i++)
|
||
{
|
||
int idx = i; // 闭包捕获
|
||
if (_ptButtons[i] != null)
|
||
_ptButtons[i].onClick.AddListener(() => OnPTButtonClicked(idx));
|
||
}
|
||
|
||
if (ChangePTPanel == null) return;
|
||
|
||
// 原版 PW_SelectBatteryView 的滑动节点 + 原位(进出场动画用)
|
||
_ptContent = FindChildByName(ChangePTPanel.transform, "mGOContent") as RectTransform;
|
||
_ptUserInfo = FindChildByName(ChangePTPanel.transform, "mBtnUserInfo") as RectTransform;
|
||
_ptGirl = FindChildByName(ChangePTPanel.transform, "mGOGirl") as RectTransform;
|
||
if (_ptContent != null) _ptContentHome = _ptContent.anchoredPosition;
|
||
if (_ptUserInfo != null) _ptUserInfoHome = _ptUserInfo.anchoredPosition;
|
||
if (_ptGirl != null) _ptGirlHome = _ptGirl.anchoredPosition;
|
||
|
||
// ⭐ 原版整屏根节点自带"点空白关闭"按钮(新版预制体里接的是 SetActive(false) 硬关),
|
||
// 这里改接原版 ExitAction 的滑动收场,关得和原版一样
|
||
var bgBtn = ChangePTPanel.GetComponent<Button>();
|
||
if (bgBtn != null)
|
||
{
|
||
bgBtn.onClick.RemoveAllListeners();
|
||
bgBtn.onClick.AddListener(HidePTPanel);
|
||
}
|
||
|
||
ChangePTPanel.SetActive(false);
|
||
}
|
||
|
||
/// <summary>长按 img_OpenPT → 开关切换面板</summary>
|
||
protected override void OnOpenPTLongPressTriggered()
|
||
{
|
||
if (ChangePTPanel == null)
|
||
{
|
||
Debug.LogWarning("[XJMH] ChangePTPanel 未配置");
|
||
return;
|
||
}
|
||
|
||
if (ChangePTPanel.activeSelf) HidePTPanel();
|
||
else ShowPTPanel();
|
||
}
|
||
|
||
/// <summary>面板隐藏时清理(父类 OnHide 调用)</summary>
|
||
protected override void CleanupTurretPanel()
|
||
{
|
||
base.CleanupTurretPanel();
|
||
|
||
// 立即收起(不走动画,面板整体隐藏后动画也看不到),并把滑动节点复位
|
||
if (ChangePTPanel != null)
|
||
{
|
||
_ptContent?.DOKill();
|
||
_ptUserInfo?.DOKill();
|
||
_ptGirl?.DOKill();
|
||
if (_ptContent != null) _ptContent.anchoredPosition = _ptContentHome;
|
||
if (_ptUserInfo != null) _ptUserInfo.anchoredPosition = _ptUserInfoHome;
|
||
if (_ptGirl != null) _ptGirl.anchoredPosition = _ptGirlHome;
|
||
ChangePTPanel.SetActive(false);
|
||
}
|
||
|
||
_goldColumn?.Cleanup();
|
||
|
||
// 核弹列表:离场时立即收起并清动画锁(下次进场是干净状态)
|
||
_nukeAnimating = false;
|
||
for (int i = 0; i < _nukeItems.Count; i++)
|
||
if (_nukeItems[i] != null) _nukeItems[i].DOKill();
|
||
if (nukeListPanel != null)
|
||
{
|
||
nukeListPanel.transform.DOKill();
|
||
nukeListPanel.transform.localScale = Vector3.one;
|
||
nukeListPanel.SetActive(false);
|
||
}
|
||
|
||
// 帮助面板:离场立即收起
|
||
if (helpPanel != null)
|
||
{
|
||
helpPanel.transform.DOKill();
|
||
helpPanel.transform.localScale = Vector3.one;
|
||
helpPanel.SetActive(false);
|
||
}
|
||
|
||
// 锁定设置面板:离场立即收起
|
||
if (lockSetPanel != null)
|
||
{
|
||
lockSetPanel.transform.DOKill();
|
||
lockSetPanel.transform.localScale = Vector3.one;
|
||
lockSetPanel.SetActive(false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开切换面板:移植原版 PW_SelectBatteryView:EnterAction ——
|
||
/// 内容从下方滑入、头像/看板娘从左侧滑入,并刷新当前炮台标记。
|
||
/// </summary>
|
||
private void ShowPTPanel()
|
||
{
|
||
// 按钮可用性:超出 xjmhTurretList 数量的按钮隐藏
|
||
var list = currSitData != null ? currSitData.xjmhTurretList : null;
|
||
int count = (list != null) ? list.Count : 0;
|
||
for (int i = 0; i < _ptButtons.Length; i++)
|
||
{
|
||
if (_ptButtons[i] == null) continue;
|
||
bool available = i < count && list[i] != null;
|
||
_ptButtons[i].gameObject.SetActive(available);
|
||
}
|
||
|
||
ChangePTPanel.SetActive(true);
|
||
ChangePTPanel.transform.localScale = Vector3.one; // 清掉旧版缩放入场可能留下的残留
|
||
|
||
// 原版 EnterAction:先摆到"屏外",再滑回原位
|
||
_ptContent?.DOKill();
|
||
_ptUserInfo?.DOKill();
|
||
_ptGirl?.DOKill();
|
||
|
||
if (_ptContent != null)
|
||
{
|
||
_ptContent.anchoredPosition = _ptContentHome + Vector2.down * ptSlideDistance;
|
||
_ptContent.DOAnchorPos(_ptContentHome, ptSlideTime).SetEase(Ease.InSine)
|
||
.OnComplete(() => { if (_ptContent != null) _ptContent.anchoredPosition = _ptContentHome; });
|
||
}
|
||
if (_ptUserInfo != null)
|
||
{
|
||
_ptUserInfo.anchoredPosition = _ptUserInfoHome + Vector2.left * ptSlideDistance;
|
||
_ptUserInfo.DOAnchorPos(_ptUserInfoHome, ptSlideTime).SetEase(Ease.InSine);
|
||
}
|
||
if (_ptGirl != null)
|
||
{
|
||
_ptGirl.anchoredPosition = _ptGirlHome + Vector2.left * ptSlideDistance;
|
||
_ptGirl.DOAnchorPos(_ptGirlHome, ptSlideTime).SetEase(Ease.InSine);
|
||
}
|
||
|
||
UpdatePTSelection();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭切换面板:移植原版 PW_SelectBatteryView:ExitAction ——
|
||
/// 内容向下滑出、头像/看板娘向左滑出,滑完隐藏并复位。
|
||
/// </summary>
|
||
private void HidePTPanel()
|
||
{
|
||
if (ChangePTPanel == null || !ChangePTPanel.activeSelf) return;
|
||
|
||
_ptContent?.DOKill();
|
||
_ptUserInfo?.DOKill();
|
||
_ptGirl?.DOKill();
|
||
|
||
if (_ptContent != null)
|
||
{
|
||
_ptContent.DOAnchorPos(_ptContentHome + Vector2.down * ptSlideDistance, ptSlideTime).SetEase(Ease.OutSine)
|
||
.OnComplete(() =>
|
||
{
|
||
if (_ptContent != null) _ptContent.anchoredPosition = _ptContentHome; // 复位,下次入场干净
|
||
if (ChangePTPanel != null) ChangePTPanel.SetActive(false);
|
||
});
|
||
}
|
||
else
|
||
{
|
||
ChangePTPanel.SetActive(false); // 找不到滑动节点时兜底直接关
|
||
}
|
||
|
||
if (_ptUserInfo != null)
|
||
_ptUserInfo.DOAnchorPos(_ptUserInfoHome + Vector2.left * ptSlideDistance, ptSlideTime).SetEase(Ease.InSine)
|
||
.OnComplete(() => { if (_ptUserInfo != null) _ptUserInfo.anchoredPosition = _ptUserInfoHome; });
|
||
if (_ptGirl != null)
|
||
_ptGirl.DOAnchorPos(_ptGirlHome + Vector2.left * ptSlideDistance, ptSlideTime).SetEase(Ease.OutSine)
|
||
.OnComplete(() => { if (_ptGirl != null) _ptGirl.anchoredPosition = _ptGirlHome; });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新"当前炮台"标记:移植原版 PW_SelectBatteryView:UpdateBattery ——
|
||
/// 选中的打勾(Tick)、骨架播 "stand01" 且取消 freeze、显示 Effect;未选中的骨架定格。
|
||
/// </summary>
|
||
private void UpdatePTSelection()
|
||
{
|
||
int cur = currSitData != null ? currSitData.currentTurretIndex : -1;
|
||
|
||
for (int i = 0; i < _ptButtons.Length; i++)
|
||
{
|
||
var btn = _ptButtons[i];
|
||
if (btn == null) continue;
|
||
|
||
bool selected = i == cur;
|
||
|
||
var tick = FindChildByName(btn.transform, "Tick");
|
||
if (tick != null) tick.gameObject.SetActive(selected);
|
||
|
||
var plane = FindChildByName(btn.transform, "Plane");
|
||
if (plane == null) continue;
|
||
|
||
var skeleton = plane.GetComponent<SkeletonGraphic>();
|
||
if (skeleton == null) skeleton = plane.GetComponentInChildren<SkeletonGraphic>(true);
|
||
if (skeleton != null && skeleton.AnimationState != null)
|
||
{
|
||
skeleton.AnimationState.SetAnimation(0, "stand01", true);
|
||
skeleton.freeze = !selected; // 原版:未选中的骨架定格
|
||
}
|
||
|
||
var effect = plane.Find("Effect");
|
||
if (effect == null) effect = FindChildByName(plane, "Effect");
|
||
if (effect != null) effect.gameObject.SetActive(selected);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按钮点击:按索引切换炮台。移植原版 PW_SelectBatteryView:SelectBattery ——
|
||
/// 点当前炮台只关闭面板;切换失败(越界/不可用)保持面板打开。
|
||
/// </summary>
|
||
private void OnPTButtonClicked(int index)
|
||
{
|
||
if (currSitData == null)
|
||
{
|
||
HidePTPanel();
|
||
return;
|
||
}
|
||
|
||
// 点的是当前炮台 → 只关闭
|
||
if (index == currSitData.currentTurretIndex)
|
||
{
|
||
HidePTPanel();
|
||
return;
|
||
}
|
||
|
||
int prevIndex = currSitData.currentTurretIndex;
|
||
bool success = currSitData.SwitchTurretByIndex(index);
|
||
if (!success)
|
||
{
|
||
Debug.LogWarning($"[XJMH] 切换炮台失败,索引越界或列表为空: {index}");
|
||
return;
|
||
}
|
||
|
||
// 实际发生切换才同步(XJMH 炮台没有编号,帧同步同样传列表索引)
|
||
if (currSitData.currentTurretIndex != prevIndex)
|
||
{
|
||
SyncChangeTurret(currSitData.currentTurretIndex);
|
||
UpdatePTSelection();
|
||
}
|
||
|
||
HidePTPanel();
|
||
}
|
||
|
||
// ==================== 金币柱 ====================
|
||
|
||
/// <summary>
|
||
/// 座位加金币时播金币柱(基类钩子覆写)。
|
||
/// 所有杀鱼/爆奖加分都会经此;自己座位用 Self 预制体、其他座位用 Other。
|
||
/// </summary>
|
||
public override void ShowGoldColumn(SitPosData sit, long earn)
|
||
{
|
||
if (sit == null || earn <= 0) return;
|
||
|
||
if (_goldColumn == null)
|
||
{
|
||
_goldColumn = goldColumnController != null ? goldColumnController : gameObject.AddComponent<GoldColumnController>();
|
||
_goldColumn.Init(transform as RectTransform);
|
||
}
|
||
|
||
// ⭐ 位置参数以面板上的字段为准(每次出柱前同步,Play 模式下改完立即生效)
|
||
_goldColumn.offsetX = goldColumnOffsetX;
|
||
_goldColumn.deltaX = goldColumnDeltaX;
|
||
_goldColumn.offsetY = goldColumnOffsetY;
|
||
|
||
bool isSelf = currSitData != null && sit == currSitData;
|
||
_goldColumn.ShowOne(sit, earn, isSelf);
|
||
}
|
||
|
||
// ==================== 道具:核弹列表(XJMH 只开放核弹) ====================
|
||
|
||
private const string PropDisabledTip = "该道具在本房间不可使用";
|
||
|
||
/// <summary>核弹使用冷却截止时刻(Time.time),与通用道具的二层冷却一致,防连点连发</summary>
|
||
private float _nukeCooldownEnd;
|
||
|
||
/// <summary>核弹列表当前条目(与 _nukeItemCfgs 一一对应:逐项动画、刷新数量用)</summary>
|
||
private readonly List<RectTransform> _nukeItems = new List<RectTransform>();
|
||
private readonly List<PropInfConfig> _nukeItemCfgs = new List<PropInfConfig>();
|
||
|
||
/// <summary>核弹列表弹出/收起的动画锁(原版 rocketAction,动画期间不响应再点)</summary>
|
||
private bool _nukeAnimating;
|
||
|
||
/// <summary>
|
||
/// 道具入口(XJMH 简化版,只改本场景、不动父类):
|
||
/// - 核弹列表按钮:优先用面板上单独拖的 btn_NukeList;没拖就用父类的道具按钮 btn_Prop
|
||
/// (不做通用的两级道具菜单,点击直接开关核弹列表:数据来自后端道具表,几个显示几个,多了自动加);
|
||
/// - 刮刮卡快捷按钮:屏蔽(本房间只开放核弹)。
|
||
/// </summary>
|
||
private void InitNukeProp()
|
||
{
|
||
if (btn_NukeList != null)
|
||
{
|
||
btn_NukeList.onClick.RemoveAllListeners();
|
||
btn_NukeList.onClick.AddListener(ToggleNukeList);
|
||
}
|
||
if (btn_Prop != null && btn_Prop != btn_NukeList)
|
||
{
|
||
btn_Prop.onClick.RemoveAllListeners(); // 换掉父类 Awake 绑的通用道具菜单
|
||
btn_Prop.onClick.AddListener(ToggleNukeList);
|
||
}
|
||
if (nukeListPanel != null)
|
||
nukeListPanel.SetActive(false); // 弹窗初始收起
|
||
if (btn_GuaGuaKa != null)
|
||
{
|
||
btn_GuaGuaKa.onClick.RemoveAllListeners();
|
||
btn_GuaGuaKa.onClick.AddListener(() => UIManager.Instance.ShowBoardError(PropDisabledTip));
|
||
}
|
||
}
|
||
|
||
/// <summary>开关核弹列表:展开/收起照原版 Rocket(逐项缩放弹跳 + 反序收缩),动画期间加锁防连点</summary>
|
||
private void ToggleNukeList()
|
||
{
|
||
if (nukeListPanel == null)
|
||
{
|
||
Debug.LogWarning("[XJMH] nukeListPanel 未配置,核弹列表无法打开");
|
||
return;
|
||
}
|
||
if (_nukeAnimating) return; // 原版:rocketAction 动画锁
|
||
|
||
if (nukeListPanel.activeSelf)
|
||
{
|
||
PlayNukeClose();
|
||
return;
|
||
}
|
||
|
||
BuildNukeList();
|
||
nukeListPanel.transform.localScale = Vector3.one; // 面板本身不做缩放,条目逐项弹出(原版 Rocket)
|
||
nukeListPanel.SetActive(true);
|
||
PlayNukeOpen();
|
||
|
||
APIRequestList.GetUser((user) =>
|
||
{
|
||
if (user != null) GameManager.Instance.UpdataUserData(user);
|
||
// 只刷新数量,不重建条目(重建会打断弹出动画)
|
||
if (nukeListPanel != null && nukeListPanel.activeSelf) RefreshNukeCounts();
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 展开动画:照原版 Rocket —— 第 i 项延迟 i×0.1s 出现,
|
||
/// 然后 0.1s 缩到 1 → 0.1s 压扁(0.9,1.1) → 0.1s 拉长(1.1,0.9) → 0.1s 回正(1,1)。
|
||
/// </summary>
|
||
private void PlayNukeOpen()
|
||
{
|
||
_nukeAnimating = true;
|
||
int pending = 0;
|
||
for (int i = 0; i < _nukeItems.Count; i++)
|
||
{
|
||
var item = _nukeItems[i];
|
||
if (item == null) continue;
|
||
pending++;
|
||
item.DOKill();
|
||
item.gameObject.SetActive(false);
|
||
|
||
var seq = DOTween.Sequence();
|
||
seq.AppendInterval(i * nukeItemStagger);
|
||
seq.AppendCallback(() =>
|
||
{
|
||
if (item == null) return;
|
||
item.gameObject.SetActive(true);
|
||
item.localScale = Vector3.zero;
|
||
});
|
||
seq.Append(item.DOScale(Vector3.one, nukeItemPopTime));
|
||
seq.Append(item.DOScale(new Vector3(0.9f, 1.1f, 1f), nukeItemPopTime));
|
||
seq.Append(item.DOScale(new Vector3(1.1f, 0.9f, 1f), nukeItemPopTime));
|
||
seq.Append(item.DOScale(Vector3.one, nukeItemPopTime));
|
||
seq.OnComplete(() =>
|
||
{
|
||
pending--;
|
||
if (pending <= 0) _nukeAnimating = false;
|
||
});
|
||
}
|
||
if (pending == 0) _nukeAnimating = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 收起动画:照原版 Rocket 反序 —— 第 i 项延迟 (总数-1-i)×0.1s,然后 0.1s 缩到 0;
|
||
/// 全部收完隐藏整个面板。
|
||
/// </summary>
|
||
private void PlayNukeClose()
|
||
{
|
||
_nukeAnimating = true;
|
||
int total = _nukeItems.Count;
|
||
int pending = 0;
|
||
for (int i = 0; i < total; i++)
|
||
{
|
||
var item = _nukeItems[i];
|
||
if (item == null || !item.gameObject.activeSelf) continue;
|
||
pending++;
|
||
item.DOKill();
|
||
|
||
var seq = DOTween.Sequence();
|
||
seq.AppendInterval((total - 1 - i) * nukeItemStagger);
|
||
seq.Append(item.DOScale(Vector3.zero, nukeItemPopTime));
|
||
seq.OnComplete(() =>
|
||
{
|
||
if (item != null) item.gameObject.SetActive(false);
|
||
pending--;
|
||
if (pending <= 0) FinishNukeClose();
|
||
});
|
||
}
|
||
if (pending == 0) FinishNukeClose();
|
||
}
|
||
|
||
private void FinishNukeClose()
|
||
{
|
||
_nukeAnimating = false;
|
||
if (nukeListPanel != null) nukeListPanel.SetActive(false);
|
||
}
|
||
|
||
/// <summary>只刷新已有条目的数量显示(使用核弹后拉最新背包;不重建、不打断动画)</summary>
|
||
private void RefreshNukeCounts()
|
||
{
|
||
for (int i = 0; i < _nukeItems.Count; i++)
|
||
{
|
||
var item = _nukeItems[i];
|
||
var cfg = i < _nukeItemCfgs.Count ? _nukeItemCfgs[i] : null;
|
||
if (item != null && cfg != null)
|
||
SetNukeItemVisual(item.gameObject, cfg, GetOwnedCount(cfg.number));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重建核弹列表:条目克隆进核弹面板(做成"道具二层"同款:child0=图标+名字、child1=数量、child2=冷却圈默认隐藏),
|
||
/// 每条 = 后端道具表里 type=13(鱼雷/核弹)的启用项 —— 后端几个就显示几个(现 4 个,多了自动加)。
|
||
/// </summary>
|
||
private void BuildNukeList()
|
||
{
|
||
if (nukeListPanel == null) return;
|
||
var template = nukeItemTemplate != null ? nukeItemTemplate : tmp_PropTwoItem;
|
||
if (template == null) return;
|
||
|
||
var parent = nukeListPanel.transform;
|
||
for (int i = parent.childCount - 1; i >= 0; i--)
|
||
{
|
||
var child = parent.GetChild(i).gameObject;
|
||
// 模板自身跳过(包括"复制道具二层面板"时里面自带的那个模板),并把自带模板隐藏
|
||
if (child == template || child.name == template.name)
|
||
{
|
||
child.SetActive(false);
|
||
continue;
|
||
}
|
||
Destroy(child);
|
||
}
|
||
|
||
_nukeItems.Clear();
|
||
_nukeItemCfgs.Clear();
|
||
|
||
var nukes = GetNukeProps();
|
||
foreach (var cfg in nukes)
|
||
{
|
||
var item = Instantiate(template, parent);
|
||
item.SetActive(true);
|
||
SetNukeItemVisual(item, cfg, GetOwnedCount(cfg.number));
|
||
|
||
_nukeItems.Add(item.transform as RectTransform);
|
||
_nukeItemCfgs.Add(cfg);
|
||
|
||
var btn = item.GetComponent<Button>();
|
||
if (btn == null) btn = item.GetComponentInChildren<Button>();
|
||
if (btn != null)
|
||
{
|
||
var data = cfg; // 闭包捕获
|
||
btn.onClick.AddListener(() => UseNuke(data));
|
||
}
|
||
}
|
||
Debug.Log($"[XJMH] 核弹列表: {nukes.Count} 条");
|
||
}
|
||
|
||
/// <summary>点击核弹:冷却中拦截、数量不足提示,否则发房间协议使用(爆奖表现走 TorpedoResultNotice 广播)</summary>
|
||
private void UseNuke(PropInfConfig t)
|
||
{
|
||
if (t == null) return;
|
||
|
||
if (Time.time < _nukeCooldownEnd)
|
||
{
|
||
UIManager.Instance.ShowBoardError("道具冷却中,请稍候");
|
||
return;
|
||
}
|
||
if (GetOwnedCount(t.number) <= 0)
|
||
{
|
||
var n = string.IsNullOrEmpty(t.name) ? "核弹" : t.name;
|
||
UIManager.Instance.ShowBoardError($"[{n}] 数量不足,暂无法使用");
|
||
return;
|
||
}
|
||
|
||
_nukeCooldownEnd = Time.time + 5f;
|
||
WebSocketSendList.UseTorpedo(
|
||
GameManager.Instance.gameData.currRoomData.roomId,
|
||
GameManager.Instance.user.id,
|
||
t.number);
|
||
}
|
||
|
||
/// <summary>后端道具表里 type=13(鱼雷/核弹)的启用项(不读背包,数量为 0 的也显示)</summary>
|
||
private static List<PropInfConfig> GetNukeProps()
|
||
{
|
||
var result = new List<PropInfConfig>();
|
||
var propConfig = GameManager.Instance?.gameConfig?.propConfig;
|
||
if (propConfig == null) return result;
|
||
foreach (var t in propConfig)
|
||
{
|
||
if (t == null || t.isEnable == false) continue;
|
||
if (t.type != (int)PropEnum.鱼雷) continue;
|
||
result.Add(t);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>背包里该编号道具的数量(未拥有返回 0)</summary>
|
||
private static int GetOwnedCount(string number)
|
||
{
|
||
var props = GameManager.Instance.user?.props;
|
||
if (props == null || string.IsNullOrEmpty(number)) return 0;
|
||
foreach (var p in props)
|
||
{
|
||
if (p != null && p.number == number) return Mathf.Max(0, p.count);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 核弹条目显示("道具二层"同款):child0 = 图标 + 名字,child1 = 数量,child2 = 冷却圈默认隐藏。
|
||
/// 兼容一级模板(child0 = 数量):名字跳过,数量落在 child0。
|
||
/// </summary>
|
||
private static void SetNukeItemVisual(GameObject item, PropInfConfig t, int count)
|
||
{
|
||
// child0 图标
|
||
var icon = item.transform.childCount > 0 ? item.transform.GetChild(0).GetComponent<Image>() : null;
|
||
if (icon == null) icon = item.GetComponent<Image>();
|
||
if (icon == null) icon = item.GetComponentInChildren<Image>(true);
|
||
if (icon != null) SetNukeIconSprite(icon, t);
|
||
|
||
// child0 名字(有 child1 才写名字,避免盖住一级模板的数量)
|
||
var nameTmp = item.transform.childCount > 0 ? item.transform.GetChild(0).GetComponent<TextMeshProUGUI>() : null;
|
||
if (nameTmp == null) nameTmp = item.GetComponent<TextMeshProUGUI>();
|
||
if (nameTmp == null) nameTmp = item.GetComponentInChildren<TextMeshProUGUI>(true);
|
||
bool hasCountChild = item.transform.childCount > 1;
|
||
if (nameTmp != null && hasCountChild && !string.IsNullOrEmpty(t.name))
|
||
nameTmp.text = t.name;
|
||
|
||
// child1 数量(没有 child1 时退回 child0)
|
||
var countTmp = hasCountChild ? item.transform.GetChild(1).GetComponent<TextMeshProUGUI>() : null;
|
||
if (countTmp == null) countTmp = nameTmp;
|
||
if (countTmp != null) countTmp.text = Mathf.Max(0, count).ToString();
|
||
|
||
// child2 冷却圈:默认隐藏(本代码自带 5 秒冷却拦截,不用圈)
|
||
if (item.transform.childCount > 2)
|
||
item.transform.GetChild(2).gameObject.SetActive(false);
|
||
}
|
||
|
||
/// <summary>核弹条目图标:优先用后端缓存 sprite_Icon,没有则按 imageUrl 下载回填(同通用道具二层惯例)</summary>
|
||
private static void SetNukeIconSprite(Image img, PropInfConfig t)
|
||
{
|
||
if (img == null || t == null) return;
|
||
if (t.sprite_Icon != null)
|
||
{
|
||
img.sprite = t.sprite_Icon;
|
||
return;
|
||
}
|
||
if (string.IsNullOrEmpty(t.imageUrl)) return;
|
||
WebTool.DownImage(APIManager.baseURL + t.imageUrl, (texture) =>
|
||
{
|
||
if (img == null || texture == null) return;
|
||
t.sprite_Icon = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f);
|
||
img.sprite = t.sprite_Icon;
|
||
});
|
||
}
|
||
|
||
// ==================== 设置列表(按钮 + 缩放面板,覆写父类虚方法) ====================
|
||
|
||
/// <summary>设置面板展开态尺寸(父类原来靠宽度动画展开,改缩放前先补到展开态)</summary>
|
||
private Vector2 _settingPanelOpenSize = new Vector2(319f, 161f);
|
||
|
||
/// <summary>设置列表入口:拖了 btn_SettingPopup 就额外绑一个开关(父类 btn_SettingList 的开关仍然有效)</summary>
|
||
private void InitSettingPopup()
|
||
{
|
||
if (btn_SettingPopup != null && btn_SettingPopup != btn_SettingList)
|
||
{
|
||
btn_SettingPopup.onClick.RemoveAllListeners();
|
||
btn_SettingPopup.onClick.AddListener(ToggleSettingPopup);
|
||
}
|
||
if (settingListPanel != null)
|
||
settingListPanel.SetActive(false); // 弹窗初始收起
|
||
|
||
// 记录设置面板的"展开态尺寸"(预制体里可能存的是收起态宽度 0)
|
||
var panel = GetSettingPanel();
|
||
var rt = panel != null ? panel.transform as RectTransform : null;
|
||
if (rt != null)
|
||
{
|
||
_settingPanelOpenSize = rt.sizeDelta;
|
||
if (_settingPanelOpenSize.x < 1f) _settingPanelOpenSize = new Vector2(319f, 161f);
|
||
}
|
||
}
|
||
|
||
/// <summary>开关设置列表(与父类 btn_SettingList 同逻辑)</summary>
|
||
private void ToggleSettingPopup()
|
||
{
|
||
if (isOpenSetting) StartCoroutine(CloseSettingList());
|
||
else StartCoroutine(OpenSettingList());
|
||
}
|
||
|
||
/// <summary>设置面板:优先用单独拖的 settingListPanel,没拖就用父类的 SittingListParent</summary>
|
||
private GameObject GetSettingPanel()
|
||
{
|
||
if (settingListPanel != null) return settingListPanel;
|
||
if (SittingListParent != null) return SittingListParent.gameObject;
|
||
return null;
|
||
}
|
||
|
||
/// <summary>打开设置列表:XJMH 用缩放弹出(覆写父类的宽度展开,父类实现不动)</summary>
|
||
public override IEnumerator OpenSettingList()
|
||
{
|
||
var panel = GetSettingPanel();
|
||
if (panel != null)
|
||
{
|
||
var rt = panel.transform as RectTransform;
|
||
if (rt != null && rt.sizeDelta.x < 1f && _settingPanelOpenSize.x >= 1f)
|
||
rt.sizeDelta = _settingPanelOpenSize; // 展开态宽度兜底(父类原先用宽度动画)
|
||
OpenScalePanel(panel);
|
||
}
|
||
isOpenSetting = true;
|
||
FlipSettingButton(true);
|
||
yield break;
|
||
}
|
||
|
||
/// <summary>关闭设置列表:缩放收起(覆写父类的宽度收起)</summary>
|
||
public override IEnumerator CloseSettingList()
|
||
{
|
||
CloseScalePanel(GetSettingPanel());
|
||
isOpenSetting = false;
|
||
FlipSettingButton(false);
|
||
yield break;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置按钮的镜像指示(与父类一致的规则):打开时 X 缩放 = |x| × -1(即 -1),收起时回到 |x|(即 +1)。
|
||
/// 父类的 btn_SettingList 和自己拖的 btn_SettingPopup 都会翻(同一个对象只翻一次)。
|
||
/// </summary>
|
||
private void FlipSettingButton(bool open)
|
||
{
|
||
FlipButtonX(btn_SettingList, open);
|
||
if (btn_SettingPopup != null && btn_SettingPopup != btn_SettingList)
|
||
FlipButtonX(btn_SettingPopup, open);
|
||
}
|
||
|
||
private static void FlipButtonX(Button btn, bool open)
|
||
{
|
||
if (btn == null) return;
|
||
var v3 = btn.transform.localScale;
|
||
v3.x = Mathf.Abs(v3.x) * (open ? -1f : 1f);
|
||
btn.transform.localScale = v3;
|
||
}
|
||
|
||
// ==================== 帮助面板(原版 PW_Help:图鉴 / 操作 / 玩法) ====================
|
||
|
||
/// <summary>帮助面板内部节点(按原版预制体名字找)</summary>
|
||
private GameObject[] _helpTabs; // mScrollView1~3(1=图鉴 2=操作 3=玩法)
|
||
private Transform _helpTuJianCol1; // mScrollView1/Viewport/Content/1
|
||
private Transform _helpTuJianCol2; // mScrollView1/Viewport/Content/2
|
||
private Transform _helpOpContent; // mScrollView2/Viewport/Content(操作条目)
|
||
private Transform _helpPlayContent; // mScrollView3/Viewport/Content(玩法条目)
|
||
private bool _helpOpBuilt; // 操作页条目已生成(静态数据,只建一次)
|
||
private bool _helpPlayBuilt; // 玩法页条目已生成
|
||
private int _helpCurTab = -1;
|
||
|
||
/// <summary>
|
||
/// 帮助面板入口:拖了 btn_HelpPopup 就能打开;面板内部节点按原版名字找(mBtnBack / mBtnItem1~3 / mScrollView1~3),
|
||
/// 预制体里"点了直接把 PW_Help 隐藏"的按钮会改接成缩放收场(同切换面板的做法)。
|
||
/// </summary>
|
||
private void InitHelpPanel()
|
||
{
|
||
if (btn_HelpPopup != null)
|
||
{
|
||
btn_HelpPopup.onClick.RemoveAllListeners();
|
||
btn_HelpPopup.onClick.AddListener(ToggleHelpPanel);
|
||
}
|
||
if (helpPanel == null) return;
|
||
|
||
helpPanel.SetActive(false); // 初始收起
|
||
|
||
_helpTabs = new GameObject[3];
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
var tab = FindChildByName(helpPanel.transform, "mScrollView" + (i + 1));
|
||
_helpTabs[i] = tab != null ? tab.gameObject : null;
|
||
|
||
var tabNode = FindChildByName(helpPanel.transform, "mBtnItem" + (i + 1));
|
||
if (tabNode != null)
|
||
{
|
||
int idx = i + 1; // 闭包捕获
|
||
var b = tabNode.GetComponent<Button>();
|
||
if (b == null) b = tabNode.GetComponentInChildren<Button>();
|
||
if (b != null)
|
||
{
|
||
b.onClick.RemoveAllListeners();
|
||
b.onClick.AddListener(() => ChangeHelpTab(idx));
|
||
}
|
||
}
|
||
}
|
||
|
||
// 图鉴页签(view1)双列容器:mScrollView1/Viewport/Content 下的 1 / 2
|
||
var content = _helpTabs[0] != null ? FindChildByName(_helpTabs[0].transform, "Content") : null;
|
||
if (content != null)
|
||
{
|
||
_helpTuJianCol1 = content.Find("1");
|
||
_helpTuJianCol2 = content.Find("2");
|
||
}
|
||
|
||
// 操作页签(view2)/ 玩法页签(view3):mScrollView2|3/Viewport/Content
|
||
_helpOpContent = _helpTabs[1] != null ? FindChildByName(_helpTabs[1].transform, "Content") : null;
|
||
_helpPlayContent = _helpTabs[2] != null ? FindChildByName(_helpTabs[2].transform, "Content") : null;
|
||
|
||
// 返回按钮
|
||
var back = FindChildByName(helpPanel.transform, "mBtnBack");
|
||
if (back != null)
|
||
{
|
||
var b = back.GetComponent<Button>();
|
||
if (b == null) b = back.GetComponentInChildren<Button>();
|
||
if (b != null)
|
||
{
|
||
b.onClick.RemoveAllListeners();
|
||
b.onClick.AddListener(CloseHelpPanel);
|
||
}
|
||
}
|
||
|
||
// 预制体里可能还接了别的按钮"直接把 PW_Help SetActive(false)"(点背景关闭),改接缩放收场
|
||
var all = helpPanel.GetComponentsInChildren<Button>(true);
|
||
for (int i = 0; i < all.Length; i++)
|
||
{
|
||
var b = all[i];
|
||
if (b == null) continue;
|
||
for (int e = 0; e < b.onClick.GetPersistentEventCount(); e++)
|
||
{
|
||
if (b.onClick.GetPersistentTarget(e) == helpPanel)
|
||
{
|
||
b.onClick.RemoveAllListeners();
|
||
b.onClick.AddListener(CloseHelpPanel);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>开关帮助面板(缩放弹出/收起,同设置弹窗)</summary>
|
||
private void ToggleHelpPanel()
|
||
{
|
||
if (helpPanel == null)
|
||
{
|
||
Debug.LogWarning("[XJMH] helpPanel 未配置,帮助面板无法打开");
|
||
return;
|
||
}
|
||
|
||
if (helpPanel.activeSelf)
|
||
{
|
||
CloseHelpPanel();
|
||
return;
|
||
}
|
||
|
||
BuildHelpTuJian();
|
||
ChangeHelpTab(1);
|
||
OpenScalePanel(helpPanel);
|
||
}
|
||
|
||
private void CloseHelpPanel()
|
||
{
|
||
if (helpPanel == null || !helpPanel.activeSelf) return;
|
||
CloseScalePanel(helpPanel);
|
||
}
|
||
|
||
/// <summary>切换页签:1=图鉴 2=操作 3=玩法(操作/玩法首次进入时按原版生成条目)</summary>
|
||
private void ChangeHelpTab(int index)
|
||
{
|
||
if (_helpTabs == null) return;
|
||
if (_helpCurTab == index) return;
|
||
_helpCurTab = index;
|
||
for (int i = 0; i < _helpTabs.Length; i++)
|
||
{
|
||
if (_helpTabs[i] != null) _helpTabs[i].SetActive(i == index - 1);
|
||
}
|
||
|
||
// 操作(2)/ 玩法(3):照原版 PW_Help:InitItem2 / InitItem3,把 PW_Config 的数据填进 PW_HelpItem2 条目
|
||
if (index == 2 && !_helpOpBuilt)
|
||
{
|
||
_helpOpBuilt = true;
|
||
BuildHelpItem2List(_helpOpContent, HelpOperationCfg);
|
||
}
|
||
else if (index == 3 && !_helpPlayBuilt)
|
||
{
|
||
_helpPlayBuilt = true;
|
||
BuildHelpItem2List(_helpPlayContent, HelpPlayMethodCfg);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 图鉴页签:借用当前图鉴功能的数据(CreateFishManager.spawnList 后端下发的房间鱼配置
|
||
/// + GameManager.dic_fishTypeConfigs + 图鉴条目 TuJianItem),
|
||
/// 按类别【普通→黄金→特殊→Boss→特殊Boss】排序(同类内分值从小到大)后双列交替排布(原版 PW_Help 的 1/2 两列)。
|
||
/// </summary>
|
||
private void BuildHelpTuJian()
|
||
{
|
||
if (_helpTuJianCol1 == null || _helpTuJianCol2 == null || helpTuJianItemTemplate == null) return;
|
||
|
||
ClearHelpColumn(_helpTuJianCol1);
|
||
ClearHelpColumn(_helpTuJianCol2);
|
||
|
||
var mgr = CreateFishManager.Instance;
|
||
var spawnList = mgr != null ? mgr.spawnList : null;
|
||
if (spawnList == null) return;
|
||
|
||
// 先收集(按鱼号去重),再排序,最后摆放
|
||
var added = new HashSet<string>();
|
||
var fishList = new List<HelpTuJianEntry>();
|
||
for (int i = 0; i < spawnList.Count; i++)
|
||
{
|
||
var item = spawnList[i];
|
||
if (item == null) continue;
|
||
|
||
if (item.type == 2)
|
||
{
|
||
// 鱼阵条目:解析出阵内实际生成的鱼再加入图鉴(与当前图鉴功能同逻辑)
|
||
if (mgr.FishArrayDatas != null && mgr.FishArrayDatas.TryGetValue(item.fishNumber, out var yz))
|
||
CollectHelpTuJianFish(yz.fishNumber, added, null, fishList);
|
||
}
|
||
else
|
||
{
|
||
CollectHelpTuJianFish(item.fishNumber, added, item, fishList);
|
||
}
|
||
}
|
||
|
||
// 排序:先按类别 普通→黄金→特殊→Boss→特殊Boss,同类内再按分值从小到大;OrderBy 稳定
|
||
var sorted = fishList.OrderBy(e => e.rank).ThenBy(e => e.score).ToList();
|
||
|
||
for (int i = 0; i < sorted.Count; i++)
|
||
{
|
||
var e = sorted[i];
|
||
var col = (i % 2 == 0) ? _helpTuJianCol1 : _helpTuJianCol2;
|
||
var go = Instantiate(helpTuJianItemTemplate.gameObject, col);
|
||
go.SetActive(true);
|
||
var comp = go.GetComponent<TuJianItem>();
|
||
if (comp != null) comp.InItItem(e.detail, e.isGuDingBeiShu);
|
||
}
|
||
Debug.Log($"[XJMH] 帮助-图鉴条目: {sorted.Count} 条");
|
||
}
|
||
|
||
/// <summary>图鉴排序用的一条数据</summary>
|
||
private struct HelpTuJianEntry
|
||
{
|
||
public RoomFishDetailConfig detail;
|
||
public bool isGuDingBeiShu;
|
||
public int score; // 排序用:鱼的分值(倍数)
|
||
public int rank; // 排序用:类别 普通0 → 黄金1 → 特殊2 → Boss3 → 特殊Boss4
|
||
}
|
||
|
||
/// <summary>收集一条图鉴数据(按鱼号去重、类型过滤;普通/黄金鱼=固定倍数,其余显示"最高X倍")</summary>
|
||
private static void CollectHelpTuJianFish(string fishNumber, HashSet<string> added,
|
||
RoomFishDetailConfig detail, List<HelpTuJianEntry> outList)
|
||
{
|
||
if (string.IsNullOrEmpty(fishNumber) || !added.Add(fishNumber)) return;
|
||
if (!GameManager.Instance.dic_fishTypeConfigs.TryGetValue(fishNumber, out var cfg)) return;
|
||
|
||
bool isGuDingBeiShu;
|
||
int rank;
|
||
switch ((FishTypeEnum)cfg.fishType)
|
||
{
|
||
case FishTypeEnum.普通鱼:
|
||
isGuDingBeiShu = true;
|
||
rank = 0;
|
||
break;
|
||
case FishTypeEnum.黄金鱼:
|
||
isGuDingBeiShu = true;
|
||
rank = 1;
|
||
break;
|
||
case FishTypeEnum.特殊鱼:
|
||
isGuDingBeiShu = false;
|
||
rank = 2;
|
||
break;
|
||
case FishTypeEnum.Boss鱼:
|
||
isGuDingBeiShu = false;
|
||
rank = 3;
|
||
break;
|
||
case FishTypeEnum.特殊Boss:
|
||
isGuDingBeiShu = false;
|
||
rank = 4;
|
||
break;
|
||
default:
|
||
return; // 其他类型不进图鉴(与当前图鉴功能一致)
|
||
}
|
||
|
||
if (detail == null) detail = new RoomFishDetailConfig { fishNumber = fishNumber };
|
||
outList.Add(new HelpTuJianEntry
|
||
{
|
||
detail = detail,
|
||
isGuDingBeiShu = isGuDingBeiShu,
|
||
score = (int)cfg.fishScore,
|
||
rank = rank,
|
||
});
|
||
}
|
||
|
||
/// <summary>清空图鉴列(模板自身/同名模板跳过,避免把模板删掉)</summary>
|
||
private void ClearHelpColumn(Transform col)
|
||
{
|
||
for (int i = col.childCount - 1; i >= 0; i--)
|
||
{
|
||
var child = col.GetChild(i).gameObject;
|
||
if (helpTuJianItemTemplate != null
|
||
&& (child == helpTuJianItemTemplate.gameObject || child.name == helpTuJianItemTemplate.name)) continue;
|
||
Destroy(child);
|
||
}
|
||
}
|
||
|
||
// ==================== 帮助面板:操作 / 玩法(原版 PW_HelpItem2,无独立 lua,逻辑在 PW_Help.txt:InitItem2/InitItem3) ====================
|
||
|
||
/// <summary>条目里文本的最大条数(原版 _textDes1~11)与图片的最大条数(原版 _imgIcon1~5)</summary>
|
||
private const int HelpDesMax = 11;
|
||
private const int HelpImgMax = 5;
|
||
|
||
/// <summary>原版 lua 图片根路径 → 本工程美术根路径(目录名小写:Common/Handbook/FreeModel)</summary>
|
||
private const string LuaHelpSpriteRoot = "Assets/Bundles/PW/PW_Sprite/";
|
||
private const string ArtHelpSpriteRoot = "Assets/Art/XJMH/pw/pw_sprite/";
|
||
|
||
/// <summary>已加载过的帮助图缓存(Addressables 常驻,避免切页签重复请求)</summary>
|
||
private static readonly Dictionary<string, Sprite> HelpSpriteCache = new Dictionary<string, Sprite>();
|
||
|
||
/// <summary>PW_HelpItem2 一条配置(对应原版 PW_Config 的一条 w/h/name/des/image)</summary>
|
||
private sealed class HelpItem2Cfg
|
||
{
|
||
public float w = 300f;
|
||
public float h = 398f;
|
||
public string name;
|
||
public float nameX;
|
||
public float nameY;
|
||
/// <summary>>0 时 _imgIcon1 直接读"图鉴"里这条鱼的图(FishResourcesData.fishResourcesSpritesDic,与图鉴页同一份),取不到再按 imgs 里的路径加载</summary>
|
||
public int fishNumber;
|
||
public string[] des; // 第 i 项 = _textDes{i},格式 "x|y|文本";null/越界 = 隐藏该 _textDes
|
||
public string[] imgs; // 第 i 项 = _imgIcon{i},格式 "x|y|scale|原版图片路径";null/越界 = 隐藏该 _imgIcon
|
||
}
|
||
|
||
/// <summary>生成操作/玩法条目:清空旧条目(跳过模板本身),逐条克隆模板并填充(原版 PW_Help:InitItem2/3)</summary>
|
||
private void BuildHelpItem2List(Transform content, HelpItem2Cfg[] cfgs)
|
||
{
|
||
if (content == null)
|
||
{
|
||
Debug.LogWarning("[XJMH] 帮助面板找不到 mScrollView2/3 的 Content,操作/玩法条目无法生成");
|
||
return;
|
||
}
|
||
if (helpItem2Template == null)
|
||
{
|
||
Debug.LogWarning("[XJMH] helpItem2Template 未配置(拖 PW_HelpItem2.prefab 到面板上),操作/玩法条目无法生成");
|
||
return;
|
||
}
|
||
if (cfgs == null) return;
|
||
|
||
for (int i = content.childCount - 1; i >= 0; i--)
|
||
{
|
||
var child = content.GetChild(i).gameObject;
|
||
if (child == helpItem2Template || child.name == helpItem2Template.name) continue;
|
||
Destroy(child);
|
||
}
|
||
|
||
for (int i = 0; i < cfgs.Length; i++)
|
||
{
|
||
var go = Instantiate(helpItem2Template, content);
|
||
go.SetActive(true);
|
||
ApplyHelpItem2(go, cfgs[i]);
|
||
}
|
||
Debug.Log($"[XJMH] 帮助-{(content == _helpOpContent ? "操作" : "玩法")}条目: {cfgs.Length} 条");
|
||
}
|
||
|
||
/// <summary>把一条配置填进条目:LayoutElement 尺寸、_textName、_textDes1~11、_imgIcon1~5(缺项隐藏)</summary>
|
||
private void ApplyHelpItem2(GameObject item, HelpItem2Cfg cfg)
|
||
{
|
||
if (item == null || cfg == null) return;
|
||
|
||
var le = item.GetComponent<LayoutElement>();
|
||
if (le == null) le = item.AddComponent<LayoutElement>();
|
||
le.minWidth = cfg.w;
|
||
le.minHeight = cfg.h;
|
||
|
||
var nameTf = FindChildByName(item.transform, "_textName");
|
||
if (nameTf != null) SetHelpText(nameTf, cfg.nameX, cfg.nameY, cfg.name);
|
||
|
||
for (int i = 1; i <= HelpDesMax; i++)
|
||
{
|
||
var tf = FindChildByName(item.transform, "_textDes" + i);
|
||
if (tf == null) continue;
|
||
|
||
string raw = (cfg.des != null && i <= cfg.des.Length) ? cfg.des[i - 1] : null;
|
||
if (!string.IsNullOrEmpty(raw) && TrySplitText(raw, out float x, out float y, out string text))
|
||
{
|
||
tf.gameObject.SetActive(true);
|
||
SetHelpText(tf, x, y, text);
|
||
}
|
||
else
|
||
{
|
||
tf.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
|
||
for (int i = 1; i <= HelpImgMax; i++)
|
||
{
|
||
var tf = FindChildByName(item.transform, "_imgIcon" + i);
|
||
if (tf == null) continue;
|
||
|
||
string raw = (cfg.imgs != null && i <= cfg.imgs.Length) ? cfg.imgs[i - 1] : null;
|
||
if (!string.IsNullOrEmpty(raw) && TrySplitImage(raw, out float x, out float y, out float scale, out string path))
|
||
{
|
||
tf.gameObject.SetActive(true);
|
||
tf.localPosition = new Vector3(x, y, 0f);
|
||
tf.localScale = new Vector3(scale, scale, scale);
|
||
|
||
var img = tf.GetComponent<Image>();
|
||
if (img == null) img = tf.GetComponentInChildren<Image>(true);
|
||
if (img == null) continue;
|
||
|
||
// ⭐ _imgIcon1:优先读"图鉴"——直接取图鉴页同一份鱼图(进房时已按 TuJian 标签加载好),取不到再走路径加载
|
||
var tuJian = (i == 1 && cfg.fishNumber > 0) ? GetTuJianFishSprite(cfg.fishNumber) : null;
|
||
if (tuJian != null)
|
||
{
|
||
img.sprite = tuJian;
|
||
img.enabled = true;
|
||
}
|
||
else
|
||
{
|
||
LoadHelpSprite(img, path);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
tf.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>取"图鉴"里的鱼图(与帮助面板图鉴页同源:FishResourcesData.fishResourcesSpritesDic,按鱼号索引,进房时已加载好)</summary>
|
||
private static Sprite GetTuJianFishSprite(int fishNumber)
|
||
{
|
||
if (fishNumber <= 0) return null;
|
||
var dic = FishResourcesData.fishResourcesSpritesDic;
|
||
if (dic == null) return null;
|
||
return dic.TryGetValue(fishNumber, out var sprite) ? sprite : null;
|
||
}
|
||
|
||
/// <summary>设置条目里某个文本节点的位置与内容(帮助/锁定条目通用:先摆位置再写文本)</summary>
|
||
private static void SetHelpText(Transform tf, float x, float y, string text)
|
||
{
|
||
if (tf == null) return;
|
||
tf.localPosition = new Vector3(x, y, 0f);
|
||
SetTextContent(tf, text);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写文本内容(不动位置)。
|
||
/// ⭐ 原版 pw 预制体(PW_HelpItem2 / PW_SetLockItem)里的文本挂的是 UGUI 老版 Text
|
||
/// (原版组件,PW_CN_Font 中文字体),不是 TMP → 两种都要兼容(TMP 优先,找不到再写老版 Text)。
|
||
/// </summary>
|
||
private static void SetTextContent(Transform tf, string text)
|
||
{
|
||
if (tf == null) return;
|
||
|
||
var tmp = tf.GetComponent<TextMeshProUGUI>();
|
||
if (tmp == null) tmp = tf.GetComponentInChildren<TextMeshProUGUI>(true);
|
||
if (tmp != null)
|
||
{
|
||
tmp.text = text ?? string.Empty;
|
||
return;
|
||
}
|
||
|
||
var legacy = tf.GetComponent<Text>();
|
||
if (legacy == null) legacy = tf.GetComponentInChildren<Text>(true);
|
||
if (legacy != null) legacy.text = text ?? string.Empty;
|
||
}
|
||
|
||
/// <summary>拆 "x|y|文本"(原版 des 的 x/y 就是条目内的 localPosition)</summary>
|
||
private static bool TrySplitText(string raw, out float x, out float y, out string text)
|
||
{
|
||
x = y = 0f;
|
||
text = null;
|
||
int p1 = raw.IndexOf('|');
|
||
if (p1 < 0) return false;
|
||
int p2 = raw.IndexOf('|', p1 + 1);
|
||
if (p2 < 0) return false;
|
||
if (!float.TryParse(raw.Substring(0, p1), NumberStyles.Float, CultureInfo.InvariantCulture, out x)) return false;
|
||
if (!float.TryParse(raw.Substring(p1 + 1, p2 - p1 - 1), NumberStyles.Float, CultureInfo.InvariantCulture, out y)) return false;
|
||
text = raw.Substring(p2 + 1);
|
||
return true;
|
||
}
|
||
|
||
/// <summary>拆 "x|y|scale|原版图片路径"</summary>
|
||
private static bool TrySplitImage(string raw, out float x, out float y, out float scale, out string path)
|
||
{
|
||
x = y = 0f;
|
||
scale = 1f;
|
||
path = null;
|
||
var parts = raw.Split('|');
|
||
if (parts.Length < 4) return false;
|
||
if (!float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out x)) return false;
|
||
if (!float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y)) return false;
|
||
if (!float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out scale)) return false;
|
||
path = parts[3];
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 原版图片路径 → 本工程 Addressables 地址(不带扩展名)。
|
||
/// Assets/Bundles/PW/PW_Sprite/Common/zd_jn_sd.png → Assets/Art/XJMH/pw/pw_sprite/common/zd_jn_sd
|
||
/// (只把目录名小写,文件名保留原样;扩展名由加载时按 .png / .asset 两种尝试)
|
||
/// </summary>
|
||
private static string MapHelpSpritePath(string luaPath)
|
||
{
|
||
if (string.IsNullOrEmpty(luaPath)) return null;
|
||
string p = luaPath.Replace("\\", "/").Trim();
|
||
if (p.StartsWith(LuaHelpSpriteRoot, StringComparison.OrdinalIgnoreCase))
|
||
p = ArtHelpSpriteRoot + p.Substring(LuaHelpSpriteRoot.Length);
|
||
|
||
int slash = p.LastIndexOf('/');
|
||
int dot = p.LastIndexOf('.');
|
||
if (dot > slash) p = p.Substring(0, dot); // 去扩展名
|
||
|
||
slash = p.LastIndexOf('/');
|
||
if (slash > 0 && p.StartsWith(ArtHelpSpriteRoot, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
string dir = p.Substring(0, slash);
|
||
string file = p.Substring(slash + 1);
|
||
p = ArtHelpSpriteRoot + dir.Substring(ArtHelpSpriteRoot.Length).ToLowerInvariant() + "/" + file;
|
||
}
|
||
return p;
|
||
}
|
||
|
||
/// <summary>给 Image 加载帮助图:命中缓存直接用;否则异步加载(模板自带图先隐藏,避免闪图)</summary>
|
||
private void LoadHelpSprite(Image img, string luaPath)
|
||
{
|
||
if (img == null) return;
|
||
|
||
string path = MapHelpSpritePath(luaPath);
|
||
if (string.IsNullOrEmpty(path))
|
||
{
|
||
img.enabled = false;
|
||
return;
|
||
}
|
||
|
||
if (HelpSpriteCache.TryGetValue(path, out var cached) && cached != null)
|
||
{
|
||
img.sprite = cached;
|
||
img.enabled = true;
|
||
return;
|
||
}
|
||
|
||
img.enabled = false;
|
||
LoadHelpSpriteAsync(img, path);
|
||
}
|
||
|
||
/// <summary>异步加载帮助图(Addressables:先 .png 再 .asset),都失败则保持隐藏</summary>
|
||
private async void LoadHelpSpriteAsync(Image img, string path)
|
||
{
|
||
string[] exts = { ".png", ".asset" };
|
||
for (int i = 0; i < exts.Length; i++)
|
||
{
|
||
AsyncOperationHandle<Sprite> handle = default;
|
||
Sprite sprite = null;
|
||
try
|
||
{
|
||
handle = Addressables.LoadAssetAsync<Sprite>(path + exts[i]);
|
||
sprite = await handle.Task;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
if (handle.IsValid()) Addressables.Release(handle);
|
||
Debug.LogWarning($"[XJMH] 帮助图加载失败:{path + exts[i]} → {e.Message}");
|
||
continue;
|
||
}
|
||
|
||
if (this == null || img == null)
|
||
{
|
||
if (handle.IsValid()) Addressables.Release(handle);
|
||
return;
|
||
}
|
||
|
||
if (handle.Status == AsyncOperationStatus.Succeeded && sprite != null)
|
||
{
|
||
HelpSpriteCache[path] = sprite;
|
||
img.sprite = sprite;
|
||
img.enabled = true;
|
||
return;
|
||
}
|
||
|
||
if (handle.IsValid()) Addressables.Release(handle);
|
||
}
|
||
Debug.LogWarning($"[XJMH] 帮助图不存在(需把该图加入 Addressables 组):{path}");
|
||
}
|
||
|
||
/// <summary>操作说明(原版 PW_Config.Operation,4 条:锁定射击/自动技能/散射技能/起飞技能)</summary>
|
||
private static readonly HelpItem2Cfg[] HelpOperationCfg =
|
||
{
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 270, h = 398, name = "锁定射击", nameX = -20, nameY = 185,
|
||
des = new[]
|
||
{
|
||
"5|80|<size=19>点击“ ”</size>",
|
||
"0|37|<size=19>图标, 可开启锁定</size>",
|
||
"-5|7|<size=19>功能; 开启该功能</size>",
|
||
"-8|-23|<size=19>后, 可以锁定并持</size>",
|
||
"-13|-53|<size=19>续攻击目标<color=#EEB548>长按</color></size>",
|
||
"-16|-83|<size=19><color=#EEB548>图标, 可打开锁定</color></size>",
|
||
"-51|-113|<size=19><color=#EEB548>设置界面</color></size>",
|
||
},
|
||
imgs = new[] { "26|83|0.8|Assets/Bundles/PW/PW_Sprite/Common/zd_jn_sd.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 270, h = 398, name = "自动技能", nameX = -20, nameY = 185,
|
||
des = new[]
|
||
{
|
||
"0|80|<size=19>点击“ ”</size>",
|
||
"-5|37|<size=19>图标, 可以自动发</size>",
|
||
"-10|7|<size=19>射子弹, 但需要玩</size>",
|
||
"-15|-23|<size=19>家自己控制方向</size>",
|
||
"-20|-53|<size=19><color=#EEB548>自动操作, 射击帮</color></size>",
|
||
"-80|-83|<size=19><color=#EEB548>手</color></size>",
|
||
},
|
||
imgs = new[] { "22|83|0.8|Assets/Bundles/PW/PW_Sprite/Common/zd_jn_zd.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 270, h = 398, name = "散射技能", nameX = -20, nameY = 185,
|
||
des = new[]
|
||
{
|
||
"5|80|<size=19>点击“ ”</size>",
|
||
"0|37|<size=19>图标, 可开启散射</size>",
|
||
"-5|7|<size=19>技能, 可以同时发</size>",
|
||
"-10|-23|<size=19>射三发子弹<color=#EEB548>三弹</color></size>",
|
||
"-20|-53|<size=19><color=#EEB548>齐发,威力无比</color></size>",
|
||
},
|
||
imgs = new[] { "27|83|0.8|Assets/Bundles/PW/PW_Sprite/Common/zd_jn_ss.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 270, h = 398, name = "起飞技能", nameX = -20, nameY = 185,
|
||
des = new[]
|
||
{
|
||
"16|80|<size=19>点击炮台右侧<color=#EEB548>“起飞”</color></size>",
|
||
"-7|37|<size=19>按钮, 玩家可以自</size>",
|
||
"-11|7|<size=19>由切换飞机和炮台</size>",
|
||
"-15|-23|<size=19>这两种形态<color=#EEB548>自由</color></size>",
|
||
"-20|-53|<size=19><color=#EEB548>切形, 趣味无穷</color></size>",
|
||
},
|
||
},
|
||
};
|
||
|
||
/// <summary>玩法说明(原版 PW_Config.PlayMethod,12 条)</summary>
|
||
private static readonly HelpItem2Cfg[] HelpPlayMethodCfg =
|
||
{
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 320, h = 398, name = "烈焰风暴", nameX = -30, nameY = 185,
|
||
fishNumber = 12034, // 图鉴:烈焰风暴
|
||
des = new[]
|
||
{
|
||
"5|80|<size=19>击毁“ ”,有概率</size>",
|
||
"-5|50|<size=19>触发<color=#EEB548>烈焰火轮攻击</color></size>",
|
||
null, // 原版无 des3
|
||
"-10|20|<size=19>共有4个火轮协助战斗</size>",
|
||
"-15|-10|<size=19>有机会获得<color=#EEB548>巨额财富</color></size>",
|
||
},
|
||
imgs = new[] { "-15|90|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_ts_lyfb.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 320, h = 398, name = "超级幸运轮", nameX = -30, nameY = 185,
|
||
fishNumber = 12031, // 图鉴:幸运轮
|
||
des = new[]
|
||
{
|
||
"5|80|<size=19>击毁“ ”,有概率</size>",
|
||
"0|50|<size=19>触发特殊玩法-<color=#EEB548>幸运连连连</color>,</size>",
|
||
"-5|20|<size=19>抽中带“ ”的倍率,</size>",
|
||
"-10|-10|<size=19>则可以继续<color=#EEB548>下一轮</color>游戏,</size>",
|
||
"-15|-50|<size=19>每轮<color=#EEB548>8</color>个倍率,每个倍率</size>",
|
||
"-15|-85|<size=19>抽中概率为<color=#EEB548>12.5%</color></size>",
|
||
},
|
||
imgs = new[]
|
||
{
|
||
"-10|90|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_xylboss.png",
|
||
"-15|20|0.14|Assets/Bundles/PW/PW_Sprite/Handbook/ts_xyl.png",
|
||
},
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 320, h = 398, name = "破坏球玩法", nameX = -30, nameY = 185,
|
||
fishNumber = 12030, // 图鉴:破坏球
|
||
des = new[]
|
||
{
|
||
"5|80|<size=19>击毁“ ”,可触发</size>",
|
||
"0|30|<size=19><color=#EEB548>仓鼠火炮支援</color>,全自动冲锋火</size>",
|
||
"0|0|<size=19>炮可粉碎一切威胁,火炮击毁</size>",
|
||
"-20|-30|<size=19>敌机,有概率获得</size>",
|
||
"-22|-60|<size=19><color=#EEB548>2-10倍金币奖励</color></size>",
|
||
},
|
||
imgs = new[] { "-10|90|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_phq.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 320, h = 398, name = "星际巡航玩法", nameX = -30, nameY = 185,
|
||
fishNumber = 12029, // 图鉴:星际巡航
|
||
des = new[]
|
||
{
|
||
"5|80|<size=19>击毁“ ”,有概率</size>",
|
||
"0|50|<size=19>触发普通模式或高级模式</size>",
|
||
"-5|20|<size=19>普通模式:可获得<color=#EEB548>金币奖励</color></size>",
|
||
"-10|-10|<size=19>高级模式:可获得<color=#EEB548>特殊武器</color></size>",
|
||
"-15|-50|<size=19>“ ”有概率获得<color=#EEB548>双倍奖励</color></size>",
|
||
"-20|-85|<size=19>“ ”最高<color=#EEB548>5倍暴击奖励</color></size>",
|
||
"-25|-120|<size=19>“ ”高爆发大范围攻击</size>",
|
||
},
|
||
imgs = new[]
|
||
{
|
||
"-10|90|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_fcboss.png",
|
||
"-98|-50|0.22|Assets/Bundles/PW/PW_Sprite/Handbook/tj_ironfist.png",
|
||
"-90|-85.6|0.22|Assets/Bundles/PW/PW_Sprite/Handbook/tj_gamma.png",
|
||
"-98|-120|0.22|Assets/Bundles/PW/PW_Sprite/Handbook/tj_symmetra.png",
|
||
},
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 320, h = 398, name = "机械蝎玩法", nameX = -30, nameY = 185,
|
||
fishNumber = 12028, // 图鉴:机械蝎
|
||
des = new[]
|
||
{
|
||
"-5|80|<size=19>击毁“ ”, 可触发</size>",
|
||
"0|50|<size=19>神秘十连抽,十连抽可获得</size>",
|
||
"-5|20|<size=19><color=#EEB548>金币、专家级武器碎片、</color></size>",
|
||
"-10|-10|<size=19><color=#EEB548>核弹头</color>奖励</size>",
|
||
"-15|-35|<size=19>卡牌等级由低到高分别为</size>",
|
||
"-20|-60|<size=19><color=#EEB548>绿、蓝、紫、红</color></size>",
|
||
"-25|-85|<size=19>级别越高, 奖励越丰厚</size>",
|
||
"-15|-110|<size=19>概率: 绿卡30%、蓝卡40%、</size>",
|
||
"-25|-135|<size=19>紫卡20%、红卡10%</size>",
|
||
},
|
||
imgs = new[] { "-24|90|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_xzboss.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 320, h = 398, name = "小丑扑克玩法", nameX = -30, nameY = 185,
|
||
fishNumber = 12023, // 图鉴:小丑扑克
|
||
des = new[]
|
||
{
|
||
"-8|80|<size=19>击毁“ ”, 可触发</size>",
|
||
"-28|50|<size=19>特殊玩法-翻牌大奖</size>",
|
||
"-19.4|20|<size=19><color=#EEB548>最高可获得1,600倍奖励</color></size>",
|
||
"-14.5|-10|<size=19><color=#EEB548>牌型: </color>散牌10倍,对子30倍</size>",
|
||
"-47|-35|<size=19>顺子50倍,同花80倍</size>",
|
||
"-28.85|-60|<size=19><color=#EEB548>同花顺100倍,豹子200倍</color></size>",
|
||
"-16|-85|<size=19><color=#EEB548>金牌:1张2倍,2张4倍,3张8倍</color></size>",
|
||
"-66.7|-110|<size=19>最终的金币计算: </size>",
|
||
"-21|-135|<size=19><color=#EEB548>炮台倍数x牌型奖励x金牌奖励</color></size>",
|
||
},
|
||
imgs = new[] { "-24|90|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_ts_xcqq.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 320, h = 398, name = "幸运转盘玩法", nameX = -30, nameY = 185,
|
||
fishNumber = 12022, // 图鉴:幸运飞碟
|
||
des = new[]
|
||
{
|
||
"-4.85|80|<size=19>击毁“ ”, 可触发</size>",
|
||
"-26|50|<size=19>特殊玩法-幸运转盘</size>",
|
||
"-18.75|20|<size=19><color=#EEB548>最高可获得1,500倍奖励</color></size>",
|
||
"-32.7|-10|<size=19>内圈最高倍数: <color=#EEB548>500倍</color></size>",
|
||
"-49|-40|<size=19>外圈最高倍数: <color=#EEB548>3倍</color></size>",
|
||
"-63.9|-70|<size=19>最终的金币计算:</size>",
|
||
"-12.8|-100|<size=19><color=#EEB548>炮台倍数x内圈倍数x外圈倍数</color></size>",
|
||
},
|
||
imgs = new[] { "-21|90|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_ts_zp.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 310, h = 398, name = "免费模式玩法", nameX = -30, nameY = 185,
|
||
fishNumber = 12025, // 图鉴:弹药箱
|
||
des = new[]
|
||
{
|
||
"0|80|<size=19>击毁“ ”, 可获得</size>",
|
||
"-10.2|55|<size=19><color=#EEB548>8~12秒</color>免费模式时间; </size>",
|
||
"-7.8|1.9|<size=19>击毁“ ”, 可获得</size>",
|
||
"-18.3825|-24.4|<size=19>3组免费子弹, 共<color=#EEB548>24~36</color></size>",
|
||
"-49.3|-50.9|<size=19><color=#EEB548>秒</color>免费模式时间; </size>",
|
||
"-63.7|-78.2|<size=19>免费模式期间: </size>",
|
||
"-13.2|-104.56|<size=19>1.发射速度比普通模式高1倍</size>",
|
||
"-47.7|-129|<size=19>2.无法调整炮台倍数</size>",
|
||
},
|
||
imgs = new[]
|
||
{
|
||
"-13|92|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_ts_jzx.png",
|
||
"-22.2|13.3|0.4|Assets/Bundles/PW/PW_Sprite/Handbook/tj_flboss.png",
|
||
},
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 300, h = 398, name = "分裂属性: ", nameX = -30, nameY = 185,
|
||
des = new[]
|
||
{
|
||
"6.7|89.29|<size=19>分裂: 击中目标后, <color=#EEB548>有概率</color></size>",
|
||
"4.720001|61.7|<size=19><color=#EEB548>分裂出多发子弹</color>, 攻击附近</size>",
|
||
"-80.7|32.2|<size=19>敌机</size>",
|
||
"-5.8|-2.92|<size=19>1级分裂: <color=#EEB548>8%概率</color>触发分裂</size>",
|
||
"-10.8|-32.13|<size=19>2级分裂(大对子): <color=#EEB548>20%</color></size>",
|
||
"-62.8|-57.64|<size=19><color=#EEB548>概率</color>触发分裂</size>",
|
||
"-15.19|-83.6|<size=19>3级分裂(大三元): <color=#EEB548>50%</color></size>",
|
||
"-68.1|-108.7|<size=19><color=#EEB548>概率</color>触发分裂</size>",
|
||
},
|
||
imgs = new[] { "93.53|187.5|0.6|Assets/Bundles/PW/PW_Sprite/FreeModel/PW_FreeItem_1.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 300, h = 398, name = "连电属性: ", nameX = -30, nameY = 185,
|
||
des = new[]
|
||
{
|
||
"6.7|89.29|<size=19>连电: 击中目标后, <color=#EEB548>有概率</color></size>",
|
||
"4.720001|61.7|<size=19><color=#EEB548>触发连电效果</color>, 攻击屏幕内</size>",
|
||
"-80.7|32.2|<size=19>敌机</size>",
|
||
"-5.8|-2.92|<size=19>1级连电: <color=#EEB548>8%概率</color>触发连电</size>",
|
||
"-10.8|-32.13|<size=19>2级连电(大对子): <color=#EEB548>20%</color></size>",
|
||
"-62.8|-57.64|<size=19><color=#EEB548>概率</color>触发连电</size>",
|
||
"-15.19|-83.6|<size=19>3级连电(大三元): <color=#EEB548>50%</color></size>",
|
||
"-68.1|-108.7|<size=19><color=#EEB548>概率</color>触发连电</size>",
|
||
},
|
||
imgs = new[] { "93.53|187.5|0.6|Assets/Bundles/PW/PW_Sprite/FreeModel/PW_FreeItem_2.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 300, h = 398, name = "暴击属性: ", nameX = -30, nameY = 185,
|
||
des = new[]
|
||
{
|
||
"6.699997|89.29|<size=19>暴击: 击毁目标, 有概率</size>",
|
||
"-18.6|61.7|<size=19>触发<color=#EEB548>多倍奖励暴击</color></size>",
|
||
"3.8|31.2|<size=19>1级暴击: <color=#EEB548>20%概率</color>触发<color=#EEB548>2倍</color></size>",
|
||
"-69.16|7.1|<size=19><color=#EEB548>奖励暴击</color></size>",
|
||
"-4.4|-21.84|<size=19>2级暴击(大对子): <color=#EEB548>40%</color></size>",
|
||
"-17.1|-46.6|<size=19><color=#EEB548>概率</color>触发<color=#EEB548>2~3倍奖励暴击</color></size>",
|
||
"-11.2|-78.6|<size=19>3级暴击(大三元): <color=#EEB548>80%</color></size>",
|
||
"-23.7|-105.1|<size=19>概率触发<color=#EEB548>3~5倍奖励暴击</color></size>",
|
||
},
|
||
imgs = new[] { "93.53|187.5|0.6|Assets/Bundles/PW/PW_Sprite/FreeModel/PW_FreeItem_3.png" },
|
||
},
|
||
new HelpItem2Cfg
|
||
{
|
||
w = 300, h = 398, name = "加速属性: ", nameX = -30, nameY = 185,
|
||
des = new[]
|
||
{
|
||
"6.699997|89.29|<size=19>加速: <color=#EEB548>提升免费模式期间</color></size>",
|
||
"-22.89|61.7|<size=19>发射子弹的速度</size>",
|
||
"-7.4|27.11|<size=19>1级加速: 攻速<color=#EEB548>提升20%</color></size>",
|
||
"-2.94|-3.8|<size=19>2级加速(大对子): 攻速</size>",
|
||
"-72.68|-30.1|<size=19><color=#EEB548>提升80%</color></size>",
|
||
"-6.68|-56.7|<size=19>3级加速(大三元): 攻速</size>",
|
||
"-70.52|-80.08|<size=19><color=#EEB548>提升300%</color></size>",
|
||
},
|
||
imgs = new[] { "93.53|187.5|0.6|Assets/Bundles/PW/PW_Sprite/FreeModel/PW_FreeItem_4.png" },
|
||
},
|
||
};
|
||
|
||
// ==================== 锁定设置面板(原版 PW_SetLock,长按锁定键打开) ====================
|
||
// 移植自原版 PW_SetLock.txt:
|
||
// - 列表 = 原版 PW_Config.Enemy 里 canAutoLock=true 的敌人(奇数进"1"行、偶数进"2"行,两行横向排列)
|
||
// - 点条目切换勾选(_choose 标记);mBtnAll 全选 / mBtnNone 全不选(mGOAllFlag/mGONoneFlag 指示)
|
||
// - 关闭(mBtnBack)时应用:有勾选 → 开锁定 + 激活自动锁定,且之后的自动锁定只锁勾选的鱼
|
||
// - 白名单通过覆写父类 IsFishAllowedBySuoDing 提供,父类通用流程不动
|
||
// - 鱼图与图鉴页同源:直接取 FishResourcesData.fishResourcesSpritesDic(不需要额外注册)
|
||
|
||
/// <summary>锁定条目配置(对应原版 canAutoLock 敌人;编号为本项目鱼表编号)</summary>
|
||
private sealed class XjmhLockCfg
|
||
{
|
||
public string name; // 仅日志用
|
||
public int fishNumber;
|
||
public string score; // 原版 PW_Config.Enemy[].score 原样(富文本)
|
||
}
|
||
|
||
/// <summary>
|
||
/// 原版 PW_Config.Enemy 中 canAutoLock=true 的敌人(按原版顺序),编号映射到本项目鱼表:
|
||
/// 12031=幸运轮、12030=破坏球、12029=星际巡航、12028=机械蝎、12026=爆裂金鲨、
|
||
/// 12034=烈焰风暴、12023=小丑扑克、12022=幸运飞碟。
|
||
/// ⚠️ 原版还有 type=2000 女王龙,本项目鱼表还没有它,先不加;接进鱼表后在这里补一行即可。
|
||
/// 这里加/删一行就会自动多/少一个锁定条目和勾选项(列表、全选/全不选、白名单都按这份配置走)。
|
||
/// </summary>
|
||
private static readonly XjmhLockCfg[] XjmhLockCfgList =
|
||
{
|
||
new XjmhLockCfg { name = "幸运轮", fishNumber = 12031, score = "<size=21>特殊奖励</size>" },
|
||
new XjmhLockCfg { name = "破坏球", fishNumber = 12030, score = "<size=21>特殊奖励</size>" },
|
||
new XjmhLockCfg { name = "星际巡航", fishNumber = 12029, score = "<size=21>特殊奖励</size>" },
|
||
new XjmhLockCfg { name = "机械蝎", fishNumber = 12028, score = "<size=21>特殊奖励</size>" },
|
||
new XjmhLockCfg { name = "爆裂金鲨", fishNumber = 12026, score = "<size=21>特殊奖励</size>" },
|
||
new XjmhLockCfg { name = "烈焰风暴", fishNumber = 12034, score = "<size=21>特殊奖励</size>" },
|
||
new XjmhLockCfg { name = "小丑扑克", fishNumber = 12023, score = "<size=21>特殊玩法</size>" },
|
||
new XjmhLockCfg { name = "幸运飞碟", fishNumber = 12022, score = "<size=21>特殊玩法</size>" },
|
||
};
|
||
|
||
/// <summary>原版 PW_Config.Text.SetLockDes(面板底部说明)</summary>
|
||
private const string LockSetDesText = "<color=#EEB548>勾选锁定敌机, 设定锁定范围</color>勾选敌机将会自动锁定勾选的敌机进行射击; 若不勾选, 则需手动点击敌机进行预订射击自动开启后, <color=#EEB548>点击敌机切换锁定目标</color>";
|
||
|
||
/// <summary>锁定面板内部节点(按原版 PW_SetLock 预制体名字找)</summary>
|
||
private Button _lockBtnBack; // mBtnBack(关闭并应用)
|
||
private Button _lockBtnAll; // mBtnAll(全选)
|
||
private Button _lockBtnNone; // mBtnNone(全不选)
|
||
private GameObject _lockAllFlag; // mGOAllFlag(全选指示)
|
||
private GameObject _lockNoneFlag; // mGONoneFlag(全不选指示)
|
||
private Transform _lockRow1; // ScrollView/Viewport/Content/1(横向第一行)
|
||
private Transform _lockRow2; // ScrollView/Viewport/Content/2(横向第二行)
|
||
private RectTransform _lockContent; // ScrollView/Viewport/Content(照原版按条数调宽度)
|
||
|
||
/// <summary>已生成条目的勾选标记(全选/全不选/单点后统一刷新用)</summary>
|
||
private sealed class LockSetItemRef
|
||
{
|
||
public string key; // fishNumber 字符串(与锁定白名单判断一致)
|
||
public GameObject choose; // _choose 标记
|
||
}
|
||
private readonly List<LockSetItemRef> _lockSetItems = new List<LockSetItemRef>();
|
||
|
||
/// <summary>勾选的锁定鱼号(对应原版 LockEnemyList,跨开关面板保留;空 = 不限制)</summary>
|
||
private readonly HashSet<string> _xjLockSelected = new HashSet<string>();
|
||
|
||
/// <summary>是否处于"锁定面板白名单"模式(面板勾选后开启;关掉锁定开关即退出,与通用版语义一致)</summary>
|
||
private bool _xjLockWhiteOn;
|
||
|
||
/// <summary>
|
||
/// 锁定面板初始化:抓内部节点、绑按钮(照原版 PW_SetLock:InitUI)、写固定文案。
|
||
/// 未拖面板时全部跳过(长按退回父类通用 SuoDing 面板,玩法不受影响)。
|
||
/// </summary>
|
||
private void InitLockSetPanel()
|
||
{
|
||
if (lockSetPanel == null) return;
|
||
|
||
lockSetPanel.SetActive(false); // 初始收起
|
||
|
||
// 背景遮挡:原版打开时禁用射击(SetCanShoot(false)),本项目等价做法 = 让面板根图挡射线,
|
||
// 避免调锁定时点到后面的打鱼输入
|
||
var rootImg = lockSetPanel.GetComponent<Image>();
|
||
if (rootImg != null) rootImg.raycastTarget = true;
|
||
|
||
// 返回(照原版:关闭即保存并应用勾选)
|
||
_lockBtnBack = FindButton(lockSetPanel.transform, "mBtnBack");
|
||
if (_lockBtnBack != null)
|
||
{
|
||
_lockBtnBack.onClick.RemoveAllListeners();
|
||
_lockBtnBack.onClick.AddListener(OnLockSetBackClicked);
|
||
}
|
||
|
||
// 全选 / 全不选
|
||
_lockBtnAll = FindButton(lockSetPanel.transform, "mBtnAll");
|
||
if (_lockBtnAll != null)
|
||
{
|
||
_lockBtnAll.onClick.RemoveAllListeners();
|
||
_lockBtnAll.onClick.AddListener(SelectAllLockItems);
|
||
}
|
||
_lockBtnNone = FindButton(lockSetPanel.transform, "mBtnNone");
|
||
if (_lockBtnNone != null)
|
||
{
|
||
_lockBtnNone.onClick.RemoveAllListeners();
|
||
_lockBtnNone.onClick.AddListener(SelectNoneLockItems);
|
||
}
|
||
|
||
// 两行条目容器(ScrollView/Viewport/Content 下的 1 / 2)
|
||
var content = FindChildByName(lockSetPanel.transform, "Content");
|
||
_lockContent = content as RectTransform;
|
||
_lockRow1 = content != null ? content.Find("1") : null;
|
||
_lockRow2 = content != null ? content.Find("2") : null;
|
||
|
||
_lockAllFlag = FindChildByName(lockSetPanel.transform, "mGOAllFlag")?.gameObject;
|
||
_lockNoneFlag = FindChildByName(lockSetPanel.transform, "mGONoneFlag")?.gameObject;
|
||
|
||
// 固定文案(照原版 InitView)
|
||
SetTextContent(FindChildByName(lockSetPanel.transform, "mTextDes"), LockSetDesText);
|
||
SetTextContent(_lockBtnAll != null ? FindChildByName(_lockBtnAll.transform, "Text") : null, "全选");
|
||
SetTextContent(_lockBtnNone != null ? FindChildByName(_lockBtnNone.transform, "Text") : null, "全不选");
|
||
}
|
||
|
||
/// <summary>按名字找节点上的按钮(节点上没有就往子节点找)</summary>
|
||
private static Button FindButton(Transform root, string name)
|
||
{
|
||
var tf = FindChildByName(root, name);
|
||
if (tf == null) return null;
|
||
var btn = tf.GetComponent<Button>();
|
||
return btn != null ? btn : tf.GetComponentInChildren<Button>(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// ⭐ 覆写父类:XJMH 长按锁定键打开原版 PW_SetLock 面板。
|
||
/// (星际迷航的可锁定敌机是固定的一批 boss/特殊鱼 —— 原版 canAutoLock 配置,
|
||
/// 而不是通用版按房间出鱼列表;通用版 SuoDing 不适用这里。)
|
||
/// 未拖面板时退回父类通用实现。
|
||
/// </summary>
|
||
protected override void OpenLockSettingPanel()
|
||
{
|
||
if (lockSetPanel == null)
|
||
{
|
||
base.OpenLockSettingPanel();
|
||
return;
|
||
}
|
||
if (lockSetPanel.activeSelf) return;
|
||
|
||
BuildLockSetList();
|
||
OpenScalePanel(lockSetPanel);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成锁定条目(照原版 PW_SetLock:InitUI):
|
||
/// 第 i 条按奇偶交替放进 1/2 两行,条目 = 克隆 PW_SetLockItem 模板后填图、分值、标记;
|
||
/// 列表宽度照原版公式(≤10 条 = 60,之后每多 2 条再加 181)。
|
||
/// </summary>
|
||
private void BuildLockSetList()
|
||
{
|
||
if (_lockRow1 == null || _lockRow2 == null || lockSetItemTemplate == null)
|
||
{
|
||
Debug.LogWarning("[XJMH] 锁定面板缺少行容器(1/2)或条目模板,条目无法生成");
|
||
return;
|
||
}
|
||
|
||
ClearLockRow(_lockRow1);
|
||
ClearLockRow(_lockRow2);
|
||
_lockSetItems.Clear();
|
||
|
||
for (int i = 0; i < XjmhLockCfgList.Length; i++)
|
||
{
|
||
var cfg = XjmhLockCfgList[i];
|
||
var row = (i % 2 == 0) ? _lockRow1 : _lockRow2; // 原版 1-based 奇数进"1",这里 0-based 偶数进"1",等价
|
||
var go = Instantiate(lockSetItemTemplate, row);
|
||
go.SetActive(true);
|
||
FillLockSetItem(go, cfg);
|
||
|
||
_lockSetItems.Add(new LockSetItemRef
|
||
{
|
||
key = cfg.fishNumber.ToString(),
|
||
choose = FindChildByName(go.transform, "_choose")?.gameObject,
|
||
});
|
||
}
|
||
|
||
// 照原版调列表宽度(横向滚动范围)
|
||
if (_lockContent != null)
|
||
{
|
||
int count = XjmhLockCfgList.Length;
|
||
float extra = count <= 10 ? 0f : Mathf.Ceil((count - 10) * 0.5f);
|
||
_lockContent.sizeDelta = new Vector2(60f + extra * 181f, _lockContent.sizeDelta.y);
|
||
}
|
||
|
||
RefreshLockSetChecks();
|
||
Debug.Log($"[XJMH] 锁定面板条目: {XjmhLockCfgList.Length} 条");
|
||
}
|
||
|
||
/// <summary>清空一行旧条目(条目模板自身/同名跳过,避免把模板删掉)</summary>
|
||
private void ClearLockRow(Transform row)
|
||
{
|
||
for (int i = row.childCount - 1; i >= 0; i--)
|
||
{
|
||
var child = row.GetChild(i).gameObject;
|
||
if (lockSetItemTemplate != null
|
||
&& (child == lockSetItemTemplate || child.name == lockSetItemTemplate.name)) continue;
|
||
Destroy(child);
|
||
}
|
||
}
|
||
|
||
/// <summary>填一条锁定条目(照原版:_imgIcon/_textScore/_flag/_choose/_bonus;点击切换勾选)</summary>
|
||
private void FillLockSetItem(GameObject item, XjmhLockCfg cfg)
|
||
{
|
||
if (item == null || cfg == null) return;
|
||
|
||
// 图标:读"图鉴"同款鱼图(进房时已按 TuJian 标签加载好;没有就保持隐藏)
|
||
var iconTf = FindChildByName(item.transform, "_imgIcon");
|
||
if (iconTf != null)
|
||
{
|
||
var img = iconTf.GetComponent<Image>();
|
||
if (img == null) img = iconTf.GetComponentInChildren<Image>(true);
|
||
var sprite = GetTuJianFishSprite(cfg.fishNumber);
|
||
if (img != null && sprite != null)
|
||
{
|
||
img.sprite = sprite;
|
||
img.enabled = true;
|
||
}
|
||
}
|
||
|
||
// 分值文本(原版富文本原样,如"<size=21>特殊奖励</size>")
|
||
SetTextContent(FindChildByName(item.transform, "_textScore"), cfg.score);
|
||
|
||
// 特殊标记(原版 setActive(v.special);这批锁定敌人全部为 special)
|
||
var flagTf = FindChildByName(item.transform, "_flag");
|
||
if (flagTf != null) flagTf.gameObject.SetActive(true);
|
||
|
||
// 奖金飞机标记:原版锁定的敌人没有 isBonus,恒隐藏
|
||
var bonusTf = FindChildByName(item.transform, "_bonus");
|
||
if (bonusTf != null) bonusTf.gameObject.SetActive(false);
|
||
|
||
// 勾选标记(保留上次勾选结果)
|
||
var chooseTf = FindChildByName(item.transform, "_choose");
|
||
if (chooseTf != null) chooseTf.gameObject.SetActive(_xjLockSelected.Contains(cfg.fishNumber.ToString()));
|
||
|
||
// 点击切换勾选(照原版 AddClick(helpItem))
|
||
var btn = item.GetComponent<Button>();
|
||
if (btn == null) btn = item.GetComponentInChildren<Button>(true);
|
||
if (btn != null)
|
||
{
|
||
string key = cfg.fishNumber.ToString();
|
||
btn.onClick.RemoveAllListeners();
|
||
btn.onClick.AddListener(() =>
|
||
{
|
||
if (_xjLockSelected.Contains(key)) _xjLockSelected.Remove(key);
|
||
else _xjLockSelected.Add(key);
|
||
RefreshLockSetChecks();
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>刷新所有条目的勾选标记 + 全选/全不选指示(照原版 CheckLock)</summary>
|
||
private void RefreshLockSetChecks()
|
||
{
|
||
for (int i = 0; i < _lockSetItems.Count; i++)
|
||
{
|
||
var it = _lockSetItems[i];
|
||
if (it.choose != null) it.choose.SetActive(_xjLockSelected.Contains(it.key));
|
||
}
|
||
RefreshLockSetFlags();
|
||
}
|
||
|
||
/// <summary>全选/全不选指示:一个没勾 → mGONoneFlag;全勾 → mGOAllFlag(照原版 CheckLock)</summary>
|
||
private void RefreshLockSetFlags()
|
||
{
|
||
int sel = CountLockSelected();
|
||
if (_lockNoneFlag != null) _lockNoneFlag.SetActive(sel == 0);
|
||
if (_lockAllFlag != null) _lockAllFlag.SetActive(sel == XjmhLockCfgList.Length);
|
||
}
|
||
|
||
/// <summary>当前勾选条数(只数列表里存在的敌人,与全选框联动)</summary>
|
||
private int CountLockSelected()
|
||
{
|
||
int sel = 0;
|
||
for (int i = 0; i < XjmhLockCfgList.Length; i++)
|
||
if (_xjLockSelected.Contains(XjmhLockCfgList[i].fishNumber.ToString())) sel++;
|
||
return sel;
|
||
}
|
||
|
||
/// <summary>全选:勾上列表里的所有敌人(照原版 mBtnAll)</summary>
|
||
private void SelectAllLockItems()
|
||
{
|
||
for (int i = 0; i < XjmhLockCfgList.Length; i++)
|
||
_xjLockSelected.Add(XjmhLockCfgList[i].fishNumber.ToString());
|
||
RefreshLockSetChecks();
|
||
}
|
||
|
||
/// <summary>全不选:清空勾选(照原版 mBtnNone)</summary>
|
||
private void SelectNoneLockItems()
|
||
{
|
||
_xjLockSelected.Clear();
|
||
RefreshLockSetChecks();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 返回键(照原版 mBtnBack):收起面板并应用勾选——
|
||
/// 有勾选 → 开锁定 + 激活自动锁定(之后自动锁定只锁勾选的鱼);没勾 → 正常锁定不限制。
|
||
/// </summary>
|
||
private void OnLockSetBackClicked()
|
||
{
|
||
int sel = CountLockSelected();
|
||
CloseScalePanel(lockSetPanel);
|
||
|
||
// 与通用 SuoDing 面板同一套应用流程(开锁定开关 + 激活自动锁定);
|
||
// 勾选结果是否限制锁定,由 XJMH 自己的白名单标记 + 覆写的 IsFishAllowedBySuoDing 决定
|
||
_xjLockWhiteOn = sel > 0;
|
||
ApplySuoDingSelection();
|
||
}
|
||
|
||
/// <summary>
|
||
/// ⭐ 覆写父类白名单:XJMH 用原版 PW_SetLock 面板的勾选列表(而不是通用 SuoDingPanel)。
|
||
/// 语义与父类一致:只有"锁定面板勾选过至少一条"才进入白名单模式,之后自动锁定只允许锁勾选的鱼;
|
||
/// 关掉锁定开关即退出该模式;未进入该模式 / 一个都没勾 → 不限制(正常锁定)。
|
||
/// </summary>
|
||
protected override bool IsFishAllowedBySuoDing(MonsterContral m)
|
||
{
|
||
if (!_xjLockWhiteOn) return true;
|
||
if (m == null) return false;
|
||
if (_xjLockSelected.Count == 0) return true;
|
||
return _xjLockSelected.Contains(m.monsterData?.fishTypeConfig?.fishNumber);
|
||
}
|
||
|
||
// ==================== 缩放弹窗工具 ====================
|
||
|
||
/// <summary>缩放弹出:面板激活 + 从 0 放大到 1(OutBack,轻微回弹)</summary>
|
||
private void OpenScalePanel(GameObject panel)
|
||
{
|
||
if (panel == null) return;
|
||
panel.SetActive(true);
|
||
var t = panel.transform;
|
||
t.DOKill();
|
||
t.localScale = Vector3.zero;
|
||
t.DOScale(Vector3.one, popupOpenTime).SetEase(Ease.OutBack);
|
||
}
|
||
|
||
/// <summary>缩放收起:从 1 缩到 0 后隐藏(已在隐藏态则跳过)</summary>
|
||
private void CloseScalePanel(GameObject panel)
|
||
{
|
||
if (panel == null || !panel.activeSelf) return;
|
||
var t = panel.transform;
|
||
t.DOKill();
|
||
t.DOScale(Vector3.zero, popupCloseTime).SetEase(Ease.InBack)
|
||
.OnComplete(() => { if (panel != null) panel.SetActive(false); });
|
||
}
|
||
|
||
// ==================== 工具 ====================
|
||
|
||
/// <summary>递归按名字查找子物体(兼容未激活节点)</summary>
|
||
private static Transform FindChildByName(Transform parent, string name)
|
||
{
|
||
if (parent == null || string.IsNullOrEmpty(name)) return null;
|
||
if (parent.name == name) return parent;
|
||
for (int i = 0; i < parent.childCount; i++)
|
||
{
|
||
var found = FindChildByName(parent.GetChild(i), name);
|
||
if (found != null) return found;
|
||
}
|
||
return null;
|
||
}
|
||
} |