C++游戏开发:CollisionManager类如何实现多Enemy碰撞检测
核心问题根因
你当前代码的逻辑存在三个会直接导致碰撞检测失效的硬伤:
- 类成员初始化顺序错误:C++规定类成员的初始化顺序严格按照头文件中的声明顺序执行,和初始化列表的书写顺序无关。你在Level类头文件里把
CollisionManager collisions声明在最前面,意味着构造Level时会最先初始化collisions,此时后续声明的player、Enemy* enemy都还未完成初始化,传给CollisionManager的是未定义的野指针。哪怕你后续在构造函数体里给enemy赋值NULL,也是在collisions初始化完成之后才执行的操作,完全没法修正collisions内部存的无效指针。 - 指针副本的逻辑错位:CollisionManager内部的
enemy成员是构造时传入的指针的值副本,你在Level的update循环里修改Level自身的enemy指针指向哪个敌人,完全不会影响CollisionManager内部存储的指针值,两个指针是完全独立的变量,没有任何关联。这就是你调试时看到指针为NULL的核心原因。 - 冗余的临时成员设计:额外在Level里维护一个
Enemy* enemy的临时遍历指针没有任何价值,反而增加了出错的路径。
适配多敌人的两种实现方案
两种方案都不需要把CollisionManager的逻辑迁移到Level类中,完全保留原有封装性,可以根据自己的需求选择。
方案1:最小修改量适配(适合快速作业迭代)
核心思路是把动态变化的敌人对象作为参数传给CollisionManager的update方法,而不是在构造时固定绑定单个敌人:
- 修改CollisionManager类定义,删掉固定存储的敌人指针成员,调整接口:
class CollisionManager { public: explicit CollisionManager(Player& play); ~CollisionManager(); // 接口改为接收当前需要检测的敌人指针 void update(Enemy* currentEnemy); private: Player* player; };
- 调整CollisionManager的实现逻辑,加入空指针校验,把原有碰撞响应逻辑里的固定
enemy成员替换为传入的currentEnemy参数:
CollisionManager::CollisionManager(Player& play) : player(&play) {} void CollisionManager::update(Enemy* currentEnemy) { if (!currentEnemy) return; // 跳过无效指针的检测 // 原有Player与Enemy的碰撞响应逻辑直接在这里编写即可 }
- 调整Level类的成员声明顺序,删除冗余的临时敌人指针,保证被依赖的对象先初始化:
class Level : BaseLevel { private: // 声明顺序必须调整为:被依赖的对象在前,依赖类在后 Player player; std::vector<Enemy> enemies; CollisionManager collisions; }
- 修改Level的构造与update逻辑:
Level::Level() : collisions(player) { for (int i = 0; i < 6; i++) { enemies.emplace_back(); // 相比push_back(Enemy())减少一次额外拷贝 } } void Level::update(float dt) { for (size_t i = 0; i < enemies.size(); i++) { // 循环把当前遍历的敌人地址传给碰撞检测接口 collisions.update(&enemies[i]); } }
方案2:完全收拢碰撞逻辑(适合可扩展的项目结构)
核心思路是让CollisionManager直接持有敌人列表的引用,所有敌人的检测逻辑全部封装在CollisionManager内部,减少Level类的冗余代码:
- 修改CollisionManager类定义,直接持有敌人列表的引用:
class CollisionManager { public: CollisionManager(Player& play, std::vector<Enemy>& enemyList); ~CollisionManager(); void update(); private: Player* player; // 存敌人列表的引用(不是值副本),Level修改列表时会自动同步 std::vector<Enemy>& enemies; };
- 调整CollisionManager的实现,在内部完成所有敌人的遍历检测:
CollisionManager::CollisionManager(Player& play, std::vector<Enemy>& enemyList) : player(&play), enemies(enemyList) { } void CollisionManager::update() { for (Enemy& enemy : enemies) { // 原有Player与Enemy的碰撞响应逻辑直接在这里编写即可 } }
- 调整Level类的成员声明顺序,把collisions放在被依赖对象的后面:
class Level : BaseLevel { private: // 保持依赖顺序:player、enemies在前,collisions在后 Player player; std::vector<Enemy> enemies; CollisionManager collisions; }
- 修改Level的构造与update逻辑:
Level::Level() : collisions(player, enemies) { for (int i = 0; i < 6; i++) { enemies.emplace_back(); } } void Level::update(float dt) { // 不需要手动遍历敌人,直接调用碰撞管理器即可完成所有检测 collisions.update(); }
注意事项
- 不要靠构造函数体内的指针赋值解决初始化问题,所有需要跨类传递的对象引用/指针,都要通过初始化列表完成绑定,从根源上避免野指针。
- 如果后续需要动态增删敌人,担心vector扩容导致的对象地址变化,可以把
std::vector<Enemy>替换为std::vector<std::unique_ptr<Enemy>>,每个敌人在堆上分配固定地址,传递指针的安全性更高。 - 所有指针传入碰撞检测逻辑前,都要做空指针判断,避免空指针访问导致程序崩溃。
内容的提问来源于stack exchange,提问作者AlexDLC
相关产品推荐
相关产品推荐

