In this tutorial we give the player the ability to fire weapons.
DOWNLOAD THE DEMO AND SOURCE CODE FOR WINDOWS
DOWNLOAD THE DEMO AND SOURCE CODE FOR LINUX
Adding weapons is one of those seemingly simple things that is actually a little more complicated than it seems on the surface, if only because we need to setup a system that will easily allow for a wide variety of weapons to be created and managed. To that end the weapons “system” will be made up of two components: the weapon classes themselves, and a database where the weapons are created and stored.
First up we need a base class for the weapons called Weapon. This class defines the basic characteristics of a weapon.
Weapon.h
/*
* Weapon.h
*
* Author: Matthew Casperson
* Email: matthewcasperson@gmail.com
* Website: http://www.brighthub.com/hubfolio/matthew-casperson.aspx
*/#ifndef WEAPON_H_
#define WEAPON_H_#include "PersistentFrameListener.h"
class Weapon :
virtual float Startup(const Vector3& position);
public PersistentFrameListener
{
public:
Weapon(bool playerWeapon, int damage, float timeBetweenShots);
virtual ~Weapon();
virtual void Shutdown();
virtual bool FrameStarted(const FrameEvent& evt);
bool IsPlayerWeapon() const {return playerWeapon;}protected:
void ShutdownIfOffScreen();
void InitialiseVariables();SceneNode* weaponSceneNode;
int damage;
bool playerWeapon;
float timeBetweenShots;
};#endif
Weapon.cpp
Until now the InitialiseVariables function has been a convenient way of setting variables to an initial state (like NULL for pointers) from both the constructor and from the Shutdown function. But up to this point every class we have created has been a Singleton. Weapon classes are not singletons – there will be many weapon classes created and in action at any one time, and when a weapon class is shut down it will be available for reuse (we will see how this works with the WeaponDatabase class).
This highlights a subtle difference between initialising variables in the constructor directly, and initialising variables inside the InitialiseVariables function. Those variables initialise by the constructor directly will not change. These variables define how the object will behave every time it is reused. So for the Weapon class, once the damage, time between shots and who fired it (the player or an enemy) have been defined, every time the object is reused these values will still be the same. Those variables initialised in the InitialiseVariables function generally point to resources, like a model, SceneNode or other Ogre object, that will be cleaned up when the object has been shut down. So here the Weapon class will set its pointer to the SceneNode to null because the SceneNode was destroyed by the Shutdown function.
#include "Weapon.h"
#include "GameConstants.h"
#include "GameLevel.h"Weapon::Weapon(bool playerWeapon, int damage, float timeBetweenShots) :
playerWeapon(playerWeapon),
damage(damage),
timeBetweenShots(timeBetweenShots)
{
InitialiseVariables();
}Weapon::~Weapon()
{}
void Weapon::InitialiseVariables()
{
weaponSceneNode = NULL;
}
The Startup and Shutdown functions create and destroy the SceneNode to which all visual weapon objects, like a mesh or billboard, will be attached. The Startup function should return the time that should elapse in between shots for this weapon.
float Weapon::Startup(const Vector3& position)
{
PersistentFrameListener::Startup();weaponSceneNode = GAMELEVEL.GetPlayerSceneNode()->createChildSceneNode(position);
return 0;
}void Weapon::Shutdown()
{
GAMELEVEL.GetPlayerSceneNode()->removeAndDestroyChild(weaponSceneNode->getName());
PersistentFrameListener::Shutdown();
InitialiseVariables();
}
Every frame the weapon will check to make sure it is on the screen, and shut itself down if it is no longer visible.
bool Weapon::FrameStarted(const FrameEvent& evt)
{
ShutdownIfOffScreen();
return true;
}void Weapon::ShutdownIfOffScreen()
{
const Vector3& position = weaponSceneNode->getPosition();
if (position.x > WEAPON_SCREEN_X ||
position.x < -WEAPON_SCREEN_X ||
position.z > WEAPON_SCREEN_Z ||
position.z < -WEAPON_SCREEN_Z)
{
this->Shutdown();
}
}
Extending the Weapon class is Bullet, which is a fairly simple weapon in that it moves in a straight line. The bullet itself is represented by a Billboard, which is a 2 dimensional rectangle that is always oriented to face the camera.
Bullet.h
/*
* Bullet.h
*
* Author: Matthew Casperson
* Email: matthewcasperson@gmail.com
* Website: http://www.brighthub.com/hubfolio/matthew-casperson.aspx
*/#include "Weapon.h"
#include "WeaponDatabase.h"
#include "Ogre.h"class Bullet :
public Weapon
{
friend class WeaponDatabase;
public:
~Bullet();bool FrameStarted(const FrameEvent& evt);
float Startup(const Vector3& position);
void Shutdown();protected:
Bullet(const bool playerWeapon, const int damage, const float timeBetweenShots, const Vector3& direction, const ColourValue& colour);
void InitialiseVariables();Vector3 direction;
ColourValue colour;
BillboardSet* billboardSet;
Billboard* billboard;
};
Bullet.cpp
Just like the Weapon class, the constructor initialises those variables that will not change.
#include "Bullet.h"
#include "GameLevel.h"
#include "Utilities.h"static const float BULLET_SCALE = 0.02f;
Bullet::Bullet(const bool playerWeapon, const int damage, const float timeBetweenShots, const Vector3& direction, const ColourValue& colour) :
Weapon(playerWeapon, damage, timeBetweenShots),
colour(colour),
direction(direction)
{
InitialiseVariables();
}Bullet::~Bullet()
}
{
The InitialiseVariables function initialises those variables that are reset by the Startup and Shutdown functions.
void Bullet::InitialiseVariables()
{
billboardSet = NULL;
billboard = NULL;
}
The Startup and Shutdown functions create and destroy the BillboardSet, and the one Billboard it contains, that is used to display the bullet.
float Bullet::Startup(const Vector3& position)
{
Weapon::Startup(position);this->billboardSet = GAMELEVEL.GetSceneManager()->createBillboardSet(Utilities::GetUniqueName("BillboardSet"), 1);
this->billboardSet->setMaterialName("Bullet");
this->billboard = billboardSet->createBillboard(Vector3::ZERO, colour);
this->weaponSceneNode->attachObject(billboardSet);
this->weaponSceneNode->scale(BULLET_SCALE, BULLET_SCALE, BULLET_SCALE);return timeBetweenShots;
}void Bullet::Shutdown()
{
this->weaponSceneNode->detachAllObjects();
this->billboardSet->removeBillboard(this->billboard);
GAMELEVEL.GetSceneManager()->destroyBillboardSet(this->billboardSet);
InitialiseVariables();
Weapon::Shutdown();
}
The FrameStarted function moved the bullet in a straight line across the screen, and then calls the Weapon FrameStarted function to remove the weapon from the game when it is no longer visible.
bool Bullet::FrameStarted(const FrameEvent& evt)
{
this->weaponSceneNode->translate(direction * evt.timeSinceLastFrame);
return Weapon::FrameStarted(evt);
}
The WeaponDatabase class maintains a collection of the different weapon objects, reusing those that have been shut down where possible, and constructing new weapon objects when there are no free weapons that can be reused.
WeaponDatabase.h
/*
* WeaponDatabase.h
*
* Author: Matthew Casperson
* Email: matthewcasperson@gmail.com
* Website: http://www.brighthub.com/hubfolio/matthew-casperson.aspx
*/#ifndef WEAPONDATABASE_H_
#define WEAPONDATABASE_H_#include "map"
#include "utility"
#include "list"
#include "Weapon.h"#define WEAPONDATABASE WeaponDatabase::Instance()
typedef std::pair WeaponDefinitionPair;
typedef std::list WeaponList;
typedef std::map WeaponMap;class WeaponDatabase
{
public:
~WeaponDatabase();
static WeaponDatabase& Instance()
{
static WeaponDatabase instance;
return instance;
}Weapon* GetWeapon(const WeaponDefinitionPair& def);
void Startup();
void Shutdown();protected:
WeaponDatabase();
Weapon* CreateWeapon(const WeaponDefinitionPair& def);
WeaponMap weaponMap;
};#endif
WeaponDatabase.cpp
The constructor, destructor and Startup functions don’t do anything, and are provided to maintain consistency with the format of the other classes in the application.
#include "WeaponDatabase.h"
#include "Bullet.h"WeaponDatabase::WeaponDatabase()
{}
WeaponDatabase::~WeaponDatabase()
{}
void WeaponDatabase::Startup()
{}
The Shutdown function will go through the pool of weapon objects, shut down those still in action, and then delete the memory those objects held.
void WeaponDatabase::Shutdown()
{
for (WeaponMap::iterator iter = weaponMap.begin(); iter != weaponMap.end(); ++iter)
{
for (WeaponList::iterator iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2)
{
Weapon* weapon = *iter2;
if (weapon->IsStarted())
weapon->Shutdown();
delete weapon;
}iter->second.clear();
}weaponMap.clear();
}
The GetWeapon function is how weapons are added to the screen. First the WeaponDatabase will try to find a shut down weapon from the pool to reuse. If one is not available a new object is created, added to the pool, and then returned.
Weapon* WeaponDatabase::GetWeapon(const WeaponDefinitionPair& def)
{
if (weaponMap.find(def) != weaponMap.end())
{
for (WeaponList::const_iterator iter = weaponMap[def].begin(); iter != weaponMap[def].end(); ++iter)
{
Weapon* weapon = *iter;
if (!weapon->IsStarted())
return weapon;
}
}Weapon* weapon = CreateWeapon(def);
if (weapon != NULL)
{
weaponMap[def].push_back(weapon);
return weapon;
}return NULL;
}
The CreateWeapon function contains the logic required to construct the different types of weapons. Each weapon is defined by two ints, combined in a std::pair object for convenience. The first int can be thought of as the type of weapon, and the second the weapons power up level. Given these two ints a new weapon is constructed and returned.
At the moment there is only one weapon type defined, but as the game progresses this function will expand to construct all of the different types of weapons.
Weapon* WeaponDatabase::CreateWeapon(const WeaponDefinitionPair& def)
{
if (def.first == 1 && def.second == 1)
return new Bullet(true, 1, 0.2f, Vector3(0, 0, -100), ColourValue(1, 0, 0));return NULL;
}
The final change is with the Player class. Here we check to see if the left mouse button is being held down and that the timeToNextShot variable has reached 0. If so we call the WeaponDatabase GetWeapon function to get the appropriate weapon object, the start it up at the players current position, and reset the timeToNextShot counter to the value returned by the Startup function.
The timeToNextShot variable is then decreased with each frame.
bool Player::FrameStarted(const FrameEvent& evt)
{
playerSceneNode->translate(
ENGINEMANAGER.GetMouse()->getMouseState().X.rel * MOUSE_SPEED,
0,
ENGINEMANAGER.GetMouse()->getMouseState().Y.rel * MOUSE_SPEED);if (ENGINEMANAGER.GetMouse()->getMouseState().buttonDown(OIS::MB_Left) &&
timeToNextShot <= 0)
{
timeToNextShot = WEAPONDATABASE.GetWeapon(this->currentWeapon)
->Startup(this->playerSceneNode->getPosition());
}timeToNextShot -= evt.timeSinceLastFrame;
KeepPlayerOnScreen();
return true;
}

Get Up to Programming Speed With Online Tutorials...
Here is a look at some great free frameworks to get started developing themes for WordPress....
The concept of software development isn't that new to our generation. But still there are so ma...
In my humble opinion, CMS Drupal has come the most close to the concept of "ideal CMS". A lot o...
Setting up WiFi services for a large event can be difficult...
In this tutorial we add mouse interactivity to the scene....
In this tutorial we allow the view of the isometric scene to be moved with the mouse....
In this tutorial we show the height of an isometric object by adding a shadow....
In this tutorial we modify the appearance of the isometric cube at runtime....
In this tutorial we add some animated isometric boxes to the scene....