Physics Scene
Enable physics in our scene
Source Code
- The code for this tutorial can be found on Github : Learn Box2D – Physics Scene
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; }
- Rename ng::Scene to ng::Box2DScene and update all related code
- Add a new attribute called m_PhysicWorld
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 } };