1742 lines
70 KiB
C#
1742 lines
70 KiB
C#
// ============================================================
|
||
// 文件:CreateFishManager.cs
|
||
// 职责:捕鱼游戏 —— 鱼的生成 / 销毁 / 鱼阵管理核心管理器
|
||
// 优化点:
|
||
// 1. 消除 Update 中重复创建 List、字典查找性能问题
|
||
// 2. 统一鱼阵路线选取逻辑,减少冗余代码
|
||
// 3. FishSpawnTimerJob shouldSpawn 逻辑修正(原逻辑只在 ==0 时计时,现保持一致)
|
||
// 4. 增加必要的空检查与防御性编程
|
||
// 5. 清理无用注释与废弃代码块
|
||
// 6. 方法职责单一化拆分
|
||
// ============================================================
|
||
|
||
using Assets.MyGame.Scripts.Characters.Monster;
|
||
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 Assets.MyGame.Scripts.Net.APIResponses.GameConfig_SubClass;
|
||
using Assets.MyGame.Scripts.Net.WebResponses;
|
||
using cfg;
|
||
using Newtonsoft.Json;
|
||
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using TMPro;
|
||
using Unity.Collections;
|
||
using Unity.Jobs;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using Random = UnityEngine.Random;
|
||
|
||
/// <summary>
|
||
/// 捕鱼游戏核心管理器:负责鱼的定时生成、网络同步、阵型调度、死亡特效。
|
||
/// 继承 MonoSingleton 保证全局唯一。
|
||
/// </summary>
|
||
public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||
{
|
||
// ----------------------------------------------------------------
|
||
// 外部依赖
|
||
// ----------------------------------------------------------------
|
||
|
||
/// <summary>当前游戏全局配置(通过 GameManager 获取)</summary>
|
||
public GameConfig M_GameConfig => GameManager.Instance.gameConfig;
|
||
|
||
// ----------------------------------------------------------------
|
||
// 配置缓存(空间换时间,避免 List.Find O(n) 查找)
|
||
// ----------------------------------------------------------------
|
||
|
||
/// <summary>roomFishConfigs 按游戏类型索引</summary>
|
||
private Dictionary<GameTypeEnum, RoomFishConfig> roomFishConfig;
|
||
|
||
/// <summary>每个游戏类型下 fishNumber → RoomFishDetailConfig</summary>
|
||
private Dictionary<GameTypeEnum, Dictionary<string, RoomFishDetailConfig>> createDic;
|
||
|
||
/// <summary>fishNumber → FishTypeConfig(基础配置缓存)</summary>
|
||
private Dictionary<string, FishTypeConfig> fishDatas;
|
||
|
||
/// <summary>fishArrayNumber → FishArrayConfig(鱼阵配置缓存)</summary>
|
||
private Dictionary<string, FishArrayConfig> fishArrayConfigDic;
|
||
|
||
|
||
|
||
|
||
|
||
// ----------------------------------------------------------------
|
||
// UI 引用
|
||
// ----------------------------------------------------------------
|
||
|
||
/// <summary>当前游戏面板(用于坐标转换、金币飞行等)</summary>
|
||
public GameMainPanel_FishBase currGameType;
|
||
|
||
// ----------------------------------------------------------------
|
||
// 死亡特效 Prefab(Inspector 赋值)
|
||
// ----------------------------------------------------------------
|
||
|
||
public GameObject dieFXPrefab; // 基础死亡特效
|
||
public GameObject dieFXPrefabAward; // 普通鱼金币奖励特效
|
||
public GameObject dieFXPrefabAwardTmp; // Boss 分数浮动文本
|
||
public GameObject dieFXPrefabAwardTmp_AddScore;
|
||
|
||
// ----------------------------------------------------------------
|
||
// 生成系统运行时数据
|
||
// ----------------------------------------------------------------
|
||
|
||
/// <summary>鱼的父节点(保持场景层级整洁)</summary>
|
||
public Transform fishRoot;
|
||
/// <summary>鱼的父节点(保持场景层级整洁)</summary>
|
||
public Transform poolRoot;
|
||
|
||
/// <summary>
|
||
/// 非 UI 爆奖特效统一父节点。
|
||
/// 仅装载那些"原本挂在 UIManager.uiRoot 下(不会随房间退出自动销毁)"
|
||
/// 或"原本无父对象(世界空间特效)"的爆奖特效:
|
||
/// - BJEndUI 系列(BJEventBase/GSL/FLXJ 的 EndBJ)
|
||
/// - DieEffect_TSY / EffectZuanTouDie 的 baoJiangEffectPrefab
|
||
/// - HHWK_SDY_DieEvent 的 explosionPrefab
|
||
/// - HHWK_ZDX_DieEvent 的 bombPrefab / explosionPrefab
|
||
/// - Boss鱼/特殊Boss isToUI=false 分支的 currEffect_DieAward
|
||
/// UI 爆奖(挂在 subPanelParent/subPanelParent.parent 下的金币飞、分数版等)
|
||
/// 不挂这里,由 UIManager 卸载游戏主面板时自动清理。
|
||
/// EnterRoom 时创建挂在 UIManager.uiRoot 下,ExitRoom 时整节点销毁。
|
||
/// </summary>
|
||
public Transform bjEffectRoot;
|
||
|
||
/// <summary>当前房间可生成鱼列表(与 NativeArray 下标对齐)</summary>
|
||
public List<RoomFishDetailConfig> spawnList;
|
||
|
||
/// <summary>当前房间 fishNumber → RoomFishDetailConfig</summary>
|
||
public Dictionary<string, RoomFishDetailConfig> spawnDic;
|
||
|
||
public bool isCreateFish; //是否可以创建鱼
|
||
public bool isFrozen = false; //是否冰冻中(冻结出鱼+所有鱼定格)
|
||
|
||
[Header("调试:手动出鱼")]
|
||
[Tooltip("按 M 键生成的鱼编号(配置 fishNumber)")]
|
||
public string debugSpawnFishNumber = "11043";
|
||
private Dictionary<string, CharacterStateType> frozenFishStates = new Dictionary<string, CharacterStateType>();
|
||
private Dictionary<string, bool> frozenFishIsMoves = new Dictionary<string, bool>();
|
||
|
||
/// <summary>按鱼类型分类的生成配置(Boss / 普通 / 黄金…)</summary>
|
||
private Dictionary<FishTypeEnum, List<RoomFishDetailConfig>> fishTypeConfig;
|
||
|
||
// ------ 鱼阵路线相关 ------
|
||
/// <summary>arrayNumber → 该阵型所有可用生成配置</summary>
|
||
private Dictionary<string, List<RoomFishDetailConfig>> yz_FishConfig_Line;
|
||
/// <summary>列表下标 → arrayNumber(用于随机取阵型)</summary>
|
||
private Dictionary<int, string> yz_FishConfig_Line_Dic;
|
||
/// <summary>arrayNumber → 列表下标(反向索引)</summary>
|
||
private Dictionary<string, int> yz_FishConfig_Line_Dic1;
|
||
/// <summary>二维列表,与字典保持同步(供随机无重复取阵型用)</summary>
|
||
private List<List<RoomFishDetailConfig>> yz_FishConfig_Line_List;
|
||
|
||
// ------ Job System NativeArray ------
|
||
private NativeArray<float> timers; // 每条鱼的累计计时
|
||
private NativeArray<float> frequencies; // 每条鱼的生成间隔
|
||
private NativeArray<int> shouldSpawn; // 1=本帧应尝试生成,0=冷却中
|
||
// ----------------------------------------------------------------
|
||
// 运行时鱼实例字典(guid 为 key)
|
||
// ----------------------------------------------------------------
|
||
|
||
/// <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>();
|
||
// ------ 各类计时器 ------
|
||
private RoomFishProbabilityConfig tsbossCreateData; // 特殊Boss 创建数据
|
||
private float tsbossTimer = 999f; // 特殊Boss 刷新倒计时(秒)
|
||
private int tsbossCount; // 本轮同时启用特殊Boss鱼数量
|
||
private int tsbossQuanZhongCount; // 本轮同时启用特殊Boss鱼数量
|
||
private List<string> tsbossRecentList = new List<string>(); // 最近出现过的特殊Boss(cool down)
|
||
private int tsbossRecentMax = 3; // N次内不重复出现
|
||
|
||
private RoomFishProbabilityConfig tsCreateData; // 特殊鱼 创建数据
|
||
private float tsTimer = 999f; // 特殊鱼 刷新倒计时(秒)
|
||
private int tsCount; // 本轮同时启用特殊鱼数量
|
||
private int tsQuanZhongCount; // 本轮同时启用特殊鱼数量
|
||
|
||
private RoomFishProbabilityConfig bossCreateData; // Boss 创建数据
|
||
private float bossTimer = 999f; // Boss 刷新倒计时(秒)
|
||
private int bossCount; // 本轮同时启用Boss鱼数量
|
||
private int bossQuanZhongCount; // 本轮同时启用Boss鱼数量
|
||
private List<string> bossRecentList = new List<string>(); // 最近出现过的Boss(cool down)
|
||
private int bossRecentMax = 3; // N次内不重复出现
|
||
|
||
/// <summary>Boss鱼路线奇偶切换标记:true=下次选奇数路线,false=下次选偶数路线</summary>
|
||
private bool bossNextPathOdd = true;
|
||
|
||
private RoomFishProbabilityConfig yzCreateData; // 鱼阵创建数据
|
||
private float yzTimer = 999f; // 鱼阵刷新倒计时(秒)
|
||
private int yzCount; // 本轮同时激活阵型数量
|
||
private int yz_LineCount = 0; // 场景内鱼阵路线总数
|
||
private int yzQuanZhongCount = 0; // 场景内鱼阵路线总数
|
||
|
||
|
||
private RoomFishProbabilityConfig ptCreateData; // 普通鱼创建数据
|
||
private float ptTimer = 999f; // 普通鱼批量启用倒计时(秒)
|
||
private int ptCount; // 本轮同时启用普通鱼数量
|
||
private int ptQuanZhongCount; // 本轮同时启用普通鱼数量
|
||
|
||
private RoomFishProbabilityConfig hjCreateData; // 普通鱼创建数据
|
||
private float hjTimer = 999f; // 黄金鱼批量启用倒计时(秒)
|
||
private int hjCount; // 本轮同时启用黄金鱼数量
|
||
private int hjQuanZhongCount; // 本轮同时启用黄金鱼数量
|
||
|
||
|
||
// fangzhu 字段已删除:鱼创建改由服务端 OnFishSpawn 统一下发,客户端无需房主出鱼
|
||
public bool isResources = false;
|
||
// ================================================================
|
||
// 懒加载属性
|
||
// ================================================================
|
||
|
||
/// <summary>
|
||
/// 鱼基础配置字典(fishNumber → FishTypeConfig),首次访问时构建。
|
||
/// </summary>
|
||
public Dictionary<string, FishTypeConfig> FishDatas
|
||
{
|
||
get
|
||
{
|
||
if (fishDatas != null) return fishDatas;
|
||
|
||
fishDatas = new Dictionary<string, FishTypeConfig>(M_GameConfig.fishTypeConfigs.Count);
|
||
foreach (var item in M_GameConfig.fishTypeConfigs)
|
||
fishDatas[item.fishNumber] = item;
|
||
|
||
return fishDatas;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 房间鱼配置(按游戏类型),首次访问时触发懒加载初始化。
|
||
/// </summary>
|
||
public Dictionary<GameTypeEnum, RoomFishConfig> RoomFishConfig
|
||
{
|
||
get { EnsureInit(); return roomFishConfig; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 游戏类型 → (fishNumber → RoomFishDetailConfig),首次访问时触发懒加载。
|
||
/// </summary>
|
||
public Dictionary<GameTypeEnum, Dictionary<string, RoomFishDetailConfig>> CreateDic
|
||
{
|
||
get { EnsureInit(); return createDic; }
|
||
}
|
||
#region 生命周期
|
||
// ================================================================
|
||
// Unity 生命周期
|
||
// ================================================================
|
||
|
||
protected override void Awake()
|
||
{
|
||
base.Awake();
|
||
yzCreateData = new RoomFishProbabilityConfig
|
||
{
|
||
countRange = new List<int> { 2, 3 },
|
||
firstFishCountRange = new List<int> { 2, 3 },
|
||
roomLevel = 3,
|
||
fishType = 0,
|
||
maxCountRange = new List<int> { 10, 20 },
|
||
timeRange = new List<int> { 3, 4 },
|
||
type = 2
|
||
};
|
||
// 确保 FishRoot 存在
|
||
if (fishRoot == null)
|
||
fishRoot = new GameObject("FishRoot").transform;
|
||
if (poolRoot == null)
|
||
poolRoot = new GameObject("poolRoot").transform;
|
||
|
||
// ⭐ 鱼创建改由服务端 OnFishSpawn 事件统一下发,客户端不再监听 createFish 帧同步
|
||
EventCenter.Instance.AddEventListener<OnFishSpawnData>(
|
||
ConstEventString.OnFishSpawn, OnFishSpawn);
|
||
}
|
||
|
||
// ================================================================
|
||
// Update:冰冻时不做任何事(鱼创建由服务端 OnFishSpawn 驱动)
|
||
// ================================================================
|
||
|
||
private void Update()
|
||
{
|
||
// 鱼创建由服务端 OnFishSpawn 事件驱动,客户端 Update 不再出鱼
|
||
// (保留 isFrozen 字段供 FreezeAllFish/UnfreezeAllFish 使用)
|
||
|
||
// ⭐ 调试用:按 M 键手动出鱼(出 Inspector 配置的 debugSpawnFishNumber)
|
||
// 使用新版输入系统(Input System):Keyboard.current.wasPressedThisFrame
|
||
if (UnityEngine.InputSystem.Keyboard.current != null &&
|
||
UnityEngine.InputSystem.Keyboard.current.mKey.wasPressedThisFrame)
|
||
{
|
||
if (string.IsNullOrEmpty(debugSpawnFishNumber))
|
||
{
|
||
Debug.LogWarning("[CreateFishManager] 未配置 debugSpawnFishNumber,请在 Inspector 设置鱼编号");
|
||
return;
|
||
}
|
||
ManualSpawn(debugSpawnFishNumber);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private void OnDestroy()
|
||
{
|
||
EventCenter.Instance.RemoveEventListener<OnFishSpawnData>(
|
||
ConstEventString.OnFishSpawn, OnFishSpawn);
|
||
|
||
// 释放 NativeArray,避免内存泄漏
|
||
DisposeNativeArrays();
|
||
}
|
||
#endregion
|
||
|
||
#region 初始化
|
||
public void InitChuYu()
|
||
{
|
||
// 随机初始化计时器,避免所有类型同帧触发
|
||
yzCount = Random.Range(yzCreateData.countRange[0], yzCreateData.countRange[1]);
|
||
ptCount = Random.Range(ptCreateData.countRange[0], ptCreateData.countRange[1]);
|
||
hjCount = Random.Range(hjCreateData.countRange[0], hjCreateData.countRange[1]);
|
||
bossCount = Random.Range(bossCreateData.countRange[0], bossCreateData.countRange[1]);
|
||
tsbossCount = Random.Range(tsbossCreateData.countRange[0], tsbossCreateData.countRange[1]);
|
||
tsCount = Random.Range(tsCreateData.countRange[0], tsCreateData.countRange[1]);
|
||
yzTimer = 0.5f;
|
||
ptTimer = 0.5f;
|
||
hjTimer = 0.5f;
|
||
tsTimer = 1f;
|
||
bossTimer = 5f;
|
||
tsbossTimer = 5f;
|
||
}
|
||
/// <summary>
|
||
/// 懒加载初始化:只执行一次,构建全部配置缓存字典。
|
||
/// </summary>
|
||
private void EnsureInit()
|
||
{
|
||
if (roomFishConfig != null) return;
|
||
|
||
FishYZ.Init();
|
||
roomFishConfig = new Dictionary<GameTypeEnum, RoomFishConfig>();
|
||
createDic = new Dictionary<GameTypeEnum, Dictionary<string, RoomFishDetailConfig>>();
|
||
|
||
var list = GameManager.Instance.gameConfig.roomFishConfigs;
|
||
if (list == null || list.Count == 0) return;
|
||
|
||
// 构建 fishArray / fishType 字典缓存
|
||
BuildConfigCache();
|
||
|
||
foreach (var item in list)
|
||
{
|
||
var gameType = (GameTypeEnum)item.gameType;
|
||
roomFishConfig[gameType] = item;
|
||
|
||
var fishDic = new Dictionary<string, RoomFishDetailConfig>();
|
||
createDic[gameType] = fishDic;
|
||
|
||
// 普通鱼列表
|
||
if (item.normalFishList != null)
|
||
foreach (var fish in item.normalFishList)
|
||
fishDic[fish.fishNumber] = fish;
|
||
|
||
// 等级鱼列表(同 fishNumber 会覆盖普通鱼,实现换皮)
|
||
if (item.roomFishLevelList != null)
|
||
foreach (var fish in item.roomFishLevelList)
|
||
fishDic[fish.fishNumber] = fish;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 构建高频查询的字典缓存(fishArrayConfigs / fishTypeConfigs)。
|
||
/// </summary>
|
||
private void BuildConfigCache()
|
||
{
|
||
fishArrayConfigDic = GameManager.Instance.gameConfig.fishArrayConfigs
|
||
.ToDictionary(a => a.fishArrayNumber, a => a);
|
||
|
||
fishDatas = GameManager.Instance.gameConfig.fishTypeConfigs
|
||
.ToDictionary(f => f.fishNumber, f => f);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
#endregion
|
||
|
||
#region 进入房间加载
|
||
/// <summary>进入房间:初始化生成系统并绑定游戏面板。</summary>
|
||
public void EnterRoom(GameTypeEnum gameType)
|
||
{
|
||
InitSpawnSystem(gameType);
|
||
BindCurrentGamePanel();
|
||
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;
|
||
}
|
||
|
||
/// <summary>根据当前 GameType 绑定对应 UI 面板到 currGameType。</summary>
|
||
private void BindCurrentGamePanel()
|
||
{
|
||
switch (GameManager.Instance.gameData.currGameType)
|
||
{
|
||
case GameType.FLZY:
|
||
currGameType = UIManager.Instance.Get<GameMainPanel_FLZY>(UIPanel.MainGamePanelFLZY);
|
||
break;
|
||
case GameType.YYSC:
|
||
currGameType = UIManager.Instance.Get<GameMainPanel_YYSC>(UIPanel.MainGamePanelYYSC);
|
||
break;
|
||
case GameType.XJMH:
|
||
currGameType = UIManager.Instance.Get<GameMainPanel_XJMH>(UIPanel.MainGamePanelXJMH);
|
||
break;
|
||
case GameType.HHWK:
|
||
currGameType = UIManager.Instance.Get<GameMainPanel_HHWK>(UIPanel.MainGamePanelHHWK);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 进入房间时初始化生成系统:加载当前房间配置、分配 NativeArray。
|
||
/// </summary>
|
||
public async void InitSpawnSystem(GameTypeEnum gameType)
|
||
{
|
||
EnsureInit();
|
||
|
||
// 初始化分类容器
|
||
fishTypeConfig = new Dictionary<FishTypeEnum, List<RoomFishDetailConfig>>();
|
||
yz_FishConfig_Line = new Dictionary<string, List<RoomFishDetailConfig>>();
|
||
yz_FishConfig_Line_Dic = new Dictionary<int, string>();
|
||
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>();
|
||
|
||
var config = RoomFishConfig[gameType];
|
||
|
||
// ---- 处理普通鱼列表 ----
|
||
if (config.normalFishList != null)
|
||
{
|
||
foreach (var item in config.normalFishList)
|
||
{
|
||
if (!spawnDic.ContainsKey(item.fishNumber))
|
||
{
|
||
spawnDic[item.fishNumber] = item;
|
||
spawnList.Add(item);
|
||
}
|
||
|
||
|
||
if (item.type == 1)
|
||
{
|
||
// 单体鱼:按鱼类型分组
|
||
AddToFishTypeConfig(item);
|
||
}
|
||
else if (item.type == 2)
|
||
{
|
||
// 鱼阵:按 arrayNumber 分组
|
||
TryAddToArrayLine(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---- 处理等级鱼列表(仅当前房间等级)----
|
||
int currRoomLevel = GameManager.Instance.gameData.currRoomData.roomLevel;
|
||
if (config.roomFishLevelList != null)
|
||
{
|
||
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.黄金鱼:
|
||
hjCreateData = item;
|
||
break;
|
||
case FishTypeEnum.特殊鱼:
|
||
tsCreateData = item;
|
||
break;
|
||
case FishTypeEnum.特殊Boss:
|
||
tsbossCreateData = item;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
case 2:
|
||
//yzTimer=item.
|
||
yzCreateData = item;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
foreach (var item in config.roomFishLevelList)
|
||
{
|
||
if (item.roomLevel != currRoomLevel) continue;
|
||
|
||
if (item.type == 2)
|
||
{
|
||
spawnDic[item.fishNumber] = item;
|
||
spawnList.Add(item);
|
||
yzQuanZhongCount += item.fishWeight;
|
||
TryAddToArrayLine(item);
|
||
}
|
||
else
|
||
{
|
||
AddToFishTypeConfig(item);
|
||
// 等级鱼也加入 spawnDic / spawnList
|
||
spawnDic[item.fishNumber] = item;
|
||
spawnList.Add(item);
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---- 分配 Job System NativeArray ----
|
||
int count = spawnList.Count;
|
||
DisposeNativeArrays(); // 防止重复进房泄漏
|
||
timers = new NativeArray<float>(count, Allocator.Persistent);
|
||
frequencies = new NativeArray<float>(count, Allocator.Persistent);
|
||
shouldSpawn = new NativeArray<int>(count, Allocator.Persistent);
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
timers[i] = 0f;
|
||
frequencies[i] = spawnList[i].fishFrequency;
|
||
shouldSpawn[i] = 1; // 初始全部允许生成
|
||
}
|
||
|
||
// ---- 统计场景内鱼阵路线总数 ----
|
||
yz_LineCount = 0;
|
||
foreach (var key in MonsterMoveLine.dic_BezierCurveDatas.Keys)
|
||
{
|
||
if (key.StartsWith("鱼阵"))
|
||
yz_LineCount++;
|
||
}
|
||
InitChuYu();
|
||
}
|
||
|
||
/// <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;
|
||
yz_FishConfig_Line_Dic[newIdx] = yz.arrayNumber;
|
||
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);
|
||
}
|
||
#endregion
|
||
|
||
#region OnFishSpawn 服务端出鱼接收处理
|
||
// ⭐ 鱼创建改由服务端 OnFishSpawn 事件统一下发
|
||
// 客户端 BroadCastCreateFish / TickXxxSpawn / SendCreateFishRequest /
|
||
// CreateInitialFishes / BroadcastExistingFishes / OnRequestSyncFish / CreateFish
|
||
// 全部删除,改为接收 OnFishSpawnData 后调用 SpawnFishMainThread 生成鱼实例
|
||
//
|
||
// type:
|
||
// "init" - 首次出鱼(玩家进入房间后服务端推送初始鱼)
|
||
// "spawn" - 定时出鱼(游戏中持续刷怪)
|
||
// "sync" - 重连/取消挂机同步(把当前场上活鱼推送给回归玩家)
|
||
|
||
/// <summary>
|
||
/// 接收服务端 OnFishSpawn 事件,生成鱼实例。
|
||
/// 遍历 data.fish 列表,对每条鱼:
|
||
/// 1. guid 去重(已有此 guid 的鱼跳过,防 sync 模式重复 Spawn)
|
||
/// 2. 构造 FrameData_CreateFish 复用 SpawnFishMainThread 生成
|
||
/// 3. 启动 expireAt 过期自动移除协程
|
||
/// </summary>
|
||
public void OnFishSpawn(OnFishSpawnData data)
|
||
{
|
||
if (data == null || data.fish == null || data.fish.Count == 0) return;
|
||
if (isFrozen) return; // 冰冻中不创建鱼
|
||
if (spawnDic == null) return; // 场景卸载后忽略
|
||
|
||
foreach (var item in data.fish)
|
||
{
|
||
if (item == null) continue;
|
||
//BeijingTimeManager.Instance.SyncBeijingTime(() =>
|
||
//{
|
||
// Debug.Log("差值:" + (BeijingTimeManager.Instance.CurrentTimestampMs - item.createdAt) / 1000);
|
||
// Debug.Log("差值:" + (BeijingTimeManager.Instance.CurrentTimestampMs - item.createdAt));
|
||
// Debug.Log("差值:" + BeijingTimeManager.Instance.CurrentTimestampMs + "创建\n" + item.createdAt);
|
||
//});
|
||
Debug.Log("差值:" + (BeijingTimeManager.Instance.CurrentTimestampMs - item.createdAt) / 1000);
|
||
Debug.Log("差值:" + (BeijingTimeManager.Instance.CurrentTimestampMs - item.createdAt));
|
||
Debug.Log("差值:" + BeijingTimeManager.Instance.CurrentTimestampMs + "创建\n" + item.createdAt);
|
||
|
||
// ⭐ 鱼阵时 fishNumber 为空,用 fishArrayNumber 作为配置 key;
|
||
// 单体鱼时 fishNumber 非空,直接用 fishNumber
|
||
string cfgKey = string.IsNullOrEmpty(item.fishNumber) ? item.fishArrayNumber : item.fishNumber;
|
||
if (string.IsNullOrEmpty(cfgKey)) continue;
|
||
// guid 用服务端 fishId
|
||
string guid = item.fishId.ToString();
|
||
|
||
// guid 去重:已有此 guid 的鱼跳过
|
||
if (!string.IsNullOrEmpty(guid) &&
|
||
fishDatas_hashCodekey != null &&
|
||
fishDatas_hashCodekey.ContainsKey(guid))
|
||
continue;
|
||
|
||
// 构造 FrameData_CreateFish 复用 SpawnFishMainThread
|
||
var createFishData = new FrameData_CreateFish
|
||
{
|
||
fishNumber = cfgKey,
|
||
guid = guid,
|
||
moveLineId = item.routeId.ToString()
|
||
};
|
||
|
||
// ⭐ 根据 type 决定 moveTime:
|
||
// - spawn/init:新鱼,传当前时间 → moveTime≈0,鱼从曲线起点开始游
|
||
// (避免服务端/客户端时间差导致 moveTime>0,鱼瞬间出现在曲线中间)
|
||
// - sync:同步已有鱼,传 createdAt → moveTime=时间差,鱼出现在路径正确位置
|
||
long spawnTime;
|
||
//if (data.type == "sync")
|
||
spawnTime = item.createdAt;
|
||
//else
|
||
// spawnTime = BeijingTimeManager.Instance.CurrentTimestampMs;
|
||
SpawnFishMainThread(createFishData, spawnTime);
|
||
|
||
// ⭐ expireAt 过期自动移除暂不启用:
|
||
// - 走路线的鱼:OnMoveUpdata 里 moveTime>=moveLine.moveTime 时自动 Id_DieFish_NotFX
|
||
// - 原点不动的鱼(Boss):正常播放结束动画后销毁
|
||
// 后续若需要服务端强制过期销毁,再启用此协程
|
||
// if (item.remainingSeconds > 0)
|
||
// StartCoroutine(ExpireFishCoroutine(guid, item.remainingSeconds));
|
||
}
|
||
|
||
Debug.Log($"[CreateFishManager] OnFishSpawn type={data.type}, count={data.fish.Count}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鱼过期自动移除协程。
|
||
/// remainingSeconds 秒后调用 Id_DieFish_NotFX 销毁鱼(无特效)。
|
||
/// </summary>
|
||
private IEnumerator ExpireFishCoroutine(string guid, int remainingSeconds)
|
||
{
|
||
yield return new WaitForSeconds(remainingSeconds);
|
||
Id_DieFish_NotFX(guid);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动出鱼(测试/调试用,不走服务端协议)。
|
||
/// 用当前时间作为 spawnTime → moveTime≈0,鱼从曲线起点开始游。
|
||
/// </summary>
|
||
/// <param name="fishNumber">鱼编号(配置 fishNumber)</param>
|
||
/// <param name="routeId">路径编号,<=0 时随机取一条</param>
|
||
/// <param name="remainingSeconds">鱼存活秒数,<=0 时默认 30 秒</param>
|
||
/// <returns>生成成功返回 true,失败返回 false</returns>
|
||
public bool ManualSpawn(string fishNumber, int routeId = 0, int remainingSeconds = 30)
|
||
{
|
||
if (isFrozen) { Debug.LogWarning("[CreateFishManager] 冰冻中,手动出鱼失败"); return false; }
|
||
if (spawnDic == null) { Debug.LogWarning("[CreateFishManager] spawnDic 未初始化,手动出鱼失败"); return false; }
|
||
if (string.IsNullOrEmpty(fishNumber)) { Debug.LogWarning("[CreateFishManager] fishNumber 为空,手动出鱼失败"); return false; }
|
||
if (!spawnDic.ContainsKey(fishNumber)) { Debug.LogWarning($"[CreateFishManager] 找不到鱼配置: {fishNumber},手动出鱼失败"); return false; }
|
||
|
||
// routeId<=0 时随机取一条路线(1~4 范围,按现有路线命名规则)
|
||
if (routeId <= 0)
|
||
routeId = Random.Range(1, 5);
|
||
|
||
// 生成唯一 guid(用手动出鱼前缀 + 时间戳,避免与服务端 fishId 冲突)
|
||
string guid = "manual_" + BeijingTimeManager.Instance.CurrentTimestampMs + "_" + Random.Range(1000, 9999);
|
||
|
||
var createFishData = new FrameData_CreateFish
|
||
{
|
||
fishNumber = fishNumber,
|
||
guid = guid,
|
||
moveLineId = routeId.ToString()
|
||
};
|
||
|
||
// 用当前时间 → moveTime≈0,鱼从曲线起点开始游
|
||
SpawnFishMainThread(createFishData, BeijingTimeManager.Instance.CurrentTimestampMs);
|
||
|
||
//if (remainingSeconds > 0)
|
||
// StartCoroutine(ExpireFishCoroutine(guid, remainingSeconds));
|
||
|
||
Debug.Log($"[CreateFishManager] 手动出鱼: fishNumber={fishNumber}, routeId={routeId}, guid={guid}");
|
||
return true;
|
||
}
|
||
#endregion
|
||
|
||
#region 旧客户端出鱼链路(已删除)
|
||
// 以下方法已删除,鱼创建改由服务端 OnFishSpawn 事件下发:
|
||
// - BroadCastCreateFish / TickBossSpawn / TickTSBossSpawn / TickFishArraySpawn
|
||
// - TickPTFishSpawn / TickHJFishSpawn / TickTSFishSpawn
|
||
// - ProcessSpawnRequests / SendCreateFishRequest
|
||
// - CreateInitialFishes / BroadcastExistingFishes / OnRequestSyncFish
|
||
// - CreateFish(SyncGameData)
|
||
// 保留方法:SpawnFishMainThread / OnlyFish / SpawnFishArray 等(被 OnFishSpawn 调用)
|
||
#endregion
|
||
|
||
#region 鱼生成核心逻辑
|
||
|
||
// ================================================================
|
||
// 旧定时 Tick 方法(已删除,鱼创建改由服务端 OnFishSpawn 事件下发)
|
||
// ================================================================
|
||
// TickBossSpawn / TickTSBossSpawn / TickFishArraySpawn / TickPTFishSpawn
|
||
// TickHJFishSpawn / TickTSFishSpawn / buchongfu / ProcessSpawnRequests
|
||
// SendCreateFishRequest / CreateInitialFishes / BroadcastExistingFishes
|
||
// OnRequestSyncFish / CreateFish(SyncGameData)
|
||
// —— 全部删除,改由 OnFishSpawn(OnFishSpawnData) 接收服务端出鱼事件 ——
|
||
|
||
// ================================================================
|
||
// 单体鱼 / 鱼阵生成(被 OnFishSpawn 调用)
|
||
// ================================================================
|
||
|
||
/// <summary>
|
||
/// 主线程实例化鱼对象(单体鱼 / 鱼阵分发)。
|
||
/// </summary>
|
||
private void SpawnFishMainThread(FrameData_CreateFish createFishData, long time)
|
||
{
|
||
// ⭐ spawnDic 可能在场景卸载时被置 null,这里再保护一次
|
||
if (spawnDic == null) return;
|
||
if (!spawnDic.TryGetValue(createFishData.fishNumber, out var cfg))
|
||
{
|
||
Debug.LogWarning($"[CreateFishManager] 找不到 spawnDic 配置: {createFishData.fishNumber}");
|
||
return;
|
||
}
|
||
|
||
if (cfg.type == 1)
|
||
{
|
||
// 单体鱼
|
||
OnlyFish(createFishData, time);
|
||
}
|
||
else if (cfg.type == 2)
|
||
{
|
||
// 鱼阵
|
||
SpawnFishArray(createFishData, cfg.fishNumber, time);
|
||
}
|
||
}
|
||
// ================================================================
|
||
// 单体鱼生成
|
||
// ================================================================
|
||
|
||
|
||
/// <summary>
|
||
/// 生成一条普通/Boss/黄金鱼并初始化路径。
|
||
/// </summary>
|
||
/// <returns>成功返回 true,配置缺失返回 false。</returns>
|
||
private bool OnlyFish(FrameData_CreateFish createFishData, long time)
|
||
{
|
||
if (!fishDatas.TryGetValue(createFishData.fishNumber, out var fishType))
|
||
{
|
||
Debug.LogError($"[CreateFishManager] 找不到 fishTypeConfig: {createFishData.fishNumber}");
|
||
return false;
|
||
}
|
||
|
||
//var prefab = FishResourcesData.GetFishPrefab(int.Parse(fishType.fishNumber));
|
||
//var fish = Instantiate(prefab, new Vector3(9999, 9999, 0), prefab.transform.rotation, fishRoot);
|
||
|
||
try
|
||
{
|
||
var fish = Pool.Instance.Get(int.Parse(createFishData.fishNumber), new Vector3(9999, 9999, 0), Quaternion.identity, fishRoot);
|
||
if (fish == null)
|
||
{
|
||
Debug.LogError($"[CreateFishManager] Pool.Get 返回 null,跳过此鱼: {createFishData.fishNumber}");
|
||
return false;
|
||
}
|
||
var mc = fish.GetComponent<MonsterContral>() ?? fish.AddComponent<MonsterContral>();
|
||
mc.monsterData.fishTypeConfig = FishDatas[createFishData.fishNumber];
|
||
mc.guid = createFishData.guid;
|
||
|
||
// ⭐ moveTime 计算:当前时间 - 创建时间(秒)
|
||
float elapsedSec = (BeijingTimeManager.Instance.CurrentTimestampMs - time) / 1000f;
|
||
|
||
// ⭐ 创建视频时长抵扣:
|
||
// - 配置了 createVideoClip 且 elapsedSec < 视频时长:视频还没播完
|
||
// → moveTime=0(视频继续播),OnMoveUpdata 会跳过移动直到视频结束
|
||
// - 配置了 createVideoClip 且 elapsedSec >= 视频时长:视频已播完
|
||
// → moveTime = elapsedSec - 视频时长(视频结束后才开始累计路线时间)
|
||
// - 未配置 createVideoClip:moveTime = elapsedSec(原逻辑)
|
||
if (mc.createVideoClip != null)
|
||
{
|
||
float videoDuration = (float)mc.createVideoClip.length;
|
||
if (elapsedSec < videoDuration)
|
||
{
|
||
// 视频还没播完,继续播放(Init 里会启动 createVideoPlayer)
|
||
mc.moveTime = 0f;
|
||
}
|
||
else
|
||
{
|
||
// 视频已播完,扣除视频时长,moveTime 为视频结束后已走的路线时间
|
||
mc.moveTime = elapsedSec - videoDuration;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
mc.moveTime = elapsedSec;
|
||
}
|
||
|
||
mc.fishenum = (FishTypeEnum)fishType.fishType;
|
||
// 按鱼类型放入对应字典
|
||
RegisterFishInstance(mc, fishType.fishType);
|
||
|
||
mc.Init(createFishData.moveLineId, spawnDic[createFishData.fishNumber].times);
|
||
|
||
Debug.Log($"[CreateFishManager] 生成鱼:{fishType.fishName}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError(createFishData.fishNumber);
|
||
Debug.LogError(ex.Message);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <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;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 鱼阵
|
||
// ================================================================
|
||
// 鱼阵生成(按 arrayNumber 前缀分发到对应阵型协程/方法)
|
||
// ================================================================
|
||
|
||
/// <summary>根据鱼阵配置分发到对应的生成逻辑。</summary>
|
||
private void SpawnFishArray(FrameData_CreateFish createFishData, string fishNumber, long time)
|
||
{
|
||
if (!fishArrayConfigDic.TryGetValue(fishNumber, out var yz))
|
||
{
|
||
Debug.LogError($"[CreateFishManager] 找不到 fishArrayConfig: {fishNumber}");
|
||
return;
|
||
}
|
||
|
||
if (!fishDatas.TryGetValue(yz.fishNumber, out var fishType))
|
||
{
|
||
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;
|
||
}
|
||
|
||
var prefab = FishResourcesData.GetFishPrefab(int.Parse(fishType.fishNumber));
|
||
var pos = new Vector3(9999, 9999, 0);
|
||
|
||
|
||
|
||
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": // 顺序阵:每条路径逐一延迟生成
|
||
case "704":
|
||
//int chosen710 = (prefix == "710")
|
||
// ? pathList[Random.Range(0, pathList.Count)]
|
||
// : -1;
|
||
|
||
StartCoroutine(YZ7007(createFishData, yz, pos, prefab, time));
|
||
//foreach (int pathId in pathList)
|
||
//{
|
||
// int actualPath = (prefix == "710") ? chosen710 : pathId;
|
||
// StartCoroutine(YZ7007(createFishData, yz, pos, prefab, actualPath, time, yz.fishCount));
|
||
//}
|
||
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)
|
||
{
|
||
if (!isResources)
|
||
{
|
||
yield break;
|
||
}
|
||
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++)
|
||
{
|
||
// 交替正负角度,形成扇形分布
|
||
float angle = i * 5f * (i % 2 == 0 ? -1f : 1f);
|
||
var rotation = Quaternion.Euler(
|
||
prefab.transform.eulerAngles + Vector3.forward * angle);
|
||
|
||
//var fish = Instantiate(prefab, pos, rotation, fishRoot);
|
||
var fish = Pool.Instance.Get(int.Parse(yz.fishNumber), pos, rotation, fishRoot);
|
||
|
||
var mc = fish.GetComponent<MonsterContral>();
|
||
|
||
mc.monsterData.fishTypeConfig = FishDatas[yz.fishNumber];
|
||
mc.guid = createFishData.guid + i;
|
||
RegisterFishInstance(mc, FishDatas[yz.fishNumber].fishType);
|
||
|
||
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++)
|
||
{
|
||
float angle = (360f / count) * i;
|
||
var rotation = Quaternion.Euler(prefab.transform.eulerAngles + Vector3.forward * angle);
|
||
|
||
//var fish = Instantiate(prefab, pos, rotation, fishRoot);
|
||
var fish = Pool.Instance.Get(int.Parse(yz.fishNumber), pos, rotation, fishRoot);
|
||
|
||
var mc = fish.GetComponent<MonsterContral>();
|
||
|
||
mc.monsterData.fishTypeConfig = FishDatas[yz.fishNumber];
|
||
mc.guid = createFishData.guid + i;
|
||
RegisterFishInstance(mc, FishDatas[yz.fishNumber].fishType);
|
||
|
||
mc.moveTime = (BeijingTimeManager.Instance.CurrentTimestampMs - time) / 1000f;
|
||
|
||
mc.Init(pathId.ToString(), cfg.times, true, yz.arrayNumber);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 顺序阵:每隔 1s 生成一条鱼,共生成 count 条。
|
||
/// </summary>
|
||
private IEnumerator YZ7007(FrameData_CreateFish createFishData, FishArrayConfig yz,
|
||
Vector3 pos, GameObject prefab, long time)
|
||
{
|
||
if (!spawnDic.TryGetValue(createFishData.fishNumber, out var cfg))
|
||
{
|
||
Debug.LogWarning($"[CreateFishManager] YZ7007 找不到 spawnDic 配置: {createFishData.fishNumber}");
|
||
yield break;
|
||
}
|
||
|
||
// ⭐ 7007 鱼阵:路线从 FishYZ.dicfishYZ[arrayNumber] 取第 0 个
|
||
// (数量由后端控制,客户端只读路线列表第 0 个)
|
||
if (!FishYZ.dicfishYZ.TryGetValue(yz.arrayNumber, out var yzPathList) || yzPathList.Count == 0)
|
||
{
|
||
Debug.LogWarning($"[CreateFishManager] YZ7007 找不到鱼阵路线: {yz.arrayNumber}");
|
||
yield break;
|
||
}
|
||
int yzPathId = yzPathList[0];
|
||
|
||
for (int i = 0; i < yz.fishCount; i++)
|
||
{
|
||
if (!isResources)
|
||
{
|
||
yield break;
|
||
}
|
||
//var fish = Instantiate(prefab, pos, prefab.transform.rotation, fishRoot);
|
||
var fish = Pool.Instance.Get(int.Parse(yz.fishNumber), pos, Quaternion.identity, fishRoot);
|
||
if (fish == null)
|
||
{
|
||
Debug.LogWarning($"[CreateFishManager] YZ7007 Pool.Get 返回 null,跳过: {yz.fishNumber}");
|
||
yield return new WaitForSeconds(1f);
|
||
continue;
|
||
}
|
||
|
||
var mc = fish.GetComponent<MonsterContral>() ?? fish.AddComponent<MonsterContral>();
|
||
|
||
|
||
mc.monsterData.fishTypeConfig = FishDatas[yz.fishNumber];
|
||
mc.guid = createFishData.guid + i;
|
||
RegisterFishInstance(mc, FishDatas[yz.fishNumber].fishType);
|
||
// 每条鱼额外减去已等待秒数,使路径起点与帧数同步
|
||
mc.moveTime = ((BeijingTimeManager.Instance.CurrentTimestampMs - time) / 1000f) - i;
|
||
|
||
// ⭐ 路线用 FishYZ 取到的第 0 个,yzID 用 arrayNumber
|
||
mc.Init(yzPathId.ToString(), cfg.times, true, yz.arrayNumber);
|
||
|
||
yield return new WaitForSeconds(1f);
|
||
}
|
||
}
|
||
#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);
|
||
|
||
}
|
||
// ⭐ 销毁爆奖特效父节点:所有挂在 bjEffectRoot 下的爆奖特效
|
||
// (Boss 分数版/加分弹出/BJEndUI/特殊爆奖等) 会一并被销毁,
|
||
// 防止退出房间时残留未结束的爆奖协程/动画造成的内存泄漏
|
||
if (bjEffectRoot != null)
|
||
{
|
||
Destroy(bjEffectRoot.gameObject);
|
||
bjEffectRoot = null;
|
||
}
|
||
//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
|
||
// ================================================================
|
||
// 随机工具方法
|
||
// ================================================================
|
||
|
||
/// <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>();
|
||
var rand = new System.Random();
|
||
|
||
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>();
|
||
var rand = new System.Random();
|
||
var result = new List<string>(count);
|
||
|
||
while (indices.Count < count)
|
||
{
|
||
int idx = rand.Next(min, max);
|
||
if (indices.Add(idx))
|
||
result.Add(keyList[idx]);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
#endregion
|
||
|
||
#region 杀鱼
|
||
// ================================================================
|
||
// 杀鱼接口
|
||
// ================================================================
|
||
|
||
/// <summary>
|
||
/// 批量杀死多条鱼(带特效 + 不足数量用纯金币补齐)。
|
||
/// </summary>
|
||
/// <param name="guids">实际场上能找到的鱼 guid 列表(可能少于 count)</param>
|
||
/// <param name="coinNum">红包拆分后的每份金额,长度应 >= count</param>
|
||
/// <param name="v3Point">金币最终飞回的世界坐标(一般是炮台位置)</param>
|
||
/// <param name="count">理论上要“死”的总数量(鱼 + 纯金币)</param>
|
||
public void Id_DieListFish_FX(List<string> guids, List<RedPacketRecord> coinNum, Vector3 v3Point, int count, bool isLocal = false)
|
||
{
|
||
if (coinNum == null || coinNum.Count == 0) return;
|
||
|
||
// 1. 先处理"真的有鱼"的部分
|
||
int realCount = Mathf.Min(guids.Count, coinNum.Count, count);
|
||
for (int i = 0; i < realCount; i++)
|
||
{
|
||
Id_DieFish_FX(guids[i], coinNum[i], v3Point, isLocal);
|
||
}
|
||
|
||
// 2. 不足的部分,用"屏幕随机位置的金币特效"补齐
|
||
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];
|
||
SpawnRandomCoinFX(red, v3Point, isLocal);
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 随机选取 count 条小鱼/黄金鱼,通知服务器同步死亡,并返回 guid 列表。
|
||
/// </summary>
|
||
/// <param name="killerSitPos">击杀者座位 ID。Boss 爆奖连锁杀鱼时传 Boss 击杀者座位
|
||
/// (含人机);-1 表示用本地 currSitData.posID(普通玩家直接击杀场景).</param>
|
||
public List<string> SyncGameData_SendRendromDieFishID(
|
||
int count, List<RedPacketRecord> coinNum, Vector3 v3Point, int killerSitPos = -1)
|
||
{
|
||
var guids = new List<string>(count);
|
||
var tempKeys = fishDatas_hashCodekey_Small.Keys.ToList();
|
||
|
||
// 若存活鱼不足 count,全部选取
|
||
if (tempKeys.Count <= count)
|
||
{
|
||
guids.AddRange(tempKeys);
|
||
}
|
||
else
|
||
{
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
int idx = Random.Range(0, tempKeys.Count);
|
||
guids.Add(tempKeys[idx]);
|
||
tempKeys.RemoveAt(idx);
|
||
}
|
||
}
|
||
|
||
// ⭐ sitPos 用击杀者座位(含人机);房主代人机击杀 Boss 时,这里传人机座位,
|
||
// 否则所有客户端都会判定为房主击杀 → 房主自己看金色(错)
|
||
int posID = killerSitPos >= 0 ? killerSitPos : currGameType.currSitData.posID;
|
||
var sgd = new SyncGameData
|
||
{
|
||
sitPos = posID,
|
||
frameType = FrameDataType.randomDieFish,
|
||
time = BeijingTimeManager.Instance.CurrentTimestampMs,
|
||
frameData = new FrameData_RandomDieFish
|
||
{
|
||
x = v3Point.x,
|
||
y = v3Point.y,
|
||
z = v3Point.z,
|
||
guids = guids,
|
||
coinNum = coinNum,
|
||
count = count
|
||
}
|
||
};
|
||
|
||
WebSocketSendList.SyncGameData(sgd);
|
||
return guids;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鱼离场
|
||
/// </summary>
|
||
public void Id_DieFish_NotFX(string fishId)
|
||
{
|
||
if (!fishDatas_hashCodekey.TryGetValue(fishId, out var monster)) return;
|
||
Pool.Instance.Return(monster.gameObject);
|
||
switch (monster.fishenum)
|
||
{
|
||
case FishTypeEnum.普通鱼:
|
||
fishDatas_hashCodekey_Small.Remove(fishId);
|
||
|
||
break;
|
||
case FishTypeEnum.Boss鱼:
|
||
fishDatas_hashCodekey_Boss.Remove(fishId);
|
||
|
||
break;
|
||
case FishTypeEnum.黄金鱼:
|
||
fishDatas_hashCodekey_HJ.Remove(fishId);
|
||
|
||
break;
|
||
case FishTypeEnum.特殊鱼:
|
||
fishDatas_hashCodekey_TS.Remove(fishId);
|
||
|
||
break;
|
||
case FishTypeEnum.特殊Boss:
|
||
fishDatas_hashCodekey_TS_Boss.Remove(fishId);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
fishDatas_hashCodekey.Remove(fishId);
|
||
}
|
||
/// <summary>
|
||
/// 按 ID 杀死一条鱼(带特效 + 金币飞出动画)。
|
||
/// </summary>
|
||
public void Id_DieFish_FX(string fishId, RedPacketRecord coinNum, Vector3 v3Point, bool isLocal = false)
|
||
{
|
||
if (!fishDatas_hashCodekey.TryGetValue(fishId, out var monster)) return;
|
||
if (false)
|
||
{
|
||
AudioManager.Instance.PlayerFX(60004);
|
||
|
||
}
|
||
|
||
// 死亡粒子特效
|
||
Instantiate(
|
||
monster.effect_Die != null ? monster.effect_Die : dieFXPrefab,
|
||
monster.transform.position, Quaternion.identity, transform.parent);
|
||
|
||
// 非 Boss 鱼:金币飞向炮台
|
||
if (monster.fishenum != FishTypeEnum.Boss鱼 && monster.fishenum != FishTypeEnum.特殊Boss)
|
||
{
|
||
var award = Instantiate(
|
||
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward,
|
||
currGameType.subPanelParent.parent);
|
||
|
||
award.GetComponent<RectTransform>().anchoredPosition =
|
||
CoordinateTransformation.WorldPos_ToCanvas(
|
||
monster.transform.position,
|
||
UIManager.Instance.uiRoot.GetComponent<Canvas>());
|
||
|
||
var coinScript = award.GetComponent<BJBase>();
|
||
coinScript.Init(coinNum.Amount, v3Point);
|
||
coinScript.isBoss = true;
|
||
coinScript.isLocalCoin = isLocal;
|
||
coinScript.SetCoinCount();
|
||
}
|
||
|
||
//MonsterDieList.Instance.SmallHuangYiHuang(
|
||
// monster.transform.position,
|
||
// monster.monsterData.fishTypeConfig.fishNumber, 1000);
|
||
|
||
monster.DieFish();
|
||
fishDatas_hashCodekey.Remove(fishId);
|
||
}
|
||
/// <summary>
|
||
/// 不依赖鱼,只在屏幕内随机一个位置生成金币特效,然后飞回指定点。
|
||
/// </summary>
|
||
private void SpawnRandomCoinFX(RedPacketRecord coinNum, Vector3 targetWorldPos, bool isLocal = false)
|
||
{
|
||
// 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(
|
||
dieFXPrefabAward, // 或者用某个专门的"掉金币" prefab
|
||
currGameType.subPanelParent.parent);
|
||
|
||
var rect = award.GetComponent<RectTransform>();
|
||
rect.anchoredPosition = canvasPos;
|
||
|
||
// 4. 设置 CoinScript 的终点为 targetWorldPos
|
||
var coinScript = award.GetComponent<CoinScript>();
|
||
|
||
coinScript.Init(coinNum.Amount, targetWorldPos);
|
||
coinScript.isBoss = true;
|
||
coinScript.isLocalCoin = isLocal;
|
||
coinScript.SetCoinCount();
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 收到服务器 WebSocket 杀鱼通知,处理金币加分、特效、音效。
|
||
/// </summary>
|
||
public void Ws_DieFish(string msg)
|
||
{
|
||
var killFish = JsonConvert.DeserializeObject<WebSocketResponseBase<RoomKillNotice>>(msg)
|
||
.arguments[0];
|
||
|
||
Debug.Log($"[CreateFishManager] 杀鱼延迟:{DateTimeOffset.Now.ToUnixTimeSeconds() - killFish.timespan}s");
|
||
SitPosData sitData = null; //击杀玩家座位数据
|
||
foreach (var item in currGameType.list_Sits)
|
||
{
|
||
if (item.user == killFish.userId)
|
||
{
|
||
// 只有死的鱼正好是当前锁定目标时才清除
|
||
if (item.monCotl_Lock != null && item.monCotl_Lock.guid == killFish.fishId)
|
||
item.monCotl_Lock = null;
|
||
//item.lockFX.gameObject.SetActive(false);
|
||
sitData = item;
|
||
}
|
||
}
|
||
|
||
|
||
if (!fishDatas_hashCodekey.TryGetValue(killFish.fishId, out var monster)) return;
|
||
|
||
|
||
// 死亡粒子特效
|
||
Instantiate(
|
||
monster.effect_Die != null ? monster.effect_Die : dieFXPrefab,
|
||
monster.transform.position, Quaternion.identity, transform.parent);
|
||
int goldToAdd = killFish.gold; // 立即复制需要的数据
|
||
string fishId = killFish.fishId; // 如果需要的话
|
||
Action<int> onScoreAdd = (audioID) =>
|
||
{
|
||
sitData.goldNum += goldToAdd; // 使用副本,不再依赖 killFish 对象
|
||
sitData.tMP_GoldNum.text = sitData.goldNum.ToString();
|
||
AudioManager.Instance.PlayerFX(audioID);
|
||
};
|
||
//// 加分回调(在金币/加分面板飞完后执行)
|
||
//Action<int> onScoreAdd = (audioID) =>
|
||
//{
|
||
// var sitList = GetCurrentPanelSitList();
|
||
// sitData.goldNum += killFish.gold;
|
||
// sitData.tMP_GoldNum.text = sitData.goldNum.ToString();
|
||
// AudioManager.Instance.PlayerFX(audioID);
|
||
//};
|
||
bool dieFishCalled = false; // Boss case 内部已调用 DieFish,底部跳过
|
||
|
||
switch (monster.fishenum)
|
||
{
|
||
case FishTypeEnum.普通鱼:
|
||
{
|
||
// 普通鱼:金币飞向炮台
|
||
var award = Instantiate(
|
||
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward,
|
||
currGameType.subPanelParent.parent);
|
||
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);
|
||
|
||
coinScript.isLocalCoin = sitData.user == GameManager.Instance.user.id;
|
||
coinScript.isBoss = false;
|
||
coinScript.JiaFen = onScoreAdd;
|
||
coinScript.SetCoinCount();
|
||
break;
|
||
}
|
||
|
||
case FishTypeEnum.Boss鱼:
|
||
{
|
||
// Boss 鱼:显示浮动分数文本
|
||
var bossCtrl = (MonsterControl_BossBase)monster;
|
||
|
||
bossCtrl.v3 = sitData.transform.position;
|
||
bossCtrl.JiaFen = onScoreAdd; // 先设置 JiaFen,DieFish 的 lambda 才能捕获到
|
||
bossCtrl.isLocalCoin = sitData.user == GameManager.Instance.user.id; // ⭐ 金/银区分
|
||
bossCtrl.killerSitPos = sitData.posID; // ⭐ 爆奖杀小鱼用击杀者座位(含人机)
|
||
monster.DieFish(killFish); // Boss 重写 action_effect_DieAward,捕获上面的 JiaFen
|
||
dieFishCalled = true;
|
||
|
||
if (bossCtrl.isToUI)
|
||
{
|
||
bossCtrl.currEffect_DieAward = Instantiate(
|
||
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp,
|
||
currGameType.subPanelParent.parent);
|
||
}
|
||
else
|
||
{
|
||
// isToUI=false:世界空间特效,无 UI 父对象 → 挂 bjEffectRoot 统一管理
|
||
bossCtrl.currEffect_DieAward = Instantiate(
|
||
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp,
|
||
bjEffectRoot);
|
||
}
|
||
bossCtrl.action_effect_DieAward?.Invoke(bossCtrl.currEffect_DieAward);
|
||
|
||
}
|
||
|
||
break;
|
||
case FishTypeEnum.黄金鱼:
|
||
{
|
||
// 普通鱼:金币飞向炮台
|
||
var award = Instantiate(
|
||
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward,
|
||
currGameType.subPanelParent.parent);
|
||
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);
|
||
|
||
coinScript.isLocalCoin = sitData.user == GameManager.Instance.user.id;
|
||
coinScript.isBoss = false;
|
||
coinScript.JiaFen = onScoreAdd;
|
||
coinScript.SetCoinCount();
|
||
|
||
}
|
||
|
||
break;
|
||
case FishTypeEnum.特殊鱼:
|
||
{
|
||
// 普通鱼:金币飞向炮台
|
||
var award = Instantiate(
|
||
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAward);
|
||
try
|
||
{
|
||
award.GetComponent<RectTransform>().anchoredPosition =
|
||
CoordinateTransformation.WorldPos_ToCanvas(
|
||
monster.transform.position,
|
||
UIManager.Instance.uiRoot.GetComponent<Canvas>());
|
||
award.transform.SetParent(currGameType.subPanelParent.parent);
|
||
}
|
||
catch
|
||
{
|
||
award.transform.position = monster.transform.position;
|
||
|
||
}
|
||
|
||
|
||
var coinScript = award.GetComponent<BJBase>();
|
||
coinScript.startPoint = award.transform.position;
|
||
coinScript.Init(killFish.gold, sitData.transform.position, killFish.userId, sitData.posID, killFish.param);
|
||
coinScript.isLocalCoin = sitData.user == GameManager.Instance.user.id;
|
||
coinScript.startPoint = award.transform.position;
|
||
coinScript.isBoss = false;
|
||
coinScript.JiaFen = onScoreAdd;
|
||
coinScript.SetCoinCount();
|
||
|
||
|
||
// // Boss 鱼:显示浮动分数文本
|
||
// var bossCtrl = (MonsterControl_BossBase)monster;
|
||
|
||
// 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;
|
||
}
|
||
break;
|
||
case FishTypeEnum.特殊Boss:
|
||
{
|
||
// Boss 鱼:显示浮动分数文本
|
||
var bossCtrl = (MonsterControl_BossBase)monster;
|
||
|
||
bossCtrl.v3 = sitData.transform.position;
|
||
bossCtrl.JiaFen = onScoreAdd; // 先设置 JiaFen
|
||
bossCtrl.isLocalCoin = sitData.user == GameManager.Instance.user.id; // ⭐ 金/银区分
|
||
bossCtrl.killerSitPos = sitData.posID; // ⭐ 爆奖杀小鱼用击杀者座位(含人机)
|
||
monster.DieFish(killFish); // Boss 重写 action_effect_DieAward
|
||
dieFishCalled = true;
|
||
|
||
if (bossCtrl.isToUI)
|
||
{
|
||
bossCtrl.currEffect_DieAward = Instantiate(
|
||
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp,
|
||
currGameType.subPanelParent.parent);
|
||
}
|
||
else
|
||
{
|
||
// isToUI=false:世界空间特效,无 UI 父对象 → 挂 bjEffectRoot 统一管理
|
||
bossCtrl.currEffect_DieAward = Instantiate(
|
||
monster.effect_DieAward != null ? monster.effect_DieAward : dieFXPrefabAwardTmp,
|
||
bjEffectRoot);
|
||
}
|
||
bossCtrl.action_effect_DieAward?.Invoke(bossCtrl.currEffect_DieAward);
|
||
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
if (!dieFishCalled)
|
||
monster.DieFish(killFish);
|
||
|
||
MonsterDieList.Instance.SmallHuangYiHuang(
|
||
monster.transform.position,
|
||
monster.monsterData.fishTypeConfig.fishNumber, 1000);
|
||
|
||
}
|
||
#endregion
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// ================================================================
|
||
// 工具:获取当前面板座位列表
|
||
// ================================================================
|
||
|
||
/// <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;
|
||
case GameType.YYSC:
|
||
return UIManager.Instance.Get<GameMainPanel_FishBase>(UIPanel.MainGamePanelYYSC).list_Sits;
|
||
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;
|
||
}
|
||
}
|
||
|
||
#region 冰冻技能
|
||
/// <summary>
|
||
/// 冰冻:记录所有鱼当前状态并强制切到 Idle(暂停移动+动画)
|
||
/// </summary>
|
||
public void FreezeAllFish()
|
||
{
|
||
// ⭐ 守卫:已冻结时直接返回,避免重复调用覆盖已记录的状态
|
||
// (其他客户端会先后被 OnUsePropNotice 和 SyncGameData_FreezeFish 触发两次)
|
||
if (isFrozen) return;
|
||
|
||
isFrozen = true;
|
||
// ⭐ 两个字典必须同步清空,否则 frozenFishIsMoves 残留旧数据
|
||
// 会导致下次解冻时用旧值覆盖 isMove 但不恢复 currState
|
||
frozenFishStates.Clear();
|
||
frozenFishIsMoves.Clear();
|
||
|
||
// 只遍历全量字典 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;
|
||
frozenFishIsMoves[m.guid] = m.isMove;
|
||
// 定格
|
||
m.isMove = false;
|
||
m.ChangeState(CharacterStateType.idle);
|
||
m.SetFreezeEffect(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <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;
|
||
|
||
m.SetFreezeEffect(false);
|
||
|
||
if (frozenFishStates.TryGetValue(m.guid, out var prevState))
|
||
{
|
||
// 被本次冰冻冻结的鱼:恢复冰冻前的 isMove 和 currState
|
||
m.isMove = frozenFishIsMoves.TryGetValue(m.guid, out var prevIsMove)
|
||
? prevIsMove
|
||
: m.baseMmove;
|
||
m.ChangeState(prevState);
|
||
}
|
||
else
|
||
{
|
||
// ⭐ 不在冰冻记录里的鱼(冰冻期间新激活的,OnEnable 把 isMove 设成了 false)
|
||
// 用 baseMmove 恢复预制体默认状态,避免解冻后永远不动
|
||
m.isMove = m.baseMmove;
|
||
if (m.baseMmove)
|
||
m.ChangeState(CharacterStateType.move);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ⭐ 两个字典同步清空
|
||
frozenFishStates.Clear();
|
||
frozenFishIsMoves.Clear();
|
||
}
|
||
#endregion
|
||
}
|