Fortunately, C++ allows something called operator overloading and it allows us to define operators that makes easier to work with our mathematical structures. This enables us to work with vectors more comfortably by defining the operations as part of the SVector2 struct, so we can do something like:
SVector2 vA = {1, 2}, vB = {3, 4}, vC; // define two vector variables and assign some values
vC = vA+vB; // add vector A to vector B and store the result in vector C
We can do this by adding the following definition as part of our SVector2 class:
struct SVector2
{     // add another vector to the current vector and return the result
    SVector2 operator + ( const SVector2& other ) const
    {   // The const modifier ensures no member variables of the structure are modified
        SVector2 result;
        result.x = x + other.x;
        result.y = y + other.y;
        return result;
    }
};The method above will declare a new vector where to store the result, and take the x and y values from the left vector and add them to the x and y values of the vector placed at the right of the operator +. The vector instance storing the resulting values will then be returned as result of the operation.
We can also add methods to our SVector2 structure in a similar manner:
struct SVector2
{     // Multiply vector elements by a scalar value
    void    Scale( float _scalar ) 
    {   // This method can't be const because it requires modifying member variables
        x *= _scalar; 
        y *= _scalar;
    };     };This allows us to modify the values of our member variables without having the arithmetic operations inside our game functions. In this case the previous code enables scaling the vector by a simple call to the Scale() member function:
SVector2 vA = {1, 2}; // define a vectorvA.Scale( 2.0f ); // multiply vector elements by 2
C++ also enables to define constructors, which are methods that are called automatically when creating a new instance from a structure. This enables to make the code simpler by allowing us to initialize the member values from parameters, much like this:
SVector2 vA(1, 2); // initialize vA with constructorSVector2 vC = vA + SVector2(3, 4); // initialize SVector2 with constructorConstructors are named as the structure they construct:
typedef struct SVector2
{
    // --- member variables
    float x, y;
    // constructors
    SVector2(){ /* do nothing */ };
    SVector2( float _x, float _y ){ x = _x; y = _y; };}; I won't dive into vector math right now because it would be too long and there are plenty of information on vector math for games out there. You can find the other methods added by looking at our point.h file, but now we are going to assume they're already there and start using them for something more interesting.
We're now going to improve our character direction system by taking advantage of our new vector methods and operators, because currently the direction is calculated only for updating the position but then it gets lost, and this is a problem because we will want to add other functions related to the player's direction (such as shooting) and we need the information to remain available for other update functions. In order to achieve this, we're going to define a new member variable at the SCharacter struct called Direction, which will hold the rotation angle of the character in relation to a reference vector, which we're going to define as (x=1, y=0).
struct SCharacter
{
    float Direction; // rotation angle in radians
    /* ... more members follow ... */We can then update our player direction code in order to calculate this value from the keys pressed:
void updatePlayer( SGame* gameObject, float fLastFrameTime  ) 
{    // Query key states
    bool _keyUP     = GetAsyncKeyState('W') ? true : false,
        _keyDOWN    = GetAsyncKeyState('S') ? true : false,
        _keyLEFT    = GetAsyncKeyState('A') ? true : false,
        _keyRIGHT   = GetAsyncKeyState('D') ? true : false;
    SVector2 dir = {0,0};
    // Determine final direction from keys pressed
    if(_keyRIGHT)
        dir.x ++; //dir.x = 1;
    if(_keyLEFT)
        dir.x --; //dir.x = -1;
    if(_keyUP)
        dir.y --; //dir.y = -1;
    if(_keyDOWN)
        dir.y ++; //dir.y = 1;    // normalize the new direction vector (make it a unit length so we can multiply it by speed to get velocity vector)
    dir.Normalize();    // we want to walk only if a direction key was pressed
    bool dirKeyPressed = _keyRIGHT || _keyLEFT || _keyUP || _keyDOWN;
    if( dirKeyPressed )
    {
        gameObject->Player.Direction = SVector2(1, 0).AngleWith(dir); // get angle in radians
        if( dir.y < 0 ) // negate rotation if more than 180º
            gameObject->Player.Direction = gameObject->Player.Direction*-1;
        // Increase 50% speed if left shift pressed.
        float fSpeed = GetAsyncKeyState(VK_LSHIFT) ? gameObject->Player.Speed*1.5f : gameObject->Player.Speed;        // scale direction depending on speed and time to get total displacement
        dir.Scale(fSpeed*fLastFrameTime); 
        // integrate our calculated displacement
        gameObject->Player.PositionDeltas = gameObject->Player.PositionDeltas + dir;
    }    /* .. more update code ... */This will give us a better direction system that will allow us to determine the direction for projectiles shot by the player and enable a more precise rotation control for game characters.We can also translate this value to other rotation or direction representations by taking advantage of our new SVector2 methods:
    // Calculate direction vector from angle in radians.
    SVector2 dirVector = SVector2(1, 0).Rotate(gameObject->Player.Direction);
    // Calculate rotation degrees from angle in radians.
    float degrees = gameObject->Player.Direction/(float)GAME_2PI*360;
We can display these values at our draw() step in order to check they're working properly:
    printf( "- Player direction: (%f, %f)\n"
            "- Player angle: %f radians or %f degrees\n"
            dirVector.x, dirVector.y, gameObject->Player.Direction, degrees );
The complete code for this tutorial can be downloaded from the repository. In the next tutorial we're going to see how to use this information to add a shooting system to the player character, which will transform our current program into a playable game. See you there!
 
No hay comentarios:
Publicar un comentario