BlogProfit.com – Blogging Tutorials – 75% Commissions
The best collection of blogging, social media, email marketing and SEO video tutorials ever made.
BlogProfit.com – Blogging Tutorials – 75% Commissions

DOWNLOAD THE DEMO AND SOURCE CODE FOR WINDOWS

DOWNLOAD THE DEMO AND SOURCE CODE FOR LINUX

RETURN TO THE TUTORIAL INDEX

In the last tutorial we used a slightly modified version of a DotScene loader to load and display a 3D terrain. Now we will add a controllable player to the scene, and have it fly over the terrain.

The terrain we have loaded is not infinite – it has definite edges that we don’t want to be seen in the final game. To avoid this we need to start the camera in a position where it can not see the edges of the terrain while looking straight down, and then stop the camera from scrolling past the end of the terrain.

The starting position of the camera is determined by the XML scene file. In this demo we have started the camera at [125, 225, 1150]. This places it 100 units from the end of the level, which is 1250 units long. At a height of 225 and looking straight down this means the camera can not see the edges of the level.

The GameLevel class will scroll the player and the camera over the terrain along the z axis until they reach 100 units (the difference between the length of the terrain and the cameras initial position on the z axis). In this way the camera will stop scrolling at a point where the end of the terrain is also just out of sight.

These calculations require that the GameLevel class know the length of the terrain (the PageWorldZ attribute in the XML file). This value is not easily accessible from the SceneManager itself, so the DotSceneLoader is modified slightly to pass this value to the GameLevel singleton directly.

void DotSceneLoader::processTerrain(TiXmlElement *XMLNode)
{
// …

String pageWorldZ = getAttrib(XMLNode, “PageWorldZ”);
if (pageWorldZ.size() != 0)
{
terrainConfig += “PageWorldZ=”;
terrainConfig += pageWorldZ;
terrainConfig += “\n”;
GAMELEVEL.SetLevelLength(Ogre::StringConverter::parseReal(pageWorldZ));
}

// …
}

Another change is that the DotSceneLoader now references the SceneManager held by the GameLevel singleton instead of maintaining it’s own internal pointer. If you look through the DotSceneLoader class you will see that the GameLevel GetSceneManager function is used when access to the Scene Manager is required.

The GameLevel class gets a new function called SetLevelLength which stored the length of the level in a new varaible called levelLength.The cameraStartZ variable is added to store the initial z position of the camera. The last new variable is a Scene Node called playerSceneNode, which the players ship model will be attached to.

GameLevel.h

/*
* GameLevel.h
*
* Created on: 18/12/2009
* Author: Matthew Casperson
* Email: matthewcasperson@gmail.com
* Website: http://www.brighthub.com/hubfolio/matthew-casperson.aspx
*/

#ifndef GAMELEVEL_H_
#define GAMELEVEL_H_

#include “Ogre.h”
#include “PersistentFrameListener.h”

#define GAMELEVEL GameLevel::Instance()

class GameLevel :
public PersistentFrameListener
{
public:
~GameLevel();
static GameLevel& Instance()
{
static GameLevel instance;
return instance
}

void Startup(std::string scene);
void Shutdown();

bool FrameStarted(const FrameEvent& evt);

SceneManager* GetSceneManager() const {return sceneManager;}
SceneNode* GetPlayerSceneNode() const {return playerSceneNode}
void SetLevelLength(float length) {levelLength = length;}

protected:
GameLevel();
void InitialiseVariables();
SceneManager* sceneManager;
Camera* camera;
Viewport* viewport;
SceneNode* playerSceneNode;
float levelLength;
float cameraStartZ;

};

#endif /* GAMELEVEL_H_ */

GameLevel.cpp

The cameraStartZ variable is set to the cameras z position after the scene has been loaded in the GameLevel Startup function. The new Player class is also initialised by calling its Startup function.

void GameLevel::Startup(std::string scene)
{
PersistentFrameListener::Startup();

sceneManager = ENGINEMANAGER.GetRoot()->createSceneManager(“TerrainSceneManager”);

camera = sceneManager->createCamera(“Camera”);

viewport = ENGINEMANAGER.GetRenderWindow()->addViewport(camera);

std::auto_ptr sceneLoader(new DotSceneLoader());
sceneLoader->parseDotScene(scene, LEVEL_GROUP_NAME);

cameraStartZ = camera->getPosition().z;

playerSceneNode = sceneManager->getRootSceneNode()->createChildSceneNode();
playerSceneNode->setPosition(camera->getPosition().x, camera->getPosition().y – PLAYER_Y, camera->getPosition().z);

PLAYER.Startup()
}

The Players Shutdown function is then called in the GameLevel Shutdown function.

void GameLevel::Shutdown()
{
PLAYER.Shutdown();

if (playerSceneNode != NULL) sceneManager->getRootSceneNode()->removeAndDestroyChild(playerSceneNode->getName());
if (viewport != NULL) ENGINEMANAGER.GetRenderWindow()->removeViewport(viewport->getZOrder());
if (sceneManager != NULL) ENGINEMANAGER.GetRoot()->destroySceneManager(sceneManager);

PersistentFrameListener::Shutdown();

InitialiseVariables();
}

