lunes, 23 de junio de 2014

Improving character movement.

In this tutorial we're going to add a smoother movement with a configurable speed for the characters of our previous program. For this we're going to be using a new data type which is called float and allows storing real numbers (or an approximation to them):

float mySpeed = 4.5678f; // units per second

By taking advantage of the frame time information received at the update() function, we can move our character the fraction corresponding to that time instead of moving it an entire cell.

if( GetKeyState(VK_UP) )
   
player->y -= player->Speed*fLastFrameTime; // decrease by speed*time as we learned in high school


The problem with our previous implementation is that our x and y coordinates are declared as integer types for using as array indices, which causes all the information after the decimal point to get lost:

float mySpeed = 4.5678f; // units per second
int myIntegerSpeed = mySpeed; // myIntegerSpeed will store only the integer value 4 and the decimal information such as 0.5678 will be lost.

So in order to circumvent this we will add a new structure for holding continuous space coordinates like this one:

struct SPoint2D
{
    float x, y;
};


typedef SPoint2D SVector2D; // Gives our structure another name (SVector2D)

We can now use this structure inside our character structure to store the distance between the cell origin (x=0 , y=0) to the player position, which will always be less than 1 because if we reached 1 of one cell it means that we are in the 0 of the next cell (because 1 is the size of a cell). The structure will then look similar to this one:

struct SCharacter // holds character data
{
    int x, z; // Coordinates in tile map
    SVector2D PositionDeltas; // location inside the (x, z) cell, being (0.5, 0.5) the center of the cell
    float Speed; // cells per second
 
    /* other character variables follow... */
};

We can then configure some speed for the enemies during the setup() step: 

// use this function to initialize enemies 
void setupEnemies( SGame* gameObject )
{
#define INITIAL_ENEMY_COUNT 5
    for( int iEnemy=0; iEnemy < INITIAL_ENEMY_COUNT; iEnemy++ )
    {
        SCharacter newEnemy;
        newEnemy.Speed = float(iEnemy);
// generate the speed from the enemy index
        newEnemy.PositionDeltas = {0,0}; // reset this to a valid value between 0 and 1
        /* other setup code... */


And also to our player character:

// Use this function to setup player at level startup.
void setupPlayer( SGame* gameObject )
{
    gameObject->Player.PositionDeltas = {0,0};
    gameObject->Player.Speed    = 5.0f;
    /* other player initialization code... */


Now that we have our data structures set up and the values initialized, we can start using them during the update() step to calculate the movement that happened in the time step of the frame:

void updatePlayer( SGame* gameObject, float fLastFrameTime  )
{
    SVector2D* playerDelta = &gameObject->Player.PositionDeltas; // get memory address of our data (pointer to data)

    float fSpeed = gameObject->Player.Speed; // get player speed
    // Update position from speed*time

    if(GetAsyncKeyState(VK_UP))
        playerDelta->y -= fSpeed*fLastFrameTime; // decrease by speed*time
    if(GetAsyncKeyState(VK_DOWN))
        playerDelta->y += fSpeed*fLastFrameTime; // increase by speed*time
    if(GetAsyncKeyState(VK_RIGHT))
        playerDelta->x += fSpeed*fLastFrameTime; // increase by speed*time
    if(GetAsyncKeyState(VK_LEFT))
        playerDelta->x -= fSpeed*fLastFrameTime; // decrease by speed*time

    // refresh tile coordinates after accumulating the distance walked
    refreshPosFromDeltas( &gameObject->Player );

}

We define an auxiliary function that will update the x, z coordinates of the tile for each unit accumulated in our deltas after calculating the displacement from the time. Then if we have x = 10 and deltas.x = 2.5f after accumulating the displacement, we shall increase x and decrease deltas.x by one until x = 12 and deltas.x = 0.5:

void refreshPosFromDeltas( SCharacter* character )
{
    SVector2D* charDeltas = &character->PositionDeltas; // get pointer to deltas
   
    // Update X coordinate until deltas.x  < 1
    while( charDeltas->x >= 1.0f )
    {
        character->x += 1.0f;

        charDeltas->x -= 1.0f;
    }
    // Update X coordinate until deltas.x  >= 0
    while( charDeltas->x < 0.0f )
    {
        character->x -= 1;
        charDeltas->x += 1.0f;
    }
    /* Do the same for the Y coordinate */

}

We also need to implement these changes in the code for the enemy updates:

    for( unsigned int iEnemy=0; iEnemy < gameObject->Enemy.size(); iEnemy++ )
    {
        SCharacter* currentEnemy = &gameObject->Enemy[iEnemy]; // get pointer to character
        SVector2D* enemyDelta = &currentEnemy->PositionDeltas; // get pointer to deltas to be updated
        float fEnemySpeed = currentEnemy->Speed; // get enemy speed
       
        if( gameObject->Player.x < currentEnemy->x )
            enemyDelta->x -= fEnemySpeed*fLastFrameTime; // decrease speed*time
        else if( gameObject->Player.x > currentEnemy->x )
            enemyDelta->x += fEnemySpeed*fLastFrameTime; // increase speed*time
   
        if( gameObject->Player.z < currentEnemy->z )
            enemyDelta->y -= fEnemySpeed*fLastFrameTime; // decrease speed*time
        else if( gameObject->Player.z > currentEnemy->z )
            enemyDelta->y += fEnemySpeed*fLastFrameTime; // increase speed*time

        // refresh cell coordinates now that we have accumulated the distance walked
        refreshPosFromDeltas( currentEnemy );
        /* more enemy update code follows */
 


This will be enough for our game characters to move smoothly depending on the actual time elapsed between frames instead of moving a whole cell on each update, giving more realism to the movement of the characters and making the game more enjoyable.

The complete code and binaries for this tutorial can be found in the repository at google code. In the next tutorial we will be adding a persistent direction system and our vector struct in order to set a base to add bullets and a improve the character controls and enemy AI. See you there!

No hay comentarios:

Publicar un comentario