Skip to content
Wout Gryseels
C++ / SDL

C++ game engine

Uses SDL for rendering 2D games. Dig Dug was recreated inside the engine.

The engine uses components, commands, observers, a service locator, and the state pattern. The renderer and managers come from the service locator. There are two state machines: player and AI. The AI state machine runs with multithreading. Commands come from a command factory. Components they affect are passed with dependency injection.

AiComponent.cpp
void dae::AiComponent::Update(float dt)
{
  if (firstFrame)
  {
    firstFrame = false; 
    result = std::future < std::pair<bool, BaseAiState *>>(std::async(&BaseAiState::Update, m_CurrentState.get(), dt));
    return;
  }
  auto newstate = result.get();
  if (newstate.first)
    m_CurrentState.reset(newstate.second);
  result = std::future < std::pair<bool, BaseAiState *>>(std::async(&BaseAiState::Update, m_CurrentState.get(), dt));
}
AiStates.cpp
std::pair<bool, dae::BaseAiState*> dae::IdleAiState::Update(float dt)
{
  m_ElapsedTime += dt;
  if (m_ElapsedTime > 0.4 && m_Owner.m_AnimationComponent.m_ActiveAnimationId <= 2)
  {
    m_ElapsedTime = 0;
    if (m_Owner.m_EnemyType == AiTypes::Fygar)
    {
      if (rand() % (int)(1 / m_ChanceToFire) == 0)
      {
        m_Owner.m_FireBreathComponent->BreathFire();
        return { true, new FireBreathAiState(m_Owner) };
      }
    }
    if (rand() % (int)(1 / m_ChanceToGhost) == 0)
    {
      const auto Commander = ServiceLocator::GetCommandFactory();
      Commander->AiGhost() (&m_Owner.m_AnimationComponent);
      Commander->ToggleCollision()(false, &m_Owner.m_CollisionComponent);
      return { true, new GhostAiState(m_Owner) };
    }	
    if (rand() % (int)(1 / m_ChanceToMove) == 0)
      return { true, new MovingAiState(m_Owner) };
  }
  return  { false, nullptr };

};

Each game object is a vector of components: position, movement, input, physics, and so on. The collision component uses the physics manager to test overlaps.

PhysicsManager.h
inline bool  CheckBoxesIntersect(Box a, Box b) const noexcept { return (abs((a.x ) - b.x) * 2 < (a.width + b.width)) && (abs(a.y - b.y) * 2 < (a.height + b.height));}  
  namespace  dae {
    struct Box {
      float x; 
      float y; 
      int width; 
      int height; 
      Box(float xpos, float ypos, int w = g_blocksize, int h = g_blocksize) :
        x(xpos ), y(ypos ), width(w), height(h)
      {}
    };
    class PhysicsManager {
    public: 
      PhysicsManager() ; 
      ~PhysicsManager() = default	; 
      void InitActiveComponents(); 
      CollisionFlags CheckPlayerCollision(dae::Vec2);

      std::pair<bool, dae::CollisionComponent *> CheckCollision(dae::Vec2 pos);

      std::pair<dae::CollisionFlags, dae::GameObject* > CheckHoseCollision(dae::Vec2 pos, dae::Vec2 size);

      std::pair<dae::CollisionFlags, dae::GameObject*> CheckFireCollision(Box b);

      inline bool  CheckBoxesIntersect(Box a, Box b) const noexcept { return (abs((a.x ) - b.x) * 2 < (a.width + b.width)) && (abs(a.y - b.y) * 2 < (a.height + b.height));} 
    
      std::vector<std::pair<PositionComponent *,GameObject*>> m_Pair_PosComp_GameObj;
    };
  }
PhysicsManager.cpp
void dae::PhysicsManager::InitActiveComponents()
{
  m_Pair_PosComp_GameObj.clear();

  auto sceneMngr = ServiceLocator::GetSceneManager(); 
  auto scene = sceneMngr->GetActiveScene(); 
  auto sObjects = scene->GetSceneObjects(); 
  for (auto sObj : sObjects)
  {
    GameObject * gObj = static_cast<GameObject *> (sObj.get()); 
    if (gObj != nullptr)
    {
      auto cComp = gObj->GetComponent<CollisionComponent>();
      if (cComp == nullptr)
        continue;;
      auto pComp = (gObj->GetComponent<PositionComponent>());
      m_Pair_PosComp_GameObj.push_back({ pComp,gObj });


    }
  }

}

dae::CollisionFlags dae::PhysicsManager::CheckPlayerCollision(dae::Vec2 pos)
{
  
  
  auto pos1 = pos;
  auto Box1 = Box((float)pos1.x + 16, (float)pos1.y + 16);
  for (auto pair : m_Pair_PosComp_GameObj)
  {
    auto posComp2 = pair.first;
    if (posComp2->GetPosition().x == pos1.x && posComp2->GetPosition().y == pos1.y)
      continue;
    auto pos2 = posComp2->GetPosition();
    auto Box2 = Box((float)pos2.x + 16, (float)pos2.y + 16);
    Box2.height = 16; Box2.width = 16;
    if (CheckBoxesIntersect(Box1, Box2))
    {  //could optimize this by changing pair into tuple with collisioncomp
      auto colComp = pair.second->GetComponent<CollisionComponent>();
      if (colComp->m_CanCollide)
        return (colComp->m_CollisionCategoryFlags); 
    }
      
  

  }
  return NoCollision;
}
CollisionComponent.cpp
bool dae::CollisionComponent::CheckCollision(dae::Vec2 pos)
{
  if (m_CanCollide && m_CollisionCategoryFlags == CollisionFlags::Player)
  {
    auto  pair = m_PhysicsManager->CheckCollision(pos); 
    if (pair.first && pair.second->m_CanCollide)
    {
    switch (pair.second->m_CollisionCategoryFlags)
      {
      case Enemy:
      case  FallingRock:
        m_CommandComponent.Death();
      case Static:
        return true;
        break;
      case Player:
      default:
        return false;
        break;
      };
    }
    
  }
}