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

Unity 3D敌人波次系统C#问题:碰撞失效与生成逻辑异常

《使命召唤》僵尸风格游戏开发问题解决指南

我正在复刻一款基础版《使命召唤》僵尸风格游戏,接触Unity和编程仅约两周,希望讲解尽量通俗易懂。


问题一:敌人碰撞失效与堆叠问题

当前波次系统生成的胶囊形敌人可追踪玩家,但即便环境带有Box Collider、敌人带有Capsule Collider,它们仍不与环境或彼此发生碰撞。尝试调整碰撞器的大小、形状并添加多个碰撞器,却出现敌人堆叠、Hitbox与放大后的碰撞器尺寸一致等问题。

解决方法

  • 必须搭配Rigidbody组件:Unity的物理碰撞需要刚体驱动,给敌人预制体添加Rigidbody组件:
    • 取消勾选Use Gravity(避免敌人不受控掉落)
    • 勾选Freeze Rotation的X/Y/Z(防止敌人移动时旋转失控)
  • 碰撞器正确设置:
    • 环境的Box Collider不要勾选Is Trigger,勾选后会变成触发器,失去物理碰撞能力
    • 敌人的碰撞器尺寸要和模型匹配,不要盲目放大;复杂模型可以用多个小碰撞器组合,但都要挂在敌人根对象上
  • 解决堆叠问题:
    • 给Rigidbody设置Mass为2-5(数值越大,碰撞时推力越强)
    • 敌人追踪玩家的代码,不要直接修改transform.position,改用Rigidbody.MovePosition()或AddForce(),让移动受物理系统约束

问题二:波次等待逻辑失效(持续生成敌人)

波次系统来自Brackys的教程视频,但等待功能失效——本该在所有存活敌人被消灭后才生成下一波,却跳过等待逻辑持续生成胶囊敌人。原代码的Update方法逻辑疑似出错,调换WaveCompleted()和return逻辑也无效,推测EnemyIsAlive()判断逻辑是根源。

核心错误分析

  1. WAITING状态逻辑完全颠倒:原代码中,有敌人存活时反而触发波次完成,完全搞反了判断逻辑
  2. EnemyIsAlive()方法逻辑漏洞:原方法用倒计时延迟检测,大部分时间都返回true,导致判断不准确

修复步骤

1. 修正Update中的WAITING状态逻辑

将原代码段:

void Update()
{
    if (state == SpawnState.WAITING)
    {
        if (EnemyIsAlive())
        {
            WaveCompleted();
        }
        else
        {
            return;
        }
    }
    // 其余代码...
}

修改为:

void Update()
{
    if (state == SpawnState.WAITING)
    {
        // 没有敌人存活时,才触发波次完成
        if (!EnemyIsAlive())
        {
            WaveCompleted();
        }
        // 还有敌人活着就直接返回,不执行后续倒计时逻辑
        else
        {
            return;
        }
    }
    // 其余代码...
}

2. 简化EnemyIsAlive()方法,修复判断逻辑

将原方法:

bool EnemyIsAlive()
{
    searchCountdown -= Time.deltaTime;
    if (searchCountdown <= 0f)
    {
        searchCountdown = 1f;
        if (GameObject.FindGameObjectWithTag("Enemy") == null)
        {
            return false;
        }
    }
    return true;
}

替换为更直接准确的版本:

bool EnemyIsAlive()
{
    // 直接统计带Enemy标签的对象数量,大于0则说明有敌人存活
    return GameObject.FindGameObjectsWithTag("Enemy").Length > 0;
}

注:如果后期敌人数量很多,可改用列表记录生成的敌人(生成时添加,销毁时移除),这样检测性能更优,当前基础版用FindGameObjectsWithTag完全够用。

3. 完整修复后的WaveSpawner代码

using UnityEngine;
using System.Collections;

public class WaveSpawner : MonoBehaviour {

    public enum SpawnState { SPAWNING, WAITING, COUNTING };

    [System.Serializable]
    public class Wave
    {
        public string name;
        public Transform enemy;
        public int count;
        public float rate;
    }

    public Wave[] waves;
    private int nextWave = 0;
    public int NextWave
    {
        get { return nextWave + 1; }
    }

    public Transform[] spawnPoints;

    public float timeBetweenWaves = 5f;
    private float waveCountdown;
    public float WaveCountdown
    {
        get { return waveCountdown; }
    }

    private SpawnState state = SpawnState.COUNTING;
    public SpawnState State
    {
        get { return state; }
    }

    void Start()
    {
        if (spawnPoints.Length == 0)
        {
            Debug.LogError("No spawn points referenced.");
        }

        waveCountdown = timeBetweenWaves;
    }

    void Update()
    {
        if (state == SpawnState.WAITING)
        {
            // 修复:没有敌人存活时触发波次完成
            if (!EnemyIsAlive())
            {
                WaveCompleted();
            }
            else
            {
                return;
            }
        }

        if (waveCountdown <= 0)
        {
            if (state != SpawnState.SPAWNING)
            {
                StartCoroutine( SpawnWave ( waves[nextWave] ) );
            }
        }
        else
        {
            waveCountdown -= Time.deltaTime;
        }
    }

    void WaveCompleted()
    {
        Debug.Log("Wave Completed!");

        state = SpawnState.COUNTING;
        waveCountdown = timeBetweenWaves;

        if (nextWave + 1 > waves.Length - 1)
        {
            nextWave = 0;
            Debug.Log("ALL WAVES COMPLETE! Looping...");
        }
        else
        {
            nextWave++;
        }
    }

    // 修复:简化敌人存活判断逻辑
    bool EnemyIsAlive()
    {
        return GameObject.FindGameObjectsWithTag("Enemy").Length > 0;
    }

    IEnumerator SpawnWave(Wave _wave)
    {
        Debug.Log("Spawning Wave: " + _wave.name);
        state = SpawnState.SPAWNING;

        for (int i = 0; i < _wave.count; i++)
        {
            SpawnEnemy(_wave.enemy);
            yield return new WaitForSeconds( 1f/_wave.rate );
        }

        state = SpawnState.WAITING;

        yield break;
    }

    void SpawnEnemy(Transform _enemy)
    {
        Debug.Log("Spawning Enemy: " + _enemy.name);

        Transform _sp = spawnPoints[ Random.Range (0, spawnPoints.Length) ];
        Instantiate(_enemy, _sp.position, _sp.rotation);
    }

}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 11:01:08