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

Unity敌人Raycast穿墙检测异常问题求助

问题:敌人Raycast无视障碍物直接命中玩家

我正在制作一款FPS游戏,使用Probuilder创建了关卡。敌人会向玩家方向发射Raycast,但无论中间存在何种障碍物,都会直接命中玩家。所有Layer已配置完成,Probuilder立方体使用Mesh Collider,尝试替换为Unity默认立方体后问题仍存在(FireProjectile函数未启用)。

敌人Raycast相关代码如下:

public class Enemy : MonoBehaviour
{
    [Header("Enemy Settings")]
    bool canShoot, isDead;
    Vector3 playerDir;
    [Header("Enemy Type")]
    [Space]
    NavMeshAgent agent;
    RaycastHit hit, shotHit;
    PlayerMovement playerM;
    public AudioSource enemyShot;
    public AudioClip gunS;
    public ParticleSystem enemyFlash;
    float timer;
    public GameObject projectilePrefab, debugSpot;
    public float Health, shootRange, noticeRange, bulletSpeed, shotCooldown, stopDistance, shotDamage;
    public LayerMask playerLayer;
    public Transform player, enemyBarrel;
    // Start is called before the first frame update
    void Start()
    {
        playerM = player.GetComponent<PlayerMovement>();
        agent = GetComponent<NavMeshAgent>();
    }

    // Update is called once per frame
    void Update()
    {
        if(Health <= 0f)
        {
            Death();
        }

        float distance = Vector3.Distance(transform.position, player.position);
        playerDir = player.position - transform.position;
        if ((distance <= noticeRange) && !isDead)
        {
            transform.LookAt(player.position);
            if (distance >= stopDistance)
            {
                agent.isStopped = false;
                agent.SetDestination(player.position);
            }
            else
            {
                agent.isStopped = true;
            }
        }

        if (distance > noticeRange)
        {
            timer = 0f;
        }

        if (timer >= shotCooldown)
        {
            canShoot = true;
        }
        if(timer <= shotCooldown)
        {
            timer += Time.deltaTime * 2f;
            canShoot = false;
        }

        if(Physics.Raycast(transform.position, playerDir, out hit, shootRange, playerLayer) && canShoot && !isDead)
        {
            Shoot();
            Debug.Log(hit.transform.name);
        }


    }

    // AI CODE
    void Shoot()
    {
        enemyFlash.Play();
        enemyShot.PlayOneShot(gunS);
        timer = 0f;
        // Calculate the player's predicted position
        Vector3 playerVelocity = player.GetComponent<Rigidbody>().velocity;
        //float distanceFromPlayer = Vector3.Distance(transform.position, player.position);
        float timeToReachPlayer = Vector3.Distance(transform.position, player.position) / shootRange;
        Vector3 predictedPosition = player.position + playerVelocity/3f * timeToReachPlayer;

        Vector3 dir = predictedPosition - transform.position;

        if(Physics.Raycast(transform.position, dir, out shotHit, shootRange, playerLayer))
        {
            shotHit.transform.gameObject.GetComponent<PlayerMovement>().Damage(shotDamage);
            Instantiate(debugSpot, shotHit.point, Quaternion.identity);
        }
        //FireProjectile(predictedPosition);
    }

    void FireProjectile(Vector3 targetPosition)
    {
        // Instantiate and launch the projectile towards the target position
        GameObject projectile = Instantiate(projectilePrefab, transform.position, Quaternion.identity);
        Rigidbody projectileRigidbody = projectile.GetComponent<Rigidbody>();

        // Calculate the direction towards the target position
        Vector3 direction = (targetPosition - transform.position).normalized;

        // Launch the projectile towards the target position
        projectileRigidbody.velocity = direction * bulletSpeed;
        Destroy(projectile, 5f);
    }

    // AI CODE

    public void Damage(float dmg)
    {
        Health -= dmg;
    }

    public void Death()
    {
        isDead = true;
        agent.enabled = false;
        transform.GetComponent<Rigidbody>().freezeRotation = false;
        Destroy(gameObject, 3f);
    }

    void OnDrawGizmos()
    {
        Gizmos.DrawLine(transform.position, player.position);
    }
}

问题根源

你的Raycast检测仅指定playerLayer作为目标层,这会导致Raycast完全忽略其他所有层的碰撞体——包括障碍物。Raycast的LayerMask参数是用来定义需要检测的层,而非需要忽略的层,只传玩家层的话,射线会直接穿过障碍物,只和玩家层物体交互。

修复方案

1. 调整Raycast的LayerMask逻辑

有两种可行的修改方式:

  • 方式一:忽略障碍物层
    新增障碍物层变量,让Raycast检测玩家层并忽略障碍物层:
    // 在Enemy类中新增变量
    public LayerMask obstacleLayer;
    
    // 修改Update中的Raycast判断
    if(Physics.Raycast(transform.position, playerDir.normalized, out hit, shootRange, playerLayer | ~obstacleLayer) && canShoot && !isDead)
    {
        Shoot();
        Debug.Log(hit.transform.name);
    }
    
    // 同时修改Shoot函数中的Raycast
    if(Physics.Raycast(transform.position, dir.normalized, out shotHit, shootRange, playerLayer | ~obstacleLayer))
    {
        shotHit.transform.gameObject.GetComponent<PlayerMovement>().Damage(shotDamage);
        Instantiate(debugSpot, shotHit.point, Quaternion.identity);
    }
    
  • 方式二:检测包含玩家和障碍物的层,再判断命中对象
    创建包含玩家层和障碍物层的LayerMask,检测后判断是否命中玩家:
    // 替换原有的playerLayer变量
    public LayerMask shootableLayers; // 在Inspector中配置为包含玩家和障碍物的层
    
    // 修改Update中的判断
    if(Physics.Raycast(transform.position, playerDir.normalized, out hit, shootRange, shootableLayers) 
       && hit.collider.gameObject.layer == LayerMask.NameToLayer("Player") 
       && canShoot && !isDead)
    {
        Shoot();
        Debug.Log(hit.transform.name);
    }
    
    // 修改Shoot函数中的判断
    if(Physics.Raycast(transform.position, dir.normalized, out shotHit, shootRange, shootableLayers)
       && shotHit.collider.gameObject.layer == LayerMask.NameToLayer("Player"))
    {
        shotHit.transform.gameObject.GetComponent<PlayerMovement>().Damage(shotDamage);
        Instantiate(debugSpot, shotHit.point, Quaternion.identity);
    }
    
    注意:必须将playerDir和dir归一化,使用方向向量而非带长度的位移向量,这是Raycast的规范用法。

2. 检查碰撞体配置

  • 确保障碍物的碰撞体处于启用状态,且Layer正确设置为障碍物层
  • Probuilder创建的Mesh Collider需要勾选Convex选项,否则无法被Raycast正确检测

3. 修复计时器逻辑

当前计时器代码会导致canShoot始终为false,因为timer <= shotCooldown的判断会覆盖之前的canShoot = true赋值,修改为:

// 替换原有的计时器逻辑
if (distance > noticeRange)
{
    timer = 0f;
}
else
{
    timer += Time.deltaTime * 2f;
}

canShoot = timer >= shotCooldown;

额外优化

将Raycast的起点改为敌人的枪口位置(enemyBarrel.position),而非敌人Transform的原点,这样射击逻辑更符合实际场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 00:11:09