Physics Scene

Enable physics in our scene

Source Code

Box2D Scene Class

In the previous tutorials, we’ve learned a lot, we now know how to use SFML to display Box2D physical world, and we also know how to grab objects with the mouse. It would be interesting to have those features by default in our Scene so we don’t have to re-implement them each time. In order to achieve that we are going to create a new base scene class called Box2DScene, this class will work exactly like the current ng::Scene class but will have physics enable by default.

class MyScene : public ng::Box2DScene
{
    //implement your Scene
};
int main()
{
    ng::Engine engine(1080, 720, 60);
        engine.setScene(MyScene::Ptr(new MyScene()));
    engine.run();
    return 0;
}
Creating the new Box2DScene class is pretty easy. We start from the current ng::Scene class and we perform two modifications:
 
  • Rename ng::Scene to ng::Box2DScene and update all related code
  • Add a new attribute called m_PhysicWorld
That’s it, all the physics features will be implemented inside the class PhysicWorld and then manage at the Engine level.

Physic World Class

The class PhysicWorld encapsulates all the code we’ve written in the tutorials SFML and Debug Draw and Mouse Grabbing. Inside its init() method, we configure the Debug Draw, inside update() we perform the physics simulation, inside render() we display the physical world. The methods onMouseGrabObject, onMouseMoveObject and onMouseDropObject are used to pick up and drop objects with the mouse. It’s the same code as in previous tutorials but with some little adaptations here and there.

void PhysicWorld::init()
{
    //setup the DebugDraw
    m_DebugDraw.setRenderWindow(m_RenderWindow);
    m_DebugDraw.SetFlags(b2Draw::e_shapeBit);
    m_PhysicWorld.SetDebugDraw(&m_DebugDraw);
    //mouse grabbing
    b2BodyDef bodyDef;
    m_MouseGround = m_PhysicWorld.CreateBody(&bodyDef);
}
void PhysicWorld::update(const sf::Time& timeStep)
{
    m_PhysicWorld.Step(timeStep.asSeconds(), 8.f, 3.f);
}
void PhysicWorld::render()
{
    m_PhysicWorld.DrawDebugData();
}

Update the Engine

As you can see in the code below, the new PhysicWorld class is used at the Engine level, inside the methods : init(), handleEvent(), update(), render() and destroy(). This approach will allow us to have all the physics features by default without doing anything. It is possible to call all the PhysicWorld methods inside the Scene class and not at the Engine level, but that would require you to make a parent call in every derived method.

void Engine::init()
{
    //pre-initialization
    m_Scene->m_PhysicWorld.setRenderWindow(&m_RenderWindow);
    m_Scene->m_PhysicWorld.init();
    //initialization
    m_Scene->init();
}
void Engine::handleEvent()
{
    sf::Event event;
    while(m_RenderWindow.pollEvent(event))
    {
        m_Scene->m_PhysicWorld.handleEvent(event);
        m_Scene->handleEvent(event);
    }
}
void Engine::update(const sf::Time& timeStep)
{
    m_Scene->m_PhysicWorld.update(m_FrameRate);
    m_Scene->update(m_FrameRate);
}
void Engine::render()
{
    m_Scene->clear();
    m_Scene->m_PhysicWorld.render();
    m_Scene->render();
    m_RenderWindow.display();
}
void Engine::destroy()
{
    m_Scene->m_PhysicWorld.destroy();
    m_Scene->destroy();
}

Test our new Scene

Now that our new Engine class and Box2DScene class are ready we can try them. In the code below we create a scene called TestScene inheriting Box2DScene. Inside the method init() we create a floor and we enable the creation of new boxes with CTRL + Mouse Left-click. It’s the same code as in the previous tutorial but as you can see, we no longer need to add a Debug Draw manually or handle the mouse grab ourselves.

class TestScene : public ng::Box2DScene
{
    public:
        typedef std::unique_ptr<testscene> ptr;
        TestScene()
        {
            setSceneName("Test Scene v0.1");
        }
        void init()
        {
            //create a rigid floor
            createFloorBody();
        }
        void onMouseButton(const sf::Mouse::Button& button, const bool& isPressed, const sf::Vector2f& position)
        {
            if(isPressed)
            {
                if (button == sf::Mouse::Left && ng::CTRL())
                {
                    createBody(ng::sf_to_b2(position, ng::SCALE));
                }
            }
        }
        void createBody(b2Vec2 position = b2Vec2(0.f, 0.f))
        {
           //create body
        }
        void createFloorBody()
        {
            // create floor
        }
};