Jump to content

  • Log In with Google      Sign In   
  • Create Account

2D Game Making, the Easy Way



Video of Attack and Defend Progress...

Posted by , 08 August 2012 - - - - - - · 1,533 views

I'll give some code samples later, but I recorded a quick video showing off what I have.



This just shows my guys marching up the paths towards the enemy. You can see the soldier select at the bottom left. I'll add costs to those soldiers, and a status of our Base soon.


Added Fog Of War via Lighting

Posted by , 02 August 2012 - - - - - - · 2,959 views

For a while, I've been looking for an easy way to add lighting to my games. Well, I stumbled across Light There Be Light (discussed here Sourceforge project here) and I've found what seems to be a really simple lighting library, used with SFML.

Using this library, I've added a Fog Of War element to the game. Basically, I'm just assigning a view range for my soldiers/towers/bases and making the lighting reach that radius:
// In EntityFactory::CreateSoldier()
    ltbl::Light_Point *light = NULL;
    // if local User, set light source for Fog of War
    if (LocalUserId == userId) {
	    light = CreateLight(Soldier.ViewRange, position);
    }
    // Add light to graphics component for this soldier

    TGraphicsObject *graphicComp = new TGraphicsObject(Soldier.ImageName, SOLDIERS_LAYER, light);
ltbl::Light_Point* TEntityFactory::CreateLight(cpFloat radius, cpVect position)
{
    sf::Vector2u size = pApp->getSize();
    double xPos = position.x;
    double yPos = (double)size.y - position.y;
    // Create a light
ltbl::Light_Point* light = new ltbl::Light_Point();
light->m_intensity = 1.0f;
light->m_center = Vec2f(xPos, yPos);
light->m_radius = radius;
light->m_size = 10.0f;
light->m_spreadAngle = ltbl::pifTimes2;
light->m_softSpreadAngle = 0.0f;
light->m_color = Color3f(1.0f, 1.0f, 1.0f);
light->CalculateAABB();
light->m_bleed = 0.0f;
light->m_linearizeFactor = 1.0f;
LightSystem->AddLight(light);
light->SetAlwaysUpdate(false);
return light;
}

This produces what I'm looking for. Granted, it looks a little weird as a typical Fog Of War, but I'm excited about the library, so I'm using it! Posted Image

Here are a few pics of it with my base footsoldiers.
Attached Image
Attached Image
Attached Image


Update on Attack and Defend...

Posted by , 29 July 2012 - - - - - - · 1,070 views

I don't have time to do an extensive update, but I have one team's soldiers moving towards the opponents base and, when in range (or contact if melee class), begins to fire upon him. The enemy base can die, and then...well, nothing happens as there's nothing else to do.

Next I need to add some simple UI for the player to choose what Unit to release (currently it only does 1 type per run).

Here are 2 images, one with a group of archers firing on the base, one with simple foot soldiers shooting the base (using temporary images for now...you can see the tiny swords from the big swords...well, imagine a someone swinging that sword):

Attached Image

Attached Image


Initial Work on Attack and Defend...

Posted by , 21 July 2012 - - - - - - · 1,126 views

Well, I've started my next little game, currently called Attack and Defend. I plan on it being a single player or multiplayer game which is part League of Legends and part Tower Defense. I'm planning on using my Escape Entity System mentioned in my last entry.

Basically, 2 (or maybe more) player have bases at opposite ends of a map. They have a starting amount of money, and they can release their own "Soldiers" and build their own "Towers". Each Soldier and Tower (both of which will have multiple types) cost some money to create. You earn money by destroying your opponents players, and getting a certain income every so often. To win, kill your other players base.

Each soldier can go down one of the multiple paths between bases. There will be a fog-of-war detail so you can't see what the enemy is doing unless you have soldiers around the area.

Work Currently Done
So far I've got the main skeleton for the code down. The 1st pieces Iv'e done are create some components and an entity factory.

The Components planned right now are: Physics, Graphics, Animation (later), Collision, Health, Soldier, Tower, Base, Projectile, Camera, Input, EnemyAI, NetworkPlayer, and Death.

Here's my current Soldier Component:
class TSoldier : public Esc::TComponent
{
public:
    TSoldier(cpFloat damageRange, uint32_t damageValue,
			 uint32_t refireRate, std::string weapon,
			 uint32_t cost, uint32_t userId) :
    Esc::TComponent("Soldier"),
    DamageRange(damageRange),
    DamageValue(damageValue),
    RefireRate(refireRate),
    Weapon(weapon),
    Cost(cost),
    TargetEntity(NULL),
    UserId(userId) {}
    // Keep member public for easy consumption
    cpFloat DamageRange;
    uint32_t DamageValue;
    uint32_t RefireRate;
    std::string Weapon;
    uint32_t Cost;
    Esc::TEntityPtr TargetEntity;
    uint32_t UserId;
};

A typical Soldier entity will contain a Physics, Graphics, Health, and Soldier. When it comes in contact with something it's concerned with, a Collision component will be added (and removed when separated). You'll also have the ability to individually control a Soldier. this might be good for the more powerful soldiers, for the player to have detailed control over. Which brings me to...

The EntityFactory. This will create all the entities for the game. When it loads, it should read from some xml files that details the entities. For example, here my current Soldiers xml data file:
<?xml version="1.0" encoding="utf-8"?>
<!--
This file details the soldiers in Attack and Defend
-->
<Soldiers>
  <Footsoldier>
    <Image>Footsoldier.png</Image>
    <Velocity>40</Velocity>
    <Damage>10</Damage>
    <Refire>600</Refire>
    <Weapon>Sword.png</Weapon>
    <Health>50</Health>
    <Range>0</Range>
    <Cost>10</Cost>
  </Footsoldier>
  <Knight>
    <Image>Knight.png</Image>
    <Velocity>70</Velocity>
    <Damage>30</Damage>
    <Refire>800</Refire>
    <Weapon>Mace.png</Weapon>
    <Health>100</Health>
    <Range>0</Range>
    <Cost>30</Cost>
  </Knight>
  <Archer>
    <Image>Archer.png</Image>
    <Velocity>40</Velocity>
    <Damage>15</Damage>
    <Refire>700</Refire>
    <Weapon>Arrow.png</Weapon>
    <Health>40</Health>
    <Range>200</Range>
    <Cost>25</Cost>
  </Archer>
  <BlackWizard>
    <Image>BlackWizard.png</Image>
    <Velocity>40</Velocity>
    <Damage>30</Damage>
    <Refire>750</Refire>
    <Weapon>Fireball.png</Weapon>
    <Health>40</Health>
    <Range>250</Range>
    <Cost>50</Cost>
  </BlackWizard>
</Soldiers>

When the player creates a soldier, the entity factory will know what it's default values are.

That's basically where I am. I plan on working on some of the systems next time.


Entity System Redux...

Posted by , 17 July 2012 - - - - - - · 5,764 views
Entity, Component, System
It's been a while since I last updated, and I had just recently implemented a Component Based Entity System. However, while it was great fun working with the components, and having the events passed between them all, I think there's a better way. The better way is how the Artemis System works.

The main idea behind it is having Components contain ONLY data no logic. Instead you have Systems which operate on Components. Entities are basically a unique value, which binds the Components together.

Escape Entity System from a Users Perspective
In the ECS (Entity Component System) I've designed, named Escape System (since I kept typing Esc instead of Ecs while developing it, I named it Escape), it holds to this premise, with only the slightest exceptions. You can check out the source by by Github repo, Escape-System (TestEcs.cpp/h are the example files). the files in the include folder are the public ones.

From the User point of view, you have your components (which inherit the Esc Component). Here's a Health Component, which would go in any entity that has health and can die:
class THealth : public Esc::TComponent
{
public:
	THealth(int maxHealth, int StartingHealth) :
	  Esc::TComponent("Health"), MaxHealth(maxHealth), CurrentHealth(StartingHealth){}
	void AddDamage(int Damage) { CurrentHealth -= Damage; if (CurrentHealth < 0) CurrentHealth = 0; }
	void AddHealth(int Damage) {CurrentHealth += Damage; if (CurrentHealth > MaxHealth) CurrentHealth = MaxHealth; };
	int GetHealth() { return CurrentHealth; }
	int GetHealthPercetage() { return ((CurrentHealth * 100) / MaxHealth); }
	bool IsDead() { return CurrentHealth == 0; }
private:
	int MaxHealth;
	int CurrentHealth;
};

You can see I cheat a little having the little bit of Logic in my class. I could've called GetHealth, addded the Damage, and SetHealth, but I see no reason.
And, here's an example Damage Component. which goes into any entity that can cause damage on collision.
/******************************************************************/
class TDamage : public Esc::TComponent
{
public:
	TDamage(int damagevalue) : Esc::TComponent("Damage"), DamageValue(damagevalue) {}
	int GetDamage() { return DamageValue; }
private:
	int DamageValue;
};


These components are added to an entity recently created. And, eventually, the entity is included into the World, like this:
Esc::TEntityPtr Entity1= pWorld->CreateEntity();
THealth* healthComp(new THealth(100, 100));
TInput* inputComp(new TInput());

pWorld->AddComponent(Entity1, healthComp);
pWorld->AddComponent(Entity1, inputComp);

pWorld->SetGroup(Entity1, "Player");
pWorld->AddEntity(Entity1);
(I threw in the SetGroup, as that is a way to tag entities into different groups)

In order for these entities and components to do anything, they have to be acted on by Systems. Here is the header for the Damage System, and the code ( including adding it to the world) follows:
class TDamageSystem : public Esc::TSystem
{
public:
	TDamageSystem();
	void Update(Esc::TEntityPtr entity, uint32_t tickDelta);
	void Initialize();
};

...
DamageSystem = new TDamageSystem();
pWorld->AddSystem(DamageSystem);

...
TDamageSystem::TDamageSystem() :
  Esc::TSystem()
{
}

void TDamageSystem::Initialize()
{
	// Set which components we want to deal with
	Esc::TSystem::HandleComponent("Collision", true);
	Esc::TSystem::HandleComponent("Health", true);
}

void TDamageSystem::Update(Esc::TEntityPtr entity, uint32_t tickDelta)
{
	// Check if this is 1st touch, and subtract damage then
	TCollision *collisionComp = static_cast<TCollision*>(entity->GetComponent("Collision"));
	if (collisionComp->IsNewCollision()) {
		Esc::TEntity *pOtherEntity;
		collisionComp->GetNewCollisionEntity((pOtherEntity));
		TDamage *damageComp = static_cast<TDamage*>(pOtherEntity->GetComponent("Damage"));

		// Ensure the thing we hit has a damage entity
		if (damageComp) {
			THealth *healthComp = static_cast<THealth*>(entity->GetComponent("Health"));
			printf("Hit! Health %d, Damage %d\n", healthComp->GetHealth(), damageComp->GetDamage());
			healthComp->AddDamage(damageComp->GetDamage());
		}	
	}
}


