domingo, 29 de junio de 2014

Kill! Kill! Kill!

At this point, the only thing missing for the game to make any sense is to add our player the ability to attack and kill the enemies. For achieving this we're going to move some members from our SCharacter struct to a new struct that we can call SMovingObject, which will be used then by SCharacter and a new struct called SShot:

// Represents different types of elements that can be positioned in the game world
struct SMovingObject 
{
    int         x, z;             // Coordinates in tile map
    SVector2    PositionDeltas;   // The position inside the tile
    float       Direction;        // rotation angle in radians
    float       Speed;            // cells/second
};

// Inherits
members from SMovingObject for storing position and physics information
struct SCharacter : public SMovingObject
{   // Inheritance prevents duplicating code and facilitate higher-level constructs
    SCharacterPoints MaxPoints; // the maximum game points that can be set to a particular character
    SCharacterPoints CurrentPoints; // the current value of the character points, updated on game events
};

// Also inherits members from SMovingObject for storing position and physics information
struct SShot : public SMovingObject
{
    int Damage; // Value that will be substracted from the character's health points
};


This makes all members declared in SMovingObject to be part of SCharacter and SShot:

SShot myShot;
myShot.Speed = 10;
// Speed is originally from SMovingObject

SCharacter myChar;
myChar.Direction = 0.0f;
// the same as Direction


We can now add a new member into our game struct in order to store the shot list:

struct SGame
{
    SMap Map;
    SCharacter Player;
    std::vector<SCharacter> Enemies;
    std::vector<SShot> Shots; // Declare the shot list
};


We're going to need some code to setup a new shot and release it into the game world:

void shoot( SGame* gameObject, SPoint2 origin, float direction )
{
    SShot newShot;
    newShot.Damage           = 10; // 10 HP
    newShot.Speed            = 10.0f; // 10 tiles per second
    newShot.PositionDeltas   = origin; // the position where the shot was generated
    newShot.Direction        = direction; // the direction of the shot trajectory
    newShot.x = newShot.z = 0; // reset this so it's updated with refreshPosFromDeltas()
    refreshPosFromDeltas( &newShot ); // update tile coordinates
    gameObject->Shots.push_back( newShot ); // copy as new element at the end of the shot list
}


The update() step will also require a new function for updating position of the world shots, handle hits to enemies and remove shots that get outside the map. In order to remove the shots from the list, we're going to use the std::vector::iterator which is like a cursor of the vector. I would never use std::vector but it's good for beginners. The code for removing a shot from the list would look something like the one below:

    std::vector<SShot>::iterator iShot = gameObject->Shots.begin(); // get iterator at the begin of the list
    int indexShot = 0; // we still need to keep track of the index
    while( iShot != gameObject->Shots.end() ) // while the iterator didn't reach the end
    {
        SShot* currentShot = &gameObject->Shots[indexShot];


       
// remove shot if reached the end of the screen

        if( currentShot->x < 0 || currentShot->z < 0
            || currentShot->x >= gameObject->Map.Width || currentShot->z >= gameObject->Map.Depth
        )
        {
            iShot = gameObject->Shots.erase( iShot ); // get valid iterator
            continue; // keep at the current index
        }

        iShot++; // move iterator to the next position
        indexShot++; // next position means next index

    }

We can also use similar code to remove the shot if it hit an enemy:

        if( gameObject->Map.EnemyCells[currentShot->z][currentShot->x] != INVALID_ENEMY )
        { // damage enemy and remove shot
            gameObject->Enemy[gameObject->Map.EnemyCells[currentShot->z][currentShot->x]].CurrentPoints.HP -= iShot->Damage;
            gameObject->Player.CurrentPoints.XP += iShot->Damage;
            iShot = gameObject->Shots.erase( iShot );
            continue; // keep at the current position/index
        }


or to remove the enemy when it reached 0 health:

        if( currentEnemy->CurrentPoints.HP <= 0 )
        { // remove enemy if zero health
            gameObject->Player.CurrentPoints.XP += iEnemy->MaxPoints.HP;
            iEnemy = gameObject->Enemy.erase( iEnemy ); 
            continue; // keep at the current enemy index
        }


Now we're only missing to update the shot position from the direction and speed and time. For this we define a basis vector (x=1, y=0) and rotate it by the angle to get the final direction:

        // Calculate direction vector
        SVector2 dirVector = SVector2(1, 0).Rotate( currentShot->Direction );


        // integrate speed*time*direction
        shotDeltas->x += currentShot->Speed*fLastFrameTime*dirVector.x;
        shotDeltas->y += currentShot->Speed*fLastFrameTime*dirVector.y; 


        // refresh cell coordinates now that we have accumulated the distance
        refreshPosFromDeltas( currentShot );


I also decided we can change the display if the player win or lost and I took as reference the player health and the amount of enemies to not overcomplicate things, but the right way of doing it is to have a variable to keeps track of the game states, so you must NOT do things like this:

// Use this function to draw our game data
void draw( const SGame* gameObject )

{
    printf("- draw() called.\n");

    if( gameObject->Player.CurrentPoints.HP <= 0 )
        drawGameOver(false); // lose
    else if( gameObject->Enemy.size() == 0 )
        drawGameOver(true); // win
    else
    {
        drawASCIIMap( gameObject );
    }
    drawASCIIGameInfo( gameObject );
};


You can find the complete working code for this tutorial and other tutorials in the repository at google code. I hope you liked the game we just wrote -a program which can be written in one or two days if you're experienced in programming-. In the following tutorial series I will be explaining how to create a proper Windows window and add some nice 2D sprite animations to this game.

No hay comentarios:

Publicar un comentario