Create a Game Engine: Part IV Scene Manager

We need a place where we can load our shaders, load and render our game models (just triangle for now) and also listen to notifications from GLUT methods which were  presented in the previous tutorial. This place is called SceneManager and it will evolve in future tutorials.

So what we have to do is to tie up this SceneManager with Init_GLUT to listen to rendering notifications. Now, passing SceneManager to Init_GLUT as a parameter is a bad practice because it creates an ugly dependency (maybe in the future we will want other scenes and/or other object to be notified). So it’s much better if we inject an interface in Init_GLUT and SceneManager implements this interface. Note that we trade some efficiency for design here.

Basic Game Manager Architecture. Scene Manager

Basic Game Manager Architecture. Scene Manager and Listener

 

First we have to create the listener interface. Let’s call it IListener (IListener.h) and place it in the Init folder:

#pragma once
namespace Core{

 class IListener
 {
   public:
     virtual ~IListener() = 0;

     //drawing functions
     virtual void notifyBeginFrame() = 0;
     virtual void notifyDisplayFrame() = 0;
     virtual void notifyEndFrame() = 0;
     virtual void notifyReshape(int width,
                                int height,
                                int previous_width,
                                int previous_height) = 0;
  };

  inline IListener::~IListener(){
     //implementation of pure virtual destructor
  }
}

We have 3 methods: notifyBeginFrame() , notifyDisplayFrame(), notifyEndFrame() which are called in the displayCallback() method from Init_GLUT class. The main reason we are splitting displayCallback() method is to ensure that later on we will have a good flow in our rendering loop. Involving some expensive CPU processes right in the middle of the rendering loop can have a huge impact over the performance. The GPU is waiting for your draw commands and you interrupting it with some CPU stuff.

GPU : “Bring it on, I’m ready, my caches are ready give my all you got!!!”

CPU: “Wait I have to do some collisions”

GPU: “zzzzZZZ”

CPU : “OK draw this but after that you should wait me to finish with physics and AI”

GPU: ” Cricket sound

You get the idea. So that’s why it’s inportat to have a good flow and it’s better to separate CPU processing from GPU rendering process. For example we can solve physics and collision at the beginning of each frame in notifyBeginFrame() and after that we can draw everything in notifyDisplayFrame() and do other stuff in notifyEndFrame(). This is just for a single thread scenario.

Now we have to modify the Init_GLUT class to accept the listener:

Init_GLUT.h
...
//add the following attributes & methods are in Init_GLUT.h

private:
static Core::IListener* listener;
static Core::WindowInfo windowInformation;

public:
static void setListener(Core::IListener*& iListener);
....
//Init_GLUT.cpp
//make sure that static attributes are visible in cpp
Core::IListener* Init_GLUT::listener = NULL;
Core::WindowInfo Init_GLUT::windowInformation;

void Init_GLUT::init(const Core::WindowInfo& windowInfo,
                     const Core::ContextInfo& contextInfo,
                     const Core::FramebufferInfo& framebufferInfo)
{
   //...
    windowInformation = windowInfo;//add this line
   //..
}

void Init_GLUT::displayCallback()
{
  //check for NULL
  if (listener)
  {
    listener->notifyBeginFrame();
    listener->notifyDisplayFrame();

    glutSwapBuffers();

    listener->notifyEndFrame();
  }
}

void Init_GLUT::reshapeCallback(int width, int height)
{
  if (windowInformation.isReshapable == true)
  {
    if (listener)
    {
      listener->notifyReshape(width,
                              height,
                              windowInformation.width,
                              windowInformation.height);
     }
     windowInformation.width = width;
     windowInformation.height = height;
  }
}

//set the listener
void Init_GLUT::setListener(Core::IListener*& iListener)
{
 listener = iListener;
}

As you can see we need a holder for our listener and a holder for our windowInfo to access previous width and height in case we need them in the future. Also we need a method to set the listener. They are static because our callbacks are also static.

Now finally we create the Scene_Manager class. First we create the Scene_Manager.h file in the Managers folder.

//Scene_Manager.h
#pragma once
#include "Shader_Manager.h"
#include "../Core/Init/IListener.h"
namespace Managers
{
  class Scene_Manager : public Core::IListener
  {
    public:
      Scene_Manager();
      ~Scene_Manager();

      virtual void notifyBeginFrame();
      virtual void notifyDisplayFrame();
      virtual void notifyEndFrame();
      virtual void notifyReshape(int width,
                                 int height,
                                 int previous_width,
                                 int previous_height);
    private:
      Managers::Shader_Manager* shader_manager;
 };
}

Now let’s see the Scene_Manager.cpp file:

//Scene_Manager.cpp
#include "Scene_Manager.h"
using namespace Managers;

Scene_Manager::Scene_Manager()
{
  glEnable(GL_DEPTH_TEST);

  shader_manager = new Shader_Manager();
  shader_manager->CreateProgram("colorShader",
                                "Shaders\\Vertex_Shader.glsl",
                                "Shaders\\Fragment_Shader.glsl");
}

Scene_Manager::~Scene_Manager()
{
   delete shader_manager;
}

void Scene_Manager::notifyBeginFrame()
{
    //nothing here for the moment
}

void Scene_Manager::notifyDisplayFrame()
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 glClearColor(0.0, 0.0, 0.0, 1.0);

}

void Scene_Manager::notifyEndFrame()
{
 //nothing here for the moment
}

void Scene_Manager::notifyReshape(int width,
                                  int height,
                                  int previous_width,
                                  int previous_height)
{
 //nothing here for the moment 

}

We don’t have anything to draw yet, the triangle is not ready, but we can clear the screen in black (because I love black).

Before we move on to the next part we have to link the Scene_Manager to Init_GLUT in main.cpp

#pragma once
#include "Core\Init\Init_GLUT.h"
#include "Managers\Scene_Manager.h"

using namespace Core;
int main(int argc, char **argv) {

 WindowInfo window(std::string("in2gpu OpenGL Beginner Tutorial"),
                                 400, 200, 800, 600, true);
 ContextInfo context(4, 5, true);
 FramebufferInfo frameBufferInfo(true, true, true, true);

 Init::Init_GLUT::init(window, context, frameBufferInfo);

 IListener* scene = new Managers::Scene_Manager();
 Init::Init_GLUT::setListener(scene);

 Init::Init_GLUT::run();

 delete scene;
 return 0;
}

 

The result

The result

That’s it for now. In the next part we deal with the rendering components. 

 


blog comments powered by Disqus