You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity3D第三人称游戏锁定目标功能随机失效问题求助

第三人称游戏锁定目标功能问题排查与优化

问题说明

开发第三人称游戏锁定目标功能时,当前脚本存在随机无法锁定的问题,移动角色调整角度偶尔能恢复锁定。需要排查问题根源,同时添加目标可见性检测,确保目标大多时候处于可见状态。

原代码

public class PlayerLockOn : MonoBehaviour
{
    public bool lockOn = false;
    float maxLockDist = 65;
    List<GameObject> availableTars = new List<GameObject>();
    float shortestDist = Mathf.Infinity;
    public GameObject closestLockOnTarget;
    public GameObject currentTarget;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //Get Input
        if (lockOn)
        {
            GetTargets();
            ManageTargets();
            if (currentTarget == null)
            {
                
                CameraMovement.instance.camRecenter();
                lockOn = false;
            }
            else
            {
                PlayerStateManager.instance.camState = 1;
                CameraMovement.instance.lockOnTarget = currentTarget;
            }

        }
        else
        {
            availableTars.Clear();
            currentTarget = null;
            CameraMovement.instance.lockOnTarget = null;
            closestLockOnTarget = null;
            PlayerStateManager.instance.camState = 0;
        }
        
    }
    void GetTargets()
    {
        Collider[] colliders = Physics.OverlapSphere(transform.position, 25);

        for (int b = 0; b < colliders.Length; b++)
        {
            var tag = colliders[b].tag;

            if (tag == "Enemy")
            {
                
                Transform tarTransform = colliders[b].transform;
                Vector3 TargetDir = transform.position - tarTransform.position;
                float distance = Vector3.Distance(tarTransform.position, transform.position);

                if (PlayerStateManager.instance.Player.transform.root != tarTransform.root &&  distance <= maxLockDist)
                {
                    availableTars.Add(colliders[b].gameObject);
                    
                }
            }
        }
    }

    void ManageTargets()
    {
        for (int i = 0; i < availableTars.Count; i++)
        {
           
            
            float dist = Vector3.Distance(transform.position, availableTars[i].transform.position);

            if (dist <= shortestDist)
            {
                shortestDist = dist;
                closestLockOnTarget = availableTars[i];
                currentTarget = closestLockOnTarget;

            }
        }
    }

    public void lockOnUpdate()
    {
        
        lockOn = !lockOn;
        
    }
}

问题根源排查

  1. shortestDist未重置:shortestDist初始设为无穷大,但每次调用ManageTargets时没有重新赋值为无穷大。第一次锁定后,后续计算最短距离会沿用之前的最小值,导致新的目标即使在范围内,只要距离比之前的shortestDist大,就不会被选中,出现无法锁定的情况。
  2. 检测范围不一致:GetTargets中Physics.OverlapSphere的半径是25,而maxLockDist是65,导致距离在25-65之间的敌人无法被检测到,只有移动角色让敌人进入25范围内才会被识别,这就是调整角度偶尔恢复锁定的原因。
  3. 目标列表未及时清空:GetTargets每次调用时没有先清空availableTars,导致列表累积之前的旧目标,可能包含已经失效或超出范围的对象,干扰目标判断。
  4. 未处理目标状态:没有检查目标是否处于激活状态,若目标被销毁或失活,currentTarget会变为null,但此时可能还有其他可用目标,却直接关闭锁定状态。

修复后的代码

public class PlayerLockOn : MonoBehaviour
{
    public bool lockOn = false;
    float maxLockDist = 65;
    List<GameObject> availableTars = new List<GameObject>();
    float shortestDist;
    public GameObject closestLockOnTarget;
    public GameObject currentTarget;

    void Update()
    {
        if (lockOn)
        {
            GetTargets();
            ManageTargets();
            
            if (currentTarget == null && availableTars.Count > 0)
            {
                // 若当前目标失效,自动选择下一个可用目标
                ManageTargets();
            }

            if (currentTarget == null)
            {
                CameraMovement.instance.camRecenter();
                lockOn = false;
            }
            else
            {
                PlayerStateManager.instance.camState = 1;
                CameraMovement.instance.lockOnTarget = currentTarget;
            }
        }
        else
        {
            availableTars.Clear();
            currentTarget = null;
            CameraMovement.instance.lockOnTarget = null;
            closestLockOnTarget = null;
            PlayerStateManager.instance.camState = 0;
        }
    }

    void GetTargets()
    {
        availableTars.Clear(); // 每次获取前清空列表
        Collider[] colliders = Physics.OverlapSphere(transform.position, maxLockDist); // 使用maxLockDist作为检测半径

        foreach (var collider in colliders)
        {
            if (collider.CompareTag("Enemy"))
            {
                Transform tarTransform = collider.transform;
                float distance = Vector3.Distance(tarTransform.position, transform.position);

                // 排除自身,检查目标是否激活,且在锁定距离内
                if (PlayerStateManager.instance.Player.transform.root != tarTransform.root 
                    && distance <= maxLockDist 
                    && collider.gameObject.activeInHierarchy)
                {
                    // 添加可见性检测
                    if (IsTargetVisible(collider.gameObject))
                    {
                        availableTars.Add(collider.gameObject);
                    }
                }
            }
        }
    }

    void ManageTargets()
    {
        shortestDist = Mathf.Infinity; // 每次计算前重置最短距离
        closestLockOnTarget = null;

        foreach (var target in availableTars)
        {
            if (target == null) continue; // 跳过已失效的目标
            
            float dist = Vector3.Distance(transform.position, target.transform.position);

            if (dist < shortestDist)
            {
                shortestDist = dist;
                closestLockOnTarget = target;
            }
        }

        currentTarget = closestLockOnTarget;
    }

    // 可见性检测:检查玩家到目标之间是否有障碍物
    bool IsTargetVisible(GameObject target)
    {
        Vector3 direction = target.transform.position - transform.position;
        RaycastHit hit;

        // 射线检测,忽略自身和目标的碰撞体
        int layerMask = ~(1 << gameObject.layer | 1 << target.layer);
        
        if (Physics.Raycast(transform.position, direction.normalized, out hit, maxLockDist, layerMask))
        {
            // 如果射线击中的不是目标,说明被遮挡
            return hit.collider.gameObject == target;
        }

        // 没有击中任何物体,说明目标可见
        return true;
    }

    public void lockOnUpdate()
    {
        lockOn = !lockOn;
        // 开启锁定时立即获取目标
        if (lockOn)
        {
            GetTargets();
            ManageTargets();
        }
    }
}

关键优化点

  • 每次调用ManageTargets前重置shortestDist为无穷大,确保每次都能正确计算最近目标。
  • 统一检测范围为maxLockDist,避免遗漏远处的敌人。
  • GetTargets每次调用前清空列表,避免累积无效目标。
  • 添加IsTargetVisible方法,通过射线检测判断目标是否被障碍物遮挡,只添加可见的目标到可用列表。
  • 增加对目标激活状态的检查,跳过已失效的目标。
  • 开启锁定时立即执行目标获取和管理逻辑,避免延迟。

内容的提问来源于stack exchange,提问作者Booling96

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 16:27:58