diff --git a/Assets/MyGame/Scripts/Turret/SitPosData.cs b/Assets/MyGame/Scripts/Turret/SitPosData.cs
index bd4bb2158..334db0fdc 100644
--- a/Assets/MyGame/Scripts/Turret/SitPosData.cs
+++ b/Assets/MyGame/Scripts/Turret/SitPosData.cs
@@ -100,6 +100,14 @@ public class SitPosData : MonoBehaviour
public MonsterContral monCotl_Lock;
public Transform monCotl_Lock_Position;
public bool isLock = false;
+ ///
+ /// ⭐ 当前锁定是否为玩家手动锁定。
+ /// - true:玩家点击锁定的鱼 → 不受路径进度(0.1~0.9)限制,鱼离场前一直锁定
+ /// - false:自动锁定/三发查找/bot → 受路径进度限制
+ /// 由 GameMainPanel_FishBase 在 SetLockTarget 前设置
+ ///
+ [System.NonSerialized]
+ public bool isManualLock = false;
public GameObject lockFX; // 主锁定图标(单发/双倍用)
public List lockFX_Triple = new List(); // 三发锁定额外2个图标
public List tripleLockTargets = new List();
@@ -744,6 +752,8 @@ public class SitPosData : MonoBehaviour
monCotl_Lock = null;
monCotl_Lock_Position = null;
isLock = false;
+ // ⭐ 清除锁定时重置手动锁定标记
+ isManualLock = false;
lockFX.SetActive(false);
HideTripleLockIcons();
ChangeState(TurretState.Idle);
diff --git a/Assets/MyGame/aa/Game/HHWK/Boss/XiaoBaiLong/Game177_lm.prefab b/Assets/MyGame/aa/Game/HHWK/Boss/XiaoBaiLong/Game177_lm.prefab
index d8e74fd37..2a3f09b19 100644
--- a/Assets/MyGame/aa/Game/HHWK/Boss/XiaoBaiLong/Game177_lm.prefab
+++ b/Assets/MyGame/aa/Game/HHWK/Boss/XiaoBaiLong/Game177_lm.prefab
@@ -6043,7 +6043,7 @@ PrefabInstance:
- target: {fileID: 9054138907369387089, guid: b91d0e921164f564a98591ba27f3c976,
type: 3}
propertyPath: m_IsActive
- value: 1
+ value: 0
objectReference: {fileID: 0}
- target: {fileID: 9054138907369387089, guid: b91d0e921164f564a98591ba27f3c976,
type: 3}
diff --git a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
index a73f6c780..f0df3cd2e 100644
--- a/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
+++ b/Assets/MyGame/aa/UI/Game/GameMainPanel_FishBase.cs
@@ -1373,6 +1373,12 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
private void FireBullet(SitPosData sit, float angle, bool isSanShe, bool isDouble, bool isLock, List bullitIDs,
List fireTargetMonsterIDs = null, List bulletGolds = null)
{
+ // ⭐ 防御:sit 或 bullitIDs 为空时直接返回,避免空引用
+ if (sit == null || bullitIDs == null || bullitIDs.Count == 0)
+ {
+ Debug.LogWarning("[FireBullet] sit 或 bullitIDs 为空,跳过本次开火");
+ return;
+ }
Vector2 startPos = sit.GetBulletStartPos();
// ⭐ 按索引取该发子弹的服务端权威扣费金额(bullets[i].gold),没有则回退到0(ConsumeGold内部用consumeGoldNum)
@@ -1401,7 +1407,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
{
if (CreateFishManager.Instance.fishDatas_hashCodekey.TryGetValue(fireTargetMonsterIDs[i], out var m)
&& m != null && m.gameObject.activeInHierarchy
- && IsFishPathLockable(m))
+ // ⭐ 手动锁定的主目标(i==0 且是本地玩家主锁)不受路径进度限制
+ && ((i == 0 && sit == currSitData && m == currSitData.monCotl_Lock && currSitData.isManualLock)
+ || IsFishPathLockable(m)))
{
targetMonster = m;
targetHitPos = ResolveLockTransform(m);
@@ -1425,9 +1433,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}
else if (isSanShe)
{
- CreateBullet(sit, startPos, angle - 20, isDouble, isLock, bullitIDs[0], null, null, GetBulletGold(0));
- CreateBullet(sit, startPos, angle, isDouble, isLock, bullitIDs[1], null, null, GetBulletGold(1));
- CreateBullet(sit, startPos, angle + 20, isDouble, isLock, bullitIDs[2], null, null, GetBulletGold(2));
+ // ⭐ 安全取值:bullitIDs 不足3个时复用最后一个,避免越界
+ string SafeBid(int i) => i < bullitIDs.Count ? bullitIDs[i] : bullitIDs[bullitIDs.Count - 1];
+ CreateBullet(sit, startPos, angle - 20, isDouble, isLock, SafeBid(0), null, null, GetBulletGold(0));
+ CreateBullet(sit, startPos, angle, isDouble, isLock, SafeBid(1), null, null, GetBulletGold(1));
+ CreateBullet(sit, startPos, angle + 20, isDouble, isLock, SafeBid(2), null, null, GetBulletGold(2));
}
else
{
@@ -1624,9 +1634,9 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
#region 路径进度过滤(锁定范围控制)
/// bot/玩家锁定时,鱼路径进度下限:小于此值表示鱼刚出生,不锁定
- private const float BotLockPathMin = 0.2f;
+ private const float BotLockPathMin = 0.1f;
/// bot/玩家锁定时,鱼路径进度上限:大于此值表示鱼即将离场,不锁定
- private const float BotLockPathMax = 0.8f;
+ private const float BotLockPathMax = 0.9f;
///
/// 计算鱼在路径上的进度 [0, 1]。
@@ -1647,7 +1657,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
///
/// 判断鱼的路径进度是否在可锁定范围内 [BotLockPathMin, BotLockPathMax]
- /// 刚出生(< 0.2)或即将离场(> 0.8)的鱼不能锁定,避免锁定后立即丢失目标
+ /// 刚出生(< 0.1)或即将离场(> 0.9)的鱼不能锁定,避免锁定后立即丢失目标
+ /// ⭐ 注意:本函数仅用于自动锁定/bot/三发查找;玩家手动锁定不调用此函数
///
private bool IsFishPathLockable(MonsterContral m)
{
@@ -1684,10 +1695,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
if (CreateFishManager.Instance == null) return result;
- // ① 主锁目标排第一(打哪锁哪)—— 必须也满足路径可锁定,否则三发列表会被它污染
+ // ① 主锁目标排第一(打哪锁哪)
+ // ⭐ 手动锁定:只看鱼是否还活着;自动锁定:还要满足路径可锁定
if (currSitData.isLock && currSitData.monCotl_Lock != null &&
currSitData.monCotl_Lock.gameObject.activeInHierarchy &&
- IsFishPathLockable(currSitData.monCotl_Lock))
+ (currSitData.isManualLock || IsFishPathLockable(currSitData.monCotl_Lock)))
{
Transform pos = currSitData.monCotl_Lock_Position != null && currSitData.monCotl_Lock_Position.gameObject.activeInHierarchy
? currSitData.monCotl_Lock_Position
@@ -1820,6 +1832,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
Transform lockTr = ResolveLockTransform(monster, out colliderIndex, true);
// ⭐ lockPoint 和 colliderList 全部未激活 → 不锁定这条鱼
if (lockTr == null) return;
+ // ⭐ 标记为手动锁定:不受路径进度(0.1~0.9)限制,鱼离场前一直锁定
+ currSitData.isManualLock = true;
currSitData.SetLockTarget(monster, lockTr);
SyncLock(new List { monster?.guid }, colliderIndex);
@@ -1902,11 +1916,11 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
var monster = currSitData.monCotl_Lock;
- // ⭐ 目标无效判定:被回收/销毁 OR 路径离场(progress > 0.8)
- // 离场的鱼仍 active,但不应该继续锁定
+ // ⭐ 目标无效判定:被回收/销毁
+ // 路径进度检查仅对自动锁定生效;手动锁定不检查(玩家自己锁的鱼离场前一直跟)
bool mainInvalid = monster == null
|| !monster.gameObject.activeInHierarchy
- || !IsFishPathLockable(monster);
+ || (!currSitData.isManualLock && !IsFishPathLockable(monster));
if (mainInvalid)
{
@@ -1955,11 +1969,13 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
// 激活后,当锁定的鱼死亡,自动转换到下一个目标
if (!_autoLockActivated) return;
- // 已经有锁定目标并且还活着且未离场,就不重复找
+ // 已经有锁定目标并且还活着,就不重复找
+ // ⭐ 手动锁定:只看鱼是否还活着,不检查路径进度(玩家自己锁的鱼离场前一直跟)
+ // 自动锁定:还要检查路径进度(超出 0.1~0.9 范围视为失效,换下一条)
if (currSitData.isLock &&
currSitData.monCotl_Lock != null &&
currSitData.monCotl_Lock.gameObject.activeInHierarchy &&
- IsFishPathLockable(currSitData.monCotl_Lock))
+ (currSitData.isManualLock || IsFishPathLockable(currSitData.monCotl_Lock)))
{
return;
}
@@ -1973,6 +1989,8 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
Transform lockTr = ResolveLockTransform(target, out colliderIndex);
// ⭐ lockPoint 和 colliderList 全部未激活(出场效果中) → 不锁定,等下一帧再找
if (lockTr == null) return;
+ // ⭐ 标记为自动锁定:受路径进度(0.1~0.9)限制
+ currSitData.isManualLock = false;
currSitData.SetLockTarget(target, lockTr);
SyncLock(new List { target?.guid }, colliderIndex);
@@ -2348,6 +2366,12 @@ public class GameMainPanel_FishBase : GameMainPanel_Base
}
var data = FrameDataParser.Parse(sync.extra.frameData);
+ // ⭐ 边界检查:防止 sitPos 越界导致 IndexOutOfRangeException
+ if (sync.extra.sitPos < 0 || sync.extra.sitPos >= list_Sits.Count)
+ {
+ Debug.LogWarning($"[SyncGameData_FireBullit] sitPos 越界: {sync.extra.sitPos}");
+ return;
+ }
var targetSit = list_Sits[sync.extra.sitPos];
FireBullet(targetSit, data.angle, data.isSanShe, data.twoRTruuect, data.isSuoDing, sync.bulletIds, data.monsterIDs, bulletGolds);
}