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(),让移动受物理系统约束
- 给Rigidbody设置
问题二:波次等待逻辑失效(持续生成敌人)
波次系统来自Brackys的教程视频,但等待功能失效——本该在所有存活敌人被消灭后才生成下一波,却跳过等待逻辑持续生成胶囊敌人。原代码的Update方法逻辑疑似出错,调换WaveCompleted()和return逻辑也无效,推测EnemyIsAlive()判断逻辑是根源。
核心错误分析
- WAITING状态逻辑完全颠倒:原代码中,有敌人存活时反而触发波次完成,完全搞反了判断逻辑
- 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
相关产品推荐
相关产品推荐

