{"id":570,"date":"2012-02-02T22:36:56","date_gmt":"2012-02-02T21:36:56","guid":{"rendered":"http:\/\/www.grandmaster.nu\/blog\/?page_id=570"},"modified":"2012-02-17T11:30:04","modified_gmt":"2012-02-17T10:30:04","slug":"game-engine-part-6","status":"publish","type":"page","link":"https:\/\/www.grandmaster.nu\/blog\/?page_id=570","title":{"rendered":"Game Engine Part 6"},"content":{"rendered":"<p>First off, I&#8217;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.<\/p>\n<p>Let&#8217;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&#8217;s a lot of wrapping to be done, so this time there&#8217;s going to be a lot of relatively straightforward code.<\/p>\n<p>I&#8217;d like to be able to display a window. There are several options to go about this (native code, <a href=\"http:\/\/www.libsdl.org\/\">SDL<\/a>, <a href=\"http:\/\/qt.nokia.com\/\">Qt<\/a>, <a href=\"http:\/\/www.wxwidgets.org\/\">WX<\/a>, <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/d06h2x6e%28v=VS.100%29.aspx\">MFC<\/a>, <a href=\"http:\/\/www.fox-toolkit.org\/\">FOX<\/a>, <a href=\"http:\/\/www.opengl.org\/resources\/libraries\/glut\/\">GLUT<\/a>, <a href=\"http:\/\/www.gtk.org\/\">GTK+<\/a>, the list goes on&#8230;) but right now I&#8217;m in favour of using <a href=\"http:\/\/www.glfw.org\/\">GLFW<\/a>. It is simple, cross-platform and powerful enough for our purpouses. There is a slight tradeoff, however &#8211; 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.<\/p>\n<p>So, the first task is to make a skeleton System class, which I shall name Video:<\/p>\n<pre lang=\"c++\">class Video: public System {\r\npublic:\r\n\tVideo();\r\n\tvirtual ~Video() {}\r\n\t\r\n\tvirtual bool init();\r\n\tvirtual void update();\r\n\tvirtual void shutdown();\r\n};\r\n<\/pre>\n<p>Right. This subsystem can neatly wrap the libraries&#8217; initialization and shutdown, taking a bit of possible errors into account:<\/p>\n<pre lang=\"c++\">Video::Video(): \r\n\tSystem(\"Video\")\r\n{\r\n}\r\n\r\nbool Video::init() {\r\n\tif (glfwInit() == GL_FALSE) {\r\n\t\tmLog << \"Failed to initialize GLFW\";\r\n\t\treturn false;\r\n\t}\r\n\t\r\n\tint versionMajor, versionMinor, versionRevision;\r\n\tglfwGetVersion(&#038;versionMajor, &#038;versionMinor, &#038;versionRevision);\r\n\tmLog << \"Initialized GLFW \" << versionMajor << \".\" << versionMinor << \" rev. \" << versionRevision;\r\n\t\r\n\treturn true;\r\n}\r\n\r\nvoid Video::shutdown() {\r\n\tglfwTerminate();\r\n}\r\n<\/pre>\n<p>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:<\/p>\n<pre lang=\"c++\">class Window {\r\npublic:\r\n\tWindow();\r\n\tvirtual ~Window() {}\r\n\r\n\tbool create();\r\n\tvoid destroy();\r\n\r\nprotected:\r\n\tunsigned int mWidth;\r\n\tunsigned int mHeight;\r\n\r\n\tbool mFullScreen;\r\n\r\n\tunsigned int mRedBits;\r\n\tunsigned int mGreenBits;\r\n\tunsigned int mBlueBits;\r\n\tunsigned int mAlphaBits;\r\n\r\n\tunsigned int mDepthBits;\r\n\tunsigned int mStencilBits;\r\n\r\n\tstd::string mTitle;\r\n};\r\n<\/pre>\n<p>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.<\/p>\n<pre lang=\"c++\">Window::Window() {\r\n\t\/\/default to 800x600x32, 24bits depth, 8 bits stencil, (windowed)\r\n\tmWidth = 800;\r\n\tmHeight = 600;\r\n\t\t\r\n\tmAlphaBits = 8;\r\n\tmRedBits = 8;\r\n\tmGreenBits = 8;\r\n\tmBlueBits = 8;\r\n\r\n\tmDepthBits = 24;\r\n\tmStencilBits = 8;\r\n\r\n\tmFullScreen = false;\r\n\r\n\tmTitle = \"Overdrive Assault\";\r\n}\r\n\r\nbool Window::create() {\r\n\tint result = glfwOpenWindow(\r\n\t\tmWidth, \r\n\t\tmHeight,\r\n\t\tmRedBits,\r\n\t\tmGreenBits,\r\n\t\tmBlueBits,\r\n\t\tmAlphaBits,\r\n\t\tmDepthBits,\r\n\t\tmStencilBits,\r\n\t\tmFullScreen ? GLFW_FULLSCREEN : GLFW_WINDOW\r\n\t);\r\n\r\n\tif (result == GL_FALSE)\r\n\t\treturn false;\r\n\r\n\tglfwSwapInterval(0); \/\/disable vertical sync\r\n\treturn true;\r\n}\r\n\r\nvoid Window::destroy() {\r\n\tglfwCloseWindow();\r\n}\r\n\r\n\/\/... in Video::init()\r\n\tif (!mWindow->create()) {\r\n\t\tmLog << \"Failed to create window\";\r\n\t\treturn false;\r\n\t}\r\n<\/pre>\n<p>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.<\/p>\n<pre lang=\"c++\">\r\n\/\/... in Video.h\r\nprivate:\r\n\tboost::shared_ptr<Window> mWindow;\r\n\r\n\/\/... in Video.cpp\r\nVideo::Video():\r\n\tSystem(\"Video\")\r\n\tmWindow(new Window())\r\n{\r\n\taddSetting(\"Width\", &mWindow->mWidth);\r\n\taddSetting(\"Height\", &mWindow->mHeight);\r\n\r\n\taddSetting(\"RedBits\", &mWindow->mRedBits);\r\n\taddSetting(\"GreenBits\", &mWindow->mGreenBits);\r\n\taddSetting(\"BlueBits\", &mWindow->mBlueBits);\r\n\taddSetting(\"AlphaBits\", &mWindow->mAlphaBits);\r\n\r\n\taddSetting(\"DepthBits\", &mWindow->mDepthBits);\r\n\taddSetting(\"StencilBits\", &mWindow->mStencilBits);\r\n\r\n\taddSetting(\"FullScreen\", &mWindow->mFullScreen);\r\n\taddSetting(\"Title\", &mWindow->mTitle);\r\n}\r\n\r\n\/\/... in Window.h\r\n\tfriend class Video;\r\n<\/pre>\n<p>And that's pretty much all there's to it \ud83d\ude42 A typical configuration file should look like this now:<\/p>\n<pre lang=\"c++\">[Video]\r\nWidth = 1280\r\nHeight = 1024\r\n\r\nRedBits = 8\r\nGreenBits = 8\r\nBlueBits = 8\r\nAlphaBits = 8\r\n\r\nDepthBits = 24\r\nStencilBits = 8\r\n\r\nFullScreen = false\r\nTitle = Overdrive Assault 0.1a\r\n<\/pre>\n<p>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.<\/p>\n<pre lang=\"c++\">\/\/... in Video::Video()\r\n\tenableUpdater(Task::SINGLETHREADED_REPEATING);\r\n\r\n\/\/... in Video.cpp\r\nvoid Video::update() {\r\n\tglfwSwapBuffers();\r\n}\r\n<\/pre>\n<p>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.<\/p>\n<pre lang=\"c++\">\/\/... in Engine.h\r\nprivate:\r\n\tboost::shared_ptr<Video> mVideo;\r\n\r\n\tconst boost::shared_ptr<Video>& video() { return mVideo; }\r\n\r\n\/\/... in Engine::Engine()\r\nEngine::Engine():\r\n\tmConfig(new Config()),\r\n\tmVideo(new Video())\r\n{\r\n\tadd(mVideo); \/\/system needs to be added to the task queues\r\n}\r\n<\/pre>\n<p>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:<\/p>\n<pre lang=\"c++\">\/\/... in Video.h\r\n\tstruct WindowCreated {\r\n\t\tWindowCreated(const boost::shared_ptr<Window>& window): mWindow(window) {}\r\n\r\n\t\tboost::shared_ptr<Window> mWindow;\r\n\t};\r\n\r\n\tstruct PreUpdate {};\r\n\tstruct PostUpdate {};\r\n\r\n\/\/... in Video::init()\r\n\tEventChannel chan;\r\n\tchan.broadcast(WindowCreated(mWindow));\r\n\t\r\n\/\/... in Video::update()\r\nvoid Video::update() {\r\n\tEventChannel chan;\r\n\r\n\tchan.broadcast(PreUpdate());\r\n\tglfwSwapBuffers();\r\n\tchan.broadcast(PostUpdate());\r\n}\r\n<\/pre>\n<p>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.<\/p>\n<p>So there you have it, preliminary work on integrating <a href=\"http:\/\/www.glfw.org\/\">GLFW<\/a> and displaying a window. The complete, runnable code can be downloaded <a href=\"http:\/\/www.grandmaster.nu\/custom\/OA_Tutorial_06.zip\">[here]<\/a>. See you next time!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>First off, I&#8217;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 <a class=\"more-link\" href=\"https:\/\/www.grandmaster.nu\/blog\/?page_id=570\">Read More&#8230;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":25,"menu_order":0,"comment_status":"open","ping_status":"open","template":"","meta":{"ngg_post_thumbnail":0,"footnotes":""},"_links":{"self":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/570"}],"collection":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=570"}],"version-history":[{"count":7,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/570\/revisions"}],"predecessor-version":[{"id":583,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/570\/revisions\/583"}],"up":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/25"}],"wp:attachment":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=570"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}