Before anything else, Initialize will be called, and it calls it's parent class to describe what kind of entities it wants to be called Update on. In this case, we only want entities with a Collision Component (added to a component when it collides, and removed when it separates) and a Health component. Then, Update is called every tick on any entity with those components. This only happens when entities are colliding, so it happens rarely.

Systems can be setup to be called at certain timed intervals as well.

Escape System Internals
Internally, the Escape System handles all changes to the World (or items living in the World) before an Update. So, everytime you add, remove, or change a System, Entity or Component (assuming they are in the World), the Escape System tracks the changes in their order. When World::Update is called, the 1st thing it does is applies all the changes. This helps alleviate any conflicts. Here's the beginning of the World::Update code:
/********************************************************************
// Update
********************************************************************/
void TWorld::Update()
{
	boost::posix_time::ptime lastTime = CurrentTime;
	CurrentTime = boost::posix_time::microsec_clock::local_time();
	boost::posix_time::time_duration tickDelta = lastTime - CurrentTime;
	// Update in the order the commands were given
	for (uint32_t i = 0; i < WorldUpdateOrder.size(); i++) {
		switch(WorldUpdateOrder[i]) {
		case COMPONENT_ADDITION:
			if (!ComponentAdditions.empty()) {
				TEntityPtr entity = ComponentAdditions[0].Entity;
				entity->AddComponent(ComponentAdditions[0].Component);
				SystemManager->ComponentAddition(entity, ComponentAdditions[0].Component);
				ComponentAdditions.erase(ComponentAdditions.begin());
			}
			break;
		case COMPONENT_REMOVAL:
			if (!ComponentRemovals.empty()) {
				TEntityPtr entity = ComponentRemovals[0].Component->GetOwnerEntity();
				SystemManager->ComponentRemoval(entity, ComponentRemovals[0].Component);
				entity->RemoveComponent(ComponentRemovals[0].Component);
				if (ComponentRemovals[0].FreeComp) {
					delete ComponentRemovals[0].Component;
				}
				ComponentRemovals.erase(ComponentRemovals.begin());
			}
			break;
// and it continues

Every time a Component is created, it gets assign a Component bit. The same Component types get the same bit, and the entities hold a bit map of all the components it owns (using boost::dynamic_bitset) These bits are what the Systems use to determine which entities/components need to be called. Here's a what happens when a System is added.
void TSystemManager::Add(TSystemPtr system, uint32_t afterTime, bool isRepeat,
						 const TEntityPtrs& entities)
{
	system->Initialize();
	// If this is a timed system, set it up
    if (afterTime) {
	    TTimedSystem sysInfo;
	    sysInfo.AtTime = afterTime + World->GetMilliSecElapsed();
	    sysInfo.AfterTime = afterTime;
	    sysInfo.IsRepeat = isRepeat;
	    sysInfo.System = system;
	    // insert in order to limit finding the shortest time
	    TTimedSystems::iterator it = TimedSystems.begin();
	    for (; it != TimedSystems.end(); it++) {
		    if (it->AtTime > sysInfo.AtTime) {
			    TimedSystems.insert(it, sysInfo);
			    break;;
		    }
	    }
	    if (it == TimedSystems.end()) {
		    TimedSystems.insert(it, sysInfo);
	    }
    }
	else {
		Systems.push_back(system);
	}
	// Update the entities/components for this system
	const boost::dynamic_bitset<> &SystemBits = system->GetComponentBits();
	for (uint32_t i = 0; i < entities.size(); i++) {
		const boost::dynamic_bitset<> &EntityBits = entities[i]->GetComponentBits();
		if (SystemBits.is_subset_of(EntityBits)) {
			// If there's only 1 component for this system, just save off the
			// Component it will call Update with
			if (SystemBits.count() == 1) {
				system->Add(entities[i]->GetComponent(SystemBits.find_first()));
			}
			else {
				system->Add(entities[i]);
			}
		}
	}
}

Each System can Overload the PreStep() function which will get called once before a round of Updates. This allows Systems to preload any content they might need for every component/entity called in Update.

Each Entity can belong to a group, and anyone can get a list of all the Entities in a Group. I use this for a Tagging system as well, by just assuming only 1 entity will be in that specific group.

Also, the User can assign a Local ID to the world. This will allow the entities to be truly unique if playing a multi-player game. Typical Entities Id' use an uint64_t, but the top 32 bits are reserved for the LocalID (which could be the IP address, or a User ID assigned by the networking system used).

So, using this System, I'm planning my next game. Hopefully I'll have an update on that soon. Please add any comments or questions you might have.


SWTOR currently has my attention...

Posted by , 03 January 2012 - - - - - - · 1,073 views

For anyone checking, my attention is being held by Star Wars: the Old Republic right now. I find I don't have time to play games and write games. For me, it's one or the other. I will get back to it as I have some cool ideas, and I want to experiment more with the Component Based Entity System.

Till then.


My Component Base Entity System, Part 4

Posted by , 24 December 2011 - - - - - - · 3,223 views

As the last part of describing my Component Based Entity System (CBES), I'll show how we tie all the components to entities, and the Entities into a game.

Again, you can get the code here: SmashPCComp-12-23-11 and the Component System library: ComponentSystem-12-23-11


I won't go over the specifics (you can find them at my old blog), but I have some xml files that I define the different bullets, items, levels, etc. I use these as my basis for defining the entities. In reality, I should use xml that are tied very closely to the components, and how they are defined, but, being lazy, I'm using my old defines, and just shaping the components from that.

In my main function, before the Game Loop, I am doing this:

int main(int argc, char *argv[])
{
    U32  u32ScreenX, u32ScreenY;
    BOOL bFullScreen;
    U32 u32Lives = 3;
    int Level = 1;
    cpSpace *pSpace;

    GameSound::Init((char *)"SmashPcSounds.xml");
    Utilities::LoadConfig((char *)"SmashPcCfg.xml");

    Utilities::ScreenResolutionGet(u32ScreenX, u32ScreenY, bFullScreen);

	sf::RenderWindow App(sf::VideoMode(u32ScreenX, u32ScreenY, 32), "SmashPC", (bFullScreen ? sf::Style::Fullscreen : sf::Style::Default));

    cpInitChipmunk();

    pSpace = cpSpaceNew();
    cpSpaceInit(pSpace);
    pSpace->iterations = 10;

    // Init the SmashPcData
    SmashPcData GameData;

    GameData.LoadLevel(Levelnames[Level-1]);

    SmashPcEntitySystem EntitySystem(&App, &GameData, pSpace);

    cpVect Location = GameData.GetSpawnLocation();

    EntitySystem.AddPlayer(Location);

    srand(time(NULL));

    App.ShowMouseCursor(true);
    App.SetCursorPosition(u32ScreenX/2, u32ScreenX/2);

    App.EnableVerticalSync(true);
    App.SetFramerateLimit(60);

Basically, just setting up my SFML window, and when I instantiate the SmashPCData variable GameData, it loads all the xml files and holds the properties. We load the 1st level, then pass that GameData variable (as well as the SFML RenderWindow and the chipmunk physics 2d space) to the SmashPcEntitySystem (which I'l get to later).

We then get a spawn location from the GameData, and call AddPlayer from the EntitySystem.

The main game loop looks like this:

    // main game loop
    while (App.IsOpened())
    {
        if (EntitySystem.IsPlayerDead())
        {
            // Check for Lives?
            if (u32Lives > 0)
            {
                Sleep(1000);
                u32Lives--;
                Location = GameData.GetSpawnLocation();
                EntitySystem.AddPlayer(Location);
            }
            else
            {
                // just quit I guess
                GameSound::Play("GameOver");
                Sleep(4000);
                break;
            }
        }

        if (GameData.IsLevelOver())
        {
            BOOL bGameOver = FALSE;

            // Change Level
            if (Level == NumLevels)
            {
                bGameOver = TRUE;
            }
            else
            {
                Level++;
            }

            // Call the intermission function
            // also handles end-game
            Intermission(&App, &GameData, bGameOver,
         				u32ScreenX, u32ScreenY);

            // Reset GameData to make sure everything is cleared
            GameData.Reset();
            GameData.LoadLevel(Levelnames[Level-1]);
            EntitySystem.Reset(true);

            // Start player at new spawn locations
            Location = GameData.GetSpawnLocation();

            EntitySystem.AddPlayer(Location);
        }

        cpSpaceStep(pSpace, 1.0f/60.0f);

        /* clear screen and draw map */
        App.Clear(sf::Color(200, 200, 200, 255));

        EntitySystem.FrameTick();

        App.Display();

        if (CheckGameEnd(&App))
        {
            App.Close();
            break;
        }
    }
I won't go over everything, but point out what we do with the Entity System. We check if a player's dead, and if so, we reload a new player at a spawn spot, unless there's no lives left.

We check if the level is over, and if so, we reset the Entity System, load the new level, find a spawn for the player, and put the player back in.

Finally, we step the 2d physics space, Clear the screen, Notify the EntitySystem of a frame tick, which kicks off all the events. When done we draw, and loop back.

Here's SmashPcEntitySystem.h

/******************************************************************************
*
* class SmashPcEntitySystem - Handles the entities and Components for SmashPC
*
******************************************************************************/
class SmashPcEntitySystem
{
public:
    /******************************************************************************
    *
    * SmashPcEntitySystem() - Loads all the data for the SmashPC Game: Items, Bullets,
    *   Enemies, etc.
    *
    ******************************************************************************/
    SmashPcEntitySystem(sf::RenderWindow *pApp,
                        SmashPcData *pGameData, cpSpace *pSpace);

    /******************************************************************************
    *
    * ~SmashPcEntitySystem() - Deletes the data
    *
    ******************************************************************************/
    ~SmashPcEntitySystem();

    void AddPlayer(cpVect &Location);    
    void Reset(bool bSavePlayer);

    void FrameTick();

    static void OnEventData(TEvent &Event, void *pThis);
    static void OnDeadEvent(TEvent &Event, void *pThis);
    static void OnAddEvent(TEvent &Event, void *pThis);

    void EventData(TEvent &Event);
    void DeadEvent(TEvent &Event);
    void AddEvent(TEvent &Event);

    bool IsPlayerDead() { return mbPlayerDead; }

  private:
    void AddItem(std::string Item, cpVect Location);
    void AddEnemy(cpVect &Location, U32 u32Level);
    void LoadLevelData();

    TEvent mTickEvent;

    sf::RenderWindow *mpApp;
    SmashPcData *mpGameData;
    cpSpace *mpSpace;
    TEntityManager *mpEntMgr;

    bool mbPlayerDead;

    // save off the player's health, armor and weaponry between level loads
    TComponent *mpWeapComp;
    TComponent *mpHealthComp;
    TComponent *mpArmorComp;

};
The main functions are the Event callbacks: These are notified from the different entities. The main events the EntitySystem is interested in is DEATH_EVENT's, so we know when a player or an enemy dies, EVENT_DATA_EVENT's, so we can give components the SFMl RenderWindow (for drawing), and ADD_ENTITY_EVENT's, so we can add an Enemy to the system when an EnemySpawn item generates one.

The Constructor does some initial setup.
1st we want to make sure we're registered for the events we want, which happens in the Reset() function:

SmashPcEntitySystem::SmashPcEntitySystem(sf::RenderWindow *pApp,
                                 		SmashPcData *pGameData,
                                 		cpSpace *pSpace) :
mTickEvent(FRAME_UPDATE_EVENT, (TEntity *)NULL)
{
    mpApp = pApp;
    mpGameData = pGameData;
    mpEntMgr = TEntityManager::GetInstance();
    mbPlayerDead = false;
    mpWeapComp = NULL;
    mpHealthComp = NULL;
    mpArmorComp = NULL;
    mpSpace = pSpace;

    Reset(false);

}
void SmashPcEntitySystem::Reset(bool bSavePlayer)
{

    printf("SmashPcEntitySystem::Reset save %d\n", bSavePlayer);

    if (bSavePlayer){
        TEntity *pPlayer = mpEntMgr->GetEntity("Player");
        mpWeapComp = pPlayer->GetComponent(WEAPONRY_COMPONENT);
        pPlayer->RemoveComponent(mpWeapComp);

        mpHealthComp = pPlayer->GetComponent(HEALTH_COMPONENT);
        pPlayer->RemoveComponent(mpHealthComp);

        mpArmorComp = pPlayer->GetComponent(ARMOR_COMPONENT);
        pPlayer->RemoveComponent(mpArmorComp);
    }
    mpEntMgr->RemoveAllEntities();

    // We want to get the message for dead events
    mpEntMgr->RegisterForEvent(SmashPcEntitySystem::OnDeadEvent,
                                        (void *)this,
                                        DEATH_EVENT);

    // We want to get the message for our location from entity manager
    mpEntMgr->RegisterForEvent(SmashPcEntitySystem::OnEventData,
                                        (void *)this,
                                        EVENT_DATA_EVENT);

    // We want to know when we shold add an entity
    mpEntMgr->RegisterForEvent(SmashPcEntitySystem::OnAddEvent,
                                        (void *)this,
                                        ADD_ENTITY_EVENT);

    LoadLevelData();

}
You can see we 1st check if we need to save the player specific into; this is the current health, armor, and weapons. This is used for changing levels, which we basically wipe clean all Entities, and start over.

We then register for the events we want to be notified (as mentioned earlier).

The 2nd thing we want to do is load the level data; this is mainly the walls, but there are also items in the level, specifically, EnemySpawn items (but others could be there too):

void SmashPcEntitySystem::LoadLevelData()
{
    GameLevel *pLevel;

    pLevel = mpGameData->GetLevel();

    // Load walls then items
    std::vector<GameLevel::tLevelWall>::iterator it;
    for (it = pLevel->mWalls.begin();
 		it != pLevel->mWalls.end(); it++)
    {
        TEntity *pWallEnt = new TEntity("Wall", TEntity::UPDATE_EARLY);
        Component::TPhysicalObject::TAttributes Attribs;

        Attribs.Angle = 0.0f;
        Attribs.MaxSpeed = 0.0f;
        Attribs.InitialForce = 0.0f;
        Attribs.CollisionType = WALL_COL_TYPE ;
        Attribs.CollisionLayer = 0xFFFFFFFF;
        Attribs.bStatic = true;

        if (it->bIsRect)
        {
            Attribs.eShape = Component::TPhysicalObject::RECTANGLE_SHAPE;
            Attribs.RectangleInfo.StartingPoint = it->StartingPoint;
            Attribs.RectangleInfo.EndingPoint = it->EndingPoint;

        }
        else
        {
            Attribs.eShape = Component::TPhysicalObject::LINE_SHAPE;
            Attribs.LineInfo.StartingPoint = it->StartingPoint;
            Attribs.LineInfo.EndingPoint = it->EndingPoint;
            Attribs.LineInfo.Width = 10.0f;
        }

        Component::TPhysicalObject *pWallComp = new Component::TPhysicalObject(
            mpSpace, &Attribs);

        pWallEnt->AddComponent(pWallComp);

        sf::Color WallColor(128, 128, 128, 255);
        Component::TGraphicsShape *pGfxShape = new Component::TGraphicsShape(
            WallColor);

        pWallEnt->AddComponent(pGfxShape);
        TEntityManager::GetInstance()->AddEntity(pWallEnt, true);

        // tell component to get callback for hittign a bullet
        pWallComp->SetCollision("Bullet", true);
        // Walls occupy all layers
    }

    std::vector<GameLevel::tLevelItem>::iterator it2;

    for (it2 = pLevel->mItems.begin(); it2 != pLevel->mItems.end(); it2++) {
        AddItem(it2->ItemName, it2->Location);
    }
}
We begin by looping through the list of walls, and for each wall, we create an entity. The wall entity only has a PhysicalObject and a GraphicsShape component. We generate those 2 components, add them to the entity, and then add the Entity to the EntityManager.

Finally, we tell the Wall's PhysicalObject component it wants to get notified when an Entity of type "Bullet" collides with it, and to remove the other entity upon collision (in this case, the Bullet). No component in the Wall's Entity wants to know about the collision, so it will basically just delete the bullet entity.


We then loop through all the items in the level, and add them to the System.

After loading the level data, we'll get notified to Add a Player to the system:

void SmashPcEntitySystem::AddPlayer(cpVect &Location)
{
    printf("SmashPcEntitySystem::AddPlayer\n");

    GameSound::Play("PlayerSpawn");

    if (mbPlayerDead) {
        printf("Delete old player 1st\n");

        TEntity *pDeadPlayer = mpEntMgr->GetEntity("Player");

        // store off weaponry
        mpWeapComp = pDeadPlayer->GetComponent(WEAPONRY_COMPONENT);
        pDeadPlayer->RemoveComponent(mpWeapComp);
        mpEntMgr->DeleteEntity(pDeadPlayer, true);
    }

    mbPlayerDead = false;

    TEntity *pPlayerEntity = new TEntity("Player", TEntity::UPDATE_LAST);

    if (mpHealthComp) {
        printf("Add in old health/armor component\n");
        pPlayerEntity->AddComponent(mpHealthComp);
        pPlayerEntity->AddComponent(mpArmorComp);
        mpHealthComp = NULL;
        mpArmorComp = NULL;
    }
    else {
        pPlayerEntity->AddComponent(new Component::TArmor(100));
        pPlayerEntity->AddComponent(new Component::THealth(100));
    }
    pPlayerEntity->AddComponent(new Component::TTimer());
    pPlayerEntity->AddComponent(new Component::TPlayer(PLAYER_SPEED));
    pPlayerEntity->AddComponent(new Component::TCamera());
    pPlayerEntity->AddComponent(new Component::TInput());
    pPlayerEntity->AddComponent(new Component::TSound());

    Component::TGraphicsObject *pGraphicsObj = new Component::TGraphicsObject(
        "Gfx/Player.bmp", PI/2.0f);
    pPlayerEntity->AddComponent(pGraphicsObj);

    uint32_t Width, Height;
    pGraphicsObj->GetDimensions(Width, Height);

    Component::TPhysicalObject::TAttributes Attribs;
    Attribs.eShape = Component::TPhysicalObject::CIRCLE_SHAPE;
    Attribs.bStatic = false;
    Attribs.CircleInfo.Radius = (Width + Height)/4;
    Attribs.CircleInfo.Location = Location;
    Attribs.Angle = PI/2.0f;
    Attribs.MaxSpeed = PLAYER_SPEED;
    Attribs.InitialForce = 0.0f;
    Attribs.CollisionType = PLAYER_COL_TYPE;
    Attribs.CollisionLayer = PLAYER_LAYER;

    Component::TPhysicalObject *pPhysicalObject =
        new Component::TPhysicalObject(mpSpace, &Attribs);

    // we want to collide with bullets and items
    pPhysicalObject->SetCollision("Bullet", true);
    pPhysicalObject->SetCollision("Armor", true);
    pPhysicalObject->SetCollision("Weapon", true);

    pPlayerEntity->AddComponent(pPhysicalObject);

    if (mpWeapComp) {
        printf("Add in old weapon component\n");
        pPlayerEntity->AddComponent(mpWeapComp);
        mpWeapComp = NULL;
    }
    else {
        SmashPcData::tBulletList BulletList;
        mpGameData->GetBulletList(BulletList);
        Component::TWeaponry::TWeaponInfo WeaponInfo;
        Component::TWeaponry::TWeapons Weapons;

        for (SmashPcData::tBulletList::iterator it = BulletList.begin();
 			it != BulletList.end(); it++) {
            WeaponInfo.Name = it->Name;
            if (it == BulletList.begin()) {
                WeaponInfo.Available = true;
            }
            else {
                WeaponInfo.Available = false;
            }

            WeaponInfo.RefireRate = it->u32Refire;
            WeaponInfo.Speed = it->Speed;
            WeaponInfo.Damage = it->u32Damage;
            WeaponInfo.TimeToLive = it->u32TimeToLive;
            WeaponInfo.CollisionType = BULLET_COL_TYPE;
            WeaponInfo.CollisionLayer = BULLET_LAYER;
            WeaponInfo.ImageName = it->ImageName;
            WeaponInfo.bContSound = it->bContSound;
            Weapons.push_back(WeaponInfo);
        }

        Component::TWeaponry *pWeaponry = new Component::TWeaponry(
            Weapons);
        pPlayerEntity->AddComponent(pWeaponry);
    }

    mpEntMgr->AddEntity(pPlayerEntity, true);

    printf("SmashPcEntitySystem::AddPlayer Exiting\n");
}
A Player Entity is made up of these Components (information about them listed in Part 3):
Health, Armor, Timer, Player Logic, Camera, Input, Sound, Physical Object, Graphics Object, and Weaponry Component.

First, we check if we're adding a player after he's died, and if so, we store off his old Weaponry Component (which stores what weapons he has), and delete the old dead player entity.

Then, we check if we have a saved Health Component; if so, that means we've changed levels, and we want to load the player with his old Health and Armor; otherwise, we gets default 100 Health, 0 armor.

Next we add all the simple components that don't need much external data.

When we load the Physical Object component, we have to tell it what general shape it is (rectangle, line, or circle) the dimensions of the object, location, angle, maximum speed, and the collision information. There are other optional attributes you can load into a PhysicalObject, but we don't need them for a player.

We tell the Player's Physical Object component that he needs to be notified of colliding with Bullets, Armor, and Weapons. This will generate collision events when the physical object his entities of those types, and different component will be register for those collisions (Armor component wants to know when it's entity acquires armor, Health wants to know when it's entity hits a bullet, and Weaponry wants to know when he hits a weapon).

Finally, we check if we have a weaponry component saved; if so we use it, otherwise we load the default weaponry component, with all the weapons in the game, but only make the 1st one available (the PeaShooter).

Then, we add the entity to the entity manager.

I wanted to point out what we do when FameTick is called:

void SmashPcEntitySystem::FrameTick()
{
    mpEntMgr->SetEvent(mTickEvent, true);
}
All we do is tell the EntityManager that a frame tick occurred. The Entity Manager will tell all the Entities of the Frame Tick, and the Components who are registered for the FRAME_UPDATE_EVENT will get notified.

So, when the user pressed a movement direction, the Input Component, when it gets a FRAME_UPDATE_EVENT, will see a movement key is pressed, and will SetEVent() with INPUT_EVENT. The Player Component gets the INPUT_EVENT, see the user presses left, and sets it's entity's Physical Object's Force, so it will start moving. The Graphics Object will draw where the entity is, based on the Physical Object's world location, and the location of the Camera in the world.

What happens now is the EnemySpawn items (loaded from the level data) timer will expire, notifying them to release an enemy. That will generate an ADD_ENTITY_EVENT with the filter "Enemy" (see function TEnemySpawn::Timer(TEvent &Event)). The EntitySystem will get a callback (since it registered for the ADD_ENTITY_EVENT) to add an Enemy. So, it will call AddEnemy(), which basically does the same thing AddPlayer did, except the Enemy uses these components:
Health, Timer, Sound, Enemy Logic, Graphics Object, Physical Object, and Weaponry
Enemy differs from Player by swapping Enemy Logic for Player Logic, and it doesn't use Input, nor Camera components.

An Enemy will set a timer to move towards the player. When it expires, the Enemy Component will ask where the Player's Location is. It will then tell it's Physical Object component to change it's angle to be towards the player, and give it a force towards the player. Also, the Enemy has a separate timer for firing bullets, and, when it expires, the enemy will ask where the player is and fire a bullet in that direction (via the Weaponry component).

When a Bullet hits an Enemy, the Enemy's Health Component will get notified of a COLLISION_EVENT (because the Enemy's Physical Object component was told it needs to generate a collision event when it hits a bullet). The Enemy's Health Component will subtract the damage of the bullet (retrieved by getting the Value from the Bullet Entity's Value Component) from it's health. If it's 0 or less, it generates a DEATH_EVENT. The SmashPC Entity System will get a callback telling it the enemy has died, and it will remove the entity form the EntityManager.


And, that's, basically, how it all works. You can look over the code for more detailed information, but that's the basis for my CBES. I'll try and use it for my next project and see how much it helps (or hinders). I'm fairly happy with it, but, I know there are some efficiency issues I could improve. I won't truly know how much it helps until I try and make a different game.

I also realize I'm missing many details. For example, I can't have a glowing image around bullets, I don't have the smoke trails, nor does my flame thrower fade out before the fire expires. I'll explore those aspects for the next game.


My Component Base Entity System, Part 3

Posted by , 23 December 2011 - - - - - - · 1,508 views

You probably notice I'm flying through these articles. Mainly, it's because I want to get these Component articles out of the way and get to writing a new game.

So, this time I'm going to go over some of the SmashPC specific Components. You can get the full Source code here:
SmashPcComp-12-23-11


So, here's a list of the components I have for the game, and what they do:
Camera - This component signifies where the center of the camera should be drawn within the game world. CUrrently, it's always on the Player, but, you could do cool things with it, like moving it to a remote-controlled entity for a short time.
Enemy - This component handles the logic for the enemy. It has 2 timers, one to control how often he turns towards the player, and one to control how often he fires his weapon.
EnemySpawn - This handles the releasing of the enemies into the world. When it's timer expires, it creates an ADD_ENTITY_EVENT for the Enemy.
GraphicsObject - Handles drawing it's entity if it's a sprite. It gets it's entity's physical component and finds the current camera location so it knows where to draw itself.
GraphicsShape - Same as GraphicsObject, except it deal with entities that are shapes, not sprites.
Input - Handles sending Input event to it's owner entity. Currently, only player is controlled, but, as with camera, could be moved around to other entities to do fun stuff.
PhysicalObject - Handles representing an entity in the physical world. It contains the location and the shape of the object. This object is given force, angle, and location. It is this object that all collisions originate via a callback from the physics library. The SetCollision() is used to detail what other type of entities it should get notified of collisions.
Player - Handles all the logic for controlling the player. It get input events and tells others where the player is (like the enemy).
Sound - Handles playing sounds on certain events. This component is controversial. I'm really not sure what it adds, since I typically can just play a sound whenever the event happens. But, I added it, and I'm using it, via the SetSound(), which plays a sound on a specific event.
Weaponry - Handles firing bullets, and, for a player, handles acquiring new weapons. Has a refire timer that, when expires, allows a new bullet to be fired.

Well take a look at the Input and Player Components here to get a better understanding of how it would work.

First, Input:

class TInput : public TComponent
{
public:

    struct TInputData
    {
        uint32_t KeyMask;
        uint32_t MouseX;
        uint32_t MouseY;
    };

    TInput();
    ~TInput();

    /******************************************************************
    *
    *  OnFrameTick - Callback for a frame tick
    *
    *******************************************************************/
    static void OnFrameTick(TEvent &Event, void *pThis);

    /******************************************************************
    *
    *  OnFrameTick - Callback for a frame tick
    *
    *******************************************************************/
    void FrameTick(TEvent &Event);

protected:
    virtual void Cleanup();
    virtual void Initialize();

private:
    sf::RenderWindow *mpApp;
    bool mbWasKeyPressed;
    uint32_t mMouseX, mMouseY;

};

The header is pretty simple. We create the TInputData struct to send during events. Otherwise, it gets notified every frame tick, and keeps the current mouse location and the main SFML Window.

Here's the source code.

/******************************************************************
*
*  TInput - Constructor, simple
*
*******************************************************************/
TInput::TInput() :
  TComponent(INPUT_COMPONENT)
{


    mMouseX = 0;
    mMouseY = 0;

    mbWasKeyPressed = false;
}

/******************************************************************
*
*  ~TInput - DeConstructor
*
*******************************************************************/
TInput::~TInput()
{

}


/******************************************************************
*
*  OnFrameTick - Callback for a frame tick
*
*******************************************************************/
void TInput::OnFrameTick(TEvent &Event, void *pThis)
{
    TInput *pInput =
        reinterpret_cast<TInput *>(pThis);

    pInput->FrameTick(Event);
}

/******************************************************************
*
*  OnFrameTick - Callback for a frame tick
*
*******************************************************************/
void TInput::FrameTick(TEvent &Event)
{
    const sf::Input& Input = mpApp->GetInput();
    uint32_t Key;
    TInputData InputData;
    InputData.KeyMask = 0;
    bool bMouse = false;

    // Loop through the possible keys, and check what's
    // being pushed
    for (Key = (uint32_t)GAME_KEY_FIRST; Key < (uint32_t)GAME_KEY_MAX; Key++) {
        if (Utilities::KeyIsDown(Input, (etGameKeys)Key)) {
            InputData.KeyMask |= (1 << Key);
            mbWasKeyPressed = true;
        }
    }

    // Check for mouse movement
    InputData.MouseX = Input.GetMouseX();
    InputData.MouseY = Input.GetMouseY();

    if (mMouseX != InputData.MouseX || mMouseY != InputData.MouseY) {
        mMouseX =InputData.MouseX;
        mMouseY = InputData.MouseY;
        bMouse = true;
    }

    if (InputData.KeyMask || bMouse) {
        TEvent InputEvent(INPUT_EVENT, this, "Action");

        InputEvent.SetData(&InputData);
        mpOwnerEntity->SetEvent(InputEvent);
    }
    else if (mbWasKeyPressed) {
        mbWasKeyPressed = false;
        TEvent InputEvent(INPUT_EVENT, this, "Action");

        InputEvent.SetData(&InputData);
        mpOwnerEntity->SetEvent(InputEvent);
    }

    // Check for number key's being pressed
    // Check for user choosing weapon (number pad)
    sf::Key::Code KeyDown = Utilities::CheckKeyRange(
                                Input, sf::Key::Num0, sf::Key::Num9);

    if (KeyDown != sf::Key::Count)
    {
        std::stringstream ss;
        ss << (uint32_t)(KeyDown - sf::Key::Num0);
        std::string Filter = ss.str();

        TEvent InputEvent(INPUT_EVENT, this,
                          Filter);

        mpOwnerEntity->SetEvent(InputEvent);
    }

}

/******************************************************************
*
*  Cleanup - Cleanup any used resources
*
*******************************************************************/
void TInput::Cleanup()
{
    printf("TInput::Cleanup\n");
}

/******************************************************************
*
*  Initialize - Initialize all resources
*
*******************************************************************/
void TInput::Initialize()
{
    TEvent AppEvent(EVENT_DATA_EVENT, this, "GetApp");
    AppEvent.SetData(&mpApp);
    TEntityManager::GetInstance()->SetEvent(AppEvent);

    // register for a Frame Tick update
    mpOwnerEntity->RegisterForEvent(TInput::OnFrameTick,
                                    (void *)this,
                                    FRAME_UPDATE_EVENT);
}

Starting at the bottom again, Initialize(), we set the event to get the main SFML RenderWindow, which generates the input. We also register to get notified of every tick.

The rest of the work is done in FrameTick(), where we check if any key is down, and check if the mouse moved. If so, we generate events saying so. If a button was down last time, but not now, we send an empty KeyMask Event.

Here's Player.h

class TPlayer : public TComponent
{
public:

    TPlayer(uint32_t Speed);
    ~TPlayer();

    /******************************************************************
    *
    *  OnInput - Callback for handling Input events
    *
    *******************************************************************/
    static void OnInput(TEvent &Event, void *pThis);

    /******************************************************************
    *
    *  OnInput - Callback for handling Input events
    *
    *******************************************************************/
    void Input(TEvent &Event);

    /******************************************************************
    *
    *  OnEventData - Callback for getting the camera location
    *
    *******************************************************************/
    static void OnEventData(TEvent &Event, void *pThis);

    /******************************************************************
    *
    *  OnEventData - Callback for getting the camera location
    *
    *******************************************************************/
    void EventData(TEvent &Event);

protected:
    virtual void Cleanup();
    virtual void Initialize();

    uint32_t mSpeed;
    TPhysicalObject *mpOurPhysicalObj;

    double mCenterX;
    double mCenterY;

};
And the source code:

/******************************************************************
*
*  TPlayer - Constructor, simple
*
*******************************************************************/
TPlayer::TPlayer(uint32_t Speed) :
  TComponent(PLAYER_LOGIC_COMPONENT),
  mSpeed(Speed)
{
    mpOurPhysicalObj = NULL;
}

/******************************************************************
*
*  ~TPlayer - DeConstructor
*
*******************************************************************/
TPlayer::~TPlayer()
{

}

/******************************************************************
*
*  OnEventData - Callback for getting the player's location
*
*******************************************************************/
void TPlayer::OnEventData(TEvent &Event, void *pThis)
{
    if (Event.GetFilter() == "PlayerLoc") {
        TPlayer *pPlayer =
            reinterpret_cast<TPlayer *>(pThis);

        pPlayer->EventData(Event);
    }
}

/******************************************************************
*
*  OnEventData - Callback for getting the player's location
*
*******************************************************************/
void TPlayer::EventData(TEvent &Event)
{
    *((TPhysicalObject **)Event.GetData()) = mpOurPhysicalObj;
}

/******************************************************************
*
*  OnInput - Callback for handling Input events
*
*******************************************************************/
void TPlayer::OnInput(TEvent &Event, void *pThis)
{
    // We don't care about the number keys, only movement here
    if (Event.GetFilter() == "Action") {
        TPlayer *pPlayer =
            reinterpret_cast<TPlayer *>(pThis);

        pPlayer->Input(Event);
    }
}

/******************************************************************
*
*  OnInput - Callback for handling Input events
*
*******************************************************************/
void TPlayer::Input(TEvent &Event)
{
    TInput::TInputData *pInputData;

    // if we don't have our physics component, get it
    if (mpOurPhysicalObj == NULL) {
        mpOurPhysicalObj = dynamic_cast<TPhysicalObject *>(
            mpOwnerEntity->GetComponent(PHYSICAL_OBJECT_COMPONENT));

        if (mpOurPhysicalObj == NULL) {
            printf("Player Object with no Physical Object! ABORT ABORT!!\n");
        }
    }
    pInputData = ((TInput::TInputData *)Event.GetData());

    cpFloat xForce = 0, yForce = 0;
    cpVect Vel;
    bool bChangeVelocity = false;

    mpOurPhysicalObj->GetVelocity(Vel);

    // We'll just assign velocities to a hardcoded value for now
    if (pInputData->KeyMask & (1 << (uint32_t)GAME_KEY_LEFT))
        xForce -= mSpeed*20;
    else if (Vel.x < -0.01f) {
        Vel.x = 0;
        bChangeVelocity = true;
    }

    if (pInputData->KeyMask & (1 << (uint32_t)GAME_KEY_RIGHT))
        xForce += mSpeed*20;
    else if (Vel.x > 0.01f) {
        Vel.x = 0;
        bChangeVelocity = true;
    }

    if (pInputData->KeyMask & (1 << (uint32_t)GAME_KEY_DOWN))
        yForce += mSpeed*20;
    else if (Vel.y > 0.01f) {
        Vel.y = 0;
        bChangeVelocity = true;
    }

    if (pInputData->KeyMask & (1 << (uint32_t)GAME_KEY_UP))
        yForce -= mSpeed*20;
    else if (Vel.y < -0.01f) {
        Vel.y = 0;
        bChangeVelocity = true;
    }

    if (bChangeVelocity) {
        mpOurPhysicalObj->SetVelocity(Vel);
    }

    mpOurPhysicalObj->SetForce(cpv(xForce, yForce));

    // Finally, find location of Mouse and point player towards it
    cpFloat MouseX = (cpFloat)pInputData->MouseX;
    cpFloat MouseY = (cpFloat)pInputData->MouseY;

    cpFloat Angle = atan2(MouseY - mCenterY,
                          MouseX - mCenterX);
    mpOurPhysicalObj->SetAngle(Angle);

}

/******************************************************************
*
*  Cleanup - Cleanup any used resources
*
*******************************************************************/
void TPlayer::Cleanup()
{
    printf("TPlayerCleanup\n");

}

/******************************************************************
*
*  Initialize - Initialize all resources
*
*******************************************************************/
void TPlayer::Initialize()
{
    // Get the render window so w know where center is
    sf::RenderWindow *pApp;
    TEvent AppEvent(EVENT_DATA_EVENT, this, "GetApp");
    AppEvent.SetData(&pApp);
    TEntityManager::GetInstance()->SetEvent(AppEvent);

    mCenterX = (double)(pApp->GetWidth()/2);
    mCenterY = (double)(pApp->GetHeight()/2);

    // We want to get the message for our location from entity manager
    mpOwnerEntity->RegisterForEvent(TPlayer::OnInput,
                                        (void *)this,
                                        INPUT_EVENT);

    mpOurPhysicalObj = dynamic_cast<TPhysicalObject *>(
        mpOwnerEntity->GetComponent(PHYSICAL_OBJECT_COMPONENT));

    if (mpOurPhysicalObj == NULL) {
        printf("TPlayer Object with no Physical Object! ABORT ABORT!!\n");
    }

    // We want to get the message for our location from entity manager
    TEntityManager::GetInstance()->RegisterForEvent(TPlayer::OnEventData,
                                        (void *)this,
                                        EVENT_DATA_EVENT, mpOwnerEntity);

    // Register for sounds on certain events
    TSound *pSound = dynamic_cast<TSound *>(
        mpOwnerEntity->GetComponent(SOUND_COMPONENT));
    if (pSound == NULL) {
        printf("TPlayer Object with no pSound! ABORT ABORT!!\n");
    }

    pSound->RegisterSound("PlayerHit", COLLISION_EVENT,
                          "Bullet");
    pSound->RegisterSound("Armor", COLLISION_EVENT,
                          "Armor");
    pSound->RegisterSound("Weapon", COLLISION_EVENT,
                          "Weapon");
    pSound->RegisterSound("PlayerDead", DEATH_EVENT,
                          mpOwnerEntity->GetType());

}

The constructor takes the speed the player moves. The 2 main things the Player component does is gets input events, and, if it's a direction event, it gives a force to the entity's PhysicalObject. If there's a mouse movement, it gives an angle to the PhysicalObject. (Notice, it doesn't handle firing bullet, that's all done by Weaponry).
It also handles events from other people wanting to know where the player is.

Finally, it tells the Sound component to play certain sounds on certain Events.

Next time we'll finish off the Component System talk when I detail how we put it all together to make the SmashPC game.


My Component Base Entity System, Part 2

Posted by , 23 December 2011 - - - - - - · 1,436 views

My last post was a pretty good bit to swallow. And, I realize I'm kind of getting away from the main purpose of this journal, which is supposed to show how to easily develop 2D games, but I wanted to show what I've been doing lately.

In this post, I'll show you some of the simple Components, and how they work. My next 2 posts will detail the more complicated Components, and putting it all together to make a game.

Again, using the same source code: ComponentSystem-12-23-11

All component's have an Initialize() and a Cleanup(). Initialize is called when an entity is added to an Entity Manager's system, and Cleanup when an entity is deleted.

First, the Value Component:

class TValue : public TComponent
{
public:

    TValue(int32_t Value);
    TValue(std::string StrValue);
    ~TValue();

    /******************************************************************
    *
    *  GetValue - Return the value of the... value
    *
    *******************************************************************/
    int32_t GetValue();

    /******************************************************************
    *
    *  SetValue - Set the value of the... value
    *
    *******************************************************************/
    void SetValue(int32_t u32NewValue);

    /******************************************************************
    *
    *  GetValue - Return the value of the... value
    *
    *******************************************************************/
    std::string GetStrValue();

    /******************************************************************
    *
    *  SetValue - Set the value of the... value
    *
    *******************************************************************/
    void SetStrValue(std::string NewStrValue);

    /******************************************************************
    *
    *  OnGetData - Handle data requests
    *
    *******************************************************************/
    static void OnGetData(TEvent &Event, void *pThis);

    /******************************************************************
    *
    *  OnGetData - Handle data requests
    *
    *******************************************************************/
    void GetData(TEvent &Event);

protected:
    virtual void Cleanup();
    virtual void Initialize();

    int32_t ms32Value;
    std::string mStrValue;

};

Notice we inherit from TComponent. So, we will have access to mpOwnerEntity, which is the Entity that holds this component. The mpOwnerEntity is set when a Component is added to an entity.

The Value component simply holds a value. This could be for a Bullet's Damage, an Armor's protection rating, a Health Box's value, etc. And, actually, I don't need the GetValue (nor GetStrValue(), since we retrieve the value using an Event. Those are left over.

You see the OnGetData() function is static; that's because it's a callback from the Event system. We;ll see how that works in a second.

Here's the important pieces from the .cpp code:

/******************************************************************
*
*  TValue - Constructor, assigns a value to the ... value
*
*******************************************************************/
TValue::TValue(int32_t Value) :
  TComponent(VALUE_COMPONENT)
{
    ms32Value = Value;

}

TValue::TValue(std::string StrValue) :
  TComponent(VALUE_COMPONENT)
{
    mStrValue = StrValue;
}

/******************************************************************
*
*  OnGetData - Handle data requests
*
*******************************************************************/

void TValue::OnGetData(TEvent &Event, void *pThis)
{
    if ((Event.GetFilter() == "int") ||
        (Event.GetFilter() == "string")) {
        TValue *pValue =
            reinterpret_cast<TValue *>(pThis);

        pValue->GetData(Event);
    }
}

/******************************************************************
*
*  OnGetData - Handle data requests
*
*******************************************************************/
void TValue::GetData(TEvent &Event)
{
    void *pData = Event.GetData();
    if (Event.GetFilter() == "int") {
        *((int32_t *)pData) = ms32Value;
    }
    else {
        *((std::string *)pData) = mStrValue;
    }
}


/******************************************************************
*
*  Initialize - Initialize all resources
*
*******************************************************************/
void TValue::Initialize()
{
    // register for Retreiving the data
    mpOwnerEntity->RegisterForEvent(TValue::OnGetData,
                                    (void *)this,
                                    EVENT_DATA_EVENT);
}
The constructor simply assigns the initial value, either a String or an integer.

Next, let's move to the bottom, Initialize(). Here we register for the Component to get notified of DATA_EVENT's. So, any EVENT_DATA_EVENT sent to the component's owner Entity will call TValue::OnGetData(), and it passes in the class as the pThis variable.

In the middle, OnGetData() check's it's an event we're interested in, then cast the pThis void* to the TValue class type (since that's what we register with), and then calls the class's GetData() function, which simply fills in the Event's Data pointer with the value.

I realize it's not the slickest piece of code, I hope it makes sense.

OK, let's take a look at the Armor Component. Armor is owned by any entity that can pick up Armor, and provides protection above the Health. Currently, only the player can wear it. So, the Player entity is given the Armor Component, which starts at 0 value. When the player's entity collides with an Armor item in game, the Armor Component gets notified, and it increases it's value to the value of the armor it collided with. The Health Component, when it collides with a bullet, will check if it's entity has Armor, and, if so, will call Armor's CalculateDamage function.

Here's the header:

class TArmor : public TComponent
{
public:

    TArmor(uint32_t MaxArmor,
       	uint32_t InitialArmor = 0);
    ~TArmor();

    /******************************************************************
    *
    *  OnArmorHit - Static callback, when our entitiy hits an armor
    *
    *******************************************************************/
    static void OnArmorHit(TEvent &Event, void *pThis);

    /******************************************************************
    *
    *  OnArmorHit - Increases armor based on Armor Value
    *
    *******************************************************************/
    void ArmorHit(TEvent &Event);

    /******************************************************************
    *
    *  CalculateDamage - Calculate how much damage the entity takes and
    *    how much damage the armor takes.
    *
    *******************************************************************/
    void CalculateDamage(uint32_t &u32Damage);

protected:
    virtual void Cleanup();
    virtual void Initialize();

    uint32_t mu32MaxArmor;
    uint32_t mu32Armor;

};
And TArmor.cpp:

/******************************************************************
*
*  TArmor - Constructor, assigns maximum armor
*
*******************************************************************/
TArmor::TArmor(uint32_t MaxArmor, uint32_t InitialArmor) :
  TComponent(ARMOR_COMPONENT)
{
    mu32MaxArmor = MaxArmor;
    mu32Armor = InitialArmor;
}

/******************************************************************
*
*  ~TArmor - DeConstructor
*
*******************************************************************/
TArmor::~TArmor() {}

/******************************************************************
*
*  OnArmorHit - Static callback, when our entitiy hits an armor
*
*******************************************************************/
void TArmor::OnArmorHit(TEvent &Event, void *pThis)
{
    if (Event.GetFilter() == "Armor") {
        TArmor *pArmor =
            reinterpret_cast<TArmor *>(pThis);

        pArmor->ArmorHit(Event);
    }
}

/******************************************************************
*
*  OnArmorHit - Increases armor based on Armor Value
*
*******************************************************************/
void TArmor::ArmorHit(TEvent &Event)
{
    uint32_t u32Value;
    printf("OnArmorHit got event %d\n", Event.GetType());

    // Set an event to get the value of the armor
    TEvent ValueEvent(EVENT_DATA_EVENT, mpOwnerEntity, "int");
    ValueEvent.SetData(&u32Value);
    Event.GetEntity()->SetEvent(ValueEvent);

    if (mu32Armor < mu32MaxArmor) {
        mu32Armor += u32Value;
        if (mu32Armor < mu32MaxArmor) {
            mu32Armor = mu32MaxArmor;
        }

        // Notify system this armor entity should be removed
        TEntityManager::GetInstance()->DeleteEntity(Event.GetEntity());
    }

}

/******************************************************************
*
*  CalculateDamage - Calculate how much damage the entity takes and
*    how much damage the armor takes.
*
*******************************************************************/
void TArmor::CalculateDamage(uint32_t &u32Damage)
{
    if (mu32Armor >= u32Damage) {
        mu32Armor -= u32Damage;
        u32Damage = 0;
    }
    else {
        u32Damage -= mu32Armor;
        mu32Armor = 0;
    }
}

/******************************************************************
*
*  Cleanup - Cleanup any used resources
*
*******************************************************************/
void TArmor::Cleanup()
{

}

/******************************************************************
*
*  Initialize - Initialize all resources
*
*******************************************************************/
void TArmor::Initialize()
{
    // register for Bullet Collisions
    mpOwnerEntity->RegisterForEvent(TArmor::OnArmorHit,
                                    (void *)this,
                                    COLLISION_EVENT);
}

You can hopefully follow how we register for a Collision event, and filter only for colliding with an Armor item.

Health Component works by registering for Collision Events, and filters for Bullets or HealthBox.
If it's a Bullet that hits him, then, he calls OnBulletHit(), which figures out how much damage thebullet will do, and subtracts health from the damage (if wearing armor, it will check with that component 1st). If Health goes to 0 or less, then it creates a Death Event, and broadcasts it to the System.
If it hits a HealthBox, then it will increase the Health by the Health box's value.

Here's Header and Source code for THealth:

class THealth : public TComponent
{
public:

    THealth(uint32_t MaxHealth, bool bPlayer = false);
    ~THealth();

    /******************************************************************
    *
    *  OnBulletHit - Static callback, when our entitiy hits a bullet
    *
    *******************************************************************/
    static void OnCollision(TEvent &Event, void *pThis);

    /******************************************************************
    *
    *  OnBulletHit - Decreases health based on bullet damage
    *
    *******************************************************************/
    virtual void OnBulletHit(TEvent &Event);

    /******************************************************************
    *
    *  OnHealthHit - Increases health based on healthbox value
    *
    *******************************************************************/
    void OnHealthHit(TEvent &Event);

protected:
    virtual void Cleanup();
    virtual void Initialize();

    uint32_t mu32MaxHealth;
    uint32_t mu32Health;
    bool mbPlayer;

};

/******************************************************************
*
*  THealth - Constructor, assigns maximum health
*
*******************************************************************/
THealth::THealth(uint32_t MaxHealth, bool bPlayer) :
  TComponent(HEALTH_COMPONENT)
{
    mu32MaxHealth = MaxHealth;
    mu32Health = mu32MaxHealth;
    mbPlayer = bPlayer;

}

/******************************************************************
*
*  ~THealth - DeConstructor
*
*******************************************************************/
THealth::~THealth() {}

/******************************************************************
*
*  OnBulletHit - Static callback, when our entitiy hits a bullet
*
*******************************************************************/
void THealth::OnCollision(TEvent &Event, void *pThis)
{
    if (Event.GetFilter() ==  "Bullet") {
        THealth *pHealth =
            reinterpret_cast<THealth *>(pThis);

        pHealth->OnBulletHit(Event);
    }
    else if (Event.GetFilter() ==  "HealthBox") {
        THealth *pHealth =
            reinterpret_cast<THealth *>(pThis);

        pHealth->OnHealthHit(Event);
    }
}

/******************************************************************
*
*  OnBulletHit - Decreases health based on bullet damage
*
*******************************************************************/
void THealth::OnBulletHit(TEvent &Event)
{
    uint32_t u32Damage;

    // Set an event to get the value of the armor
    TEvent ValueEvent(EVENT_DATA_EVENT, mpOwnerEntity, "int");
    ValueEvent.SetData(&u32Damage);
    Event.GetEntity()->SetEvent(ValueEvent);

    // Check if our Entity has armor
    if (mpOwnerEntity->GetComponent(ARMOR_COMPONENT)) {
        // Let Armor component calculate damage
        TArmor *pArmor = dynamic_cast<TArmor *>(
            mpOwnerEntity->GetComponent(ARMOR_COMPONENT));

        pArmor->CalculateDamage(u32Damage);
    }

    if (u32Damage >= mu32Health) {
        // Notify entity of Death event for next frame
        TEvent DeathEvent(DEATH_EVENT, mpOwnerEntity, mpOwnerEntity->GetType());

        TEntityManager::GetInstance()->SetEvent(DeathEvent, false);
    }
    else {
        mu32Health -= u32Damage;
    }
}

/******************************************************************
*
*  OnHealthHit - Increases health based on healthbox value
*
*******************************************************************/
void THealth::OnHealthHit(TEvent &Event)
{
    if (mbPlayer) {
        printf("PlayerHealth got event %d\n", Event.GetType());

        // Should be from a HealthItem Entiity
        // Get Value component from it

        if (mu32Health < mu32MaxHealth) {
            uint32_t u32Value;

            // Set an event to get the value of the armor
            TEvent ValueEvent(EVENT_DATA_EVENT, mpOwnerEntity, "int");
            ValueEvent.SetData(&u32Value);
            Event.GetEntity()->SetEvent(ValueEvent);
            mu32Health += u32Value;

            if (mu32Health > mu32MaxHealth) {
                mu32Health = mu32MaxHealth;
            }

            // Notify system this armor entity should be removed
            TEntityManager::GetInstance()->DeleteEntity(Event.GetEntity());
        }
    }
}

/******************************************************************
*
*  Cleanup - Cleanup any used resources
*
*******************************************************************/
void THealth::Cleanup()
{

}

/******************************************************************
*
*  Initialize - Initialize all resources
*
*******************************************************************/
void THealth::Initialize()
{
    // register for Collisions
    mpOwnerEntity->RegisterForEvent(THealth::OnCollision,
                                    (void *)this,
                                    COLLISION_EVENT);

}

And, finally, possibly the most useful of the generic Components, TTimer. The Timer Component allows other components to be notified of certain timers. We use it for when the Enemy should fire a bullet and when it should turn, when an Enemy Spawn should spawn the next enemy, when a weapon can fire again, and how long a bullet should live.

It works by calling SetTimer(), giving it a timeout value and an Event to send when the timer expires. Most often, the event is TIMER_EXPIRED_EVENT, but you can pass other events (like REMOVE_ENTITY_EVENT for when an item or bullet expires).

The Timer Component registers for FRAME_UPDATE_EVENT, which is every game tick. This allows it to accurately send it's timers out at the right time. Below is the header and source code.


class TTimer : public TComponent
{
public:

    TTimer();
    ~TTimer();

    /******************************************************************
    *
    *  SetTimer - Set to an event after u32Time mS
    *
    *******************************************************************/
    void SetTimer(uint32_t u32Time, TEvent &Event);

    /******************************************************************
    *
    *  OnFrameTick - Event every frame tick
    *
    *******************************************************************/
    static void OnFrameTick(TEvent &Event, void *pThis);

    /******************************************************************
    *
    *  OnFrameTick - Event every frame tick
    *
    *******************************************************************/
    void FrameTick(TEvent &Event);

protected:
    virtual void Cleanup();
    virtual void Initialize();

    struct TTimerDetails
    {
        uint32_t u32Time;
        TEvent *pEvent;
    };

    std::list<TTimerDetails> mTimerDetails;

};

/******************************************************************
*
*  TTimer - Constructor, simple
*
*******************************************************************/
TTimer::TTimer() :
  TComponent(TIMER_COMPONENT)
{

}

/******************************************************************
*
*  ~TTimer - DeConstructor
*
*******************************************************************/
TTimer::~TTimer()
{
    mTimerDetails.clear();
}


/******************************************************************
*
*  SetTimer - Set to an event after u32Time mS
*
*******************************************************************/
void TTimer::SetTimer(uint32_t u32Time, TEvent &Event)
{
    TTimerDetails TimerDetail;

    // Store when we want the timer to go off
    TimerDetail.u32Time = timeGetTime() + u32Time;
    TimerDetail.pEvent = new TEvent(Event);

    // Place in order in list, for faster retreival
    std::list<TTimerDetails>::iterator it;

    for (it = mTimerDetails.begin();
     	it != mTimerDetails.end(); it++) {
        if (it->u32Time > TimerDetail.u32Time) {
            // insert here
            mTimerDetails.insert(it, TimerDetail);
            break;
        }
    }

    // check it inserted it
    if (it == mTimerDetails.end()) {
        mTimerDetails.insert(it, TimerDetail);
    }
}

/******************************************************************
*
*  OnFrameTick - Event every frame tick
*
*******************************************************************/
void TTimer::OnFrameTick(TEvent &Event, void *pThis)
{
    TTimer *pTimer =
        reinterpret_cast<TTimer *>(pThis);

    pTimer->FrameTick(Event);
}

/******************************************************************
*
*  OnFrameTick - Event every frame tick
*
*******************************************************************/
void TTimer::FrameTick(TEvent &Event)
{
    uint32_t u32CurrentTime = timeGetTime();

    // Check 1st time on list, it is earliest
    if (!mTimerDetails.empty() && (mTimerDetails.front().u32Time <= u32CurrentTime)) {
        // Send this event and remove from list
        //printf("Timer Elapsed! En%s, Ev%d EF:%s\n",
        //   	mpOwnerEntity->GetType().c_str(), mTimerDetails.front().pEvent->GetType(),
        //   	mTimerDetails.front().pEvent->GetFilter().c_str());
        mpOwnerEntity->SetEvent(*(mTimerDetails.front().pEvent));
        delete mTimerDetails.front().pEvent;
        mTimerDetails.pop_front();
    }
}

/******************************************************************
*
*  Cleanup - Cleanup any used resources
*
*******************************************************************/
void TTimer::Cleanup()
{

}

/******************************************************************
*
*  Initialize - Initialize all resources
*
*******************************************************************/
void TTimer::Initialize()
{
    // register for Frame Tick updates
    mpOwnerEntity->RegisterForEvent(TTimer::OnFrameTick,
                                    (void *)this,
                                    FRAME_UPDATE_EVENT);
}

OK, that's the basics of how Components work. Next time we'll look at some of the SmashPC specific Components.

Till then!


My Component Base Entity System, Part 1

Posted by , 22 December 2011 - - - - - - · 4,192 views

As I mentioned in my last post, I become interested in Component Base Entity systems, and I decided to port my SmashPC game over to a component-based system.

Well, the results are mixed. I love the idea, and, I think, for certain game types, it's essential; however, I'm not sure it's totally worth it for a simple game like SmashPC. I'll experiment some more as I move onto my next game, and see if I can re-use much of what I've done for other games.

And, I'll also add, being my 1st ever System of this type, I realize it is lacking in many ways, but, I hope the thoughts here can help others build a better system.

Anyway, onto the interesting stuff. Let me start by saying I have some obvious efficiency issues with how I'm approaching entities, components, and the events that communicate between them. Using strings is a BAD way of filtering some events, but, I'm going to use them until I see an obvious performance hit as it's so much easier for my simple games. So, beware if you use this system in a large project.

Here's a link to my component system library I use as the basis for the work:
ComponentSystem-12-23-11


First, the Component Class (include file)

/******************************************************************
*
*  Component.h - Generic Component class
*
*******************************************************************/
#include <string>

class TEntity;

enum etComponentType
{
    TIMER_COMPONENT = 1,
    HEALTH_COMPONENT,
    ARMOR_COMPONENT,
    VALUE_COMPONENT,
    CAMERA_COMPONENT,
    ENEMY_LOGIC_COMPONENT,
    PLAYER_LOGIC_COMPONENT,
    ENEMY_SPAWN_COMPONENT,
    INPUT_COMPONENT,
    PHYSICAL_OBJECT_COMPONENT,
    GRAPHICS_OBJECT_COMPONENT,
    WEAPONRY_COMPONENT,
    GRAPHICS_SHAPE_COMPONENT,
    SOUND_COMPONENT,
    MAX_COMPONENT
};

class TComponent
{
public:
    TComponent(etComponentType eComponentType);
    ~TComponent();

    void SetOwner(TEntity *pNewOwner) { mpOwnerEntity = pNewOwner; }

    virtual TEntity *GetOwner();

    virtual etComponentType GetType();
    virtual void Initialize() = 0;
    virtual void Cleanup() = 0;

protected:
    TEntity *mpOwnerEntity;
    etComponentType meComponentType;

};

This Component class is just a base class for all the specific components. No-one should invoke the TComponent class directly. The idea is a Component knows it's Entity Owner, it's Compnent Type, and it has a generic Initialize and Cleanup. Notice there aren't any template references here; Your implementation could (and, possibly, should) have them.

The TComponent.cpp file is also quite small

TComponent::TComponent(etComponentType eComponentType)
{
    meComponentType = eComponentType;
    mpOwnerEntity = NULL;
}

TComponent::~TComponent()
{
    //printf("Delete Component %s\n", mType.c_str());

}

TEntity *TComponent::GetOwner()
{
    return mpOwnerEntity;
}

etComponentType TComponent::GetType()
{
    return meComponentType;
}
Just some simple retrieval functions.

Before we get to the Entity, I should introduce Events. In my system, Events are what is used to communicate between Components and Entities. Two examples would be
#1, the Player Logic Component needs to know when the user presses the "move left" key, so it registers for an "INPUT_EVENT" and it will give the player a velocity when it gets a left (or, more specifically, it will give the Player's Physical Object Component a Velocity).
#2, the Enemy Logic component needs to know where the Player is, so it know where to aim it's bullets. So, when it's refire timer expires (notifying it can shoot a bullet), it will ask the Entity Manager to send an event to the Player Entity and get it's Physical Location.

Here's the Event Header:

typedef enum
{
    COLLISION_EVENT,
    TIMER_EXPIRED_EVENT,
    FRAME_UPDATE_EVENT,
    INPUT_EVENT,
    BULLET_FIRED_EVENT,
    DEATH_EVENT,
    ADDED_ENTITY_EVENT,
    EVENT_DATA_EVENT,
    ADD_ENTITY_EVENT,
    REMOVE_ENTITY_EVENT,

} etEventType;

class TEvent
{
public:

    TEvent(etEventType EventType, TEntity *pEntity, std::string Filter = "");
    TEvent(etEventType EventType, TComponent *pComponent, std::string Filter = "");
    ~TEvent();

    etEventType GetType();
    std::string GetFilter();

    void SetData(void *pEventData) { mpEventData = pEventData; }
    void *GetData(void ) { return mpEventData; }

    TEntity *GetEntity();
    TComponent *GetComponent();

private:

    etEventType mType;
    TEntity *mpEntity;
    TComponent *mpComponent;
    std::string mFilter;

    void *mpEventData;

};

typedef void (*TEventCB)(TEvent &, void *);
An Event can be tied to a Component or an Entity, depending on where it is created.

The Filter is used to filter out different types of events.

SetData() is used to store the data that will be written to for certain events (like the before mentioned Player Location) and GetData() is used to get the location for where to store that data. Now, I'm using void * for this, and I'm not real happy about it. I probably should have used templates in this case, but, my familiarity with void *'s make it easier for me to do. I do come from a strictly-C background.

TEven.cpp is exclusively setting and returning values, so I won't list it here. It probably could have all been done in the header file.

Next, we'll look at our Entity class. An Entity is comprised of Components, and it also keeps track of many of the events certain components want to be notified of. More on that soon. First, the header file:

    // a map using the type of event as the key, and the value
    // is a list of pairs which could have a sub-type it is filtering on
    // and a event handler
    struct TEventCbInfo
    {
        TEntity *pEntity;
        TEventCB EventCB;
        void *pThis;
    };

    typedef std::vector<std::pair<etEventType, TEventCbInfo> > TRegisteredEvents;

class TEntity
{
public:

    enum TUpdateOrder
    {
        UPDATE_EARLY,
        UPDATE_MID,
        UPDATE_LAST
    };

    TEntity(std::string EntityType, TUpdateOrder UpdateOrder = UPDATE_MID);
    ~TEntity();

    void Initialize();

    void AddComponent(TComponent *pComponent);
    void RemoveComponent(TComponent *pComponent);


    std::string GetType();

    TComponent *GetComponent(etComponentType eComponentType);

    void SetEvent(TEvent &Event);

    void RegisterForEvent(TEventCB EventCB, void *pThis,
                          etEventType EventType);
    void UnRegisterForEvent(TEventCB EventCB, void *pThis,
                            etEventType EventType);

    void SetDead(void) { mbDead = true; }
    bool IsDead(void) { return mbDead; }
    uint32_t GetId() { return mId; }
    TUpdateOrder GetUpdateOrder() { return mUpdateOrder; }

private:
    std::string mType;
    uint32_t mId;
    bool mbDead;
    TUpdateOrder mUpdateOrder;

    TRegisteredEvents mRegisteredEvents;
    std::list<TComponent *> mComponents;
    std::list<TRegisteredEvents::iterator> mRegisteredEventsToRemove;

};
First, you'll notice we define TEventCbInfo outside the class; this is because the EntityManager will also use this structure (since things can register for event callbacks with the whole system, and not just a single enity).

The enum TUpdateOrder, mainly helps detail when entities should be updated. For example, you typically want your player to be drawn last, so he doesn't run "under" a health box, (if he's at 100% health), and you probably don't want any "decoration" items to be drawn over enemies, etc.

Add and Remove Components is useful if you want to move some components out of one entity to another. I use it when a player dies. I remove the Weaponry Component (which knows which weapons the player has), delete the dead player entity, and create a new entity with the previous Weaponry Component (so he keeps his weapons). Another example could be to move the Camera Component from the player to a bullet, if the bullet could be controllable by the player, kind of like how the unreal controllable rocket is. That way, the camera follows the bullet, not the player, for a length of time.

The big piece here is the Event system. SetEvent() basically sets an event for that entity, and any components who's registered for that event will get notified. RegisterForEvent() tells the entity that a component wants to be notified of certain events, and UnRegisterForEvent() removes previously registered event.

Here's the TEntity.cpp code to check out:

TEntity::TEntity(std::string EntityType, TUpdateOrder UpdateOrder)
{
    mUpdateOrder = UpdateOrder;

#ifdef EVENT_DEBUG
    if (fEventDebug == NULL) {
        fEventDebug = fopen("EventDebug.log", "w");
        if (fEventDebug == NULL) {
            printf("Failed opening %s for debug!!\n", "EventDebug.log");
        }
    }
#endif
    mType = EntityType;
    mId = TEntityManager::GetInstance()->GetUniqueId();
    mbDead = false;
}

TEntity::~TEntity()
{
#ifdef EVENT_DEBUG
    fprintf(fEventDebug, "DelEn %s this %p\n", GetType().c_str(), this);
    fflush(fEventDebug);
#endif

    // remove all registered events and component
    std::list<TComponent *>::iterator CompIt;
    for (CompIt = mComponents.begin();
     	CompIt != mComponents.end(); CompIt++) {
        (*CompIt)->Cleanup();
        delete (*CompIt);
    }
    mComponents.clear();

    mRegisteredEvents.clear();
}

void TEntity::Initialize()
{
    // initilize all the components
    std::list<TComponent *>::iterator CompIt;
    for (CompIt = mComponents.begin();
     	CompIt != mComponents.end(); CompIt++) {
        (*CompIt)->Initialize();
    }
}

void TEntity::AddComponent(TComponent *pComponent)
{
    pComponent->SetOwner(this);
    //printf("Adding Component %s\n", pComponent->GetType().c_str());
    mComponents.push_back(pComponent);

}

void TEntity::RemoveComponent(TComponent *pComponent)
{
    printf("TEntity::RemoveComponent Ent %s, C%d\n",
       	GetType().c_str(), pComponent->GetType());

    std::list<TComponent *>::iterator it;
    for (it = mComponents.begin();
     	it != mComponents.end(); it++) {
        if ((*it)->GetType() == pComponent->GetType()) {
            mComponents.erase(it);
            break;
        }
    }
}


void TEntity::SetEvent(TEvent &Event)
{
    // If it's a remove event, delete us
    if (Event.GetType() == REMOVE_ENTITY_EVENT) {
        DEBUG_EVENT(Event, GetType().c_str(), NULL);
        TEntityManager::GetInstance()->DeleteEntity(this);
        mbDead = true;
    }
    else if (!mbDead) {

        if ((Event.GetType() == FRAME_UPDATE_EVENT) && !mRegisteredEventsToRemove.empty()) {
            printf("On Tick, remove registered event\n");
            mRegisteredEvents.erase(*(mRegisteredEventsToRemove.begin()));
            mRegisteredEventsToRemove.erase(mRegisteredEventsToRemove.begin());
        }
        // Find all components who register for this event

        // ensure there's someone listen for this event ??
        TRegisteredEvents::iterator it;
        for (it = mRegisteredEvents.begin(); it != mRegisteredEvents.end(); it++) {
            if (it->first == Event.GetType()) {
                if (GetType() != "Wall") DEBUG_EVENT(Event, GetType().c_str(), it->second.pThis);
                it->second.EventCB(Event, it->second.pThis);
            }
        }
    }

    // Do I need to register with any system, saying we're waiting for
    // certain events? I think so
}

std::string TEntity::GetType()
{
    return mType;
}

TComponent *TEntity::GetComponent(etComponentType eComponentType)
{
    TComponent *pComp = NULL;
    std::list<TComponent *>::iterator it;
    for (it = mComponents.begin();
     	it != mComponents.end(); it++) {
        if ((*it)->GetType() == eComponentType) {
            pComp = (*it);
            break;
        }
    }

    return pComp;
}

void TEntity::RegisterForEvent(TEventCB EventCB, void *pThis,
                           	etEventType EventType)
{
    TEventCbInfo EventCbInfo;
    EventCbInfo.EventCB = EventCB;
    EventCbInfo.pThis = pThis;
    EventCbInfo.pEntity = this;

    std::pair<etEventType, TEventCbInfo> EventPair(EventType, EventCbInfo);
    mRegisteredEvents.push_back(EventPair);
}

void TEntity::UnRegisterForEvent(TEventCB EventCB, void *pThis,
                        etEventType EventType)
{
    TRegisteredEvents::iterator it;
    for (it = mRegisteredEvents.begin(); it != mRegisteredEvents.end(); it++) {
        if (it->first == EventType) {
            if ((it->second.EventCB == EventCB) &&
                (it->second.pThis == pThis)) {
                mRegisteredEventsToRemove.push_back(it);
                break;
            }
        }
    }
}

Finally, before I bore you to death, here's the EntityManager class:

class TEntityManager
{
public:

    static TEntityManager *GetInstance();

    TEntityManager();
    ~TEntityManager();

    void AddEntity(TEntity *pEntity,
               	bool bImmediate = false);
    void RemoveEntity(TEntity *pEntity);
    void DeleteEntity(TEntity *pEntity, bool bImmediate = false);

    void RemoveAllEntities();

    TEntity *GetEntity(std::string EntityType);

    void GetEntities(std::list<TEntity *> &Entities);

    void SetEvent(TEvent &Event, bool bNotifyEntities = false);

    void RegisterForEvent(TEventCB EventCB, void *pThis,
                          etEventType EventType,
                          TEntity *pEntity = NULL);

    void UnRegisterForEvent(TEventCB EventCB, void *pThis,
                            etEventType EventType);

    uint32_t GetUniqueId() { return mUniqueId++; }

private:

    TRegisteredEvents mRegisteredEvents;

    std::list<TEntity *> mEntities;
    std::list<TEntity *> mEntityDeletions;
    std::list<TEntity *> mEntityAdditions;

    uint32_t mUniqueId;
};
The idea here is Entity Manager holds all the entities in the system, and allows entities and components to set and register for system-wide events. It's the interface for adding and removing entities to the system as well. It is a singleton, and can be accessed via the TEntityManager::GetInstance() call.

In the AddEntity() and DeleteEntity() calls, we have a bImmediate parameter. This basically allows the EntityManager to Queue up Entities to add or remove until the next system tick; otherwise, an entity might get removed while the EntityManager is looping through all the entities, and it could corrupt the list.

Here's the .cpp code for it:

static TEntityManager *pEntityManager = NULL;

TEntityManager *TEntityManager::GetInstance()
{
    if (pEntityManager == NULL) {
        pEntityManager = new TEntityManager();
    }

    return pEntityManager;
}

TEntityManager::TEntityManager()
{
    mUniqueId = 1;
}

TEntityManager::~TEntityManager()
{
    // remove all register events and components
    mRegisteredEvents.clear();

    std::list<TEntity *>::iterator it;
    for (it = mEntities.begin();
     	it != mEntities.end(); it++) {
        delete (*it);
    }
}

void TEntityManager::RemoveAllEntities()
{
    // remove all register events and components
    mRegisteredEvents.clear();

    std::list<TEntity *>::iterator it;
    for (it = mEntities.begin();
     	it != mEntities.end(); it++) {
        delete (*it);
    }
    mEntities.clear();

    mEntityDeletions.clear();
    mEntityAdditions.clear();

}

void TEntityManager::AddEntity(TEntity *pEntity,
                           	bool bImmediate)
{
    DEBUG_ADD_ENTITY(pEntity, bImmediate);
    if (bImmediate) {
        pEntity->Initialize();

        // Send event notify this entity has been added
        TEvent Event(ADDED_ENTITY_EVENT, pEntity);
        SetEvent(Event, true);
        //printf("Added Entity %s, %p\n", pEntity->GetType().c_str(), pEntity);

        // Add entities in proper update order
        std::list<TEntity *>::iterator it;
        for (it = mEntities.begin();
         	it != mEntities.end(); it++) {
            if ((*it)->GetUpdateOrder() > pEntity->GetUpdateOrder()) {
                if (it == mEntities.begin()) {
                    mEntities.push_front(pEntity);
                }
                else {
                    mEntities.insert(--it, pEntity);
                }
                break;
            }
        }
        if (mEntities.end() == it) {
            mEntities.push_back(pEntity);
        }
        //mEntities.push_back(pEntity);
    }
    else {
        mEntityAdditions.push_back(pEntity);
    }
}

void TEntityManager::RemoveEntity(TEntity *pEntity)
{
    std::list<TEntity *>::iterator it;
    for (it = mEntities.begin();
     	it != mEntities.end(); it++) {
        if (pEntity == *it) {
            mEntities.erase(it);
            break;
        }
    }
}

void TEntityManager::DeleteEntity(TEntity *pEntity, bool bImmediate)
{
    DEBUG_DEL_ENTITY(pEntity, bImmediate);

    if (bImmediate) {
        TRegisteredEvents::iterator it;

        // Find the Events registered to this entity, or entity's components
        // and UnRegister
        for (it = mRegisteredEvents.begin(); it != mRegisteredEvents.end(); ) {
            if (it->second.pEntity == pEntity) {
                mRegisteredEvents.erase(it);

                if (it == mRegisteredEvents.end())
                {
                    break;
                }
            }
            else {
                it++;
            }
        }

        RemoveEntity(pEntity);
        delete pEntity;
    }
    else {
        if (!pEntity->IsDead()) {
            mEntityDeletions.push_back(pEntity);
            pEntity->SetDead();
        }
    }
}

TEntity *TEntityManager::GetEntity(std::string EntityType)
{
    TEntity *pEntity = NULL;
    std::list<TEntity *>::iterator it;
    for (it = mEntities.begin();
     	it != mEntities.end(); it++) {
        if (EntityType == (*it)->GetType()) {
            pEntity = *it;
            break;
        }
    }

    return pEntity;
}

void TEntityManager::GetEntities(std::list<TEntity *> &Entities)
{
    Entities = mEntities;
}

void TEntityManager::SetEvent(TEvent &Event, bool bNotifyEntities)
{
    // Check for update event, and delete entities in list
    if (Event.GetType() == FRAME_UPDATE_EVENT) {
        RESET_EVENTS();

        for (std::list<TEntity *>::iterator it2 = mEntityAdditions.begin();
         	it2 != mEntityAdditions.end(); it2++) {
            AddEntity(*it2, true);
        }
        mEntityAdditions.clear();

        for (std::list<TEntity *>::iterator it = mEntityDeletions.begin();
         	it != mEntityDeletions.end(); it++) {
            DeleteEntity(*it, true);
        }
        mEntityDeletions.clear();
    }

    // ensure there's someone listen for this event ??

    TRegisteredEvents::iterator it;
    for (it = mRegisteredEvents.begin(); it != mRegisteredEvents.end(); it++) {
        if (it->first == Event.GetType()) {
            DEBUG_EVENT_MGR2(Event);
            it->second.EventCB(Event, it->second.pThis);
        }
    }

    if (bNotifyEntities) {
        // tell all the entites about this
        std::list<TEntity *>::iterator it2;

        for (it2 = mEntities.begin();
         	it2 != mEntities.end(); it2++) {
#ifdef EVENT_DEBUG
            if ((*it2)->GetType() != "Wall") fprintf(fEventDebug, "EntMgr ->Entity: ");
            fflush(fEventDebug);
#endif
            (*it2)->SetEvent(Event);
        }
    }
}

void TEntityManager::RegisterForEvent(TEventCB EventCB, void *pThis,
                                      etEventType EventType, TEntity *pEntity)
{
    TEventCbInfo EventCbInfo;
    EventCbInfo.EventCB = EventCB;
    EventCbInfo.pThis = pThis;
    EventCbInfo.pEntity = pEntity;

    std::pair<etEventType, TEventCbInfo> EventPair(EventType, EventCbInfo);
    mRegisteredEvents.push_back(EventPair);
}

void TEntityManager::UnRegisterForEvent(TEventCB EventCB, void *pThis,
                        etEventType EventType)
{
    TRegisteredEvents::iterator it;
    for (it = mRegisteredEvents.begin(); it != mRegisteredEvents.end(); it++) {
        printf("EventType %d, if->first %d\n", EventType, it->first);
        if (it->first == EventType) {
            if ((it->second.EventCB == EventCB) &&
                (it->second.pThis == pThis)) {
                    printf("\n\nAGGGG! removing System Event, not safely!!!\n\n");
                mRegisteredEvents.erase(it);
                break;
            }
        }
    }
}

So, that's the basis for my system. There are some obvious faults with it, not least of which being my games probably don't need this, and could be over-kill. But, it's been a really good experience so far, and I'm going to be curious how it will help me in future games.

Next time I'll get into specific Components I use for SmashPC, and their interactions.

Remeber, you can see my old blog here: 2dGameMaking It has the details about the SmashPC game.






September 2016 »

S M T W T F S
    123
45678910
11121314151617
18192021222324
2526272829 30  


PARTNERS