1
This commit is contained in:
@@ -83,6 +83,7 @@
|
||||
|
||||
using Assets.MyGame.Scripts.Net;
|
||||
using Assets.MyGame.Scripts.Net.API;
|
||||
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
|
||||
using Assets.MyGame.Scripts.Net.API.UserData_SubClass;
|
||||
using Assets.MyGame.Scripts.Net.APIResponses.Enum;
|
||||
using Assets.MyGame.Scripts.Net.WebResponses;
|
||||
@@ -122,6 +123,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
public GameTypeEnum gameType;
|
||||
[Header("Left")]
|
||||
public PropConfigData propConfigData; // 道具菜单配置(Assets右键 Create → MyGame → 左侧道具配置)
|
||||
|
||||
[Tooltip("鱼雷特效配置(按道具编号选爆炸特效预制体,多场景共用一份 .asset;未配置回退纯逻辑版倒计时爆炸)")]
|
||||
public TorpedoEffectConfig torpedoEffectConfig;
|
||||
public Button btn_Prop;
|
||||
public GameObject obj_PropItemParent;
|
||||
public GameObject obj_PropPanel;
|
||||
@@ -611,8 +615,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
// ⭐ 数量是动态数据:每次打开面板都刷新一级角标(构建有一次性守卫,刷新无守卫)
|
||||
RefreshPropFirstLayerCounts();
|
||||
}
|
||||
else if (tmp_PropTwoPanel != null)
|
||||
tmp_PropTwoPanel.SetActive(false);
|
||||
else
|
||||
ClosePropTwo(); // 关道具面板时一并收起二层并重置展开状态
|
||||
});
|
||||
|
||||
// ⭐ 大喇叭:开/关弹幕输入面板
|
||||
@@ -1530,48 +1534,127 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
SetPropItemIcon(item, cfg.icon);
|
||||
var data = cfg; // 闭包捕获当前项,避免循环变量被覆盖
|
||||
var btn = GetPropItemButton(item);
|
||||
if (btn != null) btn.onClick.AddListener(() => ShowPropTwo(data));
|
||||
if (btn != null) btn.onClick.AddListener(() => ShowPropTwo(data, item));
|
||||
_propFirstLayerItems.Add(new KeyValuePair<PropItemData, GameObject>(cfg, item));
|
||||
}
|
||||
RefreshPropFirstLayerCounts();
|
||||
}
|
||||
|
||||
/// <summary>刷新一级道具 child0 数量:仅当该类型下只有 1 个已拥有道具(点击直接使用、不弹二层)时显示个数;
|
||||
/// 可以打开二层的一级项(0 个显示"暂无道具"占位 / 多个展开子道具列表)直接显示空</summary>
|
||||
/// <summary>当前展开二层的一级配置(用于"再点一下同一个一级项关闭二层"的开关切换)</summary>
|
||||
private PropItemData _currentPropTwoCfg;
|
||||
|
||||
/// <summary>刷新一级道具 child0 数量:该类型在道具表里只有 1 个道具(只有第一层、点击直达使用)时直接显示具体数量(为 0 就显示 0);
|
||||
/// 能展开二层的一级项(道具表里有多个道具)显示空</summary>
|
||||
private void RefreshPropFirstLayerCounts()
|
||||
{
|
||||
foreach (var kv in _propFirstLayerItems)
|
||||
{
|
||||
if (kv.Value == null) continue; // 克隆项已被销毁
|
||||
var matched = GetMatchedProps(kv.Key != null ? kv.Key.propType : PropEnum.none);
|
||||
// ⭐ 只有 1 个已拥有道具(点击直达使用)才显示个数;能弹二层的显示空
|
||||
SetPropItemTextAt(kv.Value, 0, matched.Count == 1 ? matched[0].count.ToString() : string.Empty);
|
||||
var twoProps = GetMatchedTwoProps(kv.Key != null ? kv.Key.propType : PropEnum.none);
|
||||
// ⭐ 只有一层的显示具体数量(数量为 0 就显示 0,不置空);能弹二层的显示空
|
||||
string text;
|
||||
if (twoProps.Count == 1)
|
||||
{
|
||||
var backend = FindBackendProp(twoProps[0]);
|
||||
text = backend != null ? Mathf.Max(0, backend.count).ToString() : "0";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = string.Empty;
|
||||
}
|
||||
SetPropItemTextAt(kv.Value, 0, text);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>按类型读取后端已拥有道具(none = 全部类型),只取 count > 0 的(一级角标与二级展开共用同一过滤规则)</summary>
|
||||
private List<BaseProductCount> GetMatchedProps(PropEnum type)
|
||||
/// <summary>按类型读取后端道具表(GameManager.gameConfig.propConfig,none = 全部):不读背包,数量为 0 的也显示;未启用(isEnable=false)的剔除</summary>
|
||||
private List<PropInfConfig> GetMatchedTwoProps(PropEnum type)
|
||||
{
|
||||
var matched = new List<BaseProductCount>();
|
||||
var props = GameManager.Instance.user?.props;
|
||||
if (props == null) return matched;
|
||||
foreach (var p in props)
|
||||
var matched = new List<PropInfConfig>();
|
||||
var propConfig = GameManager.Instance?.gameConfig?.propConfig;
|
||||
if (propConfig == null) return matched;
|
||||
foreach (var t in propConfig)
|
||||
{
|
||||
if (p == null || p.count <= 0) continue;
|
||||
if (type != PropEnum.none && p.type != (int)type) continue;
|
||||
matched.Add(p);
|
||||
if (t == null || t.isEnable == false) continue;
|
||||
if (type != PropEnum.none && t.type != (int)type) continue;
|
||||
matched.Add(t);
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/// <summary>按道具编号在后端背包(user.props)中查找已拥有道具(数量读取与使用判定共用;未拥有返回 null)</summary>
|
||||
private BaseProductCount FindBackendProp(PropInfConfig t)
|
||||
{
|
||||
if (t == null || string.IsNullOrEmpty(t.number)) return null;
|
||||
var props = GameManager.Instance.user?.props;
|
||||
if (props == null) return null;
|
||||
foreach (var p in props)
|
||||
{
|
||||
if (p != null && p.number == t.number) return p; // 编号精确匹配
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>关闭二级面板并重置展开状态(点同一个一级项再点一下 / 关闭道具面板时调用)</summary>
|
||||
private void ClosePropTwo()
|
||||
{
|
||||
if (tmp_PropTwoPanel != null) tmp_PropTwoPanel.SetActive(false);
|
||||
_currentPropTwoCfg = null;
|
||||
}
|
||||
|
||||
/// <summary>重建当前展开的二级列表(道具使用后数量变化时刷新显示,不改变开关状态)</summary>
|
||||
private void RebuildPropTwo()
|
||||
{
|
||||
if (_currentPropTwoCfg == null || tmp_PropTwoPanel == null || !tmp_PropTwoPanel.activeSelf) return;
|
||||
BuildPropTwoContent(GetMatchedTwoProps(_currentPropTwoCfg.propType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点击一级道具:清空后按配置的 propType 读取后端已拥有道具(user.props),
|
||||
/// 动态展开到二级 tmp_PropTwoPanel(每次点击都重建,保证数量/种类最新)
|
||||
/// 点击一级道具:按 propType 读取后端道具表(gameConfig.propConfig,不读背包,数量为 0 也显示),
|
||||
/// 展开到 tmp_PropTwoPanel —— 再点一下同一个一级项关闭;点不同的一级项切换内容并重新对齐 Y。
|
||||
/// 该类型道具表里只有 1 个道具且拥有数量 > 0 时不弹二层,直接触发使用效果。
|
||||
/// </summary>
|
||||
private void ShowPropTwo(PropItemData cfg)
|
||||
private void ShowPropTwo(PropItemData cfg, GameObject firstItem)
|
||||
{
|
||||
if (cfg == null || tmp_PropTwoPanel == null || tmp_PropTwoItem == null || tmp_PropTwoItemParent == null) return;
|
||||
|
||||
var matched = GetMatchedTwoProps(cfg.propType);
|
||||
|
||||
// ⭐ 该类型只配了 1 个二级道具且拥有数量 > 0:不用弹二层,直接触发使用效果
|
||||
if (matched.Count == 1)
|
||||
{
|
||||
var single = FindBackendProp(matched[0]);
|
||||
if (single != null && single.count > 0)
|
||||
{
|
||||
ClosePropTwo(); // 顺带关掉可能开着的其它二层
|
||||
UsePropEffect(single);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ⭐ 再点一下同一个一级项:关闭二层(点不同的一级项则切换内容)
|
||||
if (_currentPropTwoCfg == cfg && tmp_PropTwoPanel.activeSelf)
|
||||
{
|
||||
ClosePropTwo();
|
||||
return;
|
||||
}
|
||||
_currentPropTwoCfg = cfg;
|
||||
|
||||
BuildPropTwoContent(matched);
|
||||
|
||||
// ⭐ 二层面板 Y 坐标与被点击的一级项对齐(世界坐标,不同父节点下也正确)
|
||||
if (firstItem != null)
|
||||
{
|
||||
var pp = tmp_PropTwoPanel.transform.position;
|
||||
pp.y = firstItem.transform.position.y;
|
||||
tmp_PropTwoPanel.transform.position = pp;
|
||||
}
|
||||
|
||||
tmp_PropTwoPanel.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>清空并按 gameConfig 道具表重建 tmp_PropTwoItemParent 下的子道具列表(数量从背包按 number 匹配,0 也显示)</summary>
|
||||
private void BuildPropTwoContent(List<PropInfConfig> matched)
|
||||
{
|
||||
// 清空上次克隆的子道具(模板本身若是该父对象的子节点则跳过)
|
||||
for (int i = tmp_PropTwoItemParent.transform.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -1580,37 +1663,39 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
Destroy(child);
|
||||
}
|
||||
|
||||
// ⭐ 读取后端道具:按一级道具配置的类型过滤(none = 显示全部),只显示 count > 0 的
|
||||
// (与一级角标 RefreshPropFirstLayerCounts 共用 GetMatchedProps,规则一致)
|
||||
var matched = GetMatchedProps(cfg.propType);
|
||||
|
||||
// ⭐ 只有 1 个时不用弹二层,直接触发该道具的使用效果
|
||||
if (matched.Count == 1)
|
||||
{
|
||||
UsePropEffect(matched[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var p in matched)
|
||||
foreach (var t in matched)
|
||||
{
|
||||
var item = Instantiate(tmp_PropTwoItem, tmp_PropTwoItemParent.transform);
|
||||
// ⭐ child0 = 名字,child1 = 数量(tmp_PropTwoItem 的 child1 是数量 TMP)
|
||||
SetPropItemText(item, p.name);
|
||||
SetPropItemTextAt(item, 1, p.count.ToString());
|
||||
SetPropItemIconUrl(item, p.imageUrl);
|
||||
var data = p; // 闭包捕获当前项
|
||||
var backend = FindBackendProp(t);
|
||||
int count = backend != null ? Mathf.Max(0, backend.count) : 0;
|
||||
// ⭐ child0 = 名字,child1 = 数量(读 gameConfig 道具表,数量为 0 也显示)
|
||||
SetPropItemText(item, t.name);
|
||||
SetPropItemTextAt(item, 1, count.ToString());
|
||||
SetPropTwoIcon(item, t); // sprite_Icon 缓存下载(与商店面板同惯例)
|
||||
var data = t; // 闭包捕获当前项
|
||||
var btn = GetPropItemButton(item);
|
||||
if (btn != null) btn.onClick.AddListener(() => UsePropEffect(data));
|
||||
if (btn != null) btn.onClick.AddListener(() => UsePropTwoItem(data));
|
||||
}
|
||||
|
||||
// 该类型下没有已拥有道具时给出占位提示
|
||||
// 该类型下道具表里没有任何道具时给出占位提示
|
||||
if (matched.Count == 0)
|
||||
{
|
||||
var empty = Instantiate(tmp_PropTwoItem, tmp_PropTwoItemParent.transform);
|
||||
SetPropItemText(empty, "暂无道具");
|
||||
}
|
||||
}
|
||||
|
||||
tmp_PropTwoPanel.SetActive(true);
|
||||
/// <summary>点击二级道具:实时从背包取数量,数量不足提示,足够则按后端道具触发使用效果</summary>
|
||||
private void UsePropTwoItem(PropInfConfig t)
|
||||
{
|
||||
var backend = FindBackendProp(t);
|
||||
if (backend == null || backend.count <= 0)
|
||||
{
|
||||
var n = t != null && !string.IsNullOrEmpty(t.name) ? t.name : "道具";
|
||||
UIManager.Instance.ShowBoardError($"[{n}] 数量不足,暂无法使用");
|
||||
return;
|
||||
}
|
||||
UsePropEffect(backend);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1635,24 +1720,53 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
case PropEnum.VIP经验:
|
||||
// 游戏内简化为一次使用1个(背包里支持滑条选数量)
|
||||
var typeName = string.IsNullOrEmpty(p.name) ? PropEnum.VIP经验.ToString() : p.name;
|
||||
UIManager.Instance.ShowTiShi($"确定使用1个{typeName}吗?", () =>
|
||||
APIRequestList.UseVipExp(1, (result) =>
|
||||
{
|
||||
APIRequestList.UseVipExp(1, (result) =>
|
||||
UIManager.Instance.ShowBoardError(string.IsNullOrEmpty(result) ? "使用成功" : result);
|
||||
// 刷新用户数据,同步道具数量与VIP经验
|
||||
APIRequestList.GetUser((user) =>
|
||||
{
|
||||
UIManager.Instance.ShowBoardError(string.IsNullOrEmpty(result) ? "使用成功" : result);
|
||||
// 刷新用户数据,同步道具数量与VIP经验
|
||||
APIRequestList.GetUser((user) =>
|
||||
{
|
||||
if (user != null)
|
||||
GameManager.Instance.UpdataUserData(user);
|
||||
RefreshPropFirstLayerCounts(); // 数量变化后同步刷新一级角标
|
||||
});
|
||||
if (user != null)
|
||||
GameManager.Instance.UpdataUserData(user);
|
||||
RefreshPropFirstLayerCounts(); // 数量变化后同步刷新一级角标
|
||||
RebuildPropTwo(); // 二层开着的话同步刷新显示数量
|
||||
});
|
||||
});
|
||||
//UIManager.Instance.ShowTiShi($"确定使用1个{typeName}吗?", () =>
|
||||
//{
|
||||
// APIRequestList.UseVipExp(1, (result) =>
|
||||
// {
|
||||
// UIManager.Instance.ShowBoardError(string.IsNullOrEmpty(result) ? "使用成功" : result);
|
||||
// // 刷新用户数据,同步道具数量与VIP经验
|
||||
// APIRequestList.GetUser((user) =>
|
||||
// {
|
||||
// if (user != null)
|
||||
// GameManager.Instance.UpdataUserData(user);
|
||||
// RefreshPropFirstLayerCounts(); // 数量变化后同步刷新一级角标
|
||||
// RebuildPropTwo(); // 二层开着的话同步刷新显示数量
|
||||
// });
|
||||
// });
|
||||
//});
|
||||
break;
|
||||
case PropEnum.改名卡:
|
||||
UIManager.Instance.ShowBoardError("改名卡请到主界面背包中使用");
|
||||
break;
|
||||
case PropEnum.鱼雷:
|
||||
// ⭐ 鱼雷:房间 WebSocket 使用(服务端扣库存+权重随机金币+直接加钱),
|
||||
// 爆奖表现等 TorpedoResultNotice 房间广播回来(OnTorpedoResult)
|
||||
var torpedoName = string.IsNullOrEmpty(p.name) ? PropEnum.鱼雷.ToString() : p.name;
|
||||
WebSocketSendList.UseTorpedo(
|
||||
GameManager.Instance.gameData.currRoomData.roomId,
|
||||
GameManager.Instance.user.id,
|
||||
p.number);
|
||||
//UIManager.Instance.ShowTiShi($"确定使用1个{torpedoName}吗?", () =>
|
||||
//{
|
||||
// WebSocketSendList.UseTorpedo(
|
||||
// GameManager.Instance.gameData.currRoomData.roomId,
|
||||
// GameManager.Instance.user.id,
|
||||
// p.number);
|
||||
//});
|
||||
break;
|
||||
default:
|
||||
// 装备/碎片等道具没有游戏内使用流程
|
||||
UIManager.Instance.ShowBoardError($"[{(PropEnum)p.type}] 暂不支持在游戏内使用");
|
||||
@@ -1660,19 +1774,98 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>克隆项图标:按后端 imageUrl 下载(与背包面板同惯例,回调时判空防已销毁)</summary>
|
||||
private void SetPropItemIconUrl(GameObject item, string imageUrl)
|
||||
/// <summary>
|
||||
/// 服务端 TorpedoResultNotice 房间广播回调:有玩家使用鱼雷成功(服务端已扣库存+加金币)。
|
||||
/// 所有客户端都播鱼雷爆奖流程(与 Boss 爆奖 RoomKillNotice 全端实例化同惯例):
|
||||
/// 随机炸死场上小鱼 + 红包分包爆金币 + 分数面板滚分 + 飞回使用者座位结算(isSelf 决定金/银币)。
|
||||
/// </summary>
|
||||
public void OnTorpedoResult(TorpedoResultNotice data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(imageUrl)) return;
|
||||
if (data == null) return;
|
||||
|
||||
// 使用者座位(可能是本地玩家,也可能是其他人)
|
||||
SitPosData sitData = null;
|
||||
foreach (var sit in list_Sits)
|
||||
{
|
||||
if (sit != null && sit.user == data.userId) { sitData = sit; break; }
|
||||
}
|
||||
if (sitData == null)
|
||||
{
|
||||
Debug.LogWarning($"[鱼雷] 未找到使用者座位: userId={data.userId}");
|
||||
return;
|
||||
}
|
||||
|
||||
// JiaFen:结算时金币回加到使用者座位显示(服务端已加钱,这里只是表现层,与杀鱼 onScoreAdd 同惯例)
|
||||
long gold = data.gold;
|
||||
var targetSit = sitData;
|
||||
Action<int> jiaFen = (audioID) =>
|
||||
{
|
||||
targetSit.goldNum += gold;
|
||||
if (targetSit.tMP_GoldNum != null)
|
||||
targetSit.tMP_GoldNum.text = targetSit.goldNum.ToString();
|
||||
AudioManager.Instance.PlayerFX(audioID);
|
||||
};
|
||||
|
||||
// 鱼雷颜色(后端下发的十六进制色值,特效版用来渲染爆炸特效;解析失败用默认色)
|
||||
Color torpedoColor = Color.white;
|
||||
if (!string.IsNullOrEmpty(data.color)) ColorUtility.TryParseHtmlString(data.color, out torpedoColor);
|
||||
|
||||
// ⭐ 按鱼雷编号选择特效预制体(torpedoEffectConfig 里配编号→prefab;
|
||||
// 未配置的编号回退纯逻辑版:内置倒计时直接爆炸)
|
||||
TorpedoEvent evt = null;
|
||||
if (torpedoEffectConfig != null && torpedoEffectConfig.effects != null)
|
||||
{
|
||||
foreach (var e in torpedoEffectConfig.effects)
|
||||
{
|
||||
if (e == null || e.effectPrefab == null || e.propNumber != data.propNumber) continue;
|
||||
var fxGo = Instantiate(e.effectPrefab);
|
||||
// prefab 忘挂组件时兜底补挂(必须与动画同一对象,旧版动画事件才调得到)
|
||||
evt = fxGo.GetComponent<TorpedoEvent>();
|
||||
if (evt == null) evt = fxGo.AddComponent<TorpedoEvent>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (evt == null)
|
||||
{
|
||||
// 回退:纯逻辑版(无特效,内置倒计时→爆炸)
|
||||
var go = new GameObject("TorpedoEvent");
|
||||
evt = go.AddComponent<TorpedoEvent>();
|
||||
}
|
||||
evt.torpedoColor = torpedoColor;
|
||||
evt.FireTorpedo(data.gold, sitData.posID, jiaFen);
|
||||
|
||||
// 本地玩家使用成功:刷新用户数据(鱼雷库存已扣)+ 道具菜单数量
|
||||
if (data.userId == GameManager.Instance.user.id)
|
||||
{
|
||||
APIRequestList.GetUser((user) =>
|
||||
{
|
||||
if (user != null)
|
||||
GameManager.Instance.UpdataUserData(user);
|
||||
RefreshPropFirstLayerCounts(); // 鱼雷库存已扣,刷新一级角标
|
||||
RebuildPropTwo(); // 二层开着的话同步刷新数量
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>二级道具图标:优先用 gameConfig 缓存的 sprite_Icon,未缓存则按 imageUrl 下载并回填缓存(与商店面板同惯例,回调时判空防已销毁)</summary>
|
||||
private void SetPropTwoIcon(GameObject item, PropInfConfig t)
|
||||
{
|
||||
if (t == null) return;
|
||||
var img = item.transform.childCount > 0 ? item.transform.GetChild(0).GetComponent<Image>() : null;
|
||||
if (img == null) img = item.GetComponent<Image>();
|
||||
if (img == null) img = item.GetComponentInChildren<Image>();
|
||||
if (img == null) return;
|
||||
WebTool.DownImage(APIManager.baseURL + imageUrl, (texture) =>
|
||||
if (t.sprite_Icon != null)
|
||||
{
|
||||
if (item == null || img == null) return;
|
||||
if (texture != null)
|
||||
img.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 0.5f);
|
||||
img.sprite = t.sprite_Icon;
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(t.imageUrl)) return;
|
||||
WebTool.DownImage(APIManager.baseURL + t.imageUrl, (texture) =>
|
||||
{
|
||||
if (item == 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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user