In this tutorial we add some enemies to the level.
DOWNLOAD THE DEMO AND SOURCE CODE FOR WINDOWS
DOWNLOAD THE DEMO AND SOURCE CODE FOR LINUX
Enemies are added into the game based on a time line. The effect we achieve is the ability to say “after 10 seconds add enemy of type x to the screen”. Just like the weapons, enemies will be created and added to a pool, allowing shut down enemy classes to be reused. Enemies will also be created by a database class, called EnemyDatabase in this case.
To start off with we define a base class for the enemies. This Enemy class is very similar to the Weapon class.
Enemy.h
/* * Enemy.h * * Author: Matthew Casperson * Email: matthewcasperson@gmail.com * Website: http://www.brighthub.com/hubfolio/matthew-casperson.aspx */ #ifndef ENEMY_H_ #define ENEMY_H_ #include "PersistentFrameListener.h" #include "map" typedef std::map StringMap; class Enemy: public PersistentFrameListener { public: Enemy(); virtual ~Enemy(); virtual void Startup(StringMap settings); virtual void Shutdown(); virtual bool FrameStarted(const FrameEvent& evt); protected: void InitialiseVariables(); SceneNode* enemySceneNode; int shields; int score; }; #endif
Enemy.cpp
#include "Enemy.h"
#include "GameLevel.h"
#include "GameConstants.h"
Enemy::Enemy()
{
InitialiseVariables();
}
Enemy::~Enemy()
{
}
void Enemy::InitialiseVariables()
{
enemySceneNode = NULL;
shields = 0;
score = 0;
}
One of the big differences between an Enemy and a Weapon is that the enemies are defined, in the XML file, long before they are created. This means we need a way to stored the information that defines what type of enemy will be created in between that information being read from the XML file, and the Enemy object being created in the game. To do this the EnemyDatabase class (which will be described later) will store a mapping of the enemy XML attribute names and values. In this way the enemy classes will interpret these strings instead of the DotSceneManager class.
Here in the Startup function the Enemy class converts the XML attributes startX, startZ, scaleX, scaleY, scaleZ, score and shields into the values that are either stored in member variables, or used in the construction of the SceneNode. Note that the startX and startZ values are relative to the width and height of the screen (give or take). So by specifying a startZ value of 0 and a startX value of 0.5, the enemy will start in the middle of the top of the screen.
void Enemy::Startup(StringMap settings)
{
PersistentFrameListener::Startup();
Vector3 startingPosition(
StringConverter::parseReal(settings["startX"]) * WEAPON_SCREEN_X * 2 - WEAPON_SCREEN_X,
GAMELEVEL.GetPlayerSceneNode()->getPosition().y,
StringConverter::parseReal(settings["startZ"]) * WEAPON_SCREEN_Z * 2 - WEAPON_SCREEN_Z);
Vector3 scale(
StringConverter::parseReal(settings["scaleX"]),
StringConverter::parseReal(settings["scaleY"]),
StringConverter::parseReal(settings["scaleZ"]));
if (scale.x == 0) scale.x = 1;
if (scale.y == 0) scale.y = 1;
if (scale.z == 0) scale.z = 1;
enemySceneNode = GAMELEVEL.GetPlayerSceneNode()->createChildSceneNode(startingPosition);
enemySceneNode->yaw(Angle(180));
enemySceneNode->scale(scale);
shields = StringConverter::parseInt(settings["shields"]);
score = StringConverter::parseInt(settings["score"]);
}
void Enemy::Shutdown()
{
GAMELEVEL.GetPlayerSceneNode()->removeAndDestroyChild(enemySceneNode->getName());
InitialiseVariables();
PersistentFrameListener::Shutdown();
}
bool Enemy::FrameStarted(const FrameEvent& evt)
{
const Vector3& position = enemySceneNode->getPosition();
if (position.x WEAPON_SCREEN_X ||
position.z WEAPON_SCREEN_Z )
{
this->Shutdown();
}
return true;
}
The BasicEnemy class extends the Enemy class to define a simple enemy that moves in a straight line. Again this code is very similar to the Weapon/Bullet classes, with the exception being that the BasicEnemy class has to convert the XML attribute strings into C++ varibles like ints, floats and vectors.
BasicEnemy.h
/* * BasicEnemt.h * * Author: Matthew Casperson * Email: matthewcasperson@gmail.com * Website: http://www.brighthub.com/hubfolio/matthew-casperson.aspx */ #ifndef BASICENEMY_H_ #define BASICENEMY_H_ #include "Enemy.h" class BasicEnemy : public Enemy { public: BasicEnemy(); ~BasicEnemy(); void Startup(StringMap settings); void Shutdown(); virtual bool FrameStarted(const FrameEvent& evt); protected: void InitialiseVariables(); Entity* mesh; Vector3 direction; }; #endif
BasicEnemy.cpp
#include "BasicEnemy.h"
#include "GameLevel.h"
#include "Utilities.h"
BasicEnemy::BasicEnemy()
{
InitialiseVariables();
}
BasicEnemy::~BasicEnemy()
{
}
void BasicEnemy::InitialiseVariables()
{
mesh = NULL;
direction = Vector3::ZERO;
}
void BasicEnemy::Startup(StringMap settings)
{
Enemy::Startup(settings);
mesh = GAMELEVEL.GetSceneManager()->createEntity(Utilities::GetUniqueName("BasicEnemy"), settings["filename"]);
enemySceneNode->attachObject(mesh);
direction.x = StringConverter::parseReal(settings["directionX"]);
direction.y = 0;
direction.z = StringConverter::parseReal(settings["directionZ"]);
}
void BasicEnemy::Shutdown()
{
enemySceneNode->detachObject(mesh);
GAMELEVEL.GetSceneManager()->destroyEntity(mesh);
InitialiseVariables();
Enemy::Shutdown();
}
bool BasicEnemy::FrameStarted(const FrameEvent& evt)
{
enemySceneNode->translate(direction * evt.timeSinceLastFrame);
return Enemy::FrameStarted(evt);
}
The EnemyDatabase class serves two purposes. It maintains a pool of enemy objects, allowing shutdown objects to be reused. It also maintains the timeline which indicates at what time in the level enemies are added.
EnemyDatabase.h
/* * EnemyDatabase.h * * Author: Matthew Casperson * Email: matthewcasperson@gmail.com * Website: http://www.brighthub.com/hubfolio/matthew-casperson.aspx */ #ifndef ENEMYDATABASE_H_ #define ENEMYDATABASE_H_ #include "Enemy.h" #include "map" #include "list" #define ENEMYDATABASE EnemyDatabase::Instance() typedef std::list EnemyList; typedef std::map EnemyMap; typedef std::list StringMapList; typedef std::map EnemyPlacementDatabase; #include "PersistentFrameListener.h" class EnemyDatabase : public PersistentFrameListener { public: ~EnemyDatabase(); static EnemyDatabase& Instance() { static EnemyDatabase instance; return instance; } void Startup(); void Shutdown(); void AddEnemyPlacement(float time, StringMap details); bool FrameStarted(const FrameEvent& evt); protected: EnemyDatabase(); void InitialiseVariables(); Enemy* GetEnemy(std::string type); Enemy* CreateEnemy(std::string type); EnemyMap enemyMap; EnemyPlacementDatabase enemyPlacementDatabase; EnemyPlacementDatabase::iterator nextEnemyPlacement; float currentTime; }; #endif
EnemyDatabase.cpp
#include "EnemyDatabase.h"
#include "GameConstants.h"
#include "GameLevel.h"
#include "BasicEnemy.h"
EnemyDatabase::EnemyDatabase()
{
InitialiseVariables();
}
EnemyDatabase::~EnemyDatabase()
{
}
void EnemyDatabase::InitialiseVariables()
{
currentTime = 0;
nextEnemyPlacement = enemyPlacementDatabase.end();
}
void EnemyDatabase::Startup()
{
PersistentFrameListener::Startup();
}
void EnemyDatabase::Shutdown()
{
enemyMap.clear();
InitialiseVariables();
PersistentFrameListener::Shutdown();
}
The AddPlacement function takes a time and a collection of XML attribute strings and stores them in a timeline database, which is really a std::map that maps floats (the time an enemy is to be created) with a std::list of XML attribute strings (itself a std:map mapping two std::string objects). Because the std::map is sorted we make sure that the nextEnemyPlacement variable, which is an iterator over the timeline std::map, always references the first enemy placement definition.
void EnemyDatabase::AddEnemyPlacement(float time, StringMap details)
{
enemyPlacementDatabase[time].push_back(details);
nextEnemyPlacement = enemyPlacementDatabase.begin();
}
In the FrameStarted function we maintain a record of the total time spent in the level in the currentTime variable. When this time is greater than or equal to the time of the enemy placement referenced by the nextEnemyPlacement iterator we call the GetEnemy function to get the appropriate enemy object, and then start it up, passing in the XML attribute strings.
In this way we can define how the enemy objects are initialised without having to create then at the time the XML file is parsed.
bool EnemyDatabase::FrameStarted(const FrameEvent& evt)
{
currentTime += evt.timeSinceLastFrame;
while (nextEnemyPlacement != enemyPlacementDatabase.end() &&
nextEnemyPlacement->first second.begin();
iter != nextEnemyPlacement->second.end(); ++iter)
{
StringMap& map = *iter;
Enemy* enemy = GetEnemy(map["type"]);
if (enemy != NULL)
enemy->Startup(map);
}
++nextEnemyPlacement;
}
return true;
}
The GetEnemy function should look familiar by now, in that it returns an unused enemy object, or creates a new one, adds it to the pool, and returns it.
Enemy* EnemyDatabase::GetEnemy(std::string type)
{
for (EnemyList::iterator iter = enemyMap[type].begin(); iter != enemyMap[type].end(); ++iter)
{
Enemy* enemy = *iter;
if (!enemy->IsStarted())
return enemy;
}
Enemy* enemy = CreateEnemy(type);
if (enemy != NULL)
{
enemyMap[type].push_back(enemy);
return enemy;
}
return NULL;
}
The CreateEnemy function includes the logic required to create a specific enemy object when supplied with an enemy type string. We have only the one enemy type now, but this function would grow as more enemies are programmed in.
Enemy* EnemyDatabase::CreateEnemy(std::string type)
{
if (type == "basic")
return new BasicEnemy();
return NULL;
}
The DotSceneLoader gets a new function called parseEnemy, which collects the XML attributes strings and the enemy placement time and calls the EnemyDatabase AddPlacement function.
void DotSceneLoader::processEnemy(TiXmlElement *XMLNode)
{
float time = getAttribReal(XMLNode, "time");
StringMap attributes;
TiXmlAttribute* attribute = XMLNode->FirstAttribute();
while (attribute)
{
attributes[attribute->Name()] = attribute->Value();
attribute = attribute->Next();
}
ENEMYDATABASE.AddEnemyPlacement(time, attributes);
}
At this point we can load a complete level from the XML file, complete with terrain, sound effects, particle systems, additional models, and now enemy placements. This ability to define a level without modifying the source code will save time and allow non-programmers to design levels.

In C++ an array is a set of consecutive objects of the same type, in memory. We see how to crea...
A database is a set of related tables. This is part 1, division 1 of a series I have on databas...
To easy way lean java programming language with a basic concepts which will help to build basic...
its an tutorial to make a virus /worm in C language...
cracking unix system password all about unix cracking it quite easily...
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....