1. Introduction.

Note: This tutorial builds on the second YAP tutorial, and it assumes you have already worked through it. What would be a game without a GUI system ? Even the original pong game had one to display the score ! For this new version, we will use a window based GUI system, CEGUI which is supported by Yake and the raf framework.

For this tutorial I made my own layout using CEGuiLayoutEditor that you can find on the main page of CEGUI (http://www.cegui.org.uk/wiki/index.php/Main_Page). The gui has 4 windows. One is the main window with a Play and a Quit message, a help window to display the key and two small windows to display the score. (score is not yet implemented so they stay empty).

2. Initialisation.

First we need to let Yake know that we want to use the CEGUI system. This is done once again in the TheConfiguration class that we change to looks like this:

//Configuration struct, storing the module used for our application.
struct TheConfiguration : public raf::ApplicationConfiguration
{
	virtual StringVector getLibraries()
	{ return MakeStringVector() << "graphicsOgre" << "inputOgre" << "audioOpenAL" << "PhysicsODE";}

	virtual StringVector getInputSystems()
	{ return MakeStringVector() << "ogre";}
	
	virtual StringVector getGraphicsSystems()
	{ return MakeStringVector() << "ogre3d";}

	virtual StringVector getAudioSystems()
	{ return MakeStringVector() << "openalpp";}

	virtual StringVector getPhysicsSystems()
	{ return MakeStringVector() << "ode";}

	virtual bool loadCEGUI()
	{ return true; }
};

And we declare in our main application state, some variable and function to handle the GUI system:

class YapMainState :
	public raf::RtMainState
{

	/.../
//GUI
private:
	
	//Load the GUI layout and setup the listener
	void SetupGUI(void); 
	
	//Display flags.
	bool mMenuDisplayed;
	bool mHelpDisplayed;

	//Display function
	void DisplayMenu(bool display = true);
	void DisplayHelp(bool display = true);
	
	//listener
	bool onStartButton(const CEGUI::EventArgs& e);
	bool onQuitButton(const CEGUI::EventArgs& e);

	/.../
}

The SetupGUI function will take care of loading the layout and binding the callback function on GUI events. All the others intialisation needed and the redirection of the keyboard and mouse input CEGUI is already done by Yake. You just have to take care of what is specific to your application.

void YapMainState::SetupGUI(void)
{
		//Load the GUI layout and init and init the system.
		std::cout << "Loading layout ... " ;
		CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
		CEGUI::Window* myRoot = wmgr.loadWindowLayout ("YapLayout.xml");
		CEGUI::System::getSingleton().setGUISheet(myRoot);
		std::cout << "done." << std::endl;
		
		
		std::cout << "Registering button ... " ;
		//Get the button.
		CEGUI::Window* WelcomeWnd = myRoot->getChild("Welcome");
		CEGUI::Window* PlayBttn = WelcomeWnd->getChild("Start Button");
		CEGUI::Window* QuitBttn = WelcomeWnd->getChild("Quit Button");
		//register callback.
		PlayBttn->subscribeEvent (CEGUI::PushButton::EventClicked,CEGUI::Event::Subscriber(&YapMainState::onStartButton,this));
		QuitBttn->subscribeEvent (CEGUI::PushButton::EventClicked,CEGUI::Event::Subscriber(&YapMainState::onQuitButton,this));
		std::cout << "done." << std::endl;
		
		
		//init the display.
		std::cout << "Init Display ... " ;
		DisplayHelp (mHelpDisplayed);
		DisplayMenu(mMenuDisplayed);
		std::cout << "done." << std::endl;

}

The first part load the layout and set it as the active sheet. You can check the CEGUI documentation if you want to get more information on this part. The second part retreive the button from the GUI and bind the callback functions.

Finally the Display function are used to show or hide the windows.

This function will be called at the end of the onCreatScene function after the others intialisation.

3. Pause

When the menu is displayed we want the game to stop. So we have to make some modification to the previous code in order avoid moving the objetcs. Also we want the camera to stay fix while the cursor is moving for the GUI.

void YapMainState::onFrame (const real timeElapsed)
{
	real distance;
	
	if ((mMenuDisplayed)||(mHelpDisplayed)) return;

	if ( getApp().getKeyboard() )
		{
		//Camera stuff.
		distance = -10. * timeElapsed;
		if ( getApp().getKeyboard()->isKeyDown(input::KC_LEFT))
		  getDefaultCamera()->moveRelative( distance*Vector3::kUnitX ); //Strafe Left (TS_LOCAL)
		if ( getApp().getKeyboard()->isKeyDown(input::KC_RIGHT))
		  getDefaultCamera()->moveRelative( -distance*Vector3::kUnitX );  //Strafe Right (TS_LOCAL)
		if ( getApp().getKeyboard()->isKeyDown(input::KC_UP))
		  getDefaultCamera()->moveRelative( distance*Vector3::kUnitZ );//Move Forward in TS_LOCAL
		if ( getApp().getKeyboard()->isKeyDown(input::KC_DOWN))
		  getDefaultCamera()->moveRelative( -distance*Vector3::kUnitZ );//Move Backwards in TS_LOCAL
		
		//Update listener position.
		mAListener->setPosition (getDefaultCamera()->getPosition());
		

		//Paddle movement. In this version the speed of the paddle is fixed. It is however possible to move the paddles
		// by applying force instead (it's cleaner), It adds inertial to the
		distance = (mPaddleSpeed) * timeElapsed;
		if ( getApp().getKeyboard()->isKeyDown(input::KC_A)) //paddle1 down
			mPaddle1.pAFix->getBody().setLinearVelocity (Vector3(0,0,distance));
		else if ( getApp().getKeyboard()->isKeyDown(input::KC_Q)) //paddle 1up
			mPaddle1.pAFix->getBody().setLinearVelocity (Vector3(0,0,-distance));
		else
			mPaddle1.pAFix->getBody().setLinearVelocity (Vector3(0,0,0));
		//It is now possible to "smash" the ball with the paddle: 
		if ( getApp().getKeyboard()->isKeyDown(input::KC_X))
			mPaddle1.pA->getBody().addForce(Vector3(mPaddleStr,0,0));
		
		//Same stuff for paddle2.
		if ( getApp().getKeyboard()->isKeyDown(input::KC_L)) //paddle 2 down
			mPaddle2.pAFix->getBody().setLinearVelocity (Vector3(0,0,distance));
		else if ( getApp().getKeyboard()->isKeyDown(input::KC_P)) //paddle 2 up
			mPaddle2.pAFix->getBody().setLinearVelocity (Vector3(0,0,-distance));
		else
			mPaddle2.pAFix->getBody().setLinearVelocity (Vector3(0,0,0));
		if ( getApp().getKeyboard()->isKeyDown(input::KC_N))
			mPaddle2.pA->getBody().addForce(Vector3(-mPaddleStr,0,0));
	

		}

	//Apply a spring effect between paddles and the fix position. F = -d*string.
	real d;
	Vector3 pAPos, pAFixPos;
	real spring  = mPaddleStr / 2;
	pAPos = mPaddle1.pA->getPosition();
	pAFixPos = mPaddle1.pAFix->getPosition();
	d = pAPos.x - pAFixPos.x;
	mPaddle1.pA->getBody().addForce(Vector3(-d*spring,0,0));

	pAPos = mPaddle2.pA->getPosition();
	pAFixPos = mPaddle2.pAFix->getPosition();
	d = pAPos.x - pAFixPos.x;
	mPaddle2.pA->getBody().addForce(Vector3(-d*spring,0,0));



	//Reset the ball if it is not moving for a while.
	static real time =0;
	Vector3 ballspeed = mBall.pA->getBody().getLinearVelocity ();
	
	if (mMath.Abs(ballspeed.x)+mMath.Abs(ballspeed.y)+mMath.Abs(ballspeed.z) < 3)
	{
		time+= timeElapsed;
		if (time > 5.)
		{
			ResetBall();
			time = 0;
		}
	}
	else
		time = 0;

}

void YapMainState::onMMove(Vector3 mVector)
	{
		
		std::cout << "MouseMove.x: " << static_cast<int>( mVector.x ) << std::endl;
		std::cout << "MouseMove.y: " << static_cast<int>( mVector.y ) << std::endl;	
		
		if ((!mMenuDisplayed)&&(!mHelpDisplayed))
		{
			getDefaultCamera()->yaw(-( mVector.x ) * 0.13);
			getDefaultCamera()->pitch(-( mVector.y ) * 0.13);
			//Update the listener orientation.
			mAListener->setOrientation ( getDefaultCamera()->getOrientation());
		}
	}




However, this is not enough. The physics engine will still be updated at every frame so everything which was moving will keep moving. There is currently no possible way (that I'm aware of) to disable the physics step in the RtMainState class so we have to rewrite the onStep function:

void YapMainState::onStep()
	{
		real lastTime = native::getTime();
		while (!quitRequested())
		{
			const real now = native::getTime();
			real elapsed = now - lastTime;
			if (elapsed < real(0.0001))
				elapsed = real(0.0001);

			if (getApp().getInputSystem())
			{
				getApp().getInputSystem()->update();
				if (getApp().getKeyboardEventGenerator())
					getApp().getKeyboardEventGenerator()->update();
				if (getApp().getMouseEventGenerator())
					getApp().getMouseEventGenerator()->update();
			}

			if ((!mMenuDisplayed)&&(!mHelpDisplayed))
			{
				onFrame(elapsed);

				if (mPWorld)
				{
					mPWorld->step( elapsed );
				}
			}

			if (mGWorld)
			{
				mGWorld->render( elapsed );
			}

			lastTime = now;
		}
	}

4. Display / hide windows

This 2 small function will display or hide the menu and help window. They also enable the mouse input for CEGUI and show / hide the mouse cursor.


void YapMainState::DisplayMenu(bool display)
{

	//Get the window.
	CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
	CEGUI::Window* WelcomeWnd = wmgr.getWindow("Welcome");
	YAKE_ASSERT (WelcomeWnd);

	

	if (display)
	{
		//Show the window.
		WelcomeWnd->show();
		//Enable mouse for GUI.
		CEGUI::MouseCursor::getSingleton().show();
		getApp().enableMouseInputForCEGUI(true);
	}
	else
	{
		//Hide the window.
		WelcomeWnd->hide();
		//if the other window is not displayed, disable the mouse for GUI.
		if (!mHelpDisplayed)
		{
			CEGUI::MouseCursor::getSingleton().hide();
			getApp().enableMouseInputForCEGUI(false);
		}
	}

	mMenuDisplayed = display;

}

void YapMainState::DisplayHelp(bool display)
{

	//Get the window.
	CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
	CEGUI::Window* HelpWnd = wmgr.getWindow("Help");
	YAKE_ASSERT (HelpWnd);

	if (display)
	{
		//Show the window.
		HelpWnd->show();
		//Enable mouse for GUI.
		CEGUI::MouseCursor::getSingleton().show();
		getApp().enableMouseInputForCEGUI(true);
	}
	else
	{
		//Hide the window.
		HelpWnd->hide();
		//if the other window is not displayed, disable the mouse for GUI.
		if (!mMenuDisplayed)
		{
			CEGUI::MouseCursor::getSingleton().hide();
			getApp().enableMouseInputForCEGUI(false);
		}
	}

	mHelpDisplayed = display;

}


The enableMouseInputForCEGUI function is interesting, as it is all what you need to do to redirect all the mouse input to the GUI. A enableKeyoardInputForCEGUI is also available for the keyboard input.

5. Callbacks

When the player click on a button you want to execute some code. This functions can be registered at the initialisation and then called every time the user click on a button:

bool YapMainState::onStartButton(const CEGUI::EventArgs& e)
{
	DisplayMenu(false);
	DisplayHelp(false);
	return true;
}


bool YapMainState::onQuitButton(const CEGUI::EventArgs& e)
{
	requestQuit();
	return true;
}


6. Conclusion

I never used CEGUI before and I must say that using it with yake was very easy. One of the problem using CEGUI is the initalisation and redirecting all the input to the GUI. With yake, all the borring stuff is already done so you just have to focus on what is specific for your application.

Lythaniel

 
tutorials/beginners/yap_5._cegui_menu.txt · Last modified: 2008/02/21 21:59 (external edit)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki