1
This commit is contained in:
@@ -3,7 +3,7 @@ public class ConstEventString
|
||||
{
|
||||
public const string SyncGameData_Rotate = "SyncGameData_Rotate";
|
||||
public const string SyncGameData_FireBullet = "SyncGameData_FireBullet";
|
||||
public const string SyncGameData_CreateFish = "SyncGameData_CreateFish";
|
||||
// SyncGameData_CreateFish 已废弃:鱼创建改由服务端 OnFishSpawn 事件下发
|
||||
public const string SyncGameData_ChangeTargetMonster = "SyncGameData_ChangeTargetMonster";
|
||||
public const string SyncGameData_RendromDieFish = "SyncGameData_RenderDieFish";
|
||||
public const string SyncGameData_TurretState = "SyncGameData_TurretState";
|
||||
@@ -12,6 +12,29 @@ public class ConstEventString
|
||||
public const string SyncGameData_ChangeCannonMultiple = "SyncGameData_ChangeCannonMultiple";
|
||||
public const string SyncGameData_ChangeWing = "SyncGameData_ChangeWing";
|
||||
public const string SyncGameData_BotFireAngle = "SyncGameData_BotFireAngle";
|
||||
public const string SyncGameData_RequestSyncFish = "SyncGameData_RequestSyncFish";
|
||||
}
|
||||
// SyncGameData_RequestSyncFish 已废弃:鱼同步改由服务端 OnFishSpawn(type=sync) 下发
|
||||
|
||||
// ================================================================
|
||||
// 服务端 → 客户端事件 (新协议)
|
||||
// ================================================================
|
||||
/// <summary>服务端出鱼事件(破坏性替代 createFish 帧同步)</summary>
|
||||
public const string OnFishSpawn = "OnFishSpawn";
|
||||
/// <summary>鱼列表刷新</summary>
|
||||
public const string OnFishList = "OnFishList";
|
||||
/// <summary>冰冻道具生效(40003),鱼存活时间延长 15 秒,客户端写死 15s 倒计时</summary>
|
||||
public const string FishFrozen = "FishFrozen";
|
||||
/// <summary>玩家挂机通知</summary>
|
||||
public const string PlayerAfkNotice = "PlayerAfkNotice";
|
||||
/// <summary>玩家取消挂机通知</summary>
|
||||
public const string PlayerCancelAfkNotice = "PlayerCancelAfkNotice";
|
||||
/// <summary>全局击杀公告(真人击杀/机器人模拟)</summary>
|
||||
public const string KillNotice = "KillNotice";
|
||||
/// <summary>机器人退出房间</summary>
|
||||
public const string BotLeaveRoomNotice = "BotLeaveRoomNotice";
|
||||
/// <summary>房主断线,其他玩家可接受机器人控制权</summary>
|
||||
public const string HostDisconnect = "HostDisconnect";
|
||||
/// <summary>座位变更</summary>
|
||||
public const string OnSeatChange = "OnSeatChange";
|
||||
/// <summary>房主控制权移交</summary>
|
||||
public const string ChangeControllerUser = "ChangeControllerUser";
|
||||
}
|
||||
|
||||
@@ -189,8 +189,7 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
private int hjQuanZhongCount; // 本轮同时启用黄金鱼数量
|
||||
|
||||
|
||||
/// <summary>是否开启放鱼(false 时 Update 不执行生成逻辑)</summary>
|
||||
public bool fangzhu;
|
||||
// fangzhu 字段已删除:鱼创建改由服务端 OnFishSpawn 统一下发,客户端无需房主出鱼
|
||||
public bool isResources = false;
|
||||
// ================================================================
|
||||
// 懒加载属性
|
||||
@@ -251,36 +250,28 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
fishRoot = new GameObject("FishRoot").transform;
|
||||
if (poolRoot == null)
|
||||
fishRoot = new GameObject("poolRoot").transform;
|
||||
// 监听服务器下发的创鱼事件
|
||||
EventCenter.Instance.AddEventListener<SyncGameData>(
|
||||
ConstEventString.SyncGameData_CreateFish, CreateFish);
|
||||
|
||||
// 监听新玩家"请求同步鱼"事件:房主收到后广播场上活鱼
|
||||
EventCenter.Instance.AddEventListener<SyncGameData>(
|
||||
ConstEventString.SyncGameData_RequestSyncFish, OnRequestSyncFish);
|
||||
|
||||
|
||||
|
||||
// ⭐ 鱼创建改由服务端 OnFishSpawn 事件统一下发,客户端不再监听 createFish 帧同步
|
||||
EventCenter.Instance.AddEventListener<OnFishSpawnData>(
|
||||
ConstEventString.OnFishSpawn, OnFishSpawn);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Update:定时调度 + Job 驱动生成
|
||||
// Update:冰冻时不做任何事(鱼创建由服务端 OnFishSpawn 驱动)
|
||||
// ================================================================
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isFrozen)
|
||||
isCreateFish = BroadCastCreateFish();
|
||||
// 鱼创建由服务端 OnFishSpawn 事件驱动,客户端 Update 不再出鱼
|
||||
// (保留 isFrozen 字段供 FreezeAllFish/UnfreezeAllFish 使用)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
EventCenter.Instance.RemoveEventListener<SyncGameData>(
|
||||
ConstEventString.SyncGameData_CreateFish, CreateFish);
|
||||
EventCenter.Instance.RemoveEventListener<SyncGameData>(
|
||||
ConstEventString.SyncGameData_RequestSyncFish, OnRequestSyncFish);
|
||||
EventCenter.Instance.RemoveEventListener<OnFishSpawnData>(
|
||||
ConstEventString.OnFishSpawn, OnFishSpawn);
|
||||
|
||||
// 释放 NativeArray,避免内存泄漏
|
||||
DisposeNativeArrays();
|
||||
@@ -594,656 +585,97 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 创建鱼 广播部分
|
||||
#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>
|
||||
/// <returns>此玩家是否可以创建鱼</returns>
|
||||
private bool BroadCastCreateFish()
|
||||
public void OnFishSpawn(OnFishSpawnData data)
|
||||
{
|
||||
if (!isResources)
|
||||
if (data == null || data.fish == null || data.fish.Count == 0) return;
|
||||
if (isFrozen) return; // 冰冻中不创建鱼
|
||||
if (spawnDic == null) return; // 场景卸载后忽略
|
||||
|
||||
foreach (var item in data.fish)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 未初始化 或 未开启放鱼 → 跳过
|
||||
if (spawnList == null || !fangzhu) return false;
|
||||
if (item == null) continue;
|
||||
if (string.IsNullOrEmpty(item.fishNumber)) continue;
|
||||
|
||||
float dt = Time.deltaTime;
|
||||
// guid 用服务端 fishId
|
||||
string guid = item.fishId.ToString();
|
||||
|
||||
// ---- 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. 普通鱼定时批量激活 ----
|
||||
TickPTFishSpawn(dt);
|
||||
|
||||
// ---- 5. 黄金鱼定时激活 ----
|
||||
TickHJFishSpawn(dt);
|
||||
|
||||
// ---- 5. 特殊鱼定时激活 ----
|
||||
TickTSFishSpawn(dt);
|
||||
|
||||
|
||||
// ---- 6. 特殊boss定时激活 ----
|
||||
TickTSBossSpawn(dt);
|
||||
|
||||
// ---- 7. 遍历 shouldSpawn,向服务器发送生成请求 ----
|
||||
ProcessSpawnRequests();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 定时 Tick 方法(由 Update 调用)
|
||||
// ================================================================
|
||||
|
||||
/// <summary>每 25 秒随机激活一条 Boss 鱼。</summary>
|
||||
private void TickBossSpawn(float dt)
|
||||
{
|
||||
bossTimer -= dt;
|
||||
if (bossTimer > 0f) return;
|
||||
|
||||
bossCount = Random.Range(bossCreateData.countRange[0], bossCreateData.countRange[1]);
|
||||
bossTimer = Random.Range(bossCreateData.timeRange[0], bossCreateData.timeRange[1]);
|
||||
|
||||
//bossTimer = 3f;
|
||||
if (!fishTypeConfig.TryGetValue(FishTypeEnum.Boss鱼, out var bossList) || bossList.Count == 0)
|
||||
return;
|
||||
foreach (var item in bossList)
|
||||
{
|
||||
//if (item.fishNumber == "11026")
|
||||
// item.isCreate = 2;
|
||||
|
||||
//if (item.fishNumber == "11035")
|
||||
// item.isCreate = 2;
|
||||
// if (item.fishNumber == "11030")
|
||||
// item.isCreate = 2;
|
||||
|
||||
}
|
||||
|
||||
StartCoroutine(buchongfu(bossList, bossCount, fishDatas_hashCodekey_Boss, bossRecentList, bossRecentMax));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <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;
|
||||
|
||||
//foreach (var item in tsbossList)
|
||||
//{
|
||||
// if (item.fishNumber == "11041")
|
||||
// {
|
||||
// item.isCreate = 2;
|
||||
// }
|
||||
//}
|
||||
StartCoroutine(buchongfu(tsbossList, tsbossCount, fishDatas_hashCodekey_TS_Boss, tsbossRecentList, tsbossRecentMax));
|
||||
|
||||
|
||||
}
|
||||
private RoomFishDetailConfig GetRandomFishByWeight(List<RoomFishDetailConfig> configs)
|
||||
{
|
||||
if (configs == null || configs.Count == 0) return null;
|
||||
|
||||
int totalWeight = configs.Sum(c => c.fishWeight);
|
||||
int randWeight = Random.Range(0, totalWeight + 1);
|
||||
|
||||
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;
|
||||
if (randWeight < 0) // 注意这里是 <= 0
|
||||
{
|
||||
chosen = cfg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (chosen != null)
|
||||
{
|
||||
result.Add(chosen);
|
||||
candidates.Remove(chosen); // 移除已选,保证不重复
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
IEnumerator buchongfu(List<RoomFishDetailConfig> bossList, int count, Dictionary<string, MonsterContral> targetDict, List<string> recentList = null, int recentMax = 0)
|
||||
{
|
||||
var batchPicked = new HashSet<string>(); // 本轮已选,防止同批重复
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
// 1. 先筛选出未重复的候选集合(排除场上已存在的同 fishNumber 的鱼 + 最近出现过的 + 本轮已选的)
|
||||
var candidates = bossList
|
||||
.Where(b => !targetDict.Values
|
||||
.Any(existing => existing.fishTypeID.ToString() == b.fishNumber))
|
||||
.Where(b => !batchPicked.Contains(b.fishNumber))
|
||||
.ToList();
|
||||
|
||||
// 排除最近出现过的鱼
|
||||
if (recentList != null && recentList.Count > 0)
|
||||
candidates = candidates.Where(c => !recentList.Contains(c.fishNumber)).ToList();
|
||||
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
// 没有候选时清空最近列表 + 本轮记录重试
|
||||
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;
|
||||
}
|
||||
|
||||
// 2. 在候选集合里做加权随机
|
||||
var chosen = GetRandomFishByWeight(candidates);
|
||||
batchPicked.Add(chosen.fishNumber);
|
||||
|
||||
// 3. 记录到最近列表
|
||||
if (recentList != null && recentMax > 0)
|
||||
{
|
||||
recentList.Add(chosen.fishNumber);
|
||||
while (recentList.Count > recentMax)
|
||||
recentList.RemoveAt(0);
|
||||
}
|
||||
|
||||
// 4. 标记激活
|
||||
chosen.isCreate = 2;
|
||||
|
||||
yield return null; // 每帧只激活一条,避免同帧生成过多
|
||||
}
|
||||
}
|
||||
|
||||
//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("===【调试结束】本轮生成完成 ===");
|
||||
//}
|
||||
|
||||
|
||||
|
||||
/// <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)
|
||||
// guid 去重:已有此 guid 的鱼跳过
|
||||
if (!string.IsNullOrEmpty(guid) &&
|
||||
fishDatas_hashCodekey != null &&
|
||||
fishDatas_hashCodekey.ContainsKey(guid))
|
||||
continue;
|
||||
|
||||
// 随机选一条配置并标记为待生成
|
||||
var chosen = lineList[Random.Range(0, lineList.Count)];
|
||||
chosen.isCreate = 2;
|
||||
|
||||
|
||||
|
||||
|
||||
// 同步更新 spawnDic 中的状态
|
||||
if (spawnDic.TryGetValue(chosen.fishNumber, out var spawnEntry))
|
||||
spawnEntry.isCreate = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>每 3 秒随机批量激活若干条普通鱼。</summary>
|
||||
private void TickPTFishSpawn(float dt)
|
||||
{
|
||||
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 个不重复下标
|
||||
var indices = GetRandomFishByWeight(normalList, ptCount);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
// 构造 FrameData_CreateFish 复用 SpawnFishMainThread
|
||||
var createFishData = new FrameData_CreateFish
|
||||
{
|
||||
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)
|
||||
{
|
||||
var ft = FishDatas[fish.fishNumber];
|
||||
var pathList = ft.pathList;
|
||||
if (pathList != null && pathList.Count > 0)
|
||||
{
|
||||
// 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)];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
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)];
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进度条加载结束时房主调用,创建一批初始鱼(进度 0.2~0.8),让屏幕开局就有鱼不会显得空。
|
||||
/// 直接复用 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。
|
||||
/// </summary>
|
||||
/// <param name="count">要创建的鱼数量</param>
|
||||
public void CreateInitialFishes(int count)
|
||||
{
|
||||
if (!fangzhu) return;
|
||||
if (fishTypeConfig == null) return;
|
||||
if (!fishTypeConfig.TryGetValue(FishTypeEnum.普通鱼, out var normalList) || normalList.Count == 0) return;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// ⭐ 用原有的 GetRandomFishByWeight 按权重选鱼
|
||||
var fish = GetRandomFishByWeight(normalList);
|
||||
if (fish == null) continue;
|
||||
|
||||
// 从该鱼的路径列表里随机选一条
|
||||
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)];
|
||||
|
||||
// ⭐ 通过 time 字段控制创建时间,让 progress ∈ [0.2, 0.8]
|
||||
float progress = Random.Range(0.2f, 0.8f);
|
||||
long timeOffsetMs = (long)(progress * fish.times * 1000f);
|
||||
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,
|
||||
guid = Guid.NewGuid().ToString(),
|
||||
moveLineId = pathId.ToString()
|
||||
}
|
||||
fishNumber = item.fishNumber,
|
||||
guid = guid,
|
||||
moveLineId = item.routeId.ToString()
|
||||
};
|
||||
|
||||
// ⭐ 走原有的帧同步广播链路
|
||||
WebSocketSendList.SyncGameData(sgd);
|
||||
// 用 data.timestamp 作为生成时间,SpawnFishMainThread 内部会算 moveTime
|
||||
SpawnFishMainThread(createFishData, data.timestamp);
|
||||
|
||||
// expireAt 到期自动移除(remainingSeconds 秒后销毁)
|
||||
if (item.remainingSeconds > 0)
|
||||
StartCoroutine(ExpireFishCoroutine(guid, item.remainingSeconds));
|
||||
}
|
||||
|
||||
Debug.Log($"[CreateFishManager] 初始鱼已广播: {count} 条 (房主,走原有出鱼机制)");
|
||||
Debug.Log($"[CreateFishManager] OnFishSpawn type={data.type}, count={data.fish.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 房主把当前场上所有活鱼广播一遍,用于新玩家加入房间时同步场上鱼状态。
|
||||
/// 直接遍历 fishDatas_hashCodekey(全部活鱼),反推 time 字段让新玩家收到后
|
||||
/// 通过 (CurrentTimestampMs - time)/1000f 算出与房主一致的 moveTime,
|
||||
/// 鱼会出现在路径的正确中段位置,与老玩家看到的一致。
|
||||
/// guid 复用原 guid,保证后续杀鱼/锁定帧同步不会失同步。
|
||||
/// 鱼过期自动移除协程。
|
||||
/// remainingSeconds 秒后调用 Id_DieFish_NotFX 销毁鱼(无特效)。
|
||||
/// </summary>
|
||||
public void BroadcastExistingFishes()
|
||||
private IEnumerator ExpireFishCoroutine(string guid, int remainingSeconds)
|
||||
{
|
||||
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;
|
||||
|
||||
// ⭐ 跳过鱼阵鱼:鱼阵鱼 guid 是 原guid+i,单条广播会让新玩家收到后走
|
||||
// SpawnFishArray 又生成 N 条,导致鱼阵指数爆炸(16×16=256)。
|
||||
// 鱼阵鱼的同步需要鱼阵级别的广播(发鱼阵配置+原guid+数量),
|
||||
// 这里不处理,鱼阵鱼不同步只影响视觉阵型,不影响游戏核心逻辑。
|
||||
if (mc.isyz) continue;
|
||||
|
||||
// 总时长,用于过滤快死的鱼(避免新玩家刚进入就看到鱼死)
|
||||
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} 条 (房主,新玩家加入同步)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 房主收到新玩家"请求同步鱼"事件后,广播场上活鱼。
|
||||
/// 新玩家资源加载完成才会发请求,保证收到广播时 spawnDic 已初始化。
|
||||
/// 非房主收到直接忽略。
|
||||
/// </summary>
|
||||
private void OnRequestSyncFish(SyncGameData syncGameData)
|
||||
{
|
||||
// 仅房主响应,且不响应自己发的请求(syncGameData.sitPos 是发送者座位)
|
||||
if (!fangzhu) return;
|
||||
BroadcastExistingFishes();
|
||||
yield return new WaitForSeconds(remainingSeconds);
|
||||
Id_DieFish_NotFX(guid);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 创建鱼
|
||||
/// <summary>
|
||||
/// AI优化过的
|
||||
/// </summary>
|
||||
/// <param name="syncGameData"></param>
|
||||
public void CreateFish(SyncGameData syncGameData)
|
||||
{
|
||||
if (isFrozen) return; // 冰冻中不创建鱼
|
||||
// ⭐ 场景卸载/CloseSpawnSystem 后 spawnDic 会变 null,此时收到的广播直接忽略
|
||||
if (spawnDic == null) return;
|
||||
var rotateData = FrameDataParser.Parse<FrameData_CreateFish>(syncGameData.frameData);
|
||||
if (rotateData == null) return;
|
||||
#region 旧客户端出鱼链路(已删除)
|
||||
// 以下方法已删除,鱼创建改由服务端 OnFishSpawn 事件下发:
|
||||
// - BroadCastCreateFish / TickBossSpawn / TickTSBossSpawn / TickFishArraySpawn
|
||||
// - TickPTFishSpawn / TickHJFishSpawn / TickTSFishSpawn
|
||||
// - ProcessSpawnRequests / SendCreateFishRequest
|
||||
// - CreateInitialFishes / BroadcastExistingFishes / OnRequestSyncFish
|
||||
// - CreateFish(SyncGameData)
|
||||
// 保留方法:SpawnFishMainThread / OnlyFish / SpawnFishArray 等(被 OnFishSpawn 调用)
|
||||
#endregion
|
||||
|
||||
// ⭐ guid 去重:房主广播活鱼给新玩家时,服务器会广播给所有客户端(含房主自己)。
|
||||
// 老玩家已存在该 guid 的鱼 → 跳过,避免重复 Spawn 同一条鱼。
|
||||
// 仅本地无该 guid 的客户端(新加入玩家)才会真正 Spawn。
|
||||
if (!string.IsNullOrEmpty(rotateData.guid) &&
|
||||
fishDatas_hashCodekey != null &&
|
||||
fishDatas_hashCodekey.ContainsKey(rotateData.guid))
|
||||
return;
|
||||
// ================================================================
|
||||
// 旧定时 Tick 方法(已删除,鱼创建改由服务端 OnFishSpawn 事件下发)
|
||||
// ================================================================
|
||||
// TickBossSpawn / TickTSBossSpawn / TickFishArraySpawn / TickPTFishSpawn
|
||||
// TickHJFishSpawn / TickTSFishSpawn / buchongfu / ProcessSpawnRequests
|
||||
// SendCreateFishRequest / CreateInitialFishes / BroadcastExistingFishes
|
||||
// OnRequestSyncFish / CreateFish(SyncGameData)
|
||||
// —— 全部删除,改由 OnFishSpawn(OnFishSpawnData) 接收服务端出鱼事件 ——
|
||||
|
||||
// ================================================================
|
||||
// 单体鱼 / 鱼阵生成(被 OnFishSpawn 调用)
|
||||
// ================================================================
|
||||
|
||||
SpawnFishMainThread(rotateData, syncGameData.time);
|
||||
}
|
||||
/// <summary>
|
||||
/// 主线程实例化鱼对象(单体鱼 / 鱼阵分发)。
|
||||
/// </summary>
|
||||
@@ -1524,8 +956,6 @@ public class CreateFishManager : MonoSingleton<CreateFishManager>
|
||||
#endregion
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 结束游戏退出房间
|
||||
/// <summary>退出房间:关闭生成系统。</summary>
|
||||
public void ExitRoom()
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Assets.MyGame.Scripts.Net
|
||||
//public const string baseURL = "http://game5241.wzaxr.top";
|
||||
#if UNITY_EDITOR
|
||||
//public const string baseURL = "http://8.137.163.153";
|
||||
public const string baseURL = "http://47.109.34.50";
|
||||
public const string baseURL = "http://47.109.132.12";
|
||||
|
||||
#else
|
||||
//public const string baseURL = "http://8.137.8.46";
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Assets.MyGame.Scripts.Net.WebResponses
|
||||
none = 0,
|
||||
rotateTurrect=1,
|
||||
fireBullet=2,
|
||||
createFish=3,
|
||||
// createFish=3 已废弃:鱼创建改由服务端 OnFishSpawn 事件下发
|
||||
changeAutoTarget=4,
|
||||
randomDieFish=5,
|
||||
changeTurretState=6,
|
||||
@@ -52,8 +52,7 @@ namespace Assets.MyGame.Scripts.Net.WebResponses
|
||||
freezeFish=8,
|
||||
changeCannonMultiple=9,
|
||||
changeWing=10,
|
||||
/// <summary>新玩家资源加载完成,请求房主广播场上活鱼</summary>
|
||||
requestSyncFish=11
|
||||
// requestSyncFish=11 已废弃:鱼同步改由服务端 OnFishSpawn(type=sync) 事件下发
|
||||
}
|
||||
[Serializable]
|
||||
public class FireCannonSendData
|
||||
@@ -145,4 +144,93 @@ namespace Assets.MyGame.Scripts.Net.WebResponses
|
||||
{
|
||||
public int cannonMultiple;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// OnFishSpawn 事件数据结构 (服务端出鱼,破坏性替代 createFish 帧同步)
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 服务端出鱼事件 OnFishSpawn 的数据载体。
|
||||
/// type:
|
||||
/// "init" - 首次出鱼(玩家进入房间后服务端推送初始鱼)
|
||||
/// "spawn" - 定时出鱼(游戏中持续刷怪)
|
||||
/// "sync" - 重连/取消挂机同步(把当前场上活鱼推送给回归玩家)
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class OnFishSpawnData
|
||||
{
|
||||
/// <summary>"init" | "spawn" | "sync"</summary>
|
||||
public string type;
|
||||
/// <summary>服务器当前时间戳(毫秒)</summary>
|
||||
public long timestamp;
|
||||
/// <summary>本批次出鱼列表</summary>
|
||||
public List<OnFishSpawnFishItem> fish;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OnFishSpawn 单条鱼描述。
|
||||
/// 客户端据此调用 SpawnFishMainThread 生成鱼实例。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class OnFishSpawnFishItem
|
||||
{
|
||||
/// <summary>服务端分配的全局唯一鱼 ID(作为 guid 使用)</summary>
|
||||
public long fishId;
|
||||
/// <summary>鱼编号(配置 fishNumber)</summary>
|
||||
public string fishNumber;
|
||||
/// <summary>路径编号</summary>
|
||||
public int routeId;
|
||||
/// <summary>鱼阵编号(非鱼阵时为空字符串)</summary>
|
||||
public string fishArrayNumber;
|
||||
/// <summary>鱼阵的 Boss 鱼编号</summary>
|
||||
public string bossFishNumber;
|
||||
/// <summary>鱼阵普通鱼数量</summary>
|
||||
public int fishCount;
|
||||
/// <summary>鱼阵 Boss 数量</summary>
|
||||
public int bossCount;
|
||||
/// <summary>鱼过期时间戳(毫秒),到期自动移除</summary>
|
||||
public long expireAt;
|
||||
/// <summary>鱼剩余存活秒数(客户端做倒计时用)</summary>
|
||||
public int remainingSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 机器人退出房间通知数据 BotLeaveRoomNotice: { userId }
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class BotLeaveRoomNoticeData
|
||||
{
|
||||
public string userId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 玩家挂机/取消挂机通知数据: { userId }
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PlayerAfkNoticeData
|
||||
{
|
||||
public string userId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局击杀公告 KillNotice 数据。
|
||||
/// 真人击杀时只填基础字段;机器人模拟时额外填 fishName/multiplier/sceneName。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class KillNoticeData
|
||||
{
|
||||
/// <summary>鱼编号</summary>
|
||||
public string FishId;
|
||||
/// <summary>玩家名</summary>
|
||||
public string userName;
|
||||
/// <summary>金币</summary>
|
||||
public long gold;
|
||||
// —— 机器人模拟时额外字段 ——
|
||||
/// <summary>鱼名(机器人模拟时填充)</summary>
|
||||
public string fishName;
|
||||
/// <summary>倍数(机器人模拟时填充)</summary>
|
||||
public int? multiplier;
|
||||
/// <summary>场景名(机器人模拟时填充)</summary>
|
||||
public string sceneName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Assets.MyGame.Scripts.Net
|
||||
//private const string baseUrl = "ws://game5241.wzaxr.top";
|
||||
#if UNITY_EDITOR
|
||||
//private const string baseUrl = "ws://8.137.163.153";
|
||||
private const string baseUrl = "ws://47.109.34.50";
|
||||
private const string baseUrl = "ws://47.109.132.12";
|
||||
|
||||
#else
|
||||
//private const string baseUrl = "ws://8.137.8.46";
|
||||
|
||||
@@ -453,31 +453,20 @@ namespace Assets.MyGame.Scripts.Net
|
||||
break;
|
||||
case "ChangeControllerUser":
|
||||
{
|
||||
// ⭐ 房主控制权移交:仅维护 roomOwnerSit,不再设置 fangzhu
|
||||
// (鱼创建已由服务端 OnFishSpawn 统一下发,客户端无需房主出鱼)
|
||||
var userId = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
|
||||
if (userId == GameManager.Instance.user.id)
|
||||
{
|
||||
// 本地玩家成为房主:开启出鱼 + 同步本地房主引用
|
||||
CreateFishManager.Instance.fangzhu = true;
|
||||
if (SitPosData.localPlayerSit != null)
|
||||
SitPosData.roomOwnerSit = SitPosData.localPlayerSit;
|
||||
}
|
||||
else if (userId != null && userId.StartsWith("bot_"))
|
||||
{
|
||||
// 房主是人机:人机无法真正发包,由本地首个非人机玩家代发
|
||||
// 房主是人机:由本地首个非人机玩家代行房主显示职责
|
||||
SitPosData.TransferRoomOwner();
|
||||
CreateFishManager.Instance.fangzhu =
|
||||
SitPosData.roomOwnerSit != null &&
|
||||
SitPosData.roomOwnerSit.user == GameManager.Instance.user.id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ⭐ 只在 userId 非空且确实是真人玩家时才停止本地出鱼
|
||||
// userId 为空(异常情况)时保持原状,避免误关出鱼导致 bot 退出后不出鱼
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
CreateFishManager.Instance.fangzhu = false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "Exception":
|
||||
{
|
||||
@@ -590,18 +579,8 @@ namespace Assets.MyGame.Scripts.Net
|
||||
break;
|
||||
|
||||
case "KillNotice":
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
|
||||
if (data == null) break;
|
||||
// { FishId, userName, gold }
|
||||
string userName = data.TryGetValue("userName", out var un) ? un?.ToString() : "";
|
||||
string fishId = data.TryGetValue("FishId", out var fi) ? fi?.ToString() : "";
|
||||
long gold = data.TryGetValue("gold", out var g) ? Convert.ToInt64(g) : 0;
|
||||
string roomName = GameManager.Instance.gameData.currRoomData?.roomName ?? "";
|
||||
string roomLevel = GameManager.Instance.gameData.currRoomData?.roomLevel.ToString() ?? "";
|
||||
string msgText = $"恭喜用户{userName} 在 {roomName} {roomLevel}场 捕获了{fishId}*{gold}倍";
|
||||
//UIManager.Instance.ShowBoardGongGao(msgText);
|
||||
}
|
||||
// 全局击杀公告(真人击杀/机器人模拟)
|
||||
WebSocketReponseList.KillNotice(msg);
|
||||
break;
|
||||
|
||||
case "OnFishExpired":
|
||||
@@ -612,49 +591,48 @@ namespace Assets.MyGame.Scripts.Net
|
||||
break;
|
||||
|
||||
case "OnFishList":
|
||||
{
|
||||
var fishList = JsonConvert.DeserializeObject<WebSocketResponseBase<object[]>>(msg).arguments[0];
|
||||
Debug.Log($"[FishGameHub] 鱼列表刷新, 数量={fishList?.Length}");
|
||||
}
|
||||
// 鱼列表刷新
|
||||
WebSocketReponseList.OnFishList(msg);
|
||||
break;
|
||||
|
||||
// ======== FishGameHub / FishRoomHub 补充事件 ========
|
||||
|
||||
case "BotLeaveRoomNotice":
|
||||
{
|
||||
// 机器人退出房间,数据 { userId }
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
|
||||
string botUserId = data != null && data.TryGetValue("userId", out var buid) ? buid?.ToString() : "";
|
||||
Debug.Log($"[FishGameHub] 机器人退出: {botUserId}");
|
||||
WebSocketReponseList.BotLeaveRoom(botUserId);
|
||||
}
|
||||
// 机器人退出房间,数据 { userId }
|
||||
WebSocketReponseList.BotLeaveRoomNotice(msg);
|
||||
break;
|
||||
|
||||
case "HostDisconnect":
|
||||
{
|
||||
// 房主断线,其他玩家可接受机器人控制权
|
||||
Debug.Log("[FishGameHub] 房主断线,可接受机器人控制权");
|
||||
WebSocketReponseList.HostDisconnect();
|
||||
}
|
||||
// 房主断线,其他玩家可接受机器人控制权
|
||||
WebSocketReponseList.HostDisconnectNotice(msg);
|
||||
break;
|
||||
|
||||
case "OnSeatChange":
|
||||
{
|
||||
// 座位变更 { type, userId, position, showWaiting? }
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
|
||||
if (data != null)
|
||||
{
|
||||
// 注意:JSON 字段可能是 long/double/string/bool 等任意类型,
|
||||
// 直接 Convert.ToInt32(object) 对字符串会走 int.Parse 抛 FormatException。
|
||||
// 这里统一通过 ToString() + TryParse 容错。
|
||||
int seatType = data.TryGetValue("type", out var t) && int.TryParse(t?.ToString(), out var st) ? st : 0;
|
||||
string userId = data.TryGetValue("userId", out var u) ? u?.ToString() : "";
|
||||
int position = data.TryGetValue("position", out var p) && int.TryParse(p?.ToString(), out var pos) ? pos : -1;
|
||||
bool showWaiting = data.TryGetValue("showWaiting", out var sw) && (sw is bool b ? b : (sw != null && bool.TryParse(sw.ToString(), out var bv) && bv));
|
||||
Debug.Log($"[FishGameHub] 座位变更: type={seatType}, userId={userId}, position={position}, showWaiting={showWaiting}");
|
||||
WebSocketReponseList.SeatChange(seatType, userId, position, showWaiting);
|
||||
}
|
||||
}
|
||||
// 座位变更 { type, userId, position, showWaiting? }
|
||||
WebSocketReponseList.OnSeatChange(msg);
|
||||
break;
|
||||
|
||||
// ======== 新协议事件 ========
|
||||
|
||||
case "OnFishSpawn":
|
||||
// ⭐ 服务端出鱼事件(破坏性替代 createFish 帧同步)
|
||||
// type: "init"=首次出鱼 / "spawn"=定时出鱼 / "sync"=重连或取消挂机同步
|
||||
WebSocketReponseList.OnFishSpawn(msg);
|
||||
break;
|
||||
|
||||
case "FishFrozen":
|
||||
// ⭐ 冰冻道具(40003)生效:鱼存活时间延长 15 秒,客户端写死 15s 倒计时
|
||||
WebSocketReponseList.FishFrozen(msg);
|
||||
break;
|
||||
|
||||
case "PlayerAfkNotice":
|
||||
// 玩家挂机通知 { userId }
|
||||
WebSocketReponseList.PlayerAfkNotice(msg);
|
||||
break;
|
||||
|
||||
case "PlayerCancelAfkNotice":
|
||||
// 玩家取消挂机通知 { userId }
|
||||
WebSocketReponseList.PlayerCancelAfkNotice(msg);
|
||||
break;
|
||||
|
||||
case "RoomChange":
|
||||
|
||||
@@ -233,9 +233,7 @@ namespace Assets.MyGame.Scripts.Net
|
||||
// 与 RoomFireNotice(FireCannonSendData) 分开处理,仅用于存储角度到 botLastAngle
|
||||
EventCenter.Instance.EventTrigger<SyncGameData>(ConstEventString.SyncGameData_BotFireAngle, syncGameData);
|
||||
break;
|
||||
case FrameDataType.createFish:
|
||||
EventCenter.Instance.EventTrigger<SyncGameData>(ConstEventString.SyncGameData_CreateFish, syncGameData);
|
||||
break;
|
||||
// createFish=3 已废弃:鱼创建改由服务端 OnFishSpawn 事件下发
|
||||
case FrameDataType.changeAutoTarget:
|
||||
EventCenter.Instance.EventTrigger<SyncGameData>(ConstEventString.SyncGameData_ChangeTargetMonster, syncGameData);
|
||||
break;
|
||||
@@ -258,15 +256,123 @@ namespace Assets.MyGame.Scripts.Net
|
||||
case FrameDataType.changeWing:
|
||||
EventCenter.Instance.EventTrigger<SyncGameData>(ConstEventString.SyncGameData_ChangeWing, syncGameData);
|
||||
break;
|
||||
case FrameDataType.requestSyncFish:
|
||||
EventCenter.Instance.EventTrigger<SyncGameData>(ConstEventString.SyncGameData_RequestSyncFish, syncGameData);
|
||||
break;
|
||||
// requestSyncFish=11 已废弃:鱼同步改由服务端 OnFishSpawn(type=sync) 下发
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 新协议事件分发 (OnFishSpawn / PlayerAfk / FishFrozen / KillNotice 等)
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 服务端出鱼事件 OnFishSpawn 处理。
|
||||
/// 破坏性替代旧 createFish 帧同步:服务端统一出鱼,客户端只接收并生成。
|
||||
/// </summary>
|
||||
public static void OnFishSpawn(string msg)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<OnFishSpawnData>>(msg).arguments[0];
|
||||
if (data == null) return;
|
||||
EventCenter.Instance.EventTrigger<OnFishSpawnData>(ConstEventString.OnFishSpawn, data);
|
||||
}
|
||||
|
||||
/// <summary>鱼列表刷新</summary>
|
||||
public static void OnFishList(string msg)
|
||||
{
|
||||
var fishList = JsonConvert.DeserializeObject<WebSocketResponseBase<object[]>>(msg).arguments[0];
|
||||
EventCenter.Instance.EventTrigger<object[]>(ConstEventString.OnFishList, fishList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冰冻道具(40003)生效事件 FishFrozen。
|
||||
/// 鱼存活时间延长 15 秒,客户端写死 15s 倒计时。
|
||||
/// </summary>
|
||||
public static void FishFrozen(string msg)
|
||||
{
|
||||
EventCenter.Instance.EventTrigger(ConstEventString.FishFrozen);
|
||||
}
|
||||
|
||||
/// <summary>玩家挂机通知</summary>
|
||||
public static void PlayerAfkNotice(string msg)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<PlayerAfkNoticeData>>(msg).arguments[0];
|
||||
if (data == null) return;
|
||||
EventCenter.Instance.EventTrigger<PlayerAfkNoticeData>(ConstEventString.PlayerAfkNotice, data);
|
||||
}
|
||||
|
||||
/// <summary>玩家取消挂机通知</summary>
|
||||
public static void PlayerCancelAfkNotice(string msg)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<PlayerAfkNoticeData>>(msg).arguments[0];
|
||||
if (data == null) return;
|
||||
EventCenter.Instance.EventTrigger<PlayerAfkNoticeData>(ConstEventString.PlayerCancelAfkNotice, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局击杀公告 KillNotice。
|
||||
/// 真人击杀/机器人模拟,机器人模拟时额外带 fishName/multiplier/sceneName。
|
||||
/// </summary>
|
||||
public static void KillNotice(string msg)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<KillNoticeData>>(msg).arguments[0];
|
||||
if (data == null) return;
|
||||
EventCenter.Instance.EventTrigger<KillNoticeData>(ConstEventString.KillNotice, data);
|
||||
}
|
||||
|
||||
/// <summary>机器人退出房间通知</summary>
|
||||
public static void BotLeaveRoomNotice(string msg)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<BotLeaveRoomNoticeData>>(msg).arguments[0];
|
||||
if (data == null) return;
|
||||
WebSocketReponseList.BotLeaveRoom(data.userId);
|
||||
}
|
||||
|
||||
/// <summary>房主断线</summary>
|
||||
public static void HostDisconnectNotice(string msg)
|
||||
{
|
||||
EventCenter.Instance.EventTrigger(ConstEventString.HostDisconnect);
|
||||
WebSocketReponseList.HostDisconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 座位变更 OnSeatChange。
|
||||
/// 数据: { type, userId, position, showWaiting? }
|
||||
/// </summary>
|
||||
public static void OnSeatChange(string msg)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<WebSocketResponseBase<Dictionary<string, object>>>(msg).arguments[0];
|
||||
if (data == null) return;
|
||||
// JSON 字段可能是 long/double/string/bool 等任意类型,统一 ToString()+TryParse 容错
|
||||
int seatType = data.TryGetValue("type", out var t) && int.TryParse(t?.ToString(), out var st) ? st : 0;
|
||||
string userId = data.TryGetValue("userId", out var u) ? u?.ToString() : "";
|
||||
int position = data.TryGetValue("position", out var p) && int.TryParse(p?.ToString(), out var pos) ? pos : -1;
|
||||
bool showWaiting = data.TryGetValue("showWaiting", out var sw) && (sw is bool b ? b : (sw != null && bool.TryParse(sw.ToString(), out var bv) && bv));
|
||||
WebSocketReponseList.SeatChange(seatType, userId, position, showWaiting);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 房主控制权移交 ChangeControllerUser。
|
||||
/// 仅维护 SitPosData.roomOwnerSit,不再设置 fangzhu(鱼创建已由服务端统一出鱼)。
|
||||
/// </summary>
|
||||
public static void ChangeControllerUser(string msg)
|
||||
{
|
||||
var userId = JsonConvert.DeserializeObject<WebSocketResponseBase<string>>(msg).arguments[0];
|
||||
if (userId == GameManager.Instance.user.id)
|
||||
{
|
||||
// 本地玩家成为房主:同步本地房主引用
|
||||
if (SitPosData.localPlayerSit != null)
|
||||
SitPosData.roomOwnerSit = SitPosData.localPlayerSit;
|
||||
}
|
||||
else if (userId != null && userId.StartsWith("bot_"))
|
||||
{
|
||||
// 房主是人机:人机无法真正发包,由本地首个非人机玩家代行房主显示职责
|
||||
SitPosData.TransferRoomOwner();
|
||||
}
|
||||
// 其他真人玩家成为房主时无需本地操作(roomOwnerSit 由 TransferRoomOwner/ShowPlayer 维护)
|
||||
}
|
||||
public static void LeaveRoomResult(string msg)
|
||||
{
|
||||
//WebSocketManager.Instance.isGame = false;
|
||||
|
||||
@@ -242,6 +242,26 @@ public partial class WebSocketSendList
|
||||
var sendObj = new WebSocketSendList("ChangeGunMultiple", roomId, userId, multiple);
|
||||
WebSocketManager.Instance.Send(SocketType.game, JsonConvert.SerializeObject(sendObj));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 玩家挂机(对应服务端 SetPlayerAfkNEW)
|
||||
/// 服务端会向房间组广播 PlayerAfkNotice 事件
|
||||
/// </summary>
|
||||
public static void SetPlayerAfk(string roomId, string userId)
|
||||
{
|
||||
var sendObj = new WebSocketSendList("SetPlayerAfkNEW", roomId, userId);
|
||||
WebSocketManager.Instance.Send(SocketType.game, JsonConvert.SerializeObject(sendObj));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消挂机(对应服务端 CancelPlayerAfkNEW)
|
||||
/// 服务端会向房间组广播 PlayerCancelAfkNotice 事件,并通过 OnFishSpawn(type=sync) 同步当前鱼
|
||||
/// </summary>
|
||||
public static void CancelPlayerAfk(string roomId, string userId)
|
||||
{
|
||||
var sendObj = new WebSocketSendList("CancelPlayerAfkNEW", roomId, userId);
|
||||
WebSocketManager.Instance.Send(SocketType.game, JsonConvert.SerializeObject(sendObj));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步等级常量(秒)
|
||||
@@ -284,17 +304,14 @@ public partial class WebSocketSendList
|
||||
_syncLv1Append.Add(sgd);
|
||||
return;
|
||||
|
||||
case FrameDataType.createFish:
|
||||
// createFish=3 已废弃:鱼创建改由服务端 OnFishSpawn 下发
|
||||
case FrameDataType.changeTurretState:
|
||||
case FrameDataType.changeTurret:
|
||||
case FrameDataType.freezeFish:
|
||||
_syncLv2Append.Add(sgd);
|
||||
return;
|
||||
|
||||
case FrameDataType.requestSyncFish:
|
||||
// ⭐ 新玩家加载完成的"请求同步鱼":立即发送,房主尽快响应广播
|
||||
SendImmediate(BuildJson(sgd));
|
||||
return;
|
||||
// requestSyncFish=11 已废弃:鱼同步改由服务端 OnFishSpawn(type=sync) 下发
|
||||
|
||||
default:
|
||||
_syncLv3Append.Add(sgd);
|
||||
|
||||
@@ -153,6 +153,7 @@ public class SitPosData : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>房主退出后,将房主身份转移给第一个活跃的非人机玩家</summary>
|
||||
/// <remarks>鱼创建已由服务端 OnFishSpawn 统一下发,此处仅维护 roomOwnerSit 标识(用于 bot 控制/AFK 等场景)。</remarks>
|
||||
public static void TransferRoomOwner()
|
||||
{
|
||||
var list = FindObjectsOfType<SitPosData>();
|
||||
@@ -161,29 +162,18 @@ public class SitPosData : MonoBehaviour
|
||||
if (!sit.isBot && sit.gameObject.activeInHierarchy && sit != roomOwnerSit)
|
||||
{
|
||||
roomOwnerSit = sit;
|
||||
// 同步出鱼权限:继承者是本地玩家时接管出鱼
|
||||
if (CreateFishManager.Instance != null)
|
||||
{
|
||||
CreateFishManager.Instance.fangzhu =
|
||||
sit.user == GameManager.Instance?.user?.id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>分配房主(首个非人机玩家)</summary>
|
||||
/// <remarks>鱼创建已由服务端 OnFishSpawn 统一下发,此处仅维护 roomOwnerSit 标识(用于 bot 控制/AFK 等场景)。</remarks>
|
||||
public static void AssignRoomOwner(SitPosData sit)
|
||||
{
|
||||
if (roomOwnerSit == null && !sit.isBot)
|
||||
{
|
||||
roomOwnerSit = sit;
|
||||
// 同步出鱼权限:本地玩家作为首个非人机玩家时接管出鱼
|
||||
if (CreateFishManager.Instance != null)
|
||||
{
|
||||
CreateFishManager.Instance.fangzhu =
|
||||
sit.user == GameManager.Instance?.user?.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
public Image img_BingFeng;
|
||||
public GameObject bingFengEffect; // 冰封特效预制体(首次Instantiate,之后激活/隐藏)
|
||||
private GameObject _bingFengInstance; // 克隆出来的实例
|
||||
float bingFengDuration = 5f; // 冰封状态持续时间
|
||||
float bingFengDuration = 15f; // 冰封状态持续时间(服务端 FishFrozen 延长 15 秒,客户端写死 15s 倒计时)
|
||||
public float currBingFengTime = 0f;
|
||||
private Dictionary<Component, object> _frozenAnimSpeeds = new Dictionary<Component, object>();
|
||||
|
||||
@@ -437,7 +437,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeTurret, SyncGameData_ChangeTurret);
|
||||
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeCannonMultiple, SyncGameData_ChangeCannonMultiple);
|
||||
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeWing, SyncGameData_ChangeWing);
|
||||
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_FreezeFish, SyncGameData_FreezeFish);
|
||||
// SyncGameData_FreezeFish 已废弃:冰冻改由服务端 FishFrozen 事件统一触发
|
||||
EventCenter.Instance.AddEventListener(ConstEventString.FishFrozen, OnFishFrozen);
|
||||
EventCenter.Instance.AddEventListener<SyncGameData>(ConstEventString.SyncGameData_BotFireAngle, SyncGameData_BotFireAngle);
|
||||
btn_TuJian.onClick.AddListener(() =>
|
||||
{
|
||||
@@ -467,7 +468,6 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
{
|
||||
// 确认:直接退出房间
|
||||
WebSocketSendList.LeaveRoom(GameManager.Instance.gameData.currRoomData.roomId, currSitData.user);
|
||||
CreateFishManager.Instance.fangzhu = false;
|
||||
CancelLeaveCountdown();
|
||||
});
|
||||
}
|
||||
@@ -517,7 +517,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeTurret, SyncGameData_ChangeTurret);
|
||||
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeCannonMultiple, SyncGameData_ChangeCannonMultiple);
|
||||
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_ChangeWing, SyncGameData_ChangeWing);
|
||||
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_FreezeFish, SyncGameData_FreezeFish);
|
||||
// SyncGameData_FreezeFish 已废弃:冰冻改由服务端 FishFrozen 事件统一触发
|
||||
EventCenter.Instance.RemoveEventListener(ConstEventString.FishFrozen, OnFishFrozen);
|
||||
EventCenter.Instance.RemoveEventListener<SyncGameData>(ConstEventString.SyncGameData_BotFireAngle, SyncGameData_BotFireAngle);
|
||||
|
||||
// ⭐ 离场清理:子弹池 + 特效池彻底清空,防止内存泄漏
|
||||
@@ -718,25 +719,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
|
||||
await Task.Delay(2000);
|
||||
|
||||
// ⭐ 进度条加载完毕:房主创建一批初始鱼(进度 0.2~0.8),屏幕开局就有鱼
|
||||
// 只在房主端调用,通过 SyncGameData 广播给所有客户端
|
||||
// 非房主(后加入的真人玩家):发"请求同步鱼"让房主广播场上活鱼
|
||||
// 这里只发请求,实际接收广播在 CreateFishManager.CreateFish(已做 guid 去重)
|
||||
if (CreateFishManager.Instance.fangzhu)
|
||||
{
|
||||
CreateFishManager.Instance.CreateInitialFishes(Random.Range(10, 21));
|
||||
}
|
||||
else
|
||||
{
|
||||
var req = new SyncGameData
|
||||
{
|
||||
sitPos = GameManager.Instance.gameData.roomJoinNotice.position,
|
||||
frameType = FrameDataType.requestSyncFish,
|
||||
time = BeijingTimeManager.Instance.CurrentTimestampMs,
|
||||
frameData = null
|
||||
};
|
||||
WebSocketSendList.SyncGameData(req);
|
||||
}
|
||||
// ⭐ 鱼创建由服务端 OnFishSpawn 事件统一下发,客户端无需房主出鱼/请求同步。
|
||||
// 玩家进入房间后服务端会主动推送 OnFishSpawn(type="init") 初始鱼,
|
||||
// 重连/取消挂机时服务端会推送 OnFishSpawn(type="sync") 同步当前鱼。
|
||||
|
||||
JinDuTiao.SetProgress(1);
|
||||
await Task.Delay(300);
|
||||
@@ -1158,12 +1143,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
toggle_BingFeng.SetIsOnWithoutNotify(false);
|
||||
currBingFengTime = 0f;
|
||||
if (img_BingFeng != null) img_BingFeng.fillAmount = 1f;
|
||||
// ⭐ 冰冻结束:本地解冻 + 广播给其他客户端解冻
|
||||
// 之前只设 toggle.isOn=false,但 onValueChanged 监听器 if(!on)return
|
||||
// 导致解冻逻辑永远不执行,isFrozen 永远 true,之后永久不出鱼
|
||||
// ⭐ 冰冻结束:本地解冻(服务端不再下发解冻事件,15s 后客户端自动解冻)
|
||||
// 之前 SyncFreeze(false) 客户端广播已废弃
|
||||
CreateFishManager.Instance.UnfreezeAllFish();
|
||||
ResumeAllFishAnimations();
|
||||
SyncFreeze(false);
|
||||
if (_bingFengInstance != null) _bingFengInstance.SetActive(false);
|
||||
}
|
||||
}
|
||||
@@ -2133,52 +2116,47 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
|
||||
case "40003": // 冰封
|
||||
{
|
||||
// ⭐ 个人效果(数量/toggle/倒计时/广播):只有使用者自己执行
|
||||
// 服务器把 UsePropNotice 广播给房间内所有人,不判断 userId 会导致
|
||||
// 别人用冰封,本地数量也减了、toggle 也亮了、倒计时也走了
|
||||
// ⭐ 新协议:冰冻房间效果由服务端 FishFrozen 事件统一触发,
|
||||
// UsePropNotice(40003) 只处理个人效果(数量/toggle)
|
||||
// 房间效果(冻结鱼/暂停动画/冰冻特效)在 OnFishFrozen 中执行
|
||||
bool isLocalSender = userId == GameManager.Instance.user.id;
|
||||
if (isLocalSender)
|
||||
{
|
||||
_localBingFengCount--;
|
||||
UpdateSkillPropsUI();
|
||||
currBingFengTime = bingFengDuration;
|
||||
toggle_BingFeng.SetIsOnWithoutNotify(true);
|
||||
// ⭐ 只有发起者广播,避免其他客户端重复广播
|
||||
SyncFreeze(true);
|
||||
// 倒计时和 toggle 由 OnFishFrozen 统一启动,这里不重复设置
|
||||
}
|
||||
// ⭐ 房间效果(冻结鱼/暂停动画/冰冻特效):所有客户端都执行
|
||||
// FreezeAllFish/PauseAllFishAnimations 内部有 isFrozen 守卫,
|
||||
// 重复调用安全(不会覆盖已记录的状态)
|
||||
if (bingFengEffect != null)
|
||||
{
|
||||
if (_bingFengInstance == null)
|
||||
_bingFengInstance = Instantiate(bingFengEffect);
|
||||
_bingFengInstance.SetActive(true);
|
||||
}
|
||||
CreateFishManager.Instance.FreezeAllFish();
|
||||
PauseAllFishAnimations();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncGameData_FreezeFish(SyncGameData sync)
|
||||
/// <summary>
|
||||
/// 服务端 FishFrozen 事件回调:冰冻道具(40003)生效。
|
||||
/// 鱼存活时间延长 15 秒,客户端写死 15s 倒计时。
|
||||
/// 所有客户端都执行(房间效果)。
|
||||
/// </summary>
|
||||
public void OnFishFrozen()
|
||||
{
|
||||
if (sync.sitPos == currSitData.posID) return; // 自己发的不管
|
||||
// 启动 15s 倒计时
|
||||
currBingFengTime = bingFengDuration;
|
||||
toggle_BingFeng.SetIsOnWithoutNotify(true);
|
||||
|
||||
var data = FrameDataParser.Parse<FrameData_FreezeFish>(sync.frameData);
|
||||
if (data.isFreeze)
|
||||
// 冰冻特效
|
||||
if (bingFengEffect != null)
|
||||
{
|
||||
CreateFishManager.Instance.FreezeAllFish();
|
||||
PauseAllFishAnimations();
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateFishManager.Instance.UnfreezeAllFish();
|
||||
ResumeAllFishAnimations();
|
||||
if (_bingFengInstance == null)
|
||||
_bingFengInstance = Instantiate(bingFengEffect);
|
||||
_bingFengInstance.SetActive(true);
|
||||
}
|
||||
// 冻结所有鱼 + 暂停动画
|
||||
CreateFishManager.Instance.FreezeAllFish();
|
||||
PauseAllFishAnimations();
|
||||
}
|
||||
|
||||
// SyncGameData_FreezeFish 已废弃:冰冻改由服务端 FishFrozen 事件统一触发
|
||||
// SyncFreeze 已废弃:不再需要客户端广播冰冻状态
|
||||
|
||||
|
||||
#endregion
|
||||
#region 冰冻动画暂停/恢复
|
||||
@@ -2308,7 +2286,6 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
public void TuiChuHHWK()
|
||||
{
|
||||
WebSocketSendList.LeaveRoom(GameManager.Instance.gameData.currRoomData.roomId, currSitData.user);
|
||||
CreateFishManager.Instance.fangzhu = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -2707,27 +2684,16 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
botLastAngle.Remove(sit.user);
|
||||
botLastFireData.Remove(sit.user);
|
||||
|
||||
// ⭐ bot 退出后转移房主,否则房主是已退出的 bot 会导致出鱼停止
|
||||
// ⭐ bot 退出后转移房主,否则房主是已退出的 bot 会导致房主显示异常
|
||||
// 1) 清除 bot 的房主引用(如果它是房主)
|
||||
// 2) TransferRoomOwner 会把房主交给第一个活跃的非 bot 玩家
|
||||
// 注:鱼创建已由服务端 OnFishSpawn 统一下发,不再需要 fangzhu 兜底
|
||||
if (SitPosData.roomOwnerSit == sit)
|
||||
{
|
||||
SitPosData.roomOwnerSit = null;
|
||||
SitPosData.TransferRoomOwner();
|
||||
}
|
||||
|
||||
// ⭐ 强制保证本地玩家能继续出鱼:
|
||||
// bot 退出后如果本地玩家是新的房主(或房主为空但本地玩家在场),开启 fangzhu
|
||||
// 这是对 ChangeControllerUser 网络消息的兜底,防止网络消息丢失或时序问题导致不出鱼
|
||||
if (CreateFishManager.Instance != null)
|
||||
{
|
||||
bool localIsOwner = SitPosData.roomOwnerSit != null &&
|
||||
SitPosData.roomOwnerSit.user == GameManager.Instance.user.id;
|
||||
bool noOwner = SitPosData.roomOwnerSit == null;
|
||||
if (localIsOwner || noOwner)
|
||||
CreateFishManager.Instance.fangzhu = true;
|
||||
}
|
||||
|
||||
if (cfg != null && cfg.exitCondition != null && cfg.exitCondition.showWaitingAfterExit)
|
||||
sit.ShowWaiting();
|
||||
else
|
||||
|
||||
@@ -122,6 +122,7 @@ public class UIHome_BY_SelectRoomLevelPanel : SubUIBase
|
||||
else
|
||||
{
|
||||
// ⭐ 快速加入:调 /api/Room/QuickJoin 拿 roomId + webSocketUrl → 连 socket → JoinRoom(-1)
|
||||
// leave: 1=初级场 / 2=中级场 / 3=高级场 (-1=全场次快速进入,暂未实现)
|
||||
int gameType = (int)GameManager.Instance.gameData.currGameType;
|
||||
APIRequestList.RoomQuickJoin(gameType, leave, (resp) =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user