Engine and Scene

Learn how to create a simple Engine and Scene Class

Code Repository

Designing our Engine and Scene Classes

This SFML tutorial series will be divided into topics or lessons. For each of those lessons, we’ll have to create a new project and set up the basic code that provides the window and the game loop, only after that, we’ll be able to start the lesson. What if we could separate the Lesson code from the Basic code so that, for each new Lesson we’ll only have to write the Lesson code. 

The pseudo-code below shows how this would look. For each new lesson, we’ll just have to create a new class “MyNewLesson” that will inherit a base class called “BaseLesson”. This way we only have to write the code we need in MyNewLesson and the BaseLesson class will take care of the rest. Then we provide our lesson “MyNewLesson” to an Engine that will create a window and render the lesson for us.

//create a new lesson
class MyNewLesson : public BaseLesson
{
    //write the new lesson (only the code we need)
};

int main()
{
    //Let the Engine run our new lesson
    Engine engine;
        engine.setLesson(MyNewLesson());
    engine.start();
}

The Real Code

For our implementation, the BaseLesson class will be called Scene (short for Game Scene) and the Engine class will be called Engine (who could have guessed that !?)

The code below (main.cpp) shows the result we want to achieve.

  • The Engine Class lets us create a new Game Window by specifying the window width, the window height, and the frame rate we want to use (60 FPS for example)
  • The Scene Class is our actual game (or lesson here) and  can be played or run by the Engine.
  • To have a clear separation between our game/lesson code and our Engine Base code, we encapsulate all the Engine Base code inside a namespace called “ng” (short for nero games). In the code below you can see ng::Engine, this means that the class “Engine” is part of the namespace “ng“, same for ng::Scene.
////////////////////////////////////////////////////////////
// Nero Game Engine - SFML Tutorials
////////////////////////////////////////////////////////////
///////////////////////////HEADERS//////////////////////////
#include "Engine.h"
////////////////////////////////////////////////////////////
int main()
{
    //create new Engine instance with(width, height, frame-rate (60FPS or 120FPS))
    ng::Engine engine(1080, 720, 60);
        //provide our Scene to the Engine using a Smart Unique Pointer (Ptr)
        engine.setScene(ng::Scene::Ptr(new ng::Scene()));
    //run the Engine (the game)
    engine.run();
    return 0;
}

Scene Class

The image below shows the lifecycle of our Scene Class. After the Scene is created, it gets initialized, then it enters the Game Loop. After leaving the Game Loop it gets destroyed.

Base on the image above we’ll need at least five methods in our Scene class

  • Init() : used to build the game initial state (before entering the game loop)
  • HandleEvent(const sf::Event& event)  : used to capture the user inputs with SFML Events (happens at each frame during the game loop)
  • Update(const sf::Time& timeStep) : used to update the game objects properties like positions, animations, etc. (happens at each frame during the game loop)
  • Render() : used to draw all the game objects (happens during the game loop)
  • Destroy :  used to destroy all our game data (after leaving the game loop)
Finally, to be able to create Custom Scenes that inherit our ng::Scene, we need to add some Polymorphism. If you’ve learned your C++ lessons correctly, you know that polymorphism means two things : Virtual Functions and Pointers (we’ll use Smart Pointers because that’s cooler). Expend the code below (ng::Scene header file) to see the structure of the class ng::Scene. As you can see, the five methods are declared virtual and we’ve declared a Smart Unique Pointer named “Ptr” that will be later used in the class ng::Engine.
////////////////////////////////////////////////////////////
// Nero Game Engine - SFML Tutorials
////////////////////////////////////////////////////////////
#ifndef SCENE_H
#define SCENE_H
///////////////////////////HEADERS//////////////////////////
//SFML
#include <SFML/Graphics/RenderWindow.hpp>
//CPP
#include <memory>
////////////////////////////////////////////////////////////
namespace ng
{
    class Scene
    {
        public:
            typedef std::unique_ptr<Scene> Ptr;
        public:
                                        Scene();
            virtual                    ~Scene();
        public:
            virtual void                init();
            virtual void                handleEvent(const sf::Event& event);
            virtual void                update(const sf::Time& timeStep);
            virtual void                clear();
            virtual void                render();
            virtual void                destroy();
        public:
                //getter
            std::string                 getSceneName() const;
            sf::Vector2f                getSceneResolution() const;
            sf::RenderWindow&           getRenderWindow() const;
                //setter
            void                        setSceneName(const std::string& name);
        private:
            friend class                Engine;
            std::string                 m_SceneName;
            sf::Vector2f                m_Resolution;
            sf::RenderWindow*           m_RenderWindow;
    };
}
#endif // SCENE_H

