Files
5.24BuYu/Assets/MyGame/Scripts/Manager/CreateFishManager.cs
T

2159 lines
82 KiB
C#
Raw Normal View History

2026-04-29 01:18:39 +08:00
// ============================================================
// 文件:CreateFishManager.cs
// 职责:捕鱼游戏 —— 鱼的生成 / 销毁 / 鱼阵管理核心管理器
// 优化点:
// 1. 消除 Update 中重复创建 List、字典查找性能问题
// 2. 统一鱼阵路线选取逻辑,减少冗余代码
// 3. FishSpawnTimerJob shouldSpawn 逻辑修正(原逻辑只在 ==0 时计时,现保持一致)
// 4. 增加必要的空检查与防御性编程
// 5. 清理无用注释与废弃代码块
// 6. 方法职责单一化拆分
// ============================================================
using Assets.MyGame.Scripts.Characters.Monster;
2026-03-27 15:18:35 +08:00
using Assets.MyGame.Scripts.Net;
2026-02-03 11:35:19 +08:00
using Assets.MyGame.Scripts.Net.API;
using Assets.MyGame.Scripts.Net.API.GameConfig_SubClass;
using Assets.MyGame.Scripts.Net.APIResponses.Enum;
2026-05-01 06:26:50 +08:00
using Assets.MyGame.Scripts.Net.APIResponses.GameConfig_SubClass;
2026-02-03 11:35:19 +08:00
using Assets.MyGame.Scripts.Net.WebResponses;
2026-02-09 10:44:38 +08:00
using cfg;
2026-02-03 11:35:19 +08:00
using Newtonsoft.Json;
using System;
2026-02-09 10:44:38 +08:00
using System.Collections;
2026-02-03 11:35:19 +08:00
using System.Collections.Generic;
2026-03-27 15:18:35 +08:00
using System.Linq;
2026-05-01 06:26:50 +08:00
using System.Threading.Tasks;
2026-04-25 00:12:55 +08:00
using TMPro;
2026-02-03 11:35:19 +08:00
using Unity.Collections;
using Unity.Jobs;
2026-05-04 17:21:52 +08:00
using UnityEditor;
2026-02-03 11:35:19 +08:00
using UnityEngine;
using Random = UnityEngine.Random;
2026-04-29 01:18:39 +08:00
/// <summary>
/// 捕鱼游戏核心管理器:负责鱼的定时生成、网络同步、阵型调度、死亡特效。
/// 继承 MonoSingleton 保证全局唯一。
/// </summary>
2026-02-03 11:35:19 +08:00
public class CreateFishManager : MonoSingleton<CreateFishManager>
{
2026-04-29 01:18:39 +08:00
// ----------------------------------------------------------------
// 外部依赖
// ----------------------------------------------------------------
2026-02-03 11:35:19 +08:00
2026-04-29 01:18:39 +08:00
/// <summary>当前游戏全局配置(通过 GameManager 获取)</summary>
2026-02-03 11:35:19 +08:00
public GameConfig M_GameConfig => GameManager.Instance.gameConfig;
2026-03-27 15:18:35 +08:00
2026-04-29 01:18:39 +08:00
// ----------------------------------------------------------------
// 配置缓存(空间换时间,避免 List.Find O(n) 查找)
// ----------------------------------------------------------------
/// <summary>roomFishConfigs 按游戏类型索引</summary>
2026-02-03 11:35:19 +08:00
private Dictionary<GameTypeEnum, RoomFishConfig> roomFishConfig;
2026-04-29 01:18:39 +08:00
/// <summary>每个游戏类型下 fishNumber → RoomFishDetailConfig</summary>
2026-02-03 11:35:19 +08:00
private Dictionary<GameTypeEnum, Dictionary<string, RoomFishDetailConfig>> createDic;
2026-04-29 01:18:39 +08:00
/// <summary>fishNumber → FishTypeConfig(基础配置缓存)</summary>
2026-02-03 11:35:19 +08:00
private Dictionary<string, FishTypeConfig> fishDatas;
2026-04-29 01:18:39 +08:00
/// <summary>fishArrayNumber → FishArrayConfig(鱼阵配置缓存)</summary>
2026-04-03 18:32:19 +08:00
private Dictionary<string, FishArrayConfig> fishArrayConfigDic;
2026-04-29 01:18:39 +08:00
// ----------------------------------------------------------------
// UI 引用
// ----------------------------------------------------------------
/// <summary>当前游戏面板(用于坐标转换、金币飞行等)</summary>
2026-04-24 22:51:34 +08:00
public GameMainPanel_FishBase currGameType;
2026-04-03 18:32:19 +08:00
2026-04-29 01:18:39 +08:00
// ----------------------------------------------------------------
// 死亡特效 Prefab(Inspector 赋值)
// ----------------------------------------------------------------
public GameObject dieFXPrefab; // 基础死亡特效
public GameObject dieFXPrefabAward; // 普通鱼金币奖励特效
public GameObject dieFXPrefabAwardTmp; // Boss 分数浮动文本
public GameObject dieFXPrefabAwardTmp_AddScore;
// ----------------------------------------------------------------
// 生成系统运行时数据
// ----------------------------------------------------------------
/// <summary>鱼的父节点(保持场景层级整洁)</summary>
public Transform fishRoot;
2026-05-01 06:26:50 +08:00
/// <summary>鱼的父节点(保持场景层级整洁)</summary>
public Transform poolRoot;
2026-04-29 01:18:39 +08:00
2026-07-21 05:13:43 +08:00
/// <summary>
/// 爆奖特效统一父节点(Boss 分数版/加分弹出/BJEndUI/特殊爆奖等)。
/// EnterRoom 时创建挂在 UIManager.uiRoot 下,ExitRoom 时整节点销毁,
/// 避免爆奖残留特效在退出房间后仍挂在 subPanelParent.parent 上造成内存泄漏。
/// </summary>
public Transform bjEffectRoot;
2026-04-29 01:18:39 +08:00
/// <summary>当前房间可生成鱼列表(与 NativeArray 下标对齐)</summary>
public List<RoomFishDetailConfig> spawnList;
/// <summary>当前房间 fishNumber → RoomFishDetailConfig</summary>
public Dictionary<string, RoomFishDetailConfig> spawnDic;
2026-05-18 18:39:11 +08:00
public bool isCreateFish; //是否可以创建鱼
2026-05-28 18:16:45 +08:00
public bool isFrozen = false; //是否冰冻中(冻结出鱼+所有鱼定格)
private Dictionary<string, CharacterStateType> frozenFishStates = new Dictionary<string, CharacterStateType>();
2026-06-21 00:07:26 +08:00
private Dictionary<string, bool> frozenFishIsMoves = new Dictionary<string, bool>();
2026-05-18 18:39:11 +08:00
2026-04-29 01:18:39 +08:00
/// <summary>按鱼类型分类的生成配置(Boss / 普通 / 黄金…)</summary>
private Dictionary<FishTypeEnum, List<RoomFishDetailConfig>> fishTypeConfig;
// ------ 鱼阵路线相关 ------
/// <summary>arrayNumber → 该阵型所有可用生成配置</summary>
private Dictionary<string, List<RoomFishDetailConfig>> yz_FishConfig_Line;
/// <summary>列表下标 → arrayNumber(用于随机取阵型)</summary>
2026-05-01 06:26:50 +08:00
private Dictionary<int, string> yz_FishConfig_Line_Dic;
2026-04-29 01:18:39 +08:00
/// <summary>arrayNumber → 列表下标(反向索引)</summary>
2026-05-01 06:26:50 +08:00
private Dictionary<string, int> yz_FishConfig_Line_Dic1;
2026-04-29 01:18:39 +08:00
/// <summary>二维列表,与字典保持同步(供随机无重复取阵型用)</summary>
2026-05-01 06:26:50 +08:00
private List<List<RoomFishDetailConfig>> yz_FishConfig_Line_List;
2026-04-29 01:18:39 +08:00
// ------ Job System NativeArray ------
private NativeArray<float> timers; // 每条鱼的累计计时
private NativeArray<float> frequencies; // 每条鱼的生成间隔
2026-05-01 06:26:50 +08:00
private NativeArray<int> shouldSpawn; // 1=本帧应尝试生成,0=冷却中
2026-05-22 19:30:02 +08:00
// ----------------------------------------------------------------
// 运行时鱼实例字典(guid 为 key)
// ----------------------------------------------------------------
2026-04-29 01:18:39 +08:00
2026-05-22 19:30:02 +08:00
/// <summary>全部存活鱼(包含 Boss / 小鱼 / 黄金鱼)</summary>
public Dictionary<string, MonsterContral> fishDatas_hashCodekey = new Dictionary<string, MonsterContral>();
/// <summary>普通鱼</summary>
public Dictionary<string, MonsterContral> fishDatas_hashCodekey_Small = new Dictionary<string, MonsterContral>();
/// <summary>Boss 鱼</summary>
public Dictionary<string, MonsterContral> fishDatas_hashCodekey_Boss = new Dictionary<string, MonsterContral>();
/// <summary>黄金</summary>
public Dictionary<string, MonsterContral> fishDatas_hashCodekey_HJ = new Dictionary<string, MonsterContral>();
/// <summary>特殊鱼(非 Boss)</summary>
public Dictionary<string, MonsterContral> fishDatas_hashCodekey_TS = new Dictionary<string, MonsterContral>();
/// <summary>特殊 Boss</summary>
public Dictionary<string, MonsterContral> fishDatas_hashCodekey_TS_Boss = new Dictionary<string, MonsterContral>();
2026-04-29 01:18:39 +08:00
// ------ 各类计时器 ------
2026-05-18 18:39:11 +08:00
private RoomFishProbabilityConfig tsbossCreateData; // 特殊Boss 创建数据
private float tsbossTimer = 999f; // 特殊Boss 刷新倒计时(秒)
2026-05-22 19:30:02 +08:00
private int tsbossCount; // 本轮同时启用特殊Boss鱼数量
private int tsbossQuanZhongCount; // 本轮同时启用特殊Boss鱼数量
2026-06-16 15:41:22 +08:00
private List<string> tsbossRecentList = new List<string>(); // 最近出现过的特殊Boss(cool down)
private int tsbossRecentMax = 3; // N次内不重复出现
2026-05-22 19:30:02 +08:00
2026-05-18 18:39:11 +08:00
private RoomFishProbabilityConfig tsCreateData; // 特殊鱼 创建数据
private float tsTimer = 999f; // 特殊鱼 刷新倒计时(秒)
2026-05-22 19:30:02 +08:00
private int tsCount; // 本轮同时启用特殊鱼数量
private int tsQuanZhongCount; // 本轮同时启用特殊鱼数量
2026-05-01 06:26:50 +08:00
private RoomFishProbabilityConfig bossCreateData; // Boss 创建数据
private float bossTimer = 999f; // Boss 刷新倒计时(秒)
2026-05-22 19:30:02 +08:00
private int bossCount; // 本轮同时启用Boss鱼数量
private int bossQuanZhongCount; // 本轮同时启用Boss鱼数量
2026-06-16 15:41:22 +08:00
private List<string> bossRecentList = new List<string>(); // 最近出现过的Boss(cool down)
private int bossRecentMax = 3; // N次内不重复出现
2026-05-22 19:30:02 +08:00
2026-07-05 17:39:30 +08:00
/// <summary>Boss鱼路线奇偶切换标记:true=下次选奇数路线,false=下次选偶数路线</summary>
private bool bossNextPathOdd = true;
2026-05-01 06:26:50 +08:00
private RoomFishProbabilityConfig yzCreateData; // 鱼阵创建数据
2026-05-01 21:59:16 +08:00
private float yzTimer = 999f; // 鱼阵刷新倒计时(秒)
2026-05-22 19:30:02 +08:00
private int yzCount; // 本轮同时激活阵型数量
private int yz_LineCount = 0; // 场景内鱼阵路线总数
private int yzQuanZhongCount = 0; // 场景内鱼阵路线总数
2026-05-01 06:26:50 +08:00
private RoomFishProbabilityConfig ptCreateData; // 普通鱼创建数据
private float ptTimer = 999f; // 普通鱼批量启用倒计时(秒)
2026-05-22 19:30:02 +08:00
private int ptCount; // 本轮同时启用普通鱼数量
private int ptQuanZhongCount; // 本轮同时启用普通鱼数量
2026-05-18 18:39:11 +08:00
private RoomFishProbabilityConfig hjCreateData; // 普通鱼创建数据
private float hjTimer = 999f; // 黄金鱼批量启用倒计时(秒)
private int hjCount; // 本轮同时启用黄金鱼数量
2026-05-22 19:30:02 +08:00
private int hjQuanZhongCount; // 本轮同时启用黄金鱼数量
2026-04-29 01:18:39 +08:00
/// <summary>是否开启放鱼(false 时 Update 不执行生成逻辑)</summary>
public bool fangzhu;
2026-05-01 21:59:16 +08:00
public bool isResources = false;
2026-04-29 01:18:39 +08:00
// ================================================================
// 懒加载属性
// ================================================================
/// <summary>
/// 鱼基础配置字典(fishNumber → FishTypeConfig),首次访问时构建。
/// </summary>
public Dictionary<string, FishTypeConfig> FishDatas
2026-04-03 18:32:19 +08:00
{
2026-04-29 01:18:39 +08:00
get
{
if (fishDatas != null) return fishDatas;
2026-04-03 18:32:19 +08:00
2026-04-29 01:18:39 +08:00
fishDatas = new Dictionary<string, FishTypeConfig>(M_GameConfig.fishTypeConfigs.Count);
foreach (var item in M_GameConfig.fishTypeConfigs)
fishDatas[item.fishNumber] = item;
return fishDatas;
}
2026-04-03 18:32:19 +08:00
}
2026-04-29 01:18:39 +08:00
/// <summary>
/// 房间鱼配置(按游戏类型),首次访问时触发懒加载初始化。
/// </summary>
public Dictionary<GameTypeEnum, RoomFishConfig> RoomFishConfig
{
get { EnsureInit(); return roomFishConfig; }
}
2026-04-03 18:32:19 +08:00
2026-04-29 01:18:39 +08:00
/// <summary>
/// 游戏类型 → (fishNumber → RoomFishDetailConfig),首次访问时触发懒加载。
/// </summary>
public Dictionary<GameTypeEnum, Dictionary<string, RoomFishDetailConfig>> CreateDic
{
get { EnsureInit(); return createDic; }
}
2026-05-18 18:39:11 +08:00
#region 生命周期
2026-04-29 01:18:39 +08:00
// ================================================================
// Unity 生命周期
// ================================================================
2026-04-03 18:32:19 +08:00
2026-04-29 01:18:39 +08:00
protected override void Awake()
{
base.Awake();
2026-05-08 06:49:53 +08:00
yzCreateData = new RoomFishProbabilityConfig
{
2026-05-22 19:30:02 +08:00
countRange = new List<int> { 2, 3 },
2026-05-08 06:49:53 +08:00
firstFishCountRange = new List<int> { 2, 3 },
roomLevel = 3,
fishType = 0,
maxCountRange = new List<int> { 10, 20 },
timeRange = new List<int> { 3, 4 },
type = 2
};
2026-04-29 01:18:39 +08:00
// 确保 FishRoot 存在
if (fishRoot == null)
fishRoot = new GameObject("FishRoot").transform;
2026-05-01 06:26:50 +08:00
if (poolRoot == null)
fishRoot = new GameObject("poolRoot").transform;
2026-04-29 01:18:39 +08:00
// 监听服务器下发的创鱼事件
EventCenter.Instance.AddEventListener<SyncGameData>(
ConstEventString.SyncGameData_CreateFish, CreateFish);
2026-04-03 18:32:19 +08:00
2026-07-20 22:32:52 +08:00
// 监听新玩家"请求同步鱼"事件:房主收到后广播场上活鱼
EventCenter.Instance.AddEventListener<SyncGameData>(
ConstEventString.SyncGameData_RequestSyncFish, OnRequestSyncFish);
2026-05-01 21:59:16 +08:00
2026-04-03 18:32:19 +08:00
2026-05-01 06:26:50 +08:00
}
2026-05-18 18:39:11 +08:00
// ================================================================
// Update:定时调度 + Job 驱动生成
// ================================================================
private void Update()
2026-05-01 06:26:50 +08:00
{
2026-05-28 18:16:45 +08:00
if (!isFrozen)
isCreateFish = BroadCastCreateFish();
2026-05-01 06:26:50 +08:00
}
2026-05-18 18:39:11 +08:00
2026-05-22 19:30:02 +08:00
2026-05-18 18:39:11 +08:00
2026-04-29 01:18:39 +08:00
private void OnDestroy()
{
EventCenter.Instance.RemoveEventListener<SyncGameData>(
ConstEventString.SyncGameData_CreateFish, CreateFish);
2026-07-20 22:32:52 +08:00
EventCenter.Instance.RemoveEventListener<SyncGameData>(
ConstEventString.SyncGameData_RequestSyncFish, OnRequestSyncFish);
2026-04-29 01:18:39 +08:00
// 释放 NativeArray,避免内存泄漏
DisposeNativeArrays();
}
2026-05-18 18:39:11 +08:00
#endregion
2026-04-29 01:18:39 +08:00
2026-05-18 18:39:11 +08:00
#region 初始化
public void InitChuYu()
2026-04-29 01:18:39 +08:00
{
2026-05-18 18:39:11 +08:00
// 随机初始化计时器,避免所有类型同帧触发
2026-05-01 06:26:50 +08:00
yzCount = Random.Range(yzCreateData.countRange[0], yzCreateData.countRange[1]);
ptCount = Random.Range(ptCreateData.countRange[0], ptCreateData.countRange[1]);
2026-05-18 18:39:11 +08:00
hjCount = Random.Range(hjCreateData.countRange[0], hjCreateData.countRange[1]);
bossCount = Random.Range(bossCreateData.countRange[0], bossCreateData.countRange[1]);
2026-05-22 19:30:02 +08:00
tsbossCount = Random.Range(tsbossCreateData.countRange[0], tsbossCreateData.countRange[1]);
tsCount = Random.Range(tsCreateData.countRange[0], tsCreateData.countRange[1]);
2026-05-18 18:39:11 +08:00
yzTimer = 0.5f;
ptTimer = 0.5f;
hjTimer = 0.5f;
2026-05-22 19:30:02 +08:00
tsTimer = 1f;
2026-05-18 18:39:11 +08:00
bossTimer = 5f;
2026-05-22 19:30:02 +08:00
tsbossTimer = 5f;
2026-04-29 01:18:39 +08:00
}
/// <summary>
/// 懒加载初始化:只执行一次,构建全部配置缓存字典。
/// </summary>
2026-04-03 18:32:19 +08:00
private void EnsureInit()
{
2026-04-29 01:18:39 +08:00
if (roomFishConfig != null) return;
2026-04-03 18:32:19 +08:00
FishYZ.Init();
roomFishConfig = new Dictionary<GameTypeEnum, RoomFishConfig>();
2026-05-01 06:26:50 +08:00
createDic = new Dictionary<GameTypeEnum, Dictionary<string, RoomFishDetailConfig>>();
2026-04-03 18:32:19 +08:00
var list = GameManager.Instance.gameConfig.roomFishConfigs;
2026-04-29 01:18:39 +08:00
if (list == null || list.Count == 0) return;
2026-04-03 18:32:19 +08:00
2026-04-29 01:18:39 +08:00
// 构建 fishArray / fishType 字典缓存
2026-04-03 18:32:19 +08:00
BuildConfigCache();
foreach (var item in list)
{
2026-04-29 01:18:39 +08:00
var gameType = (GameTypeEnum)item.gameType;
2026-04-03 18:32:19 +08:00
roomFishConfig[gameType] = item;
var fishDic = new Dictionary<string, RoomFishDetailConfig>();
createDic[gameType] = fishDic;
2026-04-29 01:18:39 +08:00
// 普通鱼列表
2026-04-03 18:32:19 +08:00
if (item.normalFishList != null)
foreach (var fish in item.normalFishList)
fishDic[fish.fishNumber] = fish;
2026-04-29 01:18:39 +08:00
// 等级鱼列表(同 fishNumber 会覆盖普通鱼,实现换皮)
2026-04-03 18:32:19 +08:00
if (item.roomFishLevelList != null)
foreach (var fish in item.roomFishLevelList)
fishDic[fish.fishNumber] = fish;
2026-02-03 11:35:19 +08:00
}
}
/// <summary>
2026-04-29 01:18:39 +08:00
/// 构建高频查询的字典缓存(fishArrayConfigs / fishTypeConfigs)。
2026-02-03 11:35:19 +08:00
/// </summary>
2026-04-29 01:18:39 +08:00
private void BuildConfigCache()
2026-02-03 11:35:19 +08:00
{
2026-04-29 01:18:39 +08:00
fishArrayConfigDic = GameManager.Instance.gameConfig.fishArrayConfigs
.ToDictionary(a => a.fishArrayNumber, a => a);
2026-05-22 19:30:02 +08:00
fishDatas = GameManager.Instance.gameConfig.fishTypeConfigs
2026-04-29 01:18:39 +08:00
.ToDictionary(f => f.fishNumber, f => f);
2026-02-03 11:35:19 +08:00
}
2026-05-18 18:39:11 +08:00
#endregion
#region 进入房间加载
/// <summary>进入房间:初始化生成系统并绑定游戏面板。</summary>
public void EnterRoom(GameTypeEnum gameType)
{
InitSpawnSystem(gameType);
BindCurrentGamePanel();
2026-07-21 05:13:43 +08:00
EnsureBJEffectRoot();
}
/// <summary>
/// 创建爆奖特效统一父节点(挂在 UIManager.uiRoot 下)。
/// 已存在则不重复创建。所有爆奖相关 Instantiate 都应使用此节点作为父对象。
/// </summary>
private void EnsureBJEffectRoot()
{
if (bjEffectRoot != null) return;
var go = new GameObject("BJEffectRoot");
go.transform.SetParent(UIManager.Instance.uiRoot, false);
var rect = go.AddComponent<RectTransform>();
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
bjEffectRoot = go.transform;
2026-05-18 18:39:11 +08:00
}
/// <summary>根据当前 GameType 绑定对应 UI 面板到 currGameType。</summary>
private void BindCurrentGamePanel()
{
switch (GameManager.Instance.gameData.currGameType)
{
case GameType.FLZY:
2026-05-22 19:30:02 +08:00
currGameType = UIManager.Instance.Get<GameMainPanel_FLZY>(UIPanel.MainGamePanelFLZY);
2026-05-18 18:39:11 +08:00
break;
case GameType.YYSC:
currGameType = UIManager.Instance.Get<GameMainPanel_YYSC>(UIPanel.MainGamePanelYYSC);
break;
case GameType.XJMH:
2026-05-22 19:30:02 +08:00
currGameType = UIManager.Instance.Get<GameMainPanel_XJMH>(UIPanel.MainGamePanelXJMH);
2026-05-18 18:39:11 +08:00
break;
case GameType.HHWK:
2026-05-22 19:30:02 +08:00
currGameType = UIManager.Instance.Get<GameMainPanel_HHWK>(UIPanel.MainGamePanelHHWK);
2026-05-18 18:39:11 +08:00
break;
default:
break;
}
}
2026-04-29 01:18:39 +08:00
/// <summary>
/// 进入房间时初始化生成系统:加载当前房间配置、分配 NativeArray。
/// </summary>
2026-05-01 06:26:50 +08:00
public async void InitSpawnSystem(GameTypeEnum gameType)
2026-02-03 11:35:19 +08:00
{
EnsureInit();
2026-04-29 01:18:39 +08:00
// 初始化分类容器
2026-05-01 06:26:50 +08:00
fishTypeConfig = new Dictionary<FishTypeEnum, List<RoomFishDetailConfig>>();
yz_FishConfig_Line = new Dictionary<string, List<RoomFishDetailConfig>>();
2026-04-29 01:18:39 +08:00
yz_FishConfig_Line_Dic = new Dictionary<int, string>();
2026-05-01 06:26:50 +08:00
yz_FishConfig_Line_Dic1 = new Dictionary<string, int>();
yz_FishConfig_Line_List = new List<List<RoomFishDetailConfig>>();
spawnList = new List<RoomFishDetailConfig>();
spawnDic = new Dictionary<string, RoomFishDetailConfig>();
2026-04-29 01:18:39 +08:00
2026-02-03 11:35:19 +08:00
var config = RoomFishConfig[gameType];
2026-04-29 01:18:39 +08:00
// ---- 处理普通鱼列表 ----
if (config.normalFishList != null)
2026-03-18 17:26:14 +08:00
{
2026-04-29 01:18:39 +08:00
foreach (var item in config.normalFishList)
2026-03-27 15:18:35 +08:00
{
2026-05-01 21:59:16 +08:00
if (!spawnDic.ContainsKey(item.fishNumber))
{
spawnDic[item.fishNumber] = item;
spawnList.Add(item);
}
2026-04-29 01:18:39 +08:00
if (item.type == 1)
2026-03-27 15:18:35 +08:00
{
2026-04-29 01:18:39 +08:00
// 单体鱼:按鱼类型分组
AddToFishTypeConfig(item);
2026-03-27 15:18:35 +08:00
}
2026-04-29 01:18:39 +08:00
else if (item.type == 2)
2026-03-27 15:18:35 +08:00
{
2026-04-29 01:18:39 +08:00
// 鱼阵:按 arrayNumber 分组
TryAddToArrayLine(item);
2026-03-27 15:18:35 +08:00
}
}
2026-03-18 17:26:14 +08:00
}
2026-04-29 01:18:39 +08:00
// ---- 处理等级鱼列表(仅当前房间等级)----
2026-05-18 18:39:11 +08:00
int currRoomLevel = GameManager.Instance.gameData.currRoomData.roomLevel;
2026-04-29 01:18:39 +08:00
if (config.roomFishLevelList != null)
2026-03-18 17:26:14 +08:00
{
2026-05-01 06:26:50 +08:00
foreach (var item in config.roomFishProbabilityList)
{
if (item.roomLevel != currRoomLevel) continue;
switch (item.type)
{
case 1:
switch ((FishTypeEnum)item.fishType)
{
case FishTypeEnum.普通鱼:
ptCreateData = item;
break;
case FishTypeEnum.Boss鱼:
bossCreateData = item;
break;
case FishTypeEnum.黄金鱼:
2026-05-18 18:39:11 +08:00
hjCreateData = item;
2026-05-01 06:26:50 +08:00
break;
case FishTypeEnum.特殊鱼:
2026-05-22 19:30:02 +08:00
tsCreateData = item;
break;
case FishTypeEnum.特殊Boss:
2026-05-18 18:39:11 +08:00
tsbossCreateData = item;
2026-05-01 06:26:50 +08:00
break;
default:
break;
}
break;
2026-05-01 21:59:16 +08:00
case 2:
2026-05-01 06:26:50 +08:00
//yzTimer=item.
yzCreateData = item;
break;
default:
break;
}
}
2026-04-29 01:18:39 +08:00
foreach (var item in config.roomFishLevelList)
2026-03-18 17:26:14 +08:00
{
2026-04-29 01:18:39 +08:00
if (item.roomLevel != currRoomLevel) continue;
2026-05-01 21:59:16 +08:00
2026-03-27 15:18:35 +08:00
if (item.type == 2)
2026-03-18 17:26:14 +08:00
{
2026-04-29 01:18:39 +08:00
spawnDic[item.fishNumber] = item;
2026-03-27 15:18:35 +08:00
spawnList.Add(item);
2026-05-22 19:30:02 +08:00
yzQuanZhongCount += item.fishWeight;
2026-04-29 01:18:39 +08:00
TryAddToArrayLine(item);
2026-03-27 15:18:35 +08:00
}
else
{
2026-04-29 01:18:39 +08:00
AddToFishTypeConfig(item);
// 等级鱼也加入 spawnDic / spawnList
spawnDic[item.fishNumber] = item;
spawnList.Add(item);
2026-05-22 19:30:02 +08:00
fishDatas.TryGetValue(item.fishNumber, out var ft);
switch ((FishTypeEnum)ft.fishType)
{
case FishTypeEnum.普通鱼:
ptQuanZhongCount += item.fishWeight;
break;
case FishTypeEnum.Boss鱼:
bossQuanZhongCount += item.fishWeight;
break;
case FishTypeEnum.黄金鱼:
hjQuanZhongCount += item.fishWeight;
break;
case FishTypeEnum.特殊鱼:
tsQuanZhongCount += item.fishWeight;
break;
case FishTypeEnum.特殊Boss:
tsbossQuanZhongCount += item.fishWeight;
break;
default:
break;
}
2026-03-27 15:18:35 +08:00
}
}
2026-03-18 17:26:14 +08:00
}
2026-02-03 11:35:19 +08:00
2026-04-29 01:18:39 +08:00
// ---- 分配 Job System NativeArray ----
2026-02-03 11:35:19 +08:00
int count = spawnList.Count;
2026-04-29 01:18:39 +08:00
DisposeNativeArrays(); // 防止重复进房泄漏
2026-05-01 06:26:50 +08:00
timers = new NativeArray<float>(count, Allocator.Persistent);
2026-02-03 11:35:19 +08:00
frequencies = new NativeArray<float>(count, Allocator.Persistent);
2026-05-01 06:26:50 +08:00
shouldSpawn = new NativeArray<int>(count, Allocator.Persistent);
2026-02-03 11:35:19 +08:00
for (int i = 0; i < count; i++)
{
2026-05-01 06:26:50 +08:00
timers[i] = 0f;
2026-02-03 11:35:19 +08:00
frequencies[i] = spawnList[i].fishFrequency;
2026-04-29 01:18:39 +08:00
shouldSpawn[i] = 1; // 初始全部允许生成
2026-03-27 15:18:35 +08:00
}
2026-04-29 01:18:39 +08:00
// ---- 统计场景内鱼阵路线总数 ----
yz_LineCount = 0;
foreach (var key in MonsterMoveLine.dic_BezierCurveDatas.Keys)
{
if (key.StartsWith("鱼阵"))
yz_LineCount++;
}
2026-05-01 06:26:50 +08:00
InitChuYu();
2026-02-03 11:35:19 +08:00
}
2026-04-29 01:18:39 +08:00
/// <summary>
/// 将单体鱼配置按 FishTypeEnum 加入分类字典。
/// </summary>
private void AddToFishTypeConfig(RoomFishDetailConfig item)
{
if (!FishDatas.TryGetValue(item.fishNumber, out var ft)) return;
var typeEnum = (FishTypeEnum)ft.fishType;
if (!fishTypeConfig.ContainsKey(typeEnum))
fishTypeConfig[typeEnum] = new List<RoomFishDetailConfig>();
fishTypeConfig[typeEnum].Add(item);
}
/// <summary>
/// 将鱼阵配置按 arrayNumber 加入路线字典(使用缓存替代 List.Find)。
/// </summary>
private void TryAddToArrayLine(RoomFishDetailConfig item)
{
if (!fishArrayConfigDic.TryGetValue(item.fishNumber, out var yz)) return;
if (!yz_FishConfig_Line.ContainsKey(yz.arrayNumber))
{
yz_FishConfig_Line[yz.arrayNumber] = new List<RoomFishDetailConfig>();
yz_FishConfig_Line_List.Add(new List<RoomFishDetailConfig>());
int newIdx = yz_FishConfig_Line_List.Count - 1;
2026-05-01 06:26:50 +08:00
yz_FishConfig_Line_Dic[newIdx] = yz.arrayNumber;
2026-04-29 01:18:39 +08:00
yz_FishConfig_Line_Dic1[yz.arrayNumber] = newIdx;
}
yz_FishConfig_Line[yz.arrayNumber].Add(item);
yz_FishConfig_Line_List[yz_FishConfig_Line_Dic1[yz.arrayNumber]].Add(item);
}
2026-05-18 18:39:11 +08:00
#endregion
2026-04-29 01:18:39 +08:00
2026-05-18 18:39:11 +08:00
#region 创建鱼 广播部分
/// <summary>
/// 广播创建鱼
/// </summary>
/// <returns>此玩家是否可以创建鱼</returns>
private bool BroadCastCreateFish()
{
if (!isResources)
{
return false;
}
// 未初始化 或 未开启放鱼 → 跳过
if (spawnList == null || !fangzhu) return false;
float dt = Time.deltaTime;
// ---- 1. 用 Job System 并行更新所有鱼的计时器 ----
var job = new FishSpawnTimerJob
{
deltaTime = dt,
timers = timers,
frequencies = frequencies,
shouldSpawn = shouldSpawn,
};
job.Schedule().Complete();
// ---- 2. Boss 定时激活 ----
TickBossSpawn(dt);
// ---- 3. 鱼阵定时激活 ----
TickFishArraySpawn(dt);
// ---- 4. 普通鱼定时批量激活 ----
2026-05-22 19:30:02 +08:00
TickPTFishSpawn(dt);
2026-05-18 18:39:11 +08:00
2026-05-22 19:30:02 +08:00
// ---- 5. 黄金鱼定时激活 ----
TickHJFishSpawn(dt);
// ---- 5. 特殊鱼定时激活 ----
TickTSFishSpawn(dt);
// ---- 6. 特殊boss定时激活 ----
TickTSBossSpawn(dt);
// ---- 7. 遍历 shouldSpawn,向服务器发送生成请求 ----
2026-05-18 18:39:11 +08:00
ProcessSpawnRequests();
return true;
}
// ================================================================
// 定时 Tick 方法(由 Update 调用)
// ================================================================
/// <summary>每 25 秒随机激活一条 Boss 鱼。</summary>
private void TickBossSpawn(float dt)
{
bossTimer -= dt;
if (bossTimer > 0f) return;
2026-05-22 19:30:02 +08:00
bossCount = Random.Range(bossCreateData.countRange[0], bossCreateData.countRange[1]);
2026-05-18 18:39:11 +08:00
bossTimer = Random.Range(bossCreateData.timeRange[0], bossCreateData.timeRange[1]);
2026-05-22 19:30:02 +08:00
//bossTimer = 3f;
2026-05-18 18:39:11 +08:00
if (!fishTypeConfig.TryGetValue(FishTypeEnum.Boss鱼, out var bossList) || bossList.Count == 0)
return;
2026-07-24 01:37:11 +08:00
foreach (var item in bossList)
{
2026-07-25 02:17:19 +08:00
//if (item.fishNumber == "11026")
// item.isCreate = 2;
2026-06-16 02:28:58 +08:00
2026-07-25 02:17:19 +08:00
//if (item.fishNumber == "11035")
// item.isCreate = 2;
2026-07-24 01:37:11 +08:00
// if (item.fishNumber == "11030")
// item.isCreate = 2;
2026-06-16 02:28:58 +08:00
2026-07-24 01:37:11 +08:00
}
2026-05-18 18:39:11 +08:00
2026-06-16 15:41:22 +08:00
StartCoroutine(buchongfu(bossList, bossCount, fishDatas_hashCodekey_Boss, bossRecentList, bossRecentMax));
2026-05-18 18:39:11 +08:00
}
2026-05-22 19:30:02 +08:00
/// <summary>每 25 秒随机激活一条 特殊Boss 鱼。</summary>
private void TickTSBossSpawn(float dt)
{
tsbossTimer -= dt;
if (tsbossTimer > 0f) return;
tsbossCount = Random.Range(tsbossCreateData.countRange[0], tsbossCreateData.countRange[1]);
tsbossTimer = Random.Range(tsbossCreateData.timeRange[0], tsbossCreateData.timeRange[1]);
if (!fishTypeConfig.TryGetValue(FishTypeEnum.特殊Boss, out var tsbossList) || tsbossList.Count == 0)
return;
2026-05-30 19:17:51 +08:00
//foreach (var item in tsbossList)
//{
// if (item.fishNumber == "11041")
// {
// item.isCreate = 2;
// }
//}
2026-06-16 15:41:22 +08:00
StartCoroutine(buchongfu(tsbossList, tsbossCount, fishDatas_hashCodekey_TS_Boss, tsbossRecentList, tsbossRecentMax));
2026-05-22 19:30:02 +08:00
}
private RoomFishDetailConfig GetRandomFishByWeight(List<RoomFishDetailConfig> configs)
{
if (configs == null || configs.Count == 0) return null;
int totalWeight = configs.Sum(c => c.fishWeight);
2026-06-21 00:07:26 +08:00
int randWeight = Random.Range(0, totalWeight + 1);
2026-05-22 19:30:02 +08:00
foreach (var cfg in configs)
{
randWeight -= cfg.fishWeight;
if (randWeight <= 0) // 注意这里是 <= 0
{
return cfg;
}
}
return configs[configs.Count - 1]; // 理论上不会到这里
}
/// <summary>
/// 从配置列表中按权重随机选择指定数量的鱼,不重复。
/// </summary>
private List<RoomFishDetailConfig> GetRandomFishByWeight(List<RoomFishDetailConfig> configs, int count)
{
var result = new List<RoomFishDetailConfig>();
if (configs == null || configs.Count == 0 || count <= 0) return result;
// 拷贝一份候选集合,避免修改原始列表
var candidates = new List<RoomFishDetailConfig>(configs);
count = Mathf.Min(count, candidates.Count); // 不超过候选数量
for (int k = 0; k < count; k++)
{
int totalWeight = candidates.Sum(c => c.fishWeight);
int randWeight = Random.Range(0, totalWeight);
RoomFishDetailConfig chosen = null;
foreach (var cfg in candidates)
{
randWeight -= cfg.fishWeight;
2026-05-27 19:47:06 +08:00
if (randWeight < 0) // 注意这里是 <= 0
2026-05-22 19:30:02 +08:00
{
chosen = cfg;
break;
}
}
if (chosen != null)
{
result.Add(chosen);
candidates.Remove(chosen); // 移除已选,保证不重复
}
}
return result;
}
2026-05-18 18:39:11 +08:00
2026-06-16 15:41:22 +08:00
IEnumerator buchongfu(List<RoomFishDetailConfig> bossList, int count, Dictionary<string, MonsterContral> targetDict, List<string> recentList = null, int recentMax = 0)
2026-05-18 18:39:11 +08:00
{
2026-06-16 15:41:22 +08:00
var batchPicked = new HashSet<string>(); // 本轮已选,防止同批重复
2026-05-18 18:39:11 +08:00
for (int j = 0; j < count; j++)
{
2026-06-16 15:41:22 +08:00
// 1. 先筛选出未重复的候选集合(排除场上已存在的同 fishNumber 的鱼 + 最近出现过的 + 本轮已选的)
2026-05-22 19:30:02 +08:00
var candidates = bossList
2026-06-08 21:23:40 +08:00
.Where(b => !targetDict.Values
2026-05-22 19:30:02 +08:00
.Any(existing => existing.fishTypeID.ToString() == b.fishNumber))
2026-06-16 15:41:22 +08:00
.Where(b => !batchPicked.Contains(b.fishNumber))
2026-05-22 19:30:02 +08:00
.ToList();
2026-06-16 15:41:22 +08:00
// 排除最近出现过的鱼
if (recentList != null && recentList.Count > 0)
candidates = candidates.Where(c => !recentList.Contains(c.fishNumber)).ToList();
2026-05-22 19:30:02 +08:00
if (candidates.Count == 0)
2026-05-18 18:39:11 +08:00
{
2026-06-16 15:41:22 +08:00
// 没有候选时清空最近列表 + 本轮记录重试
if (recentList != null) recentList.Clear();
batchPicked.Clear();
candidates = bossList
.Where(b => !targetDict.Values
.Any(existing => existing.fishTypeID.ToString() == b.fishNumber))
.ToList();
if (candidates.Count == 0)
yield break;
2026-05-18 18:39:11 +08:00
}
2026-05-22 19:30:02 +08:00
// 2. 在候选集合里做加权随机
var chosen = GetRandomFishByWeight(candidates);
2026-06-16 15:41:22 +08:00
batchPicked.Add(chosen.fishNumber);
2026-05-22 19:30:02 +08:00
2026-06-16 15:41:22 +08:00
// 3. 记录到最近列表
if (recentList != null && recentMax > 0)
{
recentList.Add(chosen.fishNumber);
while (recentList.Count > recentMax)
recentList.RemoveAt(0);
}
// 4. 标记激活
2026-05-22 19:30:02 +08:00
chosen.isCreate = 2;
2026-06-08 21:23:40 +08:00
yield return null; // 每帧只激活一条,避免同帧生成过多
2026-05-18 18:39:11 +08:00
}
}
2026-06-19 18:18:04 +08:00
//IEnumerator buchongfu(
//List<RoomFishDetailConfig> bossList,
//int count,
//Dictionary<string, MonsterContral> targetDict,
//List<string> recentList = null,
//int recentMax = 0)
//{
// Debug.Log($"===【调试开始】本次需要生成数量: {count},配置总数: {bossList.Count} ===");
// // 打印场上已有 Boss
// Debug.Log($"当前场上已有 Boss 数量: {targetDict.Count}");
// foreach (var kv in targetDict)
// Debug.Log($"场上已有 Boss fishNumber = {kv.Value.fishTypeID}");
// var batchPicked = new HashSet<string>(); // 本轮已选
// for (int j = 0; j < count; j++)
// {
// Debug.Log($"--- 第 {j + 1}/{count} 次抽取 ---");
// // 1. 先筛选候选池
// var candidates = bossList
// .Where(b => !targetDict.Values
// .Any(existing => existing.fishTypeID.ToString() == b.fishNumber))
// .Where(b => !batchPicked.Contains(b.fishNumber))
// .ToList();
// // 打印被排除的原因
// foreach (var b in bossList)
// {
// bool inTarget = targetDict.Values.Any(e => e.fishTypeID.ToString() == b.fishNumber);
// bool inBatch = batchPicked.Contains(b.fishNumber);
// bool inRecent = recentList != null && recentList.Contains(b.fishNumber);
// if (inTarget || inBatch || inRecent)
// {
// Debug.Log(
// $"排除 fishNumber={b.fishNumber} | " +
// $"场上存在={inTarget} | 本轮已选={inBatch} | 最近出现={inRecent}"
// );
// }
// }
// // 排除最近出现过的鱼
// if (recentList != null && recentList.Count > 0)
// candidates = candidates.Where(c => !recentList.Contains(c.fishNumber)).ToList();
// Debug.Log($"候选池数量: {candidates.Count}");
// // 2. 候选池为空 → 触发重置逻辑
// if (candidates.Count == 0)
// {
// Debug.LogWarning("候选池为空,触发 recentList + batchPicked 清空重置");
// recentList?.Clear();
// batchPicked.Clear();
// candidates = bossList
// .Where(b => !targetDict.Values
// .Any(existing => existing.fishTypeID.ToString() == b.fishNumber))
// .ToList();
// Debug.Log($"重置后候选池数量: {candidates.Count}");
// if (candidates.Count == 0)
// {
// Debug.LogError("重置后仍然没有候选鱼 → 说明 targetDict 永远占着某条鱼,导致永远少生成 1 条!");
// yield break;
// }
// }
// // 3. 加权随机
// var chosen = GetRandomFishByWeight(candidates);
// Debug.Log($"选中 fishNumber = {chosen.fishNumber}");
// batchPicked.Add(chosen.fishNumber);
// // 4. 记录最近出现
// if (recentList != null && recentMax > 0)
// {
// recentList.Add(chosen.fishNumber);
// while (recentList.Count > recentMax)
// recentList.RemoveAt(0);
// }
// // 5. 标记激活
// chosen.isCreate = 2;
// yield return null;
// }
// Debug.Log("===【调试结束】本轮生成完成 ===");
//}
2026-05-22 19:30:02 +08:00
2026-05-18 18:39:11 +08:00
/// <summary>每 1~3 秒随机激活若干组鱼阵。</summary>
private void TickFishArraySpawn(float dt)
{
yzTimer -= dt;
if (yzTimer > 0f) return;
yzCount = Random.Range(yzCreateData.countRange[0], yzCreateData.countRange[1]);
yzTimer = Random.Range(yzCreateData.timeRange[0], yzCreateData.timeRange[1]);
int lineCount = yz_FishConfig_Line_List.Count;
if (lineCount == 0) return;
// 随机选 yzCount 个不重复的阵型下标
var selectedIndices = RandomUniqueNumbers(
Mathf.Min(yzCount, lineCount), 0, lineCount);
foreach (int idx in selectedIndices)
{
string arrayNumber = yz_FishConfig_Line_Dic[idx];
if (!yz_FishConfig_Line.TryGetValue(arrayNumber, out var lineList) || lineList.Count == 0)
continue;
// 随机选一条配置并标记为待生成
var chosen = lineList[Random.Range(0, lineList.Count)];
chosen.isCreate = 2;
2026-05-22 19:30:02 +08:00
2026-05-18 18:39:11 +08:00
// 同步更新 spawnDic 中的状态
if (spawnDic.TryGetValue(chosen.fishNumber, out var spawnEntry))
spawnEntry.isCreate = 2;
}
}
/// <summary>每 3 秒随机批量激活若干条普通鱼。</summary>
2026-05-22 19:30:02 +08:00
private void TickPTFishSpawn(float dt)
2026-05-18 18:39:11 +08:00
{
ptTimer -= dt;
if (ptTimer > 0f) return;
ptCount = Random.Range(ptCreateData.countRange[0], ptCreateData.countRange[1]);
ptTimer = Random.Range(ptCreateData.timeRange[0], ptCreateData.timeRange[1]);
if (!fishTypeConfig.TryGetValue(FishTypeEnum.普通鱼, out var normalList) || normalList.Count == 0)
return;
// 随机选 ptCount 个不重复下标
2026-05-22 19:30:02 +08:00
var indices = GetRandomFishByWeight(normalList, ptCount);
2026-05-18 18:39:11 +08:00
2026-05-22 19:30:02 +08:00
foreach (var fish in indices)
fish.isCreate = 2;
}
/// <summary>每 3 秒随机批量激活若干条黄金鱼。</summary>
private void TickHJFishSpawn(float dt)
{
hjTimer -= dt;
if (hjTimer > 0f) return;
hjCount = Random.Range(hjCreateData.countRange[0], hjCreateData.countRange[1]);
hjTimer = Random.Range(hjCreateData.timeRange[0], hjCreateData.timeRange[1]);
if (!fishTypeConfig.TryGetValue(FishTypeEnum.黄金鱼, out var hjlist) || hjlist.Count == 0)
return;
// 随机选 hjCount 个不重复下标
var indices = GetRandomFishByWeight(hjlist, hjCount);
foreach (var fish in indices)
fish.isCreate = 2;
}
/// <summary>每 3 秒随机批量激活若干条特殊鱼。</summary>
private void TickTSFishSpawn(float dt)
{
tsTimer -= dt;
if (tsTimer > 0f) return;
tsCount = Random.Range(tsCreateData.countRange[0], tsCreateData.countRange[1]);
tsTimer = Random.Range(tsCreateData.timeRange[0], tsCreateData.timeRange[1]);
if (!fishTypeConfig.TryGetValue(FishTypeEnum.特殊鱼, out var tslist) || tslist.Count == 0)
return;
// 随机选 tsCount 个不重复下标
var indices = GetRandomFishByWeight(tslist, tsCount);
foreach (var fish in indices)
fish.isCreate = 2;
2026-05-18 18:39:11 +08:00
}
/// <summary>
/// 遍历 shouldSpawn 数组,对本帧到期且被标记 isCreate==2 的鱼
/// 向服务器发送 WebSocket 生成同步包。
/// </summary>
private void ProcessSpawnRequests()
{
for (int i = 0; i < spawnList.Count; i++)
{
if (shouldSpawn[i] != 1) continue;
var fish = spawnList[i];
// isCreate==0:刚到期,重置为冷却中,等待外部计时器激活
if (fish.isCreate == 0)
{
fish.isCreate = 1; // 标记为"已处理但未激活"
continue;
}
// isCreate==2:已被激活,向服务器同步一次生成请求
if (fish.isCreate == 2)
{
SendCreateFishRequest(fish);
fish.isCreate = 0;
}
}
}
/// <summary>
/// 构建并发送生成鱼的 WebSocket 同步数据包。
/// </summary>
private void SendCreateFishRequest(RoomFishDetailConfig fish)
{
int pathId = 0;
// 普通鱼(type==1)才从 pathList 随机选路径;鱼阵(type==2)由后续逻辑决定
if (fish.type == 1)
{
2026-07-05 17:39:30 +08:00
var ft = FishDatas[fish.fishNumber];
var pathList = ft.pathList;
2026-05-18 18:39:11 +08:00
if (pathList != null && pathList.Count > 0)
2026-07-05 17:39:30 +08:00
{
// Boss鱼(2)/特殊Boss(5):路线奇偶交替,避免同奇/同偶路径相互覆盖
if (ft != null && (ft.fishType == 2 || ft.fishType == 5))
{
var filtered = pathList.Where(p => (p % 2 != 0) == bossNextPathOdd).ToList();
if (filtered.Count == 0)
filtered = pathList;
pathId = filtered[Random.Range(0, filtered.Count)];
bossNextPathOdd = !bossNextPathOdd;
}
else
{
pathId = pathList[Random.Range(0, pathList.Count)];
}
}
2026-05-18 18:39:11 +08:00
}
2026-05-22 19:30:02 +08:00
else
{
2026-05-18 18:39:11 +08:00
2026-05-22 19:30:02 +08:00
if (!fishArrayConfigDic.TryGetValue(fish.fishNumber, out var yz))
{
Debug.LogError($"[CreateFishManager] 找不到 fishArrayConfig: {fish.fishNumber}");
return;
}
if (!FishYZ.dicfishYZ.TryGetValue(yz.arrayNumber, out var pathList) || pathList.Count == 0)
{
Debug.LogWarning($"[CreateFishManager] 鱼阵路线为空: {yz.arrayNumber}");
return;
}
if (pathList != null && pathList.Count > 0)
pathId = pathList[Random.Range(0, pathList.Count)];
}
2026-05-18 18:39:11 +08:00
var sgd = new SyncGameData
{
sitPos = GameManager.Instance.gameData.roomJoinNotice.position,
frameType = FrameDataType.createFish,
time = BeijingTimeManager.Instance.CurrentTimestampMs,
frameData = new FrameData_CreateFish
{
fishNumber = fish.fishNumber,
guid = Guid.NewGuid().ToString(),
moveLineId = pathId.ToString()
}
};
WebSocketSendList.SyncGameData(sgd);
}
2026-07-19 15:04:15 +08:00
/// <summary>
/// 进度条加载结束时房主调用,创建一批初始鱼(进度 0.2~0.8),让屏幕开局就有鱼不会显得空。
2026-07-19 17:39:31 +08:00
/// 直接复用 CreateFishManager 原有的出鱼机制:
/// - GetRandomFishByWeight 按权重选鱼(和 TickNormalFishSpawn 一致)
/// - WebSocketSendList.SyncGameData 帧同步广播(和 SendCreateFishRequest 一致)
/// - 通过 time 字段控制创建时间,让所有客户端收到后 progress 自然在 [0.2, 0.8]
/// mc.moveTime = (CurrentTimestampMs - time) / 1000f
/// progress = moveTime / times
/// → time = CurrentTimestampMs - progress * times * 1000
///
/// 只选普通鱼(fishTypeConfig[普通鱼]),Boss/特殊Boss/黄金鱼/特殊鱼由原有 Tick 逻辑
/// 按时间间隔触发,不在这里批量生成,避免开局一堆 Boss。
2026-07-19 15:04:15 +08:00
/// </summary>
/// <param name="count">要创建的鱼数量</param>
public void CreateInitialFishes(int count)
{
if (!fangzhu) return;
2026-07-19 17:39:31 +08:00
if (fishTypeConfig == null) return;
if (!fishTypeConfig.TryGetValue(FishTypeEnum.普通鱼, out var normalList) || normalList.Count == 0) return;
2026-07-19 15:04:15 +08:00
2026-07-19 17:39:31 +08:00
for (int i = 0; i < count; i++)
2026-07-19 15:04:15 +08:00
{
2026-07-19 17:39:31 +08:00
// ⭐ 用原有的 GetRandomFishByWeight 按权重选鱼
var fish = GetRandomFishByWeight(normalList);
if (fish == null) continue;
2026-07-19 15:04:15 +08:00
// 从该鱼的路径列表里随机选一条
if (!FishDatas.TryGetValue(fish.fishNumber, out var ft)) continue;
if (ft.pathList == null || ft.pathList.Count == 0) continue;
int pathId = ft.pathList[Random.Range(0, ft.pathList.Count)];
2026-07-19 17:39:31 +08:00
// ⭐ 通过 time 字段控制创建时间,让 progress ∈ [0.2, 0.8]
2026-07-19 15:04:15 +08:00
float progress = Random.Range(0.2f, 0.8f);
2026-07-19 17:39:31 +08:00
long timeOffsetMs = (long)(progress * fish.times * 1000f);
2026-07-19 15:04:15 +08:00
long sendTime = BeijingTimeManager.Instance.CurrentTimestampMs - timeOffsetMs;
var sgd = new SyncGameData
{
sitPos = GameManager.Instance.gameData.roomJoinNotice.position,
frameType = FrameDataType.createFish,
time = sendTime,
frameData = new FrameData_CreateFish
{
fishNumber = fish.fishNumber,
2026-07-19 17:39:31 +08:00
guid = Guid.NewGuid().ToString(),
2026-07-19 15:04:15 +08:00
moveLineId = pathId.ToString()
}
};
2026-07-19 17:39:31 +08:00
// ⭐ 走原有的帧同步广播链路
2026-07-19 15:04:15 +08:00
WebSocketSendList.SyncGameData(sgd);
}
2026-07-19 17:39:31 +08:00
Debug.Log($"[CreateFishManager] 初始鱼已广播: {count} 条 (房主,走原有出鱼机制)");
2026-07-19 15:04:15 +08:00
}
2026-07-20 16:30:28 +08:00
/// <summary>
/// 房主把当前场上所有活鱼广播一遍,用于新玩家加入房间时同步场上鱼状态。
/// 直接遍历 fishDatas_hashCodekey(全部活鱼),反推 time 字段让新玩家收到后
/// 通过 (CurrentTimestampMs - time)/1000f 算出与房主一致的 moveTime,
/// 鱼会出现在路径的正确中段位置,与老玩家看到的一致。
/// guid 复用原 guid,保证后续杀鱼/锁定帧同步不会失同步。
/// </summary>
public void BroadcastExistingFishes()
{
if (!fangzhu) return;
if (fishDatas_hashCodekey == null || fishDatas_hashCodekey.Count == 0) return;
// ⭐ 复制一份快照,避免遍历过程中鱼死亡修改字典导致 InvalidOperationException
var snapshot = new List<KeyValuePair<string, MonsterContral>>(fishDatas_hashCodekey);
long nowMs = BeijingTimeManager.Instance.CurrentTimestampMs;
int count = 0;
foreach (var kv in snapshot)
{
var mc = kv.Value;
if (mc == null) continue;
if (mc.monsterData == null || mc.monsterData.fishTypeConfig == null) continue;
if (mc.monsterData.moveLine == null) continue;
if (string.IsNullOrEmpty(mc.guid)) continue;
if (string.IsNullOrEmpty(mc.lastMoveLineId)) continue;
2026-07-20 22:32:52 +08:00
// ⭐ 跳过鱼阵鱼:鱼阵鱼 guid 是 原guid+i,单条广播会让新玩家收到后走
// SpawnFishArray 又生成 N 条,导致鱼阵指数爆炸(16×16=256)。
// 鱼阵鱼的同步需要鱼阵级别的广播(发鱼阵配置+原guid+数量),
// 这里不处理,鱼阵鱼不同步只影响视觉阵型,不影响游戏核心逻辑。
if (mc.isyz) continue;
2026-07-20 16:30:28 +08:00
// 总时长,用于过滤快死的鱼(避免新玩家刚进入就看到鱼死)
float totalTime = mc.monsterData.moveLine.moveTime;
if (totalTime <= 0f) continue;
// 跳过还没初始化的鱼(moveTime < 0)和快死的鱼(进度 >= 0.9)
if (mc.moveTime < 0f) continue;
if (mc.moveTime >= totalTime * 0.9f) continue;
// ⭐ 反推 sendTime:让新玩家收到后 (CurrentTimestampMs - sendTime)/1000f ≈ mc.moveTime
long sendTime = nowMs - (long)(mc.moveTime * 1000f);
var sgd = new SyncGameData
{
sitPos = GameManager.Instance.gameData.roomJoinNotice.position,
frameType = FrameDataType.createFish,
time = sendTime,
frameData = new FrameData_CreateFish
{
fishNumber = mc.monsterData.fishTypeConfig.fishNumber,
guid = mc.guid, // ⭐ 复用原 guid
moveLineId = mc.lastMoveLineId
}
};
WebSocketSendList.SyncGameData(sgd);
count++;
}
Debug.Log($"[CreateFishManager] 广播场上活鱼: {count} 条 (房主,新玩家加入同步)");
}
2026-07-20 22:32:52 +08:00
/// <summary>
/// 房主收到新玩家"请求同步鱼"事件后,广播场上活鱼。
/// 新玩家资源加载完成才会发请求,保证收到广播时 spawnDic 已初始化。
/// 非房主收到直接忽略。
/// </summary>
private void OnRequestSyncFish(SyncGameData syncGameData)
{
// 仅房主响应,且不响应自己发的请求(syncGameData.sitPos 是发送者座位)
if (!fangzhu) return;
BroadcastExistingFishes();
}
2026-05-18 18:39:11 +08:00
#endregion
2026-05-19 17:59:23 +08:00
#region 创建鱼
2026-04-23 17:24:31 +08:00
/// <summary>
2026-05-01 06:26:50 +08:00
/// AI优化过的
2026-04-23 17:24:31 +08:00
/// </summary>
2026-05-01 06:26:50 +08:00
/// <param name="syncGameData"></param>
2026-04-29 01:18:39 +08:00
public void CreateFish(SyncGameData syncGameData)
2026-04-23 17:24:31 +08:00
{
2026-05-28 18:16:45 +08:00
if (isFrozen) return; // 冰冻中不创建鱼
2026-07-16 20:16:05 +08:00
// ⭐ 场景卸载/CloseSpawnSystem 后 spawnDic 会变 null,此时收到的广播直接忽略
if (spawnDic == null) return;
2026-05-01 06:26:50 +08:00
var rotateData = FrameDataParser.Parse<FrameData_CreateFish>(syncGameData.frameData);
2026-07-20 22:32:52 +08:00
if (rotateData == null) return;
// ⭐ guid 去重:房主广播活鱼给新玩家时,服务器会广播给所有客户端(含房主自己)。
// 老玩家已存在该 guid 的鱼 → 跳过,避免重复 Spawn 同一条鱼。
// 仅本地无该 guid 的客户端(新加入玩家)才会真正 Spawn。
if (!string.IsNullOrEmpty(rotateData.guid) &&
fishDatas_hashCodekey != null &&
fishDatas_hashCodekey.ContainsKey(rotateData.guid))
return;
SpawnFishMainThread(rotateData, syncGameData.time);
2026-04-29 01:18:39 +08:00
}
/// <summary>
/// 主线程实例化鱼对象(单体鱼 / 鱼阵分发)。
/// </summary>
private void SpawnFishMainThread(FrameData_CreateFish createFishData, long time)
{
2026-07-16 20:16:05 +08:00
// ⭐ spawnDic 可能在场景卸载时被置 null,这里再保护一次
if (spawnDic == null) return;
2026-04-29 01:18:39 +08:00
if (!spawnDic.TryGetValue(createFishData.fishNumber, out var cfg))
2026-04-23 17:24:31 +08:00
{
2026-04-29 01:18:39 +08:00
Debug.LogWarning($"[CreateFishManager] 找不到 spawnDic 配置: {createFishData.fishNumber}");
return;
}
if (cfg.type == 1)
{
// 单体鱼
OnlyFish(createFishData, time);
}
else if (cfg.type == 2)
{
// 鱼阵
2026-05-22 19:30:02 +08:00
SpawnFishArray(createFishData, cfg.fishNumber, time);
2026-04-29 01:18:39 +08:00
}
}
// ================================================================
// 单体鱼生成
// ================================================================
2026-05-19 17:59:23 +08:00
2026-04-29 01:18:39 +08:00
/// <summary>
/// 生成一条普通/Boss/黄金鱼并初始化路径。
/// </summary>
/// <returns>成功返回 true,配置缺失返回 false。</returns>
private bool OnlyFish(FrameData_CreateFish createFishData, long time)
{
2026-05-22 19:30:02 +08:00
if (!fishDatas.TryGetValue(createFishData.fishNumber, out var fishType))
2026-04-29 01:18:39 +08:00
{
Debug.LogError($"[CreateFishManager] 找不到 fishTypeConfig: {createFishData.fishNumber}");
return false;
}
2026-04-29 23:53:55 +08:00
//var prefab = FishResourcesData.GetFishPrefab(int.Parse(fishType.fishNumber));
//var fish = Instantiate(prefab, new Vector3(9999, 9999, 0), prefab.transform.rotation, fishRoot);
2026-05-22 19:30:02 +08:00
2026-05-01 06:26:50 +08:00
try
{
var fish = Pool.Instance.Get(int.Parse(createFishData.fishNumber), new Vector3(9999, 9999, 0), Quaternion.identity, fishRoot);
2026-07-13 04:48:18 +08:00
if (fish == null)
{
Debug.LogError($"[CreateFishManager] Pool.Get 返回 null,跳过此鱼: {createFishData.fishNumber}");
return false;
}
2026-05-01 06:26:50 +08:00
var mc = fish.GetComponent<MonsterContral>() ?? fish.AddComponent<MonsterContral>();
mc.monsterData.fishTypeConfig = FishDatas[createFishData.fishNumber];
mc.guid = createFishData.guid;
mc.moveTime = (BeijingTimeManager.Instance.CurrentTimestampMs - time) / 1000f;
2026-05-01 21:59:16 +08:00
mc.fishenum = (FishTypeEnum)fishType.fishType;
2026-05-01 06:26:50 +08:00
// 按鱼类型放入对应字典
RegisterFishInstance(mc, fishType.fishType);
2026-04-29 01:18:39 +08:00
2026-05-01 06:26:50 +08:00
mc.Init(createFishData.moveLineId, spawnDic[createFishData.fishNumber].times);
2026-04-29 01:18:39 +08:00
2026-05-01 06:26:50 +08:00
Debug.Log($"[CreateFishManager] 生成鱼:{fishType.fishName}");
}
catch (Exception ex)
{
Debug.LogError(createFishData.fishNumber);
Debug.LogError(ex.Message);
}
2026-04-29 01:18:39 +08:00
return true;
}
2026-05-22 19:30:02 +08:00
/// <summary>将鱼实例注册到全局字典及对应分类字典。</summary>
private void RegisterFishInstance(MonsterContral mc, int fishTypeId)
{
fishDatas_hashCodekey[mc.guid] = mc;
switch (fishTypeId)
{
case 1: // 普通鱼
fishDatas_hashCodekey_Small[mc.guid] = mc;
break;
case 2: // Boss
fishDatas_hashCodekey_Boss[mc.guid] = mc;
break;
case 3: // 特殊鱼 / 黄金鱼
fishDatas_hashCodekey_HJ[mc.guid] = mc;
break;
case 4:
fishDatas_hashCodekey_TS[mc.guid] = mc;
break;
case 5:
fishDatas_hashCodekey_TS_Boss[mc.guid] = mc;
break;
}
}
#region 鱼阵
2026-04-29 01:18:39 +08:00
// ================================================================
// 鱼阵生成(按 arrayNumber 前缀分发到对应阵型协程/方法)
// ================================================================
/// <summary>根据鱼阵配置分发到对应的生成逻辑。</summary>
2026-05-22 19:30:02 +08:00
private void SpawnFishArray(FrameData_CreateFish createFishData, string fishNumber, long time)
2026-04-29 01:18:39 +08:00
{
2026-05-22 19:30:02 +08:00
if (!fishArrayConfigDic.TryGetValue(fishNumber, out var yz))
2026-04-29 01:18:39 +08:00
{
2026-05-22 19:30:02 +08:00
Debug.LogError($"[CreateFishManager] 找不到 fishArrayConfig: {fishNumber}");
2026-04-29 01:18:39 +08:00
return;
}
2026-05-22 19:30:02 +08:00
if (!fishDatas.TryGetValue(yz.fishNumber, out var fishType))
2026-04-29 01:18:39 +08:00
{
Debug.LogError($"[CreateFishManager] 鱼阵 fishType 缺失: {yz.fishNumber}");
return;
}
if (!FishYZ.dicfishYZ.TryGetValue(yz.arrayNumber, out var pathList) || pathList.Count == 0)
{
Debug.LogWarning($"[CreateFishManager] 鱼阵路线为空: {yz.arrayNumber}");
return;
}
2026-05-22 19:30:02 +08:00
var prefab = FishResourcesData.GetFishPrefab(int.Parse(fishType.fishNumber));
var pos = new Vector3(9999, 9999, 0);
2026-04-29 01:18:39 +08:00
string prefix = yz.arrayNumber.Length >= 3
? yz.arrayNumber.Substring(0, 3)
: yz.arrayNumber;
switch (prefix)
{
case "700": // 扇形阵:逐条延迟生成
StartCoroutine(IeShanXing(createFishData, yz, pos, prefab, pathList, time, yz.fishCount));
break;
case "701": // 环形阵:同帧同路径全部生成
foreach (int pathId in pathList)
Quan(createFishData, yz, pos, prefab, pathId, yz.fishCount, time);
break;
case "702":
case "7007":
case "710": // 顺序阵:每条路径逐一延迟生成
2026-05-18 18:39:11 +08:00
case "704":
2026-05-07 03:46:03 +08:00
//int chosen710 = (prefix == "710")
// ? pathList[Random.Range(0, pathList.Count)]
// : -1;
2026-05-22 19:30:02 +08:00
StartCoroutine(YZ7007(createFishData, yz, pos, prefab, time));
2026-05-01 21:59:16 +08:00
//foreach (int pathId in pathList)
//{
// int actualPath = (prefix == "710") ? chosen710 : pathId;
// StartCoroutine(YZ7007(createFishData, yz, pos, prefab, actualPath, time, yz.fishCount));
//}
2026-04-29 01:18:39 +08:00
break;
default:
Debug.LogWarning($"[CreateFishManager] 未知鱼阵前缀: {prefix}");
break;
}
}
// ================================================================
// 鱼阵生成协程 / 方法
// ================================================================
/// <summary>扇形阵:按路线列表逐条延迟 0.8s 生成一扇。</summary>
private IEnumerator IeShanXing(FrameData_CreateFish createFishData, FishArrayConfig yz,
Vector3 pos, GameObject prefab, List<int> pathList, long time, int count)
{
foreach (int pathId in pathList)
{
2026-05-01 06:26:50 +08:00
if (!isResources)
{
yield break;
}
2026-04-29 01:18:39 +08:00
ShanXing(createFishData, yz, pos, prefab, pathId, count, time);
yield return new WaitForSeconds(0.8f);
}
}
/// <summary>
/// 扇形阵单扇:生成 16 条鱼,每条旋转角度递增(±5°交替),模拟扇形展开。
/// </summary>
private void ShanXing(FrameData_CreateFish createFishData, FishArrayConfig yz,
Vector3 pos, GameObject prefab, int pathId, int count, long time)
{
var cfg = spawnDic[createFishData.fishNumber];
for (int i = 1; i <= 16; i++)
{
// 交替正负角度,形成扇形分布
2026-05-01 06:26:50 +08:00
float angle = i * 5f * (i % 2 == 0 ? -1f : 1f);
2026-04-29 01:18:39 +08:00
var rotation = Quaternion.Euler(
prefab.transform.eulerAngles + Vector3.forward * angle);
2026-04-29 23:53:55 +08:00
//var fish = Instantiate(prefab, pos, rotation, fishRoot);
var fish = Pool.Instance.Get(int.Parse(yz.fishNumber), pos, rotation, fishRoot);
2026-05-01 21:59:16 +08:00
2026-05-01 06:26:50 +08:00
var mc = fish.GetComponent<MonsterContral>();
2026-04-29 01:18:39 +08:00
mc.monsterData.fishTypeConfig = FishDatas[yz.fishNumber];
2026-05-01 06:26:50 +08:00
mc.guid = createFishData.guid + i;
2026-05-22 19:30:02 +08:00
RegisterFishInstance(mc, FishDatas[yz.fishNumber].fishType);
2026-04-29 01:18:39 +08:00
mc.moveTime = (BeijingTimeManager.Instance.CurrentTimestampMs - time) / 1000f;
mc.Init(pathId.ToString(), cfg.times, true, yz.arrayNumber);
}
}
/// <summary>
/// 环形阵:在同一路径上生成 count 条鱼,角度均匀分布(360/count)。
/// </summary>
private void Quan(FrameData_CreateFish createFishData, FishArrayConfig yz,
Vector3 pos, GameObject prefab, int pathId, int count, long time)
{
if (count <= 0) return;
var cfg = spawnDic[createFishData.fishNumber];
for (int i = 0; i < count; i++)
{
2026-05-01 06:26:50 +08:00
float angle = (360f / count) * i;
2026-05-18 18:39:11 +08:00
var rotation = Quaternion.Euler(prefab.transform.eulerAngles + Vector3.forward * angle);
2026-04-29 01:18:39 +08:00
2026-04-29 23:53:55 +08:00
//var fish = Instantiate(prefab, pos, rotation, fishRoot);
var fish = Pool.Instance.Get(int.Parse(yz.fishNumber), pos, rotation, fishRoot);
2026-05-01 06:26:50 +08:00
var mc = fish.GetComponent<MonsterContral>();
2026-04-29 01:18:39 +08:00
mc.monsterData.fishTypeConfig = FishDatas[yz.fishNumber];
2026-05-01 06:26:50 +08:00
mc.guid = createFishData.guid + i;
2026-05-22 19:30:02 +08:00
RegisterFishInstance(mc, FishDatas[yz.fishNumber].fishType);
2026-04-29 01:18:39 +08:00
2026-05-22 19:30:02 +08:00
mc.moveTime = (BeijingTimeManager.Instance.CurrentTimestampMs - time) / 1000f;
2026-04-29 01:18:39 +08:00
mc.Init(pathId.ToString(), cfg.times, true, yz.arrayNumber);
}
}
/// <summary>
/// 顺序阵:每隔 1s 生成一条鱼,共生成 count 条。
/// </summary>
private IEnumerator YZ7007(FrameData_CreateFish createFishData, FishArrayConfig yz,
2026-05-22 19:30:02 +08:00
Vector3 pos, GameObject prefab, long time)
2026-04-29 01:18:39 +08:00
{
var cfg = spawnDic[createFishData.fishNumber];
2026-05-22 19:30:02 +08:00
for (int i = 0; i < yz.fishCount; i++)
2026-04-29 01:18:39 +08:00
{
2026-05-01 06:26:50 +08:00
if (!isResources)
{
yield break;
}
2026-04-29 23:53:55 +08:00
//var fish = Instantiate(prefab, pos, prefab.transform.rotation, fishRoot);
var fish = Pool.Instance.Get(int.Parse(yz.fishNumber), pos, Quaternion.identity, fishRoot);
2026-05-01 06:26:50 +08:00
var mc = fish.GetComponent<MonsterContral>() ?? fish.AddComponent<MonsterContral>();
2026-05-22 19:30:02 +08:00
2026-04-29 01:18:39 +08:00
mc.monsterData.fishTypeConfig = FishDatas[yz.fishNumber];
2026-05-01 06:26:50 +08:00
mc.guid = createFishData.guid + i;
2026-05-22 19:30:02 +08:00
RegisterFishInstance(mc, FishDatas[yz.fishNumber].fishType);
2026-04-29 01:18:39 +08:00
// 每条鱼额外减去已等待秒数,使路径起点与帧数同步
mc.moveTime = ((BeijingTimeManager.Instance.CurrentTimestampMs - time) / 1000f) - i;
2026-05-22 19:30:02 +08:00
mc.Init(createFishData.moveLineId, cfg.times, true, yz.arrayNumber);
2026-04-29 01:18:39 +08:00
yield return new WaitForSeconds(1f);
}
}
2026-05-22 19:30:02 +08:00
#endregion
2026-04-29 01:18:39 +08:00
2026-05-19 17:59:23 +08:00
#endregion
#region 结束游戏退出房间
/// <summary>退出房间:关闭生成系统。</summary>
public void ExitRoom()
{
CloseSpawnSystem();
}
/// <summary>
/// 关闭生成系统:清理列表,销毁场景内所有鱼对象。
/// </summary>
public async void CloseSpawnSystem()
{
//await Task.Delay(2000);
spawnList = null;
spawnDic = null;
yz_FishConfig_Line_Dic = null;
yz_FishConfig_Line = null;
yz_FishConfig_Line_Dic1 = null;
if (fishRoot == null) return;
foreach (var item in fishDatas_hashCodekey)
{
if (item.Value.gameObject != null)
{
Destroy(item.Value.gameObject);
}
}
fishDatas_hashCodekey.Clear();
fishDatas_hashCodekey_Boss.Clear();
fishDatas_hashCodekey_Small.Clear();
foreach (Transform item in fishRoot)
{
Destroy(item.gameObject);
}
2026-07-21 05:13:43 +08:00
// ⭐ 销毁爆奖特效父节点:所有挂在 bjEffectRoot 下的爆奖特效
// (Boss 分数版/加分弹出/BJEndUI/特殊爆奖等) 会一并被销毁,
// 防止退出房间时残留未结束的爆奖协程/动画造成的内存泄漏
if (bjEffectRoot != null)
{
Destroy(bjEffectRoot.gameObject);
bjEffectRoot = null;
}
2026-05-19 17:59:23 +08:00
//for (int i = fishDatas_hashCodekey.childCount - 1; i >= 0; i--)
// Destroy(fishRoot.GetChild(i).gameObject);
}
/// <summary>释放 NativeArray(避免 Unity 内存泄漏警告)。</summary>
private void DisposeNativeArrays()
{
if (timers.IsCreated) timers.Dispose();
if (frequencies.IsCreated) frequencies.Dispose();
if (shouldSpawn.IsCreated) shouldSpawn.Dispose();
}
#endregion
#region Tool
2026-04-29 01:18:39 +08:00
// ================================================================
// 随机工具方法
// ================================================================
/// <summary>
/// 从 [min, max) 区间随机取 count 个不重复整数。
/// 若 min==max 返回空列表。
/// </summary>
public static List<int> RandomUniqueNumbers(int count, int min, int max)
{
if (min >= max)
{
Debug.LogWarning("[CreateFishManager] RandomUniqueNumbers: min >= max,返回空列表");
return new List<int>();
}
count = Mathf.Min(count, max - min); // 不能超过范围
var result = new HashSet<int>();
2026-05-01 06:26:50 +08:00
var rand = new System.Random();
2026-04-29 01:18:39 +08:00
while (result.Count < count)
result.Add(rand.Next(min, max));
return result.ToList();
}
/// <summary>
/// 从 KeyCollection 中随机取 count 个不重复的 string key。
/// </summary>
public static List<string> RandomUniqueNumbers(int count, int min, int max,
Dictionary<string, List<RoomFishDetailConfig>>.KeyCollection keyCollection)
{
if (min >= max) return new List<string>();
count = Mathf.Min(count, max - min);
var keyList = keyCollection.ToList();
var indices = new HashSet<int>();
2026-05-01 06:26:50 +08:00
var rand = new System.Random();
var result = new List<string>(count);
2026-04-29 01:18:39 +08:00
while (indices.Count < count)
{
int idx = rand.Next(min, max);
if (indices.Add(idx))
result.Add(keyList[idx]);
}
return result;
}
2026-05-19 17:59:23 +08:00
#endregion
2026-04-29 01:18:39 +08:00
2026-05-19 17:59:23 +08:00
#region 杀鱼
2026-04-29 01:18:39 +08:00
// ================================================================
// 杀鱼接口
// ================================================================
2026-05-19 17:59:23 +08:00
/// <summary>
/// 批量杀死多条鱼(带特效 + 不足数量用纯金币补齐)。
/// </summary>
/// <param name="guids">实际场上能找到的鱼 guid 列表(可能少于 count)</param>
/// <param name="coinNum">红包拆分后的每份金额,长度应 >= count</param>
/// <param name="v3Point">金币最终飞回的世界坐标(一般是炮台位置)</param>
/// <param name="count">理论上要“死”的总数量(鱼 + 纯金币)</param>
2026-07-20 01:44:28 +08:00
public void Id_DieListFish_FX(List<string> guids, List<RedPacketRecord> coinNum, Vector3 v3Point, int count, bool isLocal = false)
2026-05-19 17:59:23 +08:00
{
if (coinNum == null || coinNum.Count == 0) return;
2026-07-20 01:44:28 +08:00
// 1. 先处理"真的有鱼"的部分
2026-05-19 17:59:23 +08:00
int realCount = Mathf.Min(guids.Count, coinNum.Count, count);
for (int i = 0; i < realCount; i++)
{
2026-07-20 01:44:28 +08:00
Id_DieFish_FX(guids[i], coinNum[i], v3Point, isLocal);
2026-05-19 17:59:23 +08:00
}
2026-07-20 01:44:28 +08:00
// 2. 不足的部分,用"屏幕随机位置的金币特效"补齐
2026-05-19 17:59:23 +08:00
int needExtra = count - realCount;
if (needExtra <= 0) return;
// 防御:最多用 coinNum 里剩下的
int maxExtra = Mathf.Min(needExtra, coinNum.Count - realCount);
for (int i = 0; i < maxExtra; i++)
{
var red = coinNum[realCount + i];
2026-07-20 01:44:28 +08:00
SpawnRandomCoinFX(red, v3Point, isLocal);
2026-05-19 17:59:23 +08:00
}
}
2026-04-29 01:18:39 +08:00
/// <summary>
/// 随机选取 count 条小鱼/黄金鱼,通知服务器同步死亡,并返回 guid 列表。
/// </summary>
2026-07-23 01:51:07 +08:00
/// <param name="killerSitPos">击杀者座位 ID。Boss 爆奖连锁杀鱼时传 Boss 击杀者座位
/// (含人机);-1 表示用本地 currSitData.posID(普通玩家直接击杀场景).</param>
2026-04-29 01:18:39 +08:00
public List<string> SyncGameData_SendRendromDieFishID(
2026-07-23 01:51:07 +08:00
int count, List<RedPacketRecord> coinNum, Vector3 v3Point, int killerSitPos = -1)
2026-04-29 01:18:39 +08:00
{
2026-05-01 06:26:50 +08:00
var guids = new List<string>(count);
2026-04-29 01:18:39 +08:00
var tempKeys = fishDatas_hashCodekey_Small.Keys.ToList();
// 若存活鱼不足 count,全部选取
if (tempKeys.Count <= count)
{
guids.AddRange(tempKeys);
2026-04-23 17:24:31 +08:00
}
else
{
for (int i = 0; i < count; i++)
{
2026-04-29 01:18:39 +08:00
int idx = Random.Range(0, tempKeys.Count);
guids.Add(tempKeys[idx]);
tempKeys.RemoveAt(idx);
2026-04-23 17:24:31 +08:00
}
}
2026-04-29 01:18:39 +08:00
2026-07-23 01:51:07 +08:00
// ⭐ sitPos 用击杀者座位(含人机);房主代人机击杀 Boss 时,这里传人机座位,
// 否则所有客户端都会判定为房主击杀 → 房主自己看金色(错)
int posID = killerSitPos >= 0 ? killerSitPos : currGameType.currSitData.posID;
2026-04-29 01:18:39 +08:00
var sgd = new SyncGameData
2026-04-23 17:24:31 +08:00
{
2026-05-01 06:26:50 +08:00
sitPos = posID,
2026-04-29 01:18:39 +08:00
frameType = FrameDataType.randomDieFish,
2026-05-01 06:26:50 +08:00
time = BeijingTimeManager.Instance.CurrentTimestampMs,
2026-04-29 01:18:39 +08:00
frameData = new FrameData_RandomDieFish
2026-04-23 17:24:31 +08:00
{
2026-05-01 06:26:50 +08:00
x = v3Point.x,
y = v3Point.y,
z = v3Point.z,
guids = guids,
2026-04-29 01:18:39 +08:00
coinNum = coinNum,
2026-05-01 06:26:50 +08:00
count = count
2026-04-29 01:18:39 +08:00
}
};
2026-04-23 17:24:31 +08:00
2026-04-29 01:18:39 +08:00
WebSocketSendList.SyncGameData(sgd);
return guids;
2026-04-23 17:24:31 +08:00
}
2026-04-29 01:18:39 +08:00
/// <summary>
2026-05-01 06:26:50 +08:00
/// 鱼离场
2026-04-29 01:18:39 +08:00
/// </summary>
2026-04-22 16:22:28 +08:00
public void Id_DieFish_NotFX(string fishId)
2026-03-31 17:00:05 +08:00
{
2026-04-29 01:18:39 +08:00
if (!fishDatas_hashCodekey.TryGetValue(fishId, out var monster)) return;
2026-05-01 06:26:50 +08:00
Pool.Instance.Return(monster.gameObject);
2026-05-01 21:59:16 +08:00
switch (monster.fishenum)
{
case FishTypeEnum.普通鱼:
fishDatas_hashCodekey_Small.Remove(fishId);
2026-04-29 01:18:39 +08:00
2026-05-01 21:59:16 +08:00
break;
case FishTypeEnum.Boss鱼:
fishDatas_hashCodekey_Boss.Remove(fishId);
break;
case FishTypeEnum.黄金鱼:
2026-05-22 19:30:02 +08:00
fishDatas_hashCodekey_HJ.Remove(fishId);
2026-05-01 21:59:16 +08:00
break;
case FishTypeEnum.特殊鱼:
2026-05-22 19:30:02 +08:00
fishDatas_hashCodekey_TS.Remove(fishId);
2026-05-01 21:59:16 +08:00
2026-05-22 19:30:02 +08:00
break;
case FishTypeEnum.特殊Boss:
fishDatas_hashCodekey_TS_Boss.Remove(fishId);
2026-05-01 21:59:16 +08:00
break;
default:
break;
}
2026-04-29 01:18:39 +08:00
fishDatas_hashCodekey.Remove(fishId);
2026-04-03 18:32:19 +08:00
}
2026-04-29 01:18:39 +08:00
/// <summary>
/// 按 ID 杀死一条鱼(带特效 + 金币飞出动画)。
/// </summary>
2026-07-20 01:44:28 +08:00
public void Id_DieFish_FX(string fishId, RedPacketRecord coinNum, Vector3 v3Point, bool isLocal = false)
2026-04-22 18:15:22 +08:00
{
2026-04-29 01:18:39 +08:00
if (!fishDatas_hashCodekey.TryGetValue(fishId, out var monster)) return;
2026-06-14 02:18:37 +08:00
if (false)
{
AudioManager.Instance.PlayerFX(60004);
2026-04-29 01:18:39 +08:00
2026-06-14 02:18:37 +08:00
}
2026-04-29 01:18:39 +08:00
// 死亡粒子特效
Instantiate(
monster.effect_Die != null ? monster.effect_Die : dieFXPrefab,
monster.transform.position, Quaternion.identity, transform.parent);
// 非 Boss 鱼:金币飞向炮台
2026-06-21 00:07:26 +08:00
if (monster.fishenum != FishTypeEnum.Boss鱼 && monster.fishenum != FishTypeEnum.特殊Boss)
2026-04-22 18:15:22 +08:00
{
2026-04-29 01:18:39 +08:00
var award = Instantiate(
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward,
2026-07-21 05:13:43 +08:00
bjEffectRoot);
2026-04-23 17:24:31 +08:00
2026-04-29 01:18:39 +08:00
award.GetComponent<RectTransform>().anchoredPosition =
CoordinateTransformation.WorldPos_ToCanvas(
monster.transform.position,
UIManager.Instance.uiRoot.GetComponent<Canvas>());
2026-04-22 18:15:22 +08:00
2026-05-22 19:30:02 +08:00
var coinScript = award.GetComponent<BJBase>();
coinScript.Init(coinNum.Amount, v3Point);
2026-05-01 06:26:50 +08:00
coinScript.isBoss = true;
2026-07-20 01:44:28 +08:00
coinScript.isLocalCoin = isLocal;
2026-05-22 19:30:02 +08:00
coinScript.SetCoinCount();
2026-04-22 18:15:22 +08:00
}
2026-04-29 01:18:39 +08:00
2026-05-01 06:26:50 +08:00
//MonsterDieList.Instance.SmallHuangYiHuang(
// monster.transform.position,
// monster.monsterData.fishTypeConfig.fishNumber, 1000);
2026-04-29 01:18:39 +08:00
monster.DieFish();
fishDatas_hashCodekey.Remove(fishId);
2026-04-22 18:15:22 +08:00
}
2026-05-04 17:21:52 +08:00
/// <summary>
/// 不依赖鱼,只在屏幕内随机一个位置生成金币特效,然后飞回指定点。
/// </summary>
2026-07-20 01:44:28 +08:00
private void SpawnRandomCoinFX(RedPacketRecord coinNum, Vector3 targetWorldPos, bool isLocal = false)
2026-05-04 17:21:52 +08:00
{
// 1. 随机一个屏幕坐标
Vector2 screenPos = new Vector2(
UnityEngine.Random.Range(0f, Screen.width),
UnityEngine.Random.Range(0f, Screen.height));
// 2. 屏幕坐标 → Canvas 坐标(跟你原来金币飞行逻辑保持一致)
Vector2 canvasPos = CoordinateTransformation.ScreenPoint_ToCanvas(
screenPos,
UIManager.Instance.uiRoot.GetComponent<Canvas>());
// 3. 实例化金币特效(沿用你原来的 award 逻辑)
var award = Instantiate(
2026-07-20 01:44:28 +08:00
dieFXPrefabAward, // 或者用某个专门的"掉金币" prefab
2026-07-21 05:13:43 +08:00
bjEffectRoot);
2026-05-04 17:21:52 +08:00
var rect = award.GetComponent<RectTransform>();
rect.anchoredPosition = canvasPos;
// 4. 设置 CoinScript 的终点为 targetWorldPos
var coinScript = award.GetComponent<CoinScript>();
2026-05-22 19:30:02 +08:00
coinScript.Init(coinNum.Amount, targetWorldPos);
2026-05-19 17:59:23 +08:00
coinScript.isBoss = true;
2026-07-20 01:44:28 +08:00
coinScript.isLocalCoin = isLocal;
2026-05-22 19:30:02 +08:00
coinScript.SetCoinCount();
2026-05-04 17:21:52 +08:00
}
2026-04-22 23:08:46 +08:00
2026-05-22 19:30:02 +08:00
2026-05-04 17:21:52 +08:00
2026-04-29 01:18:39 +08:00
/// <summary>
/// 收到服务器 WebSocket 杀鱼通知,处理金币加分、特效、音效。
/// </summary>
2026-03-31 17:00:05 +08:00
public void Ws_DieFish(string msg)
2026-02-03 11:35:19 +08:00
{
2026-04-29 01:18:39 +08:00
var killFish = JsonConvert.DeserializeObject<WebSocketResponseBase<RoomKillNotice>>(msg)
.arguments[0];
2026-04-25 18:53:04 +08:00
2026-04-29 01:18:39 +08:00
Debug.Log($"[CreateFishManager] 杀鱼延迟:{DateTimeOffset.Now.ToUnixTimeSeconds() - killFish.timespan}s");
2026-05-22 19:30:02 +08:00
SitPosData sitData = null; //击杀玩家座位数据
2026-05-01 06:26:50 +08:00
foreach (var item in currGameType.list_Sits)
{
2026-05-01 21:59:16 +08:00
if (item.user == killFish.userId)
2026-05-01 06:26:50 +08:00
{
2026-05-28 18:16:45 +08:00
// 只有死的鱼正好是当前锁定目标时才清除
if (item.monCotl_Lock != null && item.monCotl_Lock.guid == killFish.fishId)
item.monCotl_Lock = null;
2026-05-01 06:26:50 +08:00
//item.lockFX.gameObject.SetActive(false);
2026-05-22 19:30:02 +08:00
sitData = item;
2026-05-01 06:26:50 +08:00
}
}
2026-05-01 21:59:16 +08:00
2026-05-22 19:30:02 +08:00
2026-04-29 01:18:39 +08:00
if (!fishDatas_hashCodekey.TryGetValue(killFish.fishId, out var monster)) return;
2026-05-18 18:39:11 +08:00
2026-05-19 17:59:23 +08:00
2026-04-29 01:18:39 +08:00
// 死亡粒子特效
Instantiate(
monster.effect_Die != null ? monster.effect_Die : dieFXPrefab,
monster.transform.position, Quaternion.identity, transform.parent);
2026-06-15 18:37:06 +08:00
int goldToAdd = killFish.gold; // 立即复制需要的数据
string fishId = killFish.fishId; // 如果需要的话
Action<int> onScoreAdd = (audioID) =>
2026-04-29 01:18:39 +08:00
{
2026-06-15 18:37:06 +08:00
sitData.goldNum += goldToAdd; // 使用副本,不再依赖 killFish 对象
2026-05-22 19:30:02 +08:00
sitData.tMP_GoldNum.text = sitData.goldNum.ToString();
AudioManager.Instance.PlayerFX(audioID);
};
2026-06-15 18:37:06 +08:00
//// 加分回调(在金币/加分面板飞完后执行)
//Action<int> onScoreAdd = (audioID) =>
//{
// var sitList = GetCurrentPanelSitList();
// sitData.goldNum += killFish.gold;
// sitData.tMP_GoldNum.text = sitData.goldNum.ToString();
// AudioManager.Instance.PlayerFX(audioID);
//};
2026-07-20 22:32:52 +08:00
bool dieFishCalled = false; // Boss case 内部已调用 DieFish,底部跳过
2026-06-15 18:37:06 +08:00
2026-05-22 19:30:02 +08:00
switch (monster.fishenum)
2026-04-29 01:18:39 +08:00
{
2026-05-22 19:30:02 +08:00
case FishTypeEnum.普通鱼:
2026-05-01 06:26:50 +08:00
{
2026-05-22 19:30:02 +08:00
// 普通鱼:金币飞向炮台
var award = Instantiate(
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward,
2026-07-21 05:13:43 +08:00
bjEffectRoot);
2026-05-22 19:30:02 +08:00
try
{
award.GetComponent<RectTransform>().anchoredPosition =
CoordinateTransformation.WorldPos_ToCanvas(
monster.transform.position,
UIManager.Instance.uiRoot.GetComponent<Canvas>());
}
catch
{
award.transform.position = monster.transform.position;
}
var coinScript = award.GetComponent<BJBase>();
coinScript.Init(killFish.gold, sitData.transform.position, killFish.userId, sitData.posID, killFish.param);
2026-07-20 01:44:28 +08:00
coinScript.isLocalCoin = sitData.user == GameManager.Instance.user.id;
2026-05-22 19:30:02 +08:00
coinScript.isBoss = false;
coinScript.JiaFen = onScoreAdd;
2026-06-16 23:45:31 +08:00
coinScript.SetCoinCount();
2026-05-22 19:30:02 +08:00
break;
2026-05-01 06:26:50 +08:00
}
2026-05-01 21:59:16 +08:00
2026-05-22 19:30:02 +08:00
case FishTypeEnum.Boss鱼:
{
// Boss 鱼:显示浮动分数文本
var bossCtrl = (MonsterControl_BossBase)monster;
bossCtrl.v3 = sitData.transform.position;
2026-07-20 22:32:52 +08:00
bossCtrl.JiaFen = onScoreAdd; // 先设置 JiaFen,DieFish 的 lambda 才能捕获到
2026-07-23 01:51:07 +08:00
bossCtrl.isLocalCoin = sitData.user == GameManager.Instance.user.id; // ⭐ 金/银区分
bossCtrl.killerSitPos = sitData.posID; // ⭐ 爆奖杀小鱼用击杀者座位(含人机)
2026-07-20 22:32:52 +08:00
monster.DieFish(killFish); // Boss 重写 action_effect_DieAward,捕获上面的 JiaFen
dieFishCalled = true;
2026-06-02 14:57:23 +08:00
if (bossCtrl.isToUI)
{
bossCtrl.currEffect_DieAward = Instantiate(
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp,
2026-07-21 05:13:43 +08:00
bjEffectRoot);
2026-06-02 14:57:23 +08:00
}
else
{
bossCtrl.currEffect_DieAward = Instantiate(
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp);
}
2026-05-22 19:30:02 +08:00
bossCtrl.action_effect_DieAward?.Invoke(bossCtrl.currEffect_DieAward);
}
break;
case FishTypeEnum.黄金鱼:
{
// 普通鱼:金币飞向炮台
var award = Instantiate(
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward,
2026-07-21 05:13:43 +08:00
bjEffectRoot);
2026-05-22 19:30:02 +08:00
try
{
award.GetComponent<RectTransform>().anchoredPosition =
CoordinateTransformation.WorldPos_ToCanvas(
monster.transform.position,
UIManager.Instance.uiRoot.GetComponent<Canvas>());
}
catch
{
award.transform.position = monster.transform.position;
}
var coinScript = award.GetComponent<BJBase>();
coinScript.Init(killFish.gold, sitData.transform.position, killFish.userId, sitData.posID, killFish.param);
2026-07-20 01:44:28 +08:00
coinScript.isLocalCoin = sitData.user == GameManager.Instance.user.id;
2026-05-22 19:30:02 +08:00
coinScript.isBoss = false;
coinScript.JiaFen = onScoreAdd;
2026-06-16 23:45:31 +08:00
coinScript.SetCoinCount();
2026-05-22 19:30:02 +08:00
}
break;
case FishTypeEnum.特殊鱼:
{
// 普通鱼:金币飞向炮台
var award = Instantiate(
2026-06-17 21:26:06 +08:00
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward);
2026-05-22 19:30:02 +08:00
try
{
award.GetComponent<RectTransform>().anchoredPosition =
CoordinateTransformation.WorldPos_ToCanvas(
monster.transform.position,
UIManager.Instance.uiRoot.GetComponent<Canvas>());
2026-07-21 05:13:43 +08:00
award.transform.SetParent(bjEffectRoot);
2026-05-22 19:30:02 +08:00
}
catch
{
award.transform.position = monster.transform.position;
}
var coinScript = award.GetComponent<BJBase>();
2026-06-17 21:26:06 +08:00
coinScript.startPoint = award.transform.position;
2026-05-22 19:30:02 +08:00
coinScript.Init(killFish.gold, sitData.transform.position, killFish.userId, sitData.posID, killFish.param);
2026-07-20 01:44:28 +08:00
coinScript.isLocalCoin = sitData.user == GameManager.Instance.user.id;
2026-06-17 21:26:06 +08:00
coinScript.startPoint = award.transform.position;
2026-05-22 19:30:02 +08:00
coinScript.isBoss = false;
coinScript.JiaFen = onScoreAdd;
2026-06-16 23:45:31 +08:00
coinScript.SetCoinCount();
2026-05-22 19:30:02 +08:00
2026-06-16 23:45:31 +08:00
2026-06-21 00:07:26 +08:00
// // Boss 鱼:显示浮动分数文本
// var bossCtrl = (MonsterControl_BossBase)monster;
2026-06-16 23:45:31 +08:00
2026-06-21 00:07:26 +08:00
// bossCtrl.v3 = sitData.transform.position;
// if (bossCtrl.isToUI)
// {
// bossCtrl.currEffect_DieAward = Instantiate(
//monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp,
//currGameType.subPanelParent.parent);
// }
// else
// {
// bossCtrl.currEffect_DieAward = Instantiate(
//monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp);
// }
// bossCtrl.action_effect_DieAward?.Invoke(bossCtrl.currEffect_DieAward);
// bossCtrl.JiaFen = onScoreAdd;
2026-05-22 19:30:02 +08:00
}
break;
case FishTypeEnum.特殊Boss:
{
// Boss 鱼:显示浮动分数文本
var bossCtrl = (MonsterControl_BossBase)monster;
bossCtrl.v3 = sitData.transform.position;
2026-07-20 22:32:52 +08:00
bossCtrl.JiaFen = onScoreAdd; // 先设置 JiaFen
2026-07-23 01:51:07 +08:00
bossCtrl.isLocalCoin = sitData.user == GameManager.Instance.user.id; // ⭐ 金/银区分
bossCtrl.killerSitPos = sitData.posID; // ⭐ 爆奖杀小鱼用击杀者座位(含人机)
2026-07-20 22:32:52 +08:00
monster.DieFish(killFish); // Boss 重写 action_effect_DieAward
dieFishCalled = true;
2026-05-22 19:30:02 +08:00
2026-06-02 14:57:23 +08:00
if (bossCtrl.isToUI)
{
bossCtrl.currEffect_DieAward = Instantiate(
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp,
2026-07-21 05:13:43 +08:00
bjEffectRoot);
2026-06-02 14:57:23 +08:00
}
else
{
bossCtrl.currEffect_DieAward = Instantiate(
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp);
}
2026-05-22 19:30:02 +08:00
bossCtrl.action_effect_DieAward?.Invoke(bossCtrl.currEffect_DieAward);
}
break;
default:
break;
2026-04-29 01:18:39 +08:00
}
2026-07-20 22:32:52 +08:00
if (!dieFishCalled)
monster.DieFish(killFish);
2026-07-20 16:30:28 +08:00
2026-04-29 01:18:39 +08:00
MonsterDieList.Instance.SmallHuangYiHuang(
monster.transform.position,
monster.monsterData.fishTypeConfig.fishNumber, 1000);
}
2026-05-19 17:59:23 +08:00
#endregion
2026-04-29 01:18:39 +08:00
// ================================================================
// 工具:获取当前面板座位列表
// ================================================================
/// <summary>
/// 根据当前游戏类型返回对应 UI 面板的座位列表。
/// </summary>
private List<SitPosData> GetCurrentPanelSitList()
{
switch (GameManager.Instance.gameData.currGameType)
{
case GameType.defaultPanel:
return UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelDefault).list_Sits;
case GameType.FLZY:
return UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelFLZY).list_Sits;
2026-05-07 03:46:03 +08:00
case GameType.YYSC:
return UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelYYSC).list_Sits;
2026-04-29 01:18:39 +08:00
case GameType.XJMH:
return UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelXJMH).list_Sits;
case GameType.HHWK:
return UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelHHWK).list_Sits;
default:
return null;
2026-02-03 11:35:19 +08:00
}
}
2026-05-28 18:16:45 +08:00
#region 冰冻技能
/// <summary>
/// 冰冻:记录所有鱼当前状态并强制切到 Idle(暂停移动+动画)
/// </summary>
public void FreezeAllFish()
{
isFrozen = true;
2026-07-24 23:47:50 +08:00
// ⭐ 两个字典必须同步清空,否则 frozenFishIsMoves 残留旧数据
// 会导致下次解冻时用旧值覆盖 isMove 但不恢复 currState
2026-05-28 18:16:45 +08:00
frozenFishStates.Clear();
2026-07-24 23:47:50 +08:00
frozenFishIsMoves.Clear();
2026-05-28 18:16:45 +08:00
// 只遍历全量字典 fishDatas_hashCodekey(已包含所有鱼),不重复遍历子字典
if (fishDatas_hashCodekey != null)
{
foreach (var kv in fishDatas_hashCodekey)
{
MonsterContral m = kv.Value;
if (m == null || !m.gameObject.activeInHierarchy) continue;
// 记录原状态
frozenFishStates[m.guid] = m.currState;
2026-06-21 00:07:26 +08:00
frozenFishIsMoves[m.guid] = m.isMove;
2026-05-28 18:16:45 +08:00
// 定格
m.isMove = false;
m.ChangeState(CharacterStateType.idle);
2026-06-16 19:12:37 +08:00
m.SetFreezeEffect(true);
2026-05-28 18:16:45 +08:00
}
}
}
/// <summary>
/// 解冻:恢复鱼到冰冻前状态
/// </summary>
public void UnfreezeAllFish()
{
isFrozen = false;
if (fishDatas_hashCodekey != null)
{
foreach (var kv in fishDatas_hashCodekey)
{
MonsterContral m = kv.Value;
if (m == null || !m.gameObject.activeInHierarchy) continue;
2026-07-24 23:47:50 +08:00
// ⭐ 只恢复本次冰冻记录的鱼(frozenFishStates 有记录才恢复)
// 避免用 frozenFishIsMoves 的旧数据覆盖未冻结鱼的 isMove
if (!frozenFishStates.TryGetValue(m.guid, out var prevState))
continue;
// 恢复冰冻前的 isMove 和 currState
2026-06-21 00:07:26 +08:00
if (frozenFishIsMoves.TryGetValue(m.guid, out var prevIsMove))
m.isMove = prevIsMove;
2026-07-24 23:47:50 +08:00
else
m.isMove = true; // 兜底:没记录就恢复为可移动
2026-06-21 00:07:26 +08:00
2026-06-16 19:12:37 +08:00
m.SetFreezeEffect(false);
2026-07-24 23:47:50 +08:00
m.ChangeState(prevState);
2026-05-28 18:16:45 +08:00
}
}
2026-07-24 23:47:50 +08:00
// ⭐ 两个字典同步清空
2026-05-28 18:16:45 +08:00
frozenFishStates.Clear();
2026-07-24 23:47:50 +08:00
frozenFishIsMoves.Clear();
2026-05-28 18:16:45 +08:00
}
#endregion
2026-02-03 11:35:19 +08:00
}