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

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方法,而不是在构造时固定绑定单个敌人:

  1. 修改CollisionManager类定义,删掉固定存储的敌人指针成员,调整接口:
class CollisionManager
{
public:
    explicit CollisionManager(Player& play);
    ~CollisionManager();
    // 接口改为接收当前需要检测的敌人指针
    void update(Enemy* currentEnemy);
private:
    Player* player;
};
  1. 调整CollisionManager的实现逻辑,加入空指针校验,把原有碰撞响应逻辑里的固定enemy成员替换为传入的currentEnemy参数:
CollisionManager::CollisionManager(Player& play) : player(&play) {}

void CollisionManager::update(Enemy* currentEnemy)
{
    if (!currentEnemy) return; // 跳过无效指针的检测
    // 原有Player与Enemy的碰撞响应逻辑直接在这里编写即可
}
  1. 调整Level类的成员声明顺序,删除冗余的临时敌人指针,保证被依赖的对象先初始化:
class Level : BaseLevel {
private:
    // 声明顺序必须调整为:被依赖的对象在前,依赖类在后
    Player player;
    std::vector<Enemy> enemies;
    CollisionManager collisions;
}
  1. 修改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类的冗余代码:

  1. 修改CollisionManager类定义,直接持有敌人列表的引用:
class CollisionManager
{
public:
    CollisionManager(Player& play, std::vector<Enemy>& enemyList);
    ~CollisionManager();
    void update();
private:
    Player* player;
    // 存敌人列表的引用(不是值副本),Level修改列表时会自动同步
    std::vector<Enemy>& enemies;
};
  1. 调整CollisionManager的实现,在内部完成所有敌人的遍历检测:
CollisionManager::CollisionManager(Player& play, std::vector<Enemy>& enemyList) :
    player(&play),
    enemies(enemyList)
{
}

void CollisionManager::update()
{
    for (Enemy& enemy : enemies)
    {
        // 原有Player与Enemy的碰撞响应逻辑直接在这里编写即可
    }
}
  1. 调整Level类的成员声明顺序,把collisions放在被依赖对象的后面:
class Level : BaseLevel {
private:
    // 保持依赖顺序:player、enemies在前,collisions在后
    Player player;
    std::vector<Enemy> enemies;
    CollisionManager collisions;
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.23 03:27:04