Game Engine Part 6

First off, I’d like to apologise for both the long delays between updates and the shoddy state of the downloadable code. It should be fine for educational purpouses but there have been lots of typos and non-compiling code, which is regrettable. From this part onwards, I will publish the entire contraption in a working state, no longer in separate points-of-interest.

Let’s continue where we left off and introduce a couple of base subsystems to the engine, wrapping our first external library (not counting general-purpouse stuff like Boost). Although the actual complexity is not that high, there’s a lot of wrapping to be done, so this time there’s going to be a lot of relatively straightforward code.

I’d like to be able to display a window. There are several options to go about this (native code, SDL, Qt, WX, MFC, FOX, GLUT, GTK+, the list goes on…) but right now I’m in favour of using GLFW. It is simple, cross-platform and powerful enough for our purpouses. There is a slight tradeoff, however – it only supports a single window. That may not be a problem, but it also prevents fancy multi-monitor setups and such. I can live with it.

So, the first task is to make a skeleton System class, which I shall name Video:

class Video: public System {
public:
	Video();
	virtual ~Video() {}
	
	virtual bool init();
	virtual void update();
	virtual void shutdown();
};

Right. This subsystem can neatly wrap the libraries’ initialization and shutdown, taking a bit of possible errors into account:

Video::Video(): 
	System("Video")
{
}

bool Video::init() {
	if (glfwInit() == GL_FALSE) {
		mLog << "Failed to initialize GLFW";
		return false;
	}
	
	int versionMajor, versionMinor, versionRevision;
	glfwGetVersion(&versionMajor, &versionMinor, &versionRevision);
	mLog << "Initialized GLFW " << versionMajor << "." << versionMinor << " rev. " << versionRevision;
	
	return true;
}

void Video::shutdown() {
	glfwTerminate();
}

Ok, so now the library is being initialized, some basic error checking and feedback is being done, and the library cleanup is also being handled as it should. Let's focus on the actual window to be displayed. I choose to make a new class that encapsulates this specifically:

class Window {
public:
	Window();
	virtual ~Window() {}

	bool create();
	void destroy();

protected:
	unsigned int mWidth;
	unsigned int mHeight;

	bool mFullScreen;

	unsigned int mRedBits;
	unsigned int mGreenBits;
	unsigned int mBlueBits;
	unsigned int mAlphaBits;

	unsigned int mDepthBits;
	unsigned int mStencilBits;

	std::string mTitle;
};

As you can see, there are a bunch of members that need to be filled before a window can be constructed. Also, not all values are supported by graphics drivers. It's probaby sensible to start out with a normal, well-supported set.

Window::Window() {
	//default to 800x600x32, 24bits depth, 8 bits stencil, (windowed)
	mWidth = 800;
	mHeight = 600;
		
	mAlphaBits = 8;
	mRedBits = 8;
	mGreenBits = 8;
	mBlueBits = 8;

	mDepthBits = 24;
	mStencilBits = 8;

	mFullScreen = false;

	mTitle = "Overdrive Assault";
}

bool Window::create() {
	int result = glfwOpenWindow(
		mWidth, 
		mHeight,
		mRedBits,
		mGreenBits,
		mBlueBits,
		mAlphaBits,
		mDepthBits,
		mStencilBits,
		mFullScreen ? GLFW_FULLSCREEN : GLFW_WINDOW
	);

	if (result == GL_FALSE)
		return false;

	glfwSwapInterval(0); //disable vertical sync
	return true;
}

void Window::destroy() {
	glfwCloseWindow();
}

//... in Video::init()
	if (!mWindow->create()) {
		mLog << "Failed to create window";
		return false;
	}

Again, it's pretty straightforward. The next step is to make the settings configurable. The System class was designed to support configurations, so let's make use of that.

//... in Video.h
private:
	boost::shared_ptr mWindow;

//... in Video.cpp
Video::Video():
	System("Video")
	mWindow(new Window())
{
	addSetting("Width", &mWindow->mWidth);
	addSetting("Height", &mWindow->mHeight);

	addSetting("RedBits", &mWindow->mRedBits);
	addSetting("GreenBits", &mWindow->mGreenBits);
	addSetting("BlueBits", &mWindow->mBlueBits);
	addSetting("AlphaBits", &mWindow->mAlphaBits);

	addSetting("DepthBits", &mWindow->mDepthBits);
	addSetting("StencilBits", &mWindow->mStencilBits);

	addSetting("FullScreen", &mWindow->mFullScreen);
	addSetting("Title", &mWindow->mTitle);
}

//... in Window.h
	friend class Video;

And that's pretty much all there's to it 🙂 A typical configuration file should look like this now:

[Video]
Width = 1280
Height = 1024

RedBits = 8
GreenBits = 8
BlueBits = 8
AlphaBits = 8

DepthBits = 24
StencilBits = 8

FullScreen = false
Title = Overdrive Assault 0.1a

At this point, a window is created according to user-specified dimensions and buffer depth. Now, as we're making a game engine, I'd like the Video subsystem to continuously update the screen. This is actually pretty important for the next tutorial, where we'll tackle input handling.

//... in Video::Video()
	enableUpdater(Task::SINGLETHREADED_REPEATING);

//... in Video.cpp
void Video::update() {
	glfwSwapBuffers();
}

The frame swapping must be done in a singlethreaded fashion, as the underlying openGL system was never designed to handle multithreading. For now, there are only two more slight additions I'm willing to make. One is integration into the event broadcasting system, and the other is integration into the Engine class. Let's tackle the integration first.

//... in Engine.h
private:
	boost::shared_ptr

Righto, now the Video system is being initialized by the Engine as a default component, and is also being scheduled for continuous updates. Now let's add the integration into the event broadcasting system. When doing this, you have to ask yourself which events might be of interest to other components. An obvious candidate is the creation of the window, but the beginning and end of the video system update may also be of interest. So let's add events for all of them:

//... in Video.h
	struct WindowCreated {
		WindowCreated(const boost::shared_ptr& window): mWindow(window) {}

		boost::shared_ptr mWindow;
	};

	struct PreUpdate {};
	struct PostUpdate {};

//... in Video::init()
	EventChannel chan;
	chan.broadcast(WindowCreated(mWindow));
	
//... in Video::update()
void Video::update() {
	EventChannel chan;

	chan.broadcast(PreUpdate());
	glfwSwapBuffers();
	chan.broadcast(PostUpdate());
}

The WindowCreated event may benefit from just a tad of contextual information, namely a pointer to the freshly created window. The other two events are blank triggers.

So there you have it, preliminary work on integrating GLFW and displaying a window. The complete, runnable code can be downloaded [here]. See you next time!

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.