The FrameStarted function, which is called once per frame, is used to scroll the player (by moving the playerSceneNode to which the players mesh is attached) and camera over the terrain, stopping when the camera is as far from the end of the terrain as it was from the beginning when the scene was loaded.

bool GameLevel::FrameStarted(const FrameEvent& evt)
{
if (playerSceneNode->getPosition().z > this->levelLength – this->cameraStartZ)
{
float translateDist = SCROLL_SPEED * evt.timeSinceLastFrame;
float distToEnd = playerSceneNode->getPosition().z – (this->levelLength – this->cameraStartZ);
playerSceneNode->translate(-1 * Vector3(0, 0, translateDist>distToEnd?distToEnd:translateDist));
}

camera->setPosition(playerSceneNode->getPosition() + Vector3(0, PLAYER_Y, 0));

return true;
}

The Player class represents the players ship on the screen.

Player.h

/*
* Player.h
*
* Created on: 21/12/2009
* Author: Matthew Casperson
* Email: matthewcasperson@gmail.com
* Website: http://www.brighthub.com/hubfolio/matthew-casperson.aspx
*/

#ifndef PLAYER_H_
#define PLAYER_H_

#include “PersistentFrameListener.h”

#define PLAYER Player::Instance()

class Player :
public PersistentFrameListener
{
public:
~Player();
static Player& Instance()
{
static Player instance;
return instance;
}

void Startup();
void Shutdown();

bool FrameStarted(const FrameEvent& evt);

protected:
Player()
void InitialiseVariables();
void KeepPlayerOnScreen();

Entity* playerMesh;
SceneNode* playerSceneNode;
};

#endif

Player.cpp

The PLAYER_MESH constant defines the mesh that represents the player.

The MOUSE_SPEED constant defines how quickly the player moves with the mouse.

The SCREEN_X and SCREEN_Z constants define the bounds that the player can move in on the screen.

#include “OgreEngineManager.h”
#include “Player.h”
#include “GameLevel.h”

static const char* PLAYER_MESH = “player.mesh”;
static const float MOUSE_SPEED = 0.5f;
static const float SCREEN_X = 75.0f;
static const float SCREEN_Z = 55.0f;

Player::Player()
{
InitialiseVariables();
}

Player::~Player()
{

}

void Player::InitialiseVariables()
{
playerMesh = NULL;
playerSceneNode = NULL
}

The Startup function loads the mesh, which is called an Entity in Ogre.

void Player::Startup()
{
PersistentFrameListener::Startup();
playerMesh = GAMELEVEL.GetSceneManager()->createEntity(“Player”, PLAYER_MESH);

We then create a child scene node from the player scene node that was created by the GameLevel class. This is because you can not position an Entity directly – it has to be attached to a SceneNode, and then the SceneNode can be positioned.

playerSceneNode = GAMELEVEL.GetPlayerSceneNode()->createChildSceneNode();

The Entity is then attached to the SceneNode.

playerSceneNode->attachObject(playerMesh);

Finally, the SceneNode is rotated so the players ship faces the correct way.

playerSceneNode->rotate(Vector3(0, 1, 0), Angle(180));
}

The Shutdown function does the reverse of the Startup function, detaching and destroying the Entity and SceneNode.

void Player::Shutdown()
{
playerSceneNode->detachAllObjects();
GAMELEVEL.GetPlayerSceneNode()->removeAndDestroyChild(playerSceneNode->getName());
GAMELEVEL.GetSceneManager()->destroyEntity(playerMesh);

InitialiseVariables()

PersistentFrameListener::Shutdown();
}

The FrameStarted function is used to move the player with the mouse, and then calls the KeepPlayerOnScreen function to make sure the player does not move off the screen.

bool Player::FrameStarted(const FrameEvent& evt)
{
playerSceneNode->translate(
ENGINEMANAGER.GetMouse()->getMouseState().X.rel * MOUSE_SPEED,
0,
ENGINEMANAGER.GetMouse()->getMouseState().Y.rel * MOUSE_SPEED);

KeepPlayerOnScreen();

return true;
}

The KeepPlayerOnScreen function uses the constants SCREEN_X and SCREEN_Z to keep the player on the screen.

void Player::KeepPlayerOnScreen()
{
Vector3 newPosition = playerSceneNode->getPosition();

if (newPosition.x > SCREEN_X)
newPosition.x = SCREEN_X;
else if (newPosition.x SCREEN_Z)
newPosition.z = SCREEN_Z;
else if (newPosition.z setPosition(newPosition);
}

The end results of these changes is that a ship that can be controlled by the mouse is added to the scene. The ship and the camera then both scroll along the terrain until they reach the end of the level. Although the player can not do anything at this point except move around, we do now have something that looks