0
votes

I'm building a simple 2D game in C++ and am using Box2D for collision detection.

I have an Entity class from which an Enemy and Bullet class is derived, and an EnemySquare class is derived from the Enemy class.

I'm trying to detect collisions between the EnemySquare class and the Bullet class (will have more collision combinations to process later in development). To do this I have created a CollisionManager class deriving from the Box2D class b2ContactListener which handles the collision callbacks.

Each Entity instance has a private variable m_collisionObjectType which is an enum class of object types (shown below).

In the BeginContact() callback, I'm trying to cast the box2d fixture's user data to the correct class type so I can apply damage, mark bullets for removal etc.

(non-relevant code removed for simplicity)

Object Type Enum:

enum class COLLISION_OBJECT_TYPE {BULLET, ENEMY, PLAYER};

Entity class

.h

class Entity
{
public:
    Entity();
    ~Entity();

    COLLISION_OBJECT_TYPE getCollisionObjectType() { return m_collisionObjectType; }

protected:

    b2Body* m_body = nullptr;
    b2Fixture* m_fixtures[3];
    COLLISION_OBJECT_TYPE m_collisionObjectType;

};

Enemy Class

.h

class Enemy : public Entity
{
public:
    Enemy();
    ~Enemy();
    virtual void init(glm::vec2 position, float health, float speed, Player* player, b2World* physicsWorld) = 0;
    virtual void update(float deltaTime) = 0;

protected:

    float m_health;
    float m_speed;
    Player* m_playerTarget;

};

Enemy Square Class

.h

class EnemySquare : public Enemy
{
public:
    EnemySquare();
    ~EnemySquare();

    void init(glm::vec2 position, float health, float speed, Player* player, b2World* physicsWorld) override;
    void update(float deltaTime) override;

};

.cpp

void EnemySquare::init(glm::vec2 position, float health, float speed, Player * player, b2World* physicsWorld) {

    // init physics body
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position.Set(m_position.x, m_position.y);
    bodyDef.fixedRotation = false;
    bodyDef.angle = 0;
    bodyDef.userData = this;
    m_body = physicsWorld->CreateBody(&bodyDef);

    // init physics fixtures
    b2PolygonShape squareShape;
    squareShape.SetAsBox(m_width * 0.5f, m_height * 0.5f);
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &squareShape;
    m_fixtures[0] = m_body->CreateFixture(&fixtureDef);

}

Bullet Class

.h

class Bullet : public Entity
{
public:
    Bullet(
        b2World* world,
        glm::vec2 startPosition,
        glm::vec2 direction,
        Tempest::glTexture texture,
        float width,
        float height,
        float damage,
        float speed,
        float range
    );
    ~Bullet();

    // methods are unrelated

private:
    // private variables are unrelated

};

.cpp

Bullet::Bullet(
    b2World* world,
    glm::vec2 startPosition,
    glm::vec2 direction,
    Tempest::glTexture texture,
    float width,
    float height,
    float damage,
    float speed,
    float range
) {

    // Make the body
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position.Set(m_position.x, m_position.y);
    bodyDef.fixedRotation = true;
    bodyDef.angle = 0;
    bodyDef.userData = this;
    m_body = world->CreateBody(&bodyDef);

    // Create the box
    b2PolygonShape boxShape;
    boxShape.SetAsBox(m_height * 0.4f, m_width * 0.5f);

    b2FixtureDef boxDef;
    boxDef.shape = &boxShape;
    m_fixtures[0] = m_body->CreateFixture(&boxDef);

    m_collided = false;

    m_collisionObjectType = COLLISION_OBJECT_TYPE::BULLET;

}

In my the CollisionManager class I'm trying to retrieve the colliding fixture's userData (which is a void*) then cast it to an Entity* to call the getCollisionObjectType() method. When I know what type of entity I'm dealing with I then want to cast it to the correct object type and do things like apply damage, mark bullets for removal etc. Code below:

void CollisionManager::BeginContact(b2Contact * contact) {

    void* fixtureABodyData = contact->GetFixtureA()->GetBody()->GetUserData();
    void* fixtureBBodyData = contact->GetFixtureB()->GetBody()->GetUserData();
    if (fixtureABodyData && fixtureBBodyData) {
        Entity* fixtureAData = static_cast<Entity*>(fixtureABodyData);
        Entity* fixtureBData = static_cast<Entity*>(fixtureBBodyData);
        if (fixtureAData->getCollisionObjectType() == COLLISION_OBJECT_TYPE::BULLET) {
            std::cout << "A BULLET" << std::endl;
        }
        if (fixtureBData->getCollisionObjectType() == COLLISION_OBJECT_TYPE::BULLET) {
            std::cout << "B BULLET" << std::endl;
        }
        if (fixtureAData->getCollisionObjectType() == COLLISION_OBJECT_TYPE::ENEMY) {
            std::cout << "A ENEMY" << std::endl;
        }
        if (fixtureBData->getCollisionObjectType() == COLLISION_OBJECT_TYPE::ENEMY) {
            std::cout << "B ENEMY" << std::endl;
        }
        std::cout << "----------------------" << std::endl;
}

}

For some reason, the casting works for the Bullet class but not for the Enemy class. I think it returns a nullptr. So I know one of the colliding bodies is a bullet, but I can't tell what the second body is.

I have a feeling I'm doing something wrong with the static_cast calls or it might be because the EnemySquare class is twice removed from the Entity class? or I could be doing something wrong in the Box2D code. Any advice would be appreciated!

1
Hi there! Regarding the static_casts Have a look type casting. I have a feeling it should be reinterpret_cast instead. - TrebledJ
Hey, Thanks a lot for the suggestion, unfortunately still no luck. I'll keep looking into reinterpret_cast though, feels like that's a good solution I just maybe need to figure out how to use it properly. - RHSmith159

1 Answers

0
votes

Set the m_collisionObjectType member variable for the Enemy derived class. Preferably to COLLISION_OBJECT_TYPE::ENEMY.

As the code appears now, it's only setting m_collisionObjectType in the code for the Bullet derived class. So the Enemy derived class instances are getting constructed with their m_collisionObjectType member variables uninitialized. I.e. the value out of the getCollisionObjectType() method could be whatever was in the memory location of m_collisionObjectType before it got used by the constructor.

Hope this helps!