<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> <meta http-equiv="X-UA-Compatible" content="IE=9"/> <meta name="generator" content="Doxygen 1.8.13"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Tutorial 18: Splitscreen</title> <html xmlns="http://www.w3.org/1999/xhtml"> <!-- Wanted to avoid copying .css to each folder, so copied default .css from doxyen in here, kicked out most stuff we don't need for examples and modified some a little bit. Target was having a single html in each example folder which is created from the main.cpp files and needs no files besides some images below media folder. Feel free to improve :) --> <style> body, table, div, p, dl { font: 400 14px/22px; } body { background-color: #F0F0F0; color: black; margin-left: 5%; margin-right: 5%; } p.reference, p.definition { font: 400 14px/22px; } .title { font: 400 14px/28px; font-size: 150%; font-weight: bold; margin: 10px 2px; } h1, h2, h3, h4, h5, h6 { -webkit-transition: text-shadow 0.5s linear; -moz-transition: text-shadow 0.5s linear; -ms-transition: text-shadow 0.5s linear; -o-transition: text-shadow 0.5s linear; transition: text-shadow 0.5s linear; margin-right: 15px; } caption { font-weight: bold; } h3.version { font-size: 90%; text-align: center; } a { color: #3D578C; font-weight: normal; text-decoration: none; } .contents a:visited { color: #4665A2; } a:hover { text-decoration: underline; } a.el { font-weight: bold; } a.code, a.code:visited, a.line, a.line:visited { color: #4665A2; } a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { color: #4665A2; } pre.fragment { border: 1px solid #C4CFE5; background-color: #FBFCFD; padding: 4px 6px; margin: 4px 8px 4px 2px; overflow: auto; word-wrap: break-word; font-size: 9pt; line-height: 125%; font-family: monospace, fixed; font-size: 105%; } div.fragment { padding: 0px; margin: 4px 8px 4px 2px; background-color: #FBFCFD; border: 1px solid #C4CFE5; } div.line { font-family: monospace, fixed; font-size: 13px; min-height: 13px; line-height: 1.0; text-wrap: unrestricted; white-space: -moz-pre-wrap; /* Moz */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ white-space: pre-wrap; /* CSS3 */ word-wrap: break-word; /* IE 5.5+ */ text-indent: -53px; padding-left: 53px; padding-bottom: 0px; margin: 0px; -webkit-transition-property: background-color, box-shadow; -webkit-transition-duration: 0.5s; -moz-transition-property: background-color, box-shadow; -moz-transition-duration: 0.5s; -ms-transition-property: background-color, box-shadow; -ms-transition-duration: 0.5s; -o-transition-property: background-color, box-shadow; -o-transition-duration: 0.5s; transition-property: background-color, box-shadow; transition-duration: 0.5s; } div.contents { margin-top: 10px; margin-left: 12px; margin-right: 8px; } div.center { text-align: center; margin-top: 0px; margin-bottom: 0px; padding: 0px; } div.center img { border: 0px; } span.keyword { color: #008000 } span.keywordtype { color: #604020 } span.keywordflow { color: #e08000 } span.comment { color: #800000 } span.preprocessor { color: #806020 } span.stringliteral { color: #002080 } span.charliteral { color: #008080 } blockquote { background-color: #F7F8FB; border-left: 2px solid #9CAFD4; margin: 0 24px 0 4px; padding: 0 12px 0 16px; } hr { height: 0px; border: none; border-top: 1px solid #4A6AAA; } address { font-style: normal; color: #2A3D61; } div.header { background-image:url('nav_h.png'); background-repeat:repeat-x; background-color: #F9FAFC; margin: 0px; border-bottom: 1px solid #C4CFE5; } div.headertitle { padding: 5px 5px 5px 10px; } .image { text-align: center; } .caption { font-weight: bold; } div.zoom { border: 1px solid #90A5CE; } tr.heading h2 { margin-top: 12px; margin-bottom: 4px; } </style> </head> <div id="top"><!-- do not remove this div, it is closed by doxygen! --> <!--END TITLEAREA--> <!-- end header part --> <!-- Generated by Doxygen 1.8.13 --> </div><!-- top --> <div class="header"> <div class="headertitle"> <div class="title">Tutorial 18: Splitscreen </div> </div> </div><!--header--> <div class="contents"> <div class="textblock"><div class="image"> <img src="../../media/example_screenshots/018shot.jpg" alt="018shot.jpg"/> </div> <p>A tutorial by Max Winkel.</p> <p>In this tutorial we'll learn how to use splitscreen (e.g. for racing-games) with Irrlicht. We'll create a viewport divided into 4 parts, with 3 fixed cameras and one user-controlled.</p> <p>Ok, let's start with the headers (I think there's nothing to say about it) </p><div class="fragment"><div class="line"><span class="preprocessor">#include <irrlicht.h></span></div><div class="line"><span class="preprocessor">#include "driverChoice.h"</span></div><div class="line"><span class="preprocessor">#include "exampleHelper.h"</span></div><div class="line"></div><div class="line"><span class="preprocessor">#ifdef _MSC_VER</span></div><div class="line"><span class="preprocessor">#pragma comment(lib, "Irrlicht.lib")</span></div><div class="line"><span class="preprocessor">#endif</span></div><div class="line"></div><div class="line"><span class="comment">//Namespaces for the engine</span></div><div class="line"><span class="keyword">using namespace </span>irr;</div><div class="line"><span class="keyword">using namespace </span>core;</div><div class="line"><span class="keyword">using namespace </span>video;</div><div class="line"><span class="keyword">using namespace </span>scene;</div></div><!-- fragment --><p> Now we'll define the resolution in a constant for use in initializing the device and setting up the viewport. In addition we set up a global variable saying splitscreen is active or not. </p><div class="fragment"><div class="line"><span class="comment">//Resolution</span></div><div class="line"><span class="keyword">const</span> <span class="keywordtype">int</span> ResX=800;</div><div class="line"><span class="keyword">const</span> <span class="keywordtype">int</span> ResY=600;</div><div class="line"><span class="keyword">const</span> <span class="keywordtype">bool</span> fullScreen=<span class="keyword">false</span>;</div><div class="line"></div><div class="line"><span class="comment">//Use SplitScreen?</span></div><div class="line"><span class="keywordtype">bool</span> SplitScreen=<span class="keyword">true</span>;</div></div><!-- fragment --><p> Now we need four pointers to our cameras which are created later: </p><div class="fragment"><div class="line"><span class="comment">//cameras</span></div><div class="line">ICameraSceneNode *camera[4]={0,0,0,0};</div></div><!-- fragment --><p> In our event-receiver we switch the SplitScreen-variable, whenever the user press the S-key. All other events are sent to the FPS camera. </p><div class="fragment"><div class="line"><span class="keyword">class </span>MyEventReceiver : <span class="keyword">public</span> IEventReceiver</div><div class="line">{</div><div class="line"> <span class="keyword">public</span>:</div><div class="line"> <span class="keyword">virtual</span> <span class="keywordtype">bool</span> OnEvent(<span class="keyword">const</span> SEvent& event)</div><div class="line"> {</div><div class="line"> <span class="comment">//Key S enables/disables SplitScreen</span></div><div class="line"> <span class="keywordflow">if</span> (event.EventType == irr::EET_KEY_INPUT_EVENT &&</div><div class="line"> event.KeyInput.Key == KEY_KEY_S && event.KeyInput.PressedDown)</div><div class="line"> {</div><div class="line"> SplitScreen = !SplitScreen;</div><div class="line"> <span class="keywordflow">return</span> <span class="keyword">true</span>;</div><div class="line"> }</div><div class="line"> <span class="comment">//Send all other events to camera4</span></div><div class="line"> <span class="keywordflow">if</span> (camera[3])</div><div class="line"> <span class="keywordflow">return</span> camera[3]->OnEvent(event);</div><div class="line"> <span class="keywordflow">return</span> <span class="keyword">false</span>;</div><div class="line"> }</div><div class="line">};</div></div><!-- fragment --><p> Ok, now the main-function: First, we initialize the device, get the SourceManager and VideoDriver, load an animated mesh from .md2 and a map from .pk3. Because that's old stuff, I won't explain every step. Just take care of the maps position. </p><div class="fragment"><div class="line"><span class="keywordtype">int</span> main()</div><div class="line">{</div><div class="line"> <span class="comment">// ask user for driver</span></div><div class="line"> video::E_DRIVER_TYPE driverType=driverChoiceConsole();</div><div class="line"> <span class="keywordflow">if</span> (driverType==video::EDT_COUNT)</div><div class="line"> <span class="keywordflow">return</span> 1;</div><div class="line"></div><div class="line"> <span class="comment">//Instance of the EventReceiver</span></div><div class="line"> MyEventReceiver receiver;</div><div class="line"></div><div class="line"> <span class="comment">//Initialise the engine</span></div><div class="line"> IrrlichtDevice *device = createDevice(driverType,</div><div class="line"> dimension2du(ResX,ResY), 32, fullScreen,</div><div class="line"> <span class="keyword">false</span>, <span class="keyword">false</span>, &receiver);</div><div class="line"> <span class="keywordflow">if</span> (!device)</div><div class="line"> <span class="keywordflow">return</span> 1;</div><div class="line"></div><div class="line"> ISceneManager *smgr = device->getSceneManager();</div><div class="line"> IVideoDriver *driver = device->getVideoDriver();</div><div class="line"></div><div class="line"> <span class="keyword">const</span> io::path mediaPath = getExampleMediaPath();</div><div class="line"></div><div class="line"> <span class="comment">//Load model</span></div><div class="line"> IAnimatedMesh *model = smgr->getMesh(mediaPath + <span class="stringliteral">"sydney.md2"</span>);</div><div class="line"> <span class="keywordflow">if</span> (!model)</div><div class="line"> <span class="keywordflow">return</span> 1;</div><div class="line"> IAnimatedMeshSceneNode *model_node = smgr->addAnimatedMeshSceneNode(model);</div><div class="line"> <span class="comment">//Load texture</span></div><div class="line"> <span class="keywordflow">if</span> (model_node)</div><div class="line"> {</div><div class="line"> ITexture *texture = driver->getTexture(mediaPath + <span class="stringliteral">"sydney.bmp"</span>);</div><div class="line"> model_node->setMaterialTexture(0,texture);</div><div class="line"> model_node->setMD2Animation(scene::EMAT_RUN);</div><div class="line"> <span class="comment">//Disable lighting (we've got no light)</span></div><div class="line"> model_node->setMaterialFlag(EMF_LIGHTING,<span class="keyword">false</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">//Load map</span></div><div class="line"> device->getFileSystem()->addFileArchive(mediaPath + <span class="stringliteral">"map-20kdm2.pk3"</span>);</div><div class="line"> IAnimatedMesh *map = smgr->getMesh(<span class="stringliteral">"20kdm2.bsp"</span>);</div><div class="line"> <span class="keywordflow">if</span> (map)</div><div class="line"> {</div><div class="line"> ISceneNode *map_node = smgr->addOctreeSceneNode(map->getMesh(0));</div><div class="line"> <span class="comment">//Set position</span></div><div class="line"> map_node->setPosition(vector3df(-850,-220,-850));</div><div class="line"> }</div></div><!-- fragment --><p> Now we create our four cameras. One is looking at the model from the front, one from the top and one from the side. In addition there's a FPS-camera which can be controlled by the user. </p><div class="fragment"><div class="line"><span class="comment">// Create 3 fixed and one user-controlled cameras</span></div><div class="line"><span class="comment">//Front</span></div><div class="line">camera[0] = smgr->addCameraSceneNode(0, vector3df(50,0,0), vector3df(0,0,0));</div><div class="line"><span class="comment">//Top</span></div><div class="line">camera[1] = smgr->addCameraSceneNode(0, vector3df(0,50,0), vector3df(0,0,0));</div><div class="line"><span class="comment">//Left</span></div><div class="line">camera[2] = smgr->addCameraSceneNode(0, vector3df(0,0,50), vector3df(0,0,0));</div><div class="line"><span class="comment">//User-controlled</span></div><div class="line">camera[3] = smgr->addCameraSceneNodeFPS();</div><div class="line"><span class="comment">// don't start at sydney's position</span></div><div class="line"><span class="keywordflow">if</span> (camera[3])</div><div class="line"> camera[3]->setPosition(core::vector3df(-50,0,-50));</div></div><!-- fragment --><p> Create a variable for counting the fps and hide the mouse: </p><div class="fragment"><div class="line"><span class="comment">//Hide mouse</span></div><div class="line">device->getCursorControl()->setVisible(<span class="keyword">false</span>);</div><div class="line"><span class="comment">//We want to count the fps</span></div><div class="line"><span class="keywordtype">int</span> lastFPS = -1;</div></div><!-- fragment --><p> There wasn't much new stuff - till now! Only by defining four cameras, the game won't be splitscreen. To do this you need several steps:</p><ul> <li>Set the viewport to the whole screen</li> <li>Begin a new scene (Clear screen)</li> <li>The following 3 steps are repeated for every viewport in the splitscreen<ul> <li>Set the viewport to the area you wish</li> <li>Activate the camera which should be "linked" with the viewport</li> <li>Render all objects</li> </ul> </li> <li>If you have a GUI:<ul> <li>Set the viewport the whole screen</li> <li>Display the GUI</li> </ul> </li> <li>End scene</li> </ul> <p>Sounds a little complicated, but you'll see it isn't: </p><div class="fragment"><div class="line"><span class="keywordflow">while</span>(device->run())</div><div class="line">{</div><div class="line"> <span class="comment">//Set the viewpoint to the whole screen and begin scene</span></div><div class="line"> driver->setViewPort(rect<s32>(0,0,ResX,ResY));</div><div class="line"> driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, SColor(255,100,100,100));</div><div class="line"> <span class="comment">//If SplitScreen is used</span></div><div class="line"> <span class="keywordflow">if</span> (SplitScreen)</div><div class="line"> {</div><div class="line"> <span class="comment">//Activate camera1</span></div><div class="line"> smgr->setActiveCamera(camera[0]);</div><div class="line"> <span class="comment">//Set viewpoint to the first quarter (left top)</span></div><div class="line"> driver->setViewPort(rect<s32>(0,0,ResX/2,ResY/2));</div><div class="line"> <span class="comment">//Draw scene</span></div><div class="line"> smgr->drawAll();</div><div class="line"> <span class="comment">//Activate camera2</span></div><div class="line"> smgr->setActiveCamera(camera[1]);</div><div class="line"> <span class="comment">//Set viewpoint to the second quarter (right top)</span></div><div class="line"> driver->setViewPort(rect<s32>(ResX/2,0,ResX,ResY/2));</div><div class="line"> <span class="comment">//Draw scene</span></div><div class="line"> smgr->drawAll();</div><div class="line"> <span class="comment">//Activate camera3</span></div><div class="line"> smgr->setActiveCamera(camera[2]);</div><div class="line"> <span class="comment">//Set viewpoint to the third quarter (left bottom)</span></div><div class="line"> driver->setViewPort(rect<s32>(0,ResY/2,ResX/2,ResY));</div><div class="line"> <span class="comment">//Draw scene</span></div><div class="line"> smgr->drawAll();</div><div class="line"> <span class="comment">//Set viewport the last quarter (right bottom)</span></div><div class="line"> driver->setViewPort(rect<s32>(ResX/2,ResY/2,ResX,ResY));</div><div class="line"> }</div><div class="line"> <span class="comment">//Activate camera4</span></div><div class="line"> smgr->setActiveCamera(camera[3]);</div><div class="line"> <span class="comment">//Draw scene</span></div><div class="line"> smgr->drawAll();</div><div class="line"> driver->endScene();</div></div><!-- fragment --><p> As you can probably see, the image is rendered for every viewport separately. That means, that you'll loose much performance. Ok, if you're asking "How do I have to set the viewport to get this or that screen?", don't panic. It's really easy: In the rect-function you define 4 coordinates:</p><ul> <li>X-coordinate of the corner left top</li> <li>Y-coordinate of the corner left top</li> <li>X-coordinate of the corner right bottom</li> <li>Y-coordinate of the corner right bottom</li> </ul> <p>That means, if you want to split the screen into 2 viewports you would give the following coordinates:</p><ul> <li>1st viewport: 0,0,ResX/2,ResY</li> <li>2nd viewport: ResX/2,0,ResX,ResY</li> </ul> <p>If you didn't fully understand, just play around with the example to check out what happens.</p> <p>Now we just view the current fps and shut down the engine, when the user wants to: </p><div class="fragment"><div class="line"> <span class="comment">//Get and show fps</span></div><div class="line"> <span class="keywordflow">if</span> (driver->getFPS() != lastFPS)</div><div class="line"> {</div><div class="line"> lastFPS = driver->getFPS();</div><div class="line"> core::stringw tmp = L<span class="stringliteral">"Irrlicht SplitScreen-Example (FPS: "</span>;</div><div class="line"> tmp += lastFPS;</div><div class="line"> tmp += <span class="stringliteral">")"</span>;</div><div class="line"> device->setWindowCaption(tmp.c_str());</div><div class="line"> }</div><div class="line"> }</div><div class="line"> <span class="comment">//Delete device</span></div><div class="line"> device->drop();</div><div class="line"> <span class="keywordflow">return</span> 0;</div><div class="line">}</div></div><!-- fragment --><p> That's it! Just compile and play around with the program. Note: With the S-Key you can switch between using splitscreen and not. </p> </div></div><!-- contents --> <!-- HTML footer for doxygen 1.8.13--> <!-- start footer part --> <p> </p> </body> </html>