/*
* ORXONOX - the hottest 3D action shooter ever to exist
*
*
* License notice:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*
* Author:
* Reto Grieder
* Co-authors:
* ...
*/
/**
* RunManager is the basic control object during the game.
*
* The RunManger class is designed to actually "run" the main part of the
* game. The Idea is, that you could derive from the RunManager in order
* to distinguish between a first person shooter or a space craft shooter.
* RunManager loads and initialises everything in the scene (like the ship,
* the enemies in the scene, any scripts, the physics, window events,
* environment, HUD, etc.).
* It also captures any input from keyboard, mous, joystick (optional) or
* Ogre (window events).
*/
#include "run_manager.h"
/**
* Contructor only needs the render window and the Root object which are both
* the OgreControl object.
* Right now the constructor does all the initialisation work. This could also
* be done in a new method "initialize()", for whatever purpose.
*
*
* @param mOgre The OgreControl object holding the render window and the Root
*/
RunManager::RunManager(OgreControl * mOgre, bool bufferedKeys,
bool bufferedMouse, bool bufferedJoy )
: mOgre(mOgre), mWindow(mOgre->getRenderWindow()), leftButtonDown(false),
mStatsOn(true), mNumScreenShots(0), mTimeUntilNextToggle(0),
mFiltering(TFO_BILINEAR), mAniso(1), mSceneDetailIndex(0),
mDebugOverlay(0), mInputManager(0), mMouse(0), mKeyboard(0), mJoy(0)
{
// SETTING UP THE SCENE
// create one new SceneManger
mSceneMgr = mOgre->getRoot()->createSceneManager(ST_GENERIC, "mScene");
// background scene (world objects, skybox, lights, etc.)
mScene = new OrxonoxScene(mSceneMgr);
// PLAYER SPACESHIP
// create a steerable SceneNode (not derived!) object. The idea is that this
// object only receives the mouse and the keyboard input (not specifi keys,
// more like up, down, left, right, roll left, roll right, move down,
// move up). The steering class can then decide how to control the node for
// the spaceship. This gives a certain flexibility.
// It should also be considered, that this class should provide another Node
// for a camera to be attached (otherwise the spaceship in front of the
// would be very static, never moving at all.
mShipNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("ShipNode",
Vector3(20, 20, 20));
// Construct a new spaceship and attach it to the node
mShip = new OrxonoxShip(mSceneMgr, mShipNode);
// RESOURCE LOADING (using ResourceGroups if implemented)
// load all resources and create the entities by calling the initialise()
// methods for each object (don't initialise in the constructor!).
mScene->initialise();
mShip->initialise();
// CAMERA AND VIEWPORT
// TODO: create a camera manager. It should be able to change its position
// around the space ship (predefined states would be nice too). And it should
// also be able to switch between different locations (like ship, spactator,
// certain fixed positions (e.g. finish line, etc.)). These are just ideas.
// create camera and viewport
createCamera();
createViewports();
// Set default mipmap level (NB some APIs ignore this)
TextureManager::getSingleton().setDefaultNumMipmaps(5);
// BULLET LIST FOR THE TEST APPLICATION
// TODO: Use STL to make life easier. But it works this way too..
mBullets = new Bullet*[10];
mBulletsPosition = 0;
mBulletsSize = 10;
// HUMAN INTERFACE
using namespace OIS;
mDebugOverlay = OverlayManager::getSingleton()
.getByName("Core/DebugOverlay");
LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***");
ParamList pl;
size_t windowHnd = 0;
std::ostringstream windowHndStr;
mWindow->getCustomAttribute("WINDOW", &windowHnd);
windowHndStr << windowHnd;
pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
mInputManager = InputManager::createInputSystem( pl );
// Create all devices (We only catch joystick exceptions here,
// as, most people have Key/Mouse)
mKeyboard = static_cast(mInputManager
->createInputObject( OISKeyboard, bufferedKeys ));
mMouse = static_cast(mInputManager
->createInputObject( OISMouse, bufferedMouse ));
try {
mJoy = static_cast(mInputManager
->createInputObject( OISJoyStick, bufferedJoy ));
}
catch(...) {
mJoy = 0;
}
//Set initial mouse clipping size
windowResized(mWindow);
showDebugOverlay(true);
// REGISTER THIS OBJECT AS A WINDOW EVENT LISTENER IN OGRE
// It will then receive events liek windowClosed, windowResized, etc.
WindowEventUtilities::addWindowEventListener(mWindow, this);
}
/**
* Standard destructor.
* Removes this object as a window event listener and deletes all created
* variables.
*/
RunManager::~RunManager()
{
//Remove ourself as a Window listener
WindowEventUtilities::removeWindowEventListener(mWindow, this);
windowClosed(mWindow);
if (mScene)
delete mScene;
if (mShip)
delete mShip;
// clean up the bullet list
for (int i = 0; i < mBulletsPosition; i++)
delete mBullets[i];
delete mBullets;
}
/**
* Method to compute anyting between 2 frames.
*
* Everything that needs to be computed during the games happens right here.
* The only exception are the listeners (which should only set variables,
* not actually do something).
*
* @param time Absolute play time
* @param deltaTime Time passed since last frame
* @return Return false to end rendering
*/
bool RunManager::tick(unsigned long time, float deltaTime)
{
// synchronize with internal class timer
mTime = time;
// Call tick() for every object
// This could be done by registering (needs a factory..)
mScene->tick(time, deltaTime);
mShip->tick(time, deltaTime);
// Update the 'HUD'
updateStats();
// update the bullet positions
for (int i = 0; i < mBulletsPosition; i++)
{
mBullets[i]->mNode->translate(mBullets[i]->mSpeed*deltaTime);
mBullets[i]->mNode->yaw(Degree(deltaTime*100));
mBullets[i]->mNode->roll(Degree(deltaTime*300));
}
// HUMAN INTERFACE
using namespace OIS;
if(mWindow->isClosed()) return false;
//Need to capture/update each device
mKeyboard->capture();
mMouse->capture();
if( mJoy ) mJoy->capture();
bool buffJ = (mJoy) ? mJoy->buffered() : true;
//Check if one of the devices is not buffered
if( !mMouse->buffered() || !mKeyboard->buffered() || !buffJ )
{
// one of the input modes is immediate, so setup what
// is needed for immediate movement
if (mTimeUntilNextToggle >= 0)
mTimeUntilNextToggle -= deltaTime;
}
//Check to see which device is not buffered, and handle it
if( !mKeyboard->buffered() )
if( processUnbufferedKeyInput() == false )
return false;
if( !mMouse->buffered() )
if( processUnbufferedMouseInput() == false )
return false;
// keep rendering
return true;
}
/**
* Adjust mouse clipping area.
* This method is called by Ogre without regards of tick()!
* Avoid doing too much in this call.
* @param rw render window
*/
void RunManager::windowResized(RenderWindow* rw)
{
unsigned int width, height, depth;
int left, top;
rw->getMetrics(width, height, depth, left, top);
const OIS::MouseState &ms = mMouse->getMouseState();
ms.width = width;
ms.height = height;
}
/**
* Unattach OIS before window shutdown (very important under Linux).
* Again, avoid computing a lot in this function.
* @param rw Render Window
*/
void RunManager::windowClosed(RenderWindow* rw)
{
//Only close for window that created OIS (the main window in these demos)
if( rw == mWindow )
{
if( mInputManager )
{
mInputManager->destroyInputObject( mMouse );
mInputManager->destroyInputObject( mKeyboard );
mInputManager->destroyInputObject( mJoy );
OIS::InputManager::destroyInputSystem(mInputManager);
mInputManager = 0;
}
}
}
/**
* Processes the Keyboard input.
* TODO: Use listeners to improve performance.
* A lookup table should be implemented to bind any key to a specific action.
* @return Return true to keep rendering
*/
bool RunManager::processUnbufferedKeyInput()
{
using namespace OIS;
if(mKeyboard->isKeyDown(KC_A) || mKeyboard->isKeyDown(KC_LEFT))
mShip->setSideThrust(1);
else if(mKeyboard->isKeyDown(KC_D) || mKeyboard->isKeyDown(KC_RIGHT))
mShip->setSideThrust(-1);
else
mShip->setSideThrust(0);
if(mKeyboard->isKeyDown(KC_UP) || mKeyboard->isKeyDown(KC_W) )
mShip->setThrust(1);
else if(mKeyboard->isKeyDown(KC_DOWN) || mKeyboard->isKeyDown(KC_S) )
mShip->setThrust(-1);
else
mShip->setThrust(0);
if( mKeyboard->isKeyDown(KC_ESCAPE) || mKeyboard->isKeyDown(KC_Q) )
return false;
if( mKeyboard->isKeyDown(KC_F) && mTimeUntilNextToggle <= 0 )
{
mStatsOn = !mStatsOn;
showDebugOverlay(mStatsOn);
mTimeUntilNextToggle = 1;
}
if( mKeyboard->isKeyDown(KC_T) && mTimeUntilNextToggle <= 0 )
{
switch(mFiltering)
{
case TFO_BILINEAR:
mFiltering = TFO_TRILINEAR;
mAniso = 1;
break;
case TFO_TRILINEAR:
mFiltering = TFO_ANISOTROPIC;
mAniso = 8;
break;
case TFO_ANISOTROPIC:
mFiltering = TFO_BILINEAR;
mAniso = 1;
break;
default: break;
}
MaterialManager::getSingleton().setDefaultTextureFiltering(mFiltering);
MaterialManager::getSingleton().setDefaultAnisotropy(mAniso);
showDebugOverlay(mStatsOn);
mTimeUntilNextToggle = 1;
}
if(mKeyboard->isKeyDown(KC_SYSRQ) && mTimeUntilNextToggle <= 0)
{
std::ostringstream ss;
ss << "screenshot_" << ++mNumScreenShots << ".png";
mWindow->writeContentsToFile(ss.str());
mTimeUntilNextToggle = 0.5;
mDebugText = "Saved: " + ss.str();
}
if(mKeyboard->isKeyDown(KC_R) && mTimeUntilNextToggle <=0)
{
mSceneDetailIndex = (mSceneDetailIndex+1)%3 ;
switch(mSceneDetailIndex) {
case 0 : mCamera->setPolygonMode(PM_SOLID); break;
case 1 : mCamera->setPolygonMode(PM_WIREFRAME); break;
case 2 : mCamera->setPolygonMode(PM_POINTS); break;
}
mTimeUntilNextToggle = 0.5;
}
static bool displayCameraDetails = false;
if(mKeyboard->isKeyDown(KC_P) && mTimeUntilNextToggle <= 0)
{
displayCameraDetails = !displayCameraDetails;
mTimeUntilNextToggle = 0.5;
if (!displayCameraDetails)
mDebugText = "";
}
// Print camera details
if(displayCameraDetails)
mDebugText = StringConverter::toString(mShip->getThrust())
+ " | Speed = " + StringConverter::toString(mShip->speed);
// mDebugText = "P: " + StringConverter::toString(mCamera
// ->getDerivedPosition()) + " " + "O: "
// + StringConverter::toString(mCamera->getDerivedOrientation());
// Return true to continue rendering
return true;
}
/**
* Processes the Mouse input.
* TODO: Use listeners to improve performance.
* A lookup table should be implemented to bind ANY button or movement
* to a specific action.
* @return Return true to keep rendering
*/
bool RunManager::processUnbufferedMouseInput()
{
using namespace OIS;
const MouseState &ms = mMouse->getMouseState();
// This is a 'hack' to show some flying barrels..
// Usually, the Bullet created by the ship should be managed
// by the physics engine..
if (ms.buttonDown(MB_Left) && !leftButtonDown)
{
// Prevent continuous fire for the moment.
leftButtonDown = true;
// let ship fire one shot with its only weapon (Barrels..)
Bullet *mTempBullet = mShip->fire();
// resize array if neccessary (double the size then)
if (mBulletsPosition >= mBulletsSize)
{
// redimension the array
Bullet **mTempArray = new Bullet*[2*mBulletsSize];
for (int i = 0; i < mBulletsSize; i++)
mTempArray[i] = mBullets[i];
mBulletsSize *= 2;
delete mBullets;
mBullets = mTempArray;
}
// add the bullet to the list
mBullets[mBulletsPosition++] = mTempBullet;
}
else if (!ms.buttons)
leftButtonDown = false;
// space ship steering. This should definitely be done in the steering object
// Simply give it the mouse movements.
mShip->mRootNode->pitch(Degree(-ms.Y.rel * 0.13), Ogre::Node::TransformSpace::TS_LOCAL);
mShip->mRootNode->yaw(Degree(-ms.X.rel * 0.13), Ogre::Node::TransformSpace::TS_PARENT);
// keep rendering
return true;
}
/**
* Show the debug overlay of desired.
* @param show Whether or not to show the debug overlay
*/
void RunManager::showDebugOverlay(bool show)
{
if (mDebugOverlay)
{
if (show)
mDebugOverlay->show();
else
mDebugOverlay->hide();
}
}
/**
* Show stats (e.g. FPS) in the left lower corner of the screen.
* Copied from the ExampleFrameListener.h in the Ogre SDK
*/
void RunManager::updateStats(void)
{
static String currFps = "Current FPS: ";
static String avgFps = "Average FPS: ";
static String bestFps = "Best FPS: ";
static String worstFps = "Worst FPS: ";
static String tris = "Triangle Count: ";
static String batches = "Batch Count: ";
// update stats when necessary
try {
OverlayElement* guiAvg = OverlayManager::getSingleton()
.getOverlayElement("Core/AverageFps");
OverlayElement* guiCurr = OverlayManager::getSingleton()
.getOverlayElement("Core/CurrFps");
OverlayElement* guiBest = OverlayManager::getSingleton()
.getOverlayElement("Core/BestFps");
OverlayElement* guiWorst = OverlayManager::getSingleton()
.getOverlayElement("Core/WorstFps");
const RenderTarget::FrameStats& stats = mWindow->getStatistics();
guiAvg->setCaption(avgFps + StringConverter::toString(stats.avgFPS));
guiCurr->setCaption(currFps + StringConverter::toString(stats.lastFPS));
guiBest->setCaption(bestFps + StringConverter::toString(stats.bestFPS)
+" "+StringConverter::toString(stats.bestFrameTime)+" ms");
guiWorst->setCaption(worstFps + StringConverter::toString(stats.worstFPS)
+" "+StringConverter::toString(stats.worstFrameTime)+" ms");
OverlayElement* guiTris = OverlayManager::getSingleton()
.getOverlayElement("Core/NumTris");
guiTris->setCaption(tris + StringConverter::toString(stats.triangleCount));
OverlayElement* guiBatches = OverlayManager::getSingleton()
.getOverlayElement("Core/NumBatches");
guiBatches->setCaption(batches
+ StringConverter::toString(stats.batchCount));
OverlayElement* guiDbg = OverlayManager::getSingleton()
.getOverlayElement("Core/DebugText");
guiDbg->setCaption(mDebugText);
}
catch(...) { /* ignore */ }
}
/**
* Simple camera creator.
* mShipNode->attachObject(mCamera) should no be here! This is what the camera
* manager is for. Right now, this method should do just fine, setting the
* cam behind the ship.
*/
void RunManager::createCamera(void)
{
mCamera = mSceneMgr->createCamera("PlayerCam");
mShipNode->attachObject(mCamera);
mCamera->setNearClipDistance(5);
mCamera->setPosition(Vector3(0,10,500));
mCamera->lookAt(Vector3(0,0,0));
}
/**
* Simple viewport creator.
* TODO: fully understand the concept of viewports concerning orxnox.
* E.g. do we need splitscreen mode?
* For now the viewport uses the entire render window and is based on the one
* camera created so far.
*/
void RunManager::createViewports(void)
{
// Create one viewport, entire window
Viewport* vp = mWindow->addViewport(mCamera);
vp->setBackgroundColour(ColourValue(0,0,0));
// Alter the camera aspect ratio to match the viewport
mCamera->setAspectRatio(
Real(vp->getActualWidth()) / Real(vp->getActualHeight()));
}