1
This commit is contained in:
@@ -217,10 +217,14 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
foreach (var item in GameManager.Instance.gameData.currRoomData.players)
|
||||
{
|
||||
JoinGame(item.position, item.gold, item.diamond, item.cannonMultiple, 0, item.userId, item.cannonId, item.wingsId);
|
||||
// 后端 players 列表里 userId 以 "bot_" 开头的是机器人
|
||||
// ⭐ 优先用后端 isBot 字段,兼容 userId.StartsWith("bot_")
|
||||
var sit = list_Sits.Find(s => s.posID == item.position);
|
||||
if (sit != null && !string.IsNullOrEmpty(item.userId) && item.userId.StartsWith("bot_"))
|
||||
sit.isBot = true;
|
||||
if (sit != null)
|
||||
{
|
||||
bool isBot = item.isBot ||
|
||||
(!string.IsNullOrEmpty(item.userId) && item.userId.StartsWith("bot_"));
|
||||
if (isBot) sit.isBot = true;
|
||||
}
|
||||
}
|
||||
GameManager.Instance.gameData.roomJoinNotice = v.arguments[0];
|
||||
isfirstShow = false;
|
||||
@@ -249,12 +253,25 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
}
|
||||
else
|
||||
{
|
||||
// 玩家加入 → 如果座位被bot占用则替换
|
||||
var targetSit = list_Sits.Find(s => s.posID == roomJoinNotice.position);
|
||||
if (targetSit != null && targetSit.isBot)
|
||||
// ⭐ 优先用后端 isBot 字段判断,兼容 userId.StartsWith("bot_")
|
||||
bool isBotJoin = roomJoinNotice.isBot ||
|
||||
(!string.IsNullOrEmpty(roomJoinNotice.userId) && roomJoinNotice.userId.StartsWith("bot_"));
|
||||
|
||||
if (isBotJoin)
|
||||
{
|
||||
targetSit.isBot = false;
|
||||
targetSit.ShowWaiting();
|
||||
// bot 加入 → 标记 isBot=true (JoinGame 里会自动 SetActive(true) 座位)
|
||||
if (targetSit != null)
|
||||
targetSit.isBot = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 真人玩家加入 → 如果座位被bot占用则替换
|
||||
if (targetSit != null && targetSit.isBot)
|
||||
{
|
||||
targetSit.isBot = false;
|
||||
targetSit.ShowWaiting();
|
||||
}
|
||||
}
|
||||
JoinGame(roomJoinNotice.position, roomJoinNotice.gold, roomJoinNotice.diamond, roomJoinNotice.cannonMultiple, 0, roomJoinNotice.userId, roomJoinNotice.cannonId, roomJoinNotice.wingsId);
|
||||
|
||||
@@ -1171,6 +1188,81 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 锁定位置解析(统一工具)
|
||||
|
||||
/// <summary>
|
||||
/// 统一锁定位置解析:colliderList 第一个活跃碰撞器 → lockPoint → monster.transform
|
||||
/// 所有设置锁定位置的地方都应该调用此方法,避免不同地方用不同优先级导致锁定图标位置和子弹终点不一致。
|
||||
/// 注意:Unity 序列化字段未赋值时是"假 null",?? 运算符无法识别,必须用 == null 判断。
|
||||
/// </summary>
|
||||
private Transform ResolveLockTransform(MonsterContral monster, out int colliderIndex)
|
||||
{
|
||||
colliderIndex = -1;
|
||||
if (monster == null) return null;
|
||||
|
||||
// 1. 优先用 colliderList 中第一个活跃的碰撞器
|
||||
if (monster.colliderList != null && monster.colliderList.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < monster.colliderList.Count; i++)
|
||||
{
|
||||
var col = monster.colliderList[i];
|
||||
if (col != null && col.gameObject.activeInHierarchy)
|
||||
{
|
||||
colliderIndex = i;
|
||||
return col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 其次用 lockPoint(锁定标志锚点,通常是屏幕中间/鱼身中心)
|
||||
if (monster.lockPoint != null)
|
||||
return monster.lockPoint;
|
||||
|
||||
// 3. 兜底用 monster.transform
|
||||
return monster.transform;
|
||||
}
|
||||
|
||||
/// <summary>简化重载,不关心 colliderIndex 时用</summary>
|
||||
private Transform ResolveLockTransform(MonsterContral monster)
|
||||
{
|
||||
return ResolveLockTransform(monster, out _);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 路径进度过滤(锁定范围控制)
|
||||
|
||||
/// <summary>bot/玩家锁定时,鱼路径进度下限:小于此值表示鱼刚出生,不锁定</summary>
|
||||
private const float BotLockPathMin = 0.2f;
|
||||
/// <summary>bot/玩家锁定时,鱼路径进度上限:大于此值表示鱼即将离场,不锁定</summary>
|
||||
private const float BotLockPathMax = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// 计算鱼在路径上的进度 [0, 1]。
|
||||
/// 进度 = moveTime / monsterData.moveLine.moveTime
|
||||
/// 0 = 刚出生,1 = 即将离场
|
||||
/// </summary>
|
||||
private float GetFishPathProgress(MonsterContral m)
|
||||
{
|
||||
if (m == null || m.monsterData == null || m.monsterData.moveLine == null) return 1f;
|
||||
float total = m.monsterData.moveLine.moveTime;
|
||||
if (total <= 0f) return 1f;
|
||||
return Mathf.Clamp01(m.moveTime / total);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断鱼的路径进度是否在可锁定范围内 [BotLockPathMin, BotLockPathMax]
|
||||
/// 刚出生(< 0.2)或即将离场(> 0.8)的鱼不能锁定,避免锁定后立即丢失目标
|
||||
/// </summary>
|
||||
private bool IsFishPathLockable(MonsterContral m)
|
||||
{
|
||||
if (m == null) return false;
|
||||
float progress = GetFishPathProgress(m);
|
||||
return progress >= BotLockPathMin && progress <= BotLockPathMax;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 多目标查找(TripleLock)
|
||||
@@ -1229,15 +1321,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
MonsterContral m = kv.Value;
|
||||
if (m == null || !m.gameObject.activeInHierarchy) continue;
|
||||
if (selected.Contains(m.guid)) continue; // 跳过已选中的
|
||||
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的鱼不作为目标
|
||||
if (!IsFishPathLockable(m)) continue;
|
||||
|
||||
Transform pos = m.transform;
|
||||
if (m.colliderList != null && m.colliderList.Count > 0)
|
||||
{
|
||||
foreach (var col in m.colliderList)
|
||||
{
|
||||
if (col.gameObject.activeInHierarchy) { pos = col; break; }
|
||||
}
|
||||
}
|
||||
// ⭐ 统一用 ResolveLockTransform:colliderList → lockPoint → transform
|
||||
Transform pos = ResolveLockTransform(m);
|
||||
|
||||
result.Add((m, pos));
|
||||
selected.Add(m.guid);
|
||||
@@ -1356,17 +1444,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
|
||||
// 处理选中的最佳命中
|
||||
MonsterContral monster = bestHit.collider.GetComponentInParent<MonsterContral>();
|
||||
// ⭐ 两个判断:lockPoint(屏幕中间/锁定锚点) 优先 → colliderList 中命中的碰撞器 → 兜底命中碰撞器
|
||||
// 不用 fish.gameObject.position;与 bot 锁定逻辑保持一致
|
||||
Transform lockTr;
|
||||
if (monster.lockPoint != null)
|
||||
lockTr = monster.lockPoint;
|
||||
else if (monster.colliderList != null && monster.colliderList.Count > 0)
|
||||
lockTr = bestHit.collider.transform; // 命中的碰撞器(在 colliderList 内)
|
||||
else
|
||||
lockTr = bestHit.collider.transform; // 兜底
|
||||
// ⭐ 统一用 ResolveLockTransform:colliderList 第一个活跃 → lockPoint → transform
|
||||
// 返回的 colliderIndex 用于网络同步,其他客户端按此索引取同一碰撞器
|
||||
int colliderIndex;
|
||||
Transform lockTr = ResolveLockTransform(monster, out colliderIndex);
|
||||
currSitData.SetLockTarget(monster, lockTr);
|
||||
int colliderIndex = (monster.colliderList != null) ? monster.colliderList.IndexOf(bestHit.collider.transform) : -1;
|
||||
SyncLock(new List<string> { monster?.guid }, colliderIndex);
|
||||
}
|
||||
private void UpdateLockFX()
|
||||
@@ -1397,35 +1479,15 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
|
||||
if (!hitPos.gameObject.activeInHierarchy)
|
||||
{
|
||||
if (monster.colliderList != null && monster.colliderList.Count > 0)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (Transform t in monster.colliderList)
|
||||
{
|
||||
if (t.gameObject.activeInHierarchy)
|
||||
{
|
||||
pos = t.position;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
if (icon.activeSelf) icon.SetActive(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 注意:Unity 序列化字段未赋值时是"假 null",?? 运算符无法识别,必须用 == null
|
||||
var lp = (monster.lockPoint != null) ? monster.lockPoint : monster.transform;
|
||||
pos = lp.position;
|
||||
}
|
||||
if (!hitPos.gameObject.activeInHierarchy)
|
||||
// ⭐ 原始 hitPos 失效 → 用 ResolveLockTransform 重新找一个有效位置
|
||||
// 统一逻辑:colliderList 第一个活跃 → lockPoint → transform
|
||||
Transform fallback = ResolveLockTransform(monster);
|
||||
if (fallback == null || !fallback.gameObject.activeInHierarchy)
|
||||
{
|
||||
if (icon.activeSelf) icon.SetActive(false);
|
||||
return;
|
||||
}
|
||||
pos = fallback.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1472,30 +1534,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
MonsterContral target = FindAutoLockTarget();
|
||||
if (target != null)
|
||||
{
|
||||
// ⭐ 锁定位置与 HandleLock 一致:lockPoint 优先 → colliderList 第一个活跃碰撞器 → target.transform
|
||||
Transform lockTr;
|
||||
int colliderIndex = -1;
|
||||
if (target.lockPoint != null)
|
||||
{
|
||||
lockTr = target.lockPoint;
|
||||
}
|
||||
else if (target.colliderList != null && target.colliderList.Count > 0)
|
||||
{
|
||||
lockTr = target.transform;
|
||||
for (int i = 0; i < target.colliderList.Count; i++)
|
||||
{
|
||||
if (target.colliderList[i].gameObject.activeInHierarchy)
|
||||
{
|
||||
lockTr = target.colliderList[i];
|
||||
colliderIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lockTr = target.transform;
|
||||
}
|
||||
// ⭐ 统一用 ResolveLockTransform:colliderList 第一个活跃 → lockPoint → transform
|
||||
int colliderIndex;
|
||||
Transform lockTr = ResolveLockTransform(target, out colliderIndex);
|
||||
currSitData.SetLockTarget(target, lockTr);
|
||||
SyncLock(new List<string> { target?.guid }, colliderIndex);
|
||||
}
|
||||
@@ -1517,12 +1558,15 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
MonsterContral best = null;
|
||||
float bestSqr = float.MaxValue;
|
||||
|
||||
Vector3 origin = currSitData.tra_CurrTurret.position;
|
||||
// ⭐ 用世界坐标(Z=0平面),和 bot AI 一致,否则距离计算会异常(出现 3.4E+38 之类)
|
||||
Vector3 origin = currSitData.turretWorldPos;
|
||||
|
||||
foreach (var kv in CreateFishManager.Instance.fishDatas_hashCodekey)
|
||||
{
|
||||
MonsterContral m = kv.Value;
|
||||
if (m == null || !m.gameObject.activeInHierarchy) continue;
|
||||
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的鱼不作为自动锁定目标
|
||||
if (!IsFishPathLockable(m)) continue;
|
||||
|
||||
float sqr = (m.transform.position - origin).sqrMagnitude;
|
||||
if (sqr < bestSqr)
|
||||
@@ -1840,15 +1884,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
}
|
||||
|
||||
MonsterContral monster = CreateFishManager.Instance.fishDatas_hashCodekey[data.targetGuid[0]];
|
||||
Transform hitPos;
|
||||
|
||||
// ⭐ 与本地 HandleLock 保持一致:lockPoint(屏幕中间) 优先 → colliderList[colliderIndex] → fish.transform
|
||||
if (monster.lockPoint != null)
|
||||
hitPos = monster.lockPoint;
|
||||
else if (monster.colliderList != null && monster.colliderList.Count > 0 && data.colliderIndex != -1)
|
||||
hitPos = monster.colliderList[data.colliderIndex];
|
||||
else
|
||||
hitPos = monster.transform;
|
||||
// ⭐ 统一用 ResolveLockTransform:colliderList 第一个活跃 → lockPoint → transform
|
||||
// 注:网络同步过来的 colliderIndex 在统一逻辑下不再使用,因为客户端之间
|
||||
// colliderList 顺序可能不一致,统一用"第一个活跃"更稳定
|
||||
Transform hitPos = ResolveLockTransform(monster);
|
||||
|
||||
list_Sits[sync.sitPos].SetLockTarget(monster, hitPos);
|
||||
}
|
||||
@@ -2004,6 +2044,10 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
{
|
||||
if (sit.posID != sitNum) continue;
|
||||
|
||||
// ⭐ 确保座位 GameObject 本身是激活的(可能被 HandleBotExit 隐藏或初始为 inactive)
|
||||
if (!sit.gameObject.activeSelf)
|
||||
sit.gameObject.SetActive(true);
|
||||
|
||||
sit.user = user ?? GameManager.Instance.user.id;
|
||||
sit.goldNum = goldNum;
|
||||
sit.consumeGoldNum = consumeGold;
|
||||
@@ -2074,7 +2118,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
// ====== 退出检查 ======
|
||||
if (CheckBotExit(sit, state, cfg)) continue;
|
||||
|
||||
Vector3 turretPos = sit.tra_CurrTurret.position;
|
||||
Vector3 turretPos = sit.turretWorldPos;
|
||||
|
||||
// ====== 技能刷新(底层随机技能) ======
|
||||
if (cfg != null && cfg.skills != null)
|
||||
@@ -2101,10 +2145,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
{
|
||||
sit.isLock = true;
|
||||
sit.monCotl_Lock = target;
|
||||
// ⭐ 锁定位置优先用 lockPoint(锁定标志锚点),与玩家锁定保持一致
|
||||
sit.monCotl_Lock_Position = (target.lockPoint != null) ? target.lockPoint
|
||||
: (target.colliderList != null && target.colliderList.Count > 0
|
||||
? target.colliderList[0].transform : target.transform);
|
||||
// ⭐ 统一用 ResolveLockTransform:colliderList 第一个活跃 → lockPoint → transform
|
||||
sit.monCotl_Lock_Position = ResolveLockTransform(target);
|
||||
if (sit.lockFX != null) sit.lockFX.SetActive(true);
|
||||
}
|
||||
}
|
||||
@@ -2195,6 +2237,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
{
|
||||
var m = kv.Value;
|
||||
if (m == null || !m.gameObject.activeInHierarchy) continue;
|
||||
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的 Boss 不作为目标
|
||||
if (!IsFishPathLockable(m)) continue;
|
||||
|
||||
int ft = (int)m.fishenum;
|
||||
bool isBoss = false;
|
||||
@@ -2312,7 +2356,7 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>处理 bot 退出:发离房消息、座位清理</summary>
|
||||
/// <summary>处理 bot 退出:发离房消息、座位清理、转移房主、保证出鱼</summary>
|
||||
private void HandleBotExit(SitPosData sit, BotRuntimeState state, BotConfig cfg)
|
||||
{
|
||||
state.hasExited = true;
|
||||
@@ -2337,6 +2381,27 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
botLastAngle.Remove(sit.user);
|
||||
botLastFireData.Remove(sit.user);
|
||||
|
||||
// ⭐ bot 退出后转移房主,否则房主是已退出的 bot 会导致出鱼停止
|
||||
// 1) 清除 bot 的房主引用(如果它是房主)
|
||||
// 2) TransferRoomOwner 会把房主交给第一个活跃的非 bot 玩家
|
||||
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
|
||||
@@ -2399,15 +2464,30 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
if (fishList.fishDatas_hashCodekey.TryGetValue(guid, out var m)
|
||||
&& m != null && m.gameObject.activeInHierarchy)
|
||||
{
|
||||
// 锁定时不检查 inFront,始终跟踪目标
|
||||
if (isLocked) return m;
|
||||
|
||||
Vector3 fp = m.transform.position;
|
||||
bool inFront = sit.IsFlipped ? fp.y < turretPos.y : fp.y > turretPos.y;
|
||||
if (inFront) return m;
|
||||
// ⭐ 路径进度过滤:如果记忆目标已经超出可锁定范围(即将离场),清除记忆重新找
|
||||
if (!IsFishPathLockable(m))
|
||||
{
|
||||
botTargetGuid.Remove(sit.posID);
|
||||
}
|
||||
else if (isLocked)
|
||||
{
|
||||
// 锁定时不检查 inFront,始终跟踪目标
|
||||
return m;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 fp = m.transform.position;
|
||||
bool inFront = sit.IsFlipped ? fp.y < turretPos.y : fp.y > turretPos.y;
|
||||
if (inFront) return m;
|
||||
// 不在前方 → 清除记忆
|
||||
botTargetGuid.Remove(sit.posID);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 目标无效 → 清除记忆
|
||||
botTargetGuid.Remove(sit.posID);
|
||||
}
|
||||
// 目标无效 → 清除记忆
|
||||
botTargetGuid.Remove(sit.posID);
|
||||
}
|
||||
|
||||
// 2. 锁定Boss模式 → 优先最大倍率Boss
|
||||
@@ -2468,6 +2548,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
{
|
||||
var m = kv.Value;
|
||||
if (m == null || !m.gameObject.activeInHierarchy) continue;
|
||||
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的鱼不作为目标
|
||||
if (!IsFishPathLockable(m)) continue;
|
||||
|
||||
Vector3 fishPos = m.transform.position;
|
||||
if (sit.IsFlipped && fishPos.y >= turretPos.y) continue;
|
||||
@@ -2505,13 +2587,16 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
|
||||
var result = new List<MonsterContral>();
|
||||
if (CreateFishManager.Instance == null) return result;
|
||||
var fishList = CreateFishManager.Instance;
|
||||
Vector3 turretPos = sit.tra_CurrTurret.position;
|
||||
// ⭐ 用世界坐标(Z=0平面),和 bot AI / 玩家自动锁定一致
|
||||
Vector3 turretPos = sit.turretWorldPos;
|
||||
|
||||
foreach (var kv in fishList.fishDatas_hashCodekey)
|
||||
{
|
||||
var m = kv.Value;
|
||||
if (m == null || !m.gameObject.activeInHierarchy) continue;
|
||||
if (m == exclude) continue;
|
||||
// ⭐ 路径进度过滤:刚出生(< 0.2)或即将离场(> 0.8)的鱼不作为目标
|
||||
if (!IsFishPathLockable(m)) continue;
|
||||
|
||||
Vector3 fishPos = m.transform.position;
|
||||
if (sit.IsFlipped && fishPos.y >= turretPos.y) continue;
|
||||
|
||||
Reference in New Issue
Block a user