Engine Class

The image below shows the lifecycle of our Engine class. After the Engine is created, we provide it our Scene, then we let it run which will initialize the Scene, run the Game Loop and destroy the Scene.

The code below shows the content of the ng::Engine run() method. At the very beginning of the method, the method init() is called, this is when our Scene is initialized. Then we enter the Game Loop where the game will Handle Events, Update and Render. Finally, when we exit the game loop the method destroy() is called, this is when our Scene gets destroyed.

void Engine::run()
{
    //Initialize game
    init();
    //Variable to store the time that passes
    sf::Time timePassed = sf::Time::Zero;
    //Create a clock, the clock starts automatically
    sf::Clock clock;
    //Entering the game loop
    while(m_RenderWindow.isOpen())
    {
        //Retrieve the clock time and reset the clock at the same time
        sf::Time clockTime = clock.restart();
        //Accumulate the time passing in our variable (this happens at each cycle of the while loop)
        timePassed += clockTime;
        //When the time passing is over our Fixed Time only Capture Inputs and Update the Game
        while(timePassed > m_FrameRate)
        {
            //Remove (0.0166) seconds from the passing time
            //Only the overflow will remain for the next frame
            timePassed -= m_FrameRate;
            //capture the user inputs using SFML Events
            handleEvent();
            //update the game at the fixed time frame of (0.0166 second)
            update(m_FrameRate);
        }
        //Render the game
        //We do not render the game at 60 FPS, only the update happens at 60 FPS
        render();
    }
    destroy();
}

Example : Create a Custom Scene

Now that we have our Engine and Scene classes, let’s try them. The code below shows how to create a custom Scene named Circle Scene. This scene is pretty simple, it displays a green circle that follows the movement of your mouse.

Here is the full code of the Circle Scene Class.

////////////////////////////////////////////////////////////
// Nero Game Engine - SFML Tutorials
////////////////////////////////////////////////////////////
#ifndef CIRCLESCENE_H_INCLUDED
#define CIRCLESCENE_H_INCLUDED
///////////////////////////HEADERS//////////////////////////
//Nero Games
#include "Scene.h"
//SFML
#include "SFML/Graphics.hpp"
////////////////////////////////////////////////////////////
class CircleScene : public ng::Scene
{
    private:
        sf::CircleShape     mCircle;
        sf::Vector2f        mCirclePosition;
    public:
        typedef std::unique_ptr<CircleScene> Ptr;
        CircleScene()
        {
            setSceneName("Circle Scene v0.1");
        }
    public:
        void init()
        {
            mCircle.setRadius(100.f);
            mCircle.setOrigin(100.f, 100.f);
            mCircle.setFillColor(sf::Color::Green);
            mCirclePosition.x = getSceneResolution().x/2.f;
            mCirclePosition.y = getSceneResolution().y/2.f;
            mCircle.setPosition(mCirclePosition);
        }
        void handleEvent(const sf::Event& event)
        {
            //default event handle (close the window)
            ng::Scene::handleEvent(event);
            //capture the mouse position
            if (event.type == sf::Event::MouseMoved)
            {
                mCirclePosition.x =  event.mouseMove.x;
                mCirclePosition.y =  event.mouseMove.y;
            }
        }
        void update(const sf::Time& timeStep)
        {
            mCircle.setPosition(mCirclePosition);
        }
        void render()
        {
            getRenderWindow().draw(mCircle);
        }
};
#endif // CIRCLESCENE_H_INCLUDED

Here is the code inside the main.cpp file

////////////////////////////////////////////////////////////
// Nero Game Engine - SFML Tutorials
////////////////////////////////////////////////////////////
///////////////////////////HEADERS//////////////////////////
#include "Engine.h"
#include "CircleScene.h"
////////////////////////////////////////////////////////////
int main()
{
    //create new Engine instance
    ng::Engine engine(1080, 720, 60);
        //provide Scene to Engine
        engine.setScene(CircleScene::Ptr(new CircleScene()));
    //run the Engine
    engine.run();
    return 0;
}

You have successfully subscribed to the newsletter

There was an error while trying to send your request. Please try again.

Nero Games will use the information you provide on this form to be in touch with you and to provide updates and marketing.