Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Namespace Members   Compound Members   Related Pages  

Complete game example

Here is a complete step-by-step game example, consisting of: Be warned, that code here is bad: public members everywhere, big fat procedures, etc.

The idea

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.

Data structures

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
We'll want to shoot the entities, hence we need a grid sector that holds shooting ray (see using_grid). We also want it to contain "damage from evil gods" so that good guys know when they should die. So create a file src/game/Sector.h and insert into game project:
    #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
We'll put our ray&damage grid into game, put some statistic variables and a list of pointers to gods: add includes and forwards near the beginning of MyGame.h:
    #include "../engine/Grid.h" // the grid
    #include "Sector.h" // our sector
    #include <list> // STL list
    class CJumpShotDecider; // these you'll write later
    class CDieDecider;
and some members near the end of CMyGame declaration:
    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 should now compile well, but with no visual difference in the game :)

The initialization

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
Slightly below, CMyGame::createCamera() already is good for us, so leave it as is. We'll now do the initialization in CMyGame::onInitialize() method:
    // 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();
Replace for loop contents with this:
    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;
    }
As you probably see, we're using the same picture for all the entities, but it's not a requirement.

The game

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();
And lastly we'll draw some text on the screen. So in CMyGame::onRenderAfterAll():
    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 );
That's all with the game. Still, there's one thing left: entity logic (aka deciders). Without them all this stuff does not even compile :)

The deciders

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
Create file src/game/Deciders.cpp and insert into game project:
    #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.


Generated on Thu Dec 5 17:27:58 2002 for LT Game Jam Session by doxygen1.2.17