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

Case study: selecting entities

Here is a short example of how to implement entity selection (for strategy game, for example):

The idea

Selection is performed by mouse dragging. So we have to record one point at the beginning of the drag, and another one at the end. While dragging, we'll indicate the selection area by drawing it on the terrain. When drag is complete, we'll put the selection into grid, from where the entities can see if they are selected.

Data structures

We'll gave a grid that can hold it's "selected" status. A ready-made one and utilities is in Selector.h, so we'll put that into CMyGame. At near top of MyGame.h:

    #include "../engine/Selector.h" // selection sector and utilities
    #include "../engine/Grid.h"     // the grid
and some members near the end of CMyGame declaration:
    public:
        typedef CGrid<SSelectionSector,32,32>   TSelectionGrid;
    public:
        TSelectionGrid  mSelGrid;
        bool            mDragging;  // dragging at the moment
        bool            mDragValid; // drag is still valid
        bool            mSelectionHappened; // drag just ended
        D3DXVECTOR3     mDragPointBegin;
        D3DXVECTOR3     mDragPointEnd;

The entity logic

The entity itself is responsible for "selecting" - it consults a grid about selection, and acts accordingly. We'll make a IDecider for this - create file src/game/SelDecider.h and insert into game project:

    #ifndef __SEL_DECIDER_H
    #define __SEL_DECIDER_H
    #include "Decider.h"
    #include "MyGame.h"

    class CSelectionDecider : public IDecider {
    public:
        virtual void decideFor( CMyEntity& e ) const {
            // get my sector
            float x = e.getPosition().x;
            float y = e.getPosition().y;
            const CMyGame::TSelectionGrid& grid = CMyGame::getInstance().mSelGrid;
            const CMyGame::TSelectionGrid::TSector& s = grid.getSectorByPosition( x, y );
            // see if i'm selected
            if( s.isSelected( x, y ) ) {
                // change my color (alpha at maximum)
                e.setColor( 0xFF000000 | (e.getColor() + 0x0073A93B) );
            }
            // decider gets called every update
            e.mDeciderCounter = 1; 
        };
    };
    #endif

The initialization

Everything goes into CMyGame here.

Put includes at near top of MyGame.cpp:

    #include "../engine/TerrainPicker.h"    // ray-terrain intersection utility
    #include "SelDecider.h"                 // our decider
Now do the initialization in beginning of CMyGame::onInitialize() method:
    // not dragging
    mDragging = false;
    mDragValid = false;
    // one shared decider
    IDecider* decider = new CSelectionDecider();
In for loop change this:
    e.mDeciderCounter = rand()&15;
    e.mDecider = NULL;
into this:
    e.mDeciderCounter = 1;
    e.mDecider = decider;

The game

Again, everything is in CMyGame (MyGame.cpp). Put some additional logic in CMyGame::onUpdate() - insert at the beginning of the method:

    // clear selection grid
    int n = mSelGrid.getSecsCount();
    for( int i = 0; i < n; ++i ) {
        mSelGrid.getSectorByIndex( i ).beginFrame();
    }
    // if we have just got a selection - put it into grid
    if( mSelectionHappened ) {
        CSelector<TSelectionGrid>::putSelection( mSelGrid, mDragPointBegin.x, mDragPointBegin.y, mDragPointEnd.x, mDragPointEnd.y );
        mSelectionHappened = false;
    }
    // update entities - this one is already present

Now we'll respond to mouse clicks and movements. In CMyGame::onMouseLChange():

    // respond to mouse left clicks
    D3DXVECTOR3 origin = getCamera().getPosition();
    D3DXVECTOR3 dir = getCamera().getWorldRay( getMouse().getMouseX(), getMouse().getMouseY() );
    if( pressed ) {
        // mouse pressed - begin drag
        mDragging = false;
        mDragValid = false;
        mSelectionHappened = false;
        CTerrainPick pick = CTerrainPicker::pick( getTerrain(), origin, dir );
        if( pick.isValid() ) {
            mDragging = true;
            mDragValid = true;
            mDragPointBegin = mDragPointEnd = pick.getLocation();
        }
    } else {
        // mouse released - end drag
        mDragging = false;
        mSelectionHappened = true;
    }
At the end of CMyGame::onProcessInput():
    // mouse move for dragging
    if( mDragging ) {
        D3DXVECTOR3 origin = getCamera().getPosition();
        D3DXVECTOR3 dir = getCamera().getWorldRay( getMouse().getMouseX(), getMouse().getMouseY() );
        CTerrainPick pick = CTerrainPicker::pick( getTerrain(), origin, dir );
        if( pick.isValid() ) {
            mDragValid = true;
            mDragPointEnd = pick.getLocation();
        }
    }

Now there's one last thing left: while dragging, indicate the selection are by drawing it over the terrain. The terrain class has helper methods for drawing parts of terrain above itself, so in CMyGame::onRenderAfterAll():

    // draw selection over terrain
    if( mDragging && mDragValid ) {
        getTerrain().prepareQuad( mDragPointBegin.x, mDragPointBegin.y, mDragPointEnd.x, mDragPointEnd.y, 4.0f );
        getTerrain().renderQuad( 0x60FF0000 ); // slightly transparent, red color
    }

That's all. Selection should now work.


Prev: Complete game example.
Next: Details: Using the Grid.


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