The same old one: there are guys you should shoot (the bad ones), and there are evil gods that kill the good guys. If you manage to kill all the bad ones before evil gods kill all the good ones, you win. Some of the good guys are good gods, but if you accidentaly shoot the good god, it becomes evil.
We'll be ok with one entity structure for all needs. The simple CMyEntity is ok, we add more flags to existing ones (in MyEntity.h):
F_KILLED = 8, // this one is already there. F_SMALL = 16, // the small guy. you must kill big ones. F_GOD = 32, // this guy is god. F_NOTHING = 64, // this guy is spawned, he's just nothing. F_EVILGOD = 128 // the evil god
#ifndef __MY_SECTOR_H #define __MY_SECTOR_H #include "../engine/EntityRayPicker.h" struct SSectorWithRayNuke : public SSectorWithRay { public: SSectorWithRayNuke() : SSectorWithRay(), mNukeAmount(0) { } void beginFrame() { SSectorWithRay::beginFrame(); if( mNukeAmount > 0 ) --mNukeAmount; } public: int mNukeAmount; }; #endif
#include "../engine/Grid.h" // the grid #include "Sector.h" // our sector #include <list> // STL list class CJumpShotDecider; // these you'll write later class CDieDecider;
public: typedef CGrid<SSectorWithRayNuke,32,32> TGrid; // our grid type typedef std::list<CMyEntity*> TEntityList; // list of pointers to entities public: TGrid mGrid; int mTotalBads, mKilledBads; int mTotalGoods, mKilledGoods; int mTotalGods, mKilledGods; TEntityList mGods; CJumpShotDecider* mJumpDecider; CDieDecider* mDieDecider;
Everything here will be inserted into CMyGame class (MyGame.cpp or MyGame.h). The code here refers to custom entity logic deciders (IDecider), these I'll give later.
Put includes at near top of MyGame.cpp:
#include "Deciders.h" // this refers to the file you'll create later #include "../engine/Billboarder.h" // for billboard drawing #include "../engine/TextRenderer.h" // for text drawing
// init stats mTotalBads = mTotalGoods = mTotalGods = 0; mKilledBads = mKilledGoods = mKilledGods = 0; // create deciders (they will be shared across many entities) mJumpDecider = new CJumpShotDecider(); mDieDecider = new CDieDecider();
CMyEntity& e = mPool->add(); getEntities().add( e ); e.getPosition() = D3DXVECTOR3( random::randf(gcon::WORLD_X), random::randf(gcon::WORLD_Y), 0.0f ); e.mVelocity = D3DXVECTOR3( random::randfs(3.0f), random::randf(3.0f), 0.0f ); e.getOldPosition() = e.getPosition(); e.mDeciderCounter = rand()&15; e.mDecider = mJumpDecider; // normal decider e.setPicture( pic1 ); // good or bad guy? int t = rand()&255; if( t < 255 ) { // this is good guy e.setSize( 3.5f ); // he's small e.setColor( 0xFFC0C0C0 ); // he's gray e.mFlags = (CMyEntity::F_GRAVITY) | (CMyEntity::F_SMALL); ++mTotalGoods; } else { // this is bad guy - approx. every 1 of 256 e.setSize( 8.0f ); // he's big e.setColor( 0xFFFFFF00 ); // he's yellow e.mFlags = CMyEntity::F_GRAVITY; ++mTotalBads; } // god or not? if( (rand()%50) == 7 ) { // approx. every 1 of 50 mGods.push_back( &e ); // insert into god list e.mFlags |= CMyEntity::F_GOD; if( !mTotalGods ) // make the first god initially evil e.mFlags |= CMyEntity::F_EVILGOD; ++mTotalGods; }
Again, everything is in CMyGame (MyGame.cpp). Put some additional logic in CMyGame::onUpdate() - insert at the beginning of the method:
// is the game ended? if( mKilledBads >= mTotalBads || mKilledGoods >= mTotalGoods ) { // set NULL decider for all the entities, so that you can't shoot them // anymore, and the gods don't kill anymore. everyone just wanders around. TPool::iterator it; for( it = mPool->begin(); it != mPool->end(); ++it ) { CMyEntity& e = *it; e.mDecider = NULL; } } // clear grid - remove rays from it and decrease "evil god damage" int n = mGrid.getSecsCount(); for( int i = 0; i < n; ++i ) { mGrid.getSectorByIndex( i ).beginFrame(); } // shoot (put ray into grid) if( getMouse().isMouseLPressed() ) { // construct a ray that goes from a camera to world CEntityRayPicker<TGrid>::putRay( mGrid, getCamera().getPosition(), getCamera().getWorldRay( getMouse().getMouseX(), getMouse().getMouseY() ) ); } // update entities - this one is already present
Now we'll do some billboards (plain sprites) for game indicators and god indicators. Recall that we used the same picture for all entities, and now there's no way to tell which ones are gods (but we could have used different pictures of course). Instead, just to illustrate, we'll draw sprites above all the gods. So, in CMyGame::onRenderBeforeAll() - that's the place where you need to do all your billboards - put this:
CBillboarder& bill = getBillboarder(); bill.beginBillboards(); // draw slightly transparent billboards above gods heads CResourceId god1( "God.png" ); // peaceful god picture CResourceId god2( "AngryGod.png" ); // evil god picture TEntityList::const_iterator it; for( it = mGods.begin(); it != mGods.end(); ++it ) { const CMyEntity& e = **it; bill.putEntityBill( (e.mFlags & CMyEntity::F_EVILGOD) ? god2 : god1, 0xC0FFFFFF, e, 20, 20 ); } // indicators - one for your progress, other for evil gods progress float xx; xx = ((float)mKilledBads)/mTotalBads * 2.0f - 1.0f; bill.putScreenBill( CResourceId("Bar.png"), 0xFF00FF00, -1.0f, 0.8f, xx, 0.88f ); xx = ((float)mKilledGoods)/mTotalGoods * 2.0f - 1.0f; bill.putScreenBill( CResourceId("Bar.png"), 0xFFFF0000, -1.0f, 0.9f, xx, 0.98f ); bill.endBillboards();
CTextRenderer& text = getTextRenderer(); // stats char buf[200]; sprintf( buf, "Guys %i", mPool->size() ); text.renderScreenText( 10, 25, buf, 0xFF808080 ); sprintf( buf, "Bads\nGoods\nGods" ); text.renderScreenText( 10, 40, buf, 0xFF808080 ); sprintf( buf, "%i/%i\n%i/%i\n%i/%i", mKilledBads, mTotalBads, mKilledGoods, mTotalGoods, mKilledGods, mTotalGods ); text.renderScreenText( 60, 40, buf, 0xFFC00000 ); // win/lose text if( mKilledBads >= mTotalBads ) text.renderScreenText( 240, 200, "You win!!!", 0xFFFFFF00 ); if( mKilledGoods >= mTotalGoods ) text.renderScreenText( 240, 200, "You lose...", 0xFFFF8000 );
We'll create our common entity logic in the deciders. Create file src/game/Deciders.h and insert into game project:
#ifndef __MY_DECIDERS_H #define __MY_DECIDERS_H #include "Decider.h" #include "MyGame.h" // just jumps occasionally class CJumpDecider : public IDecider { public: virtual void decideFor( CMyEntity& e ) const; }; // "main" decider for all uses :) class CJumpShotDecider : public CJumpDecider { public: virtual void decideFor( CMyEntity& e ) const; }; // die class CDieDecider : public IDecider { public: virtual void decideFor( CMyEntity& e ) const; }; #endif
#include "Deciders.h" #include "MyEntity.h" #include "../engine/EntityRenderer.h" #include "../engine/EntityRayPicker.h" #include "../utils/Random.h" void CDieDecider::decideFor( CMyEntity& e ) const { // just mark as dead e.mFlags |= CMyEntity::F_KILLED; } void CJumpDecider::decideFor( CMyEntity& e ) const { // if i'm on the ground - jump from time to time if( (e.mFlags & CMyEntity::F_TOUCHES_TERRAIN) && !(rand()&1023) ) e.mVelocity.z += 10.0f; e.mDeciderCounter = 15; } void CJumpShotDecider::decideFor( CMyEntity& e ) const { CMyGame& game = CMyGame::getInstance(); // get my sector CMyGame::TGrid::TSector& sector = game.mGrid.getSectorByPosition( e.getPosition().x, e.getPosition().y ); // i'm initially not shot bool shot = false; if( sector.mNukeAmount > 0 && (e.mFlags & CMyEntity::F_SMALL) && !(e.mFlags & CMyEntity::F_EVILGOD) ) { // if there's "evil god activity" in my sector and i'm a no-god small guy, then i'm shot shot = true; } else if( sector.mHasRay ) { // if my sector has player's shooting ray: test me shot = CEntityRayPicker<CMyGame::TGrid>::intersects( e, sector ); } // if i'm shot (but don't allow shooting evil gods) if( shot && !(e.mFlags&CMyEntity::F_EVILGOD) ) { // throw me up! e.mVelocity.z += 15.0f; // statistics - small/big? if( e.mFlags & CMyEntity::F_SMALL ) ++game.mKilledGoods; else if( !(e.mFlags & CMyEntity::F_NOTHING) ) ++game.mKilledBads; // shot a good god? if( e.mFlags & CMyEntity::F_GOD ) { CMyGame::TPool& entities = *game.mPool; ++game.mKilledGods; sector.mNukeAmount = 10; // kill others in my sector for 10 updates e.mFlags |= CMyEntity::F_EVILGOD; // become evil god e.mVelocity.x += random::randfs(10.0f); e.mVelocity.y += random::randfs(10.0f); // spawn 50 "nothing" entities in my place (just for fun) int n = 50; while( entities.hasSpace() && n > 0 ) { CMyEntity& ee = entities.add(); game.getEntities().add( ee ); // add for rendering ee.getPosition() = e.getPosition(); ee.mVelocity = e.mVelocity; ee.mVelocity.x += random::randfs( 1.0f ); ee.mVelocity.y += random::randfs( 1.0f ); ee.mVelocity.z += random::randfs( 0.5f ); ee.setColor( 0xFF8080FF ); // blue color ee.mDecider = (IDecider*)this; ee.mDeciderCounter = 1; ee.setSize( 3.5f ); ee.setPicture( e.getPicture() ); ee.mFlags = CMyEntity::F_GRAVITY | CMyEntity::F_NOTHING; --n; } } // make it red color for now, and die after some time, unless he's a god e.setColor( 0xFFFF0000 ); if( !(e.mFlags & CMyEntity::F_GOD) ) { e.mDecider = game.mDieDecider; e.mDeciderCounter = 60 + (rand()&63); e.mVelocity.x = 0.0f; // stop moving e.mVelocity.y = 0.0f; } } else { // i'm not shot // but if i'm an evil god, increase "evil god activity" here if( e.mFlags & CMyEntity::F_EVILGOD ) ++sector.mNukeAmount; // default decider - jump ocasionally CJumpDecider::decideFor( e ); // call this decider for me next update e.mDeciderCounter = 1; } }
That's all folks! The game now should just work. Initial number of guys is controlled via config file game.cfg - it's half of the maxEntities value. We have experienced normal play with 20000-30000 maxEntities.
Prev: Getting started.
Next: Case study: selecting entities.
1.2.17