Implementing player movement and rotation

So what now? We created a character, but how do we interact with it? This is where player possession comes into play. In game development, the act of controlling an object (typically a character) is commonly known as player possession. This is the concept of taking input from the user to control how the player or object behaves in the game world. CRYENGINE has an extremely powerful mechanism for handling input called Action Maps, which maps input from devices to game actions. This allows your game to listen for these actions instead of raw input from devices, taking the pain out of handling multiple devices. Using Action Maps has another advantage: there can be multiple inputs mapping to the same action, for example, let's say you wanted your character to jump around and at the same time work for both gamepads and keyboards. You would simply define each input device's buttons/keys to an action named Jump. Then, our game could listen for the Jump action, completely removing the need to worry about low-level device input. Since GAS is a very small game, we do not need such a system and we will detect input manually. In this section, you will learn how to accept input from the keyboard and transform that data into the movement of the player.

Since GAS is a side-scroller game, this movement will only be on the x and z axis (left/right and up/down, respectively). Let's get started:

  1. Implement the private ProcessMovement() method in the CPlayer class so that we can handle movement logic in every frame. It should look like this:
    void CPlayer::ProcessMovement( float DeltaTime )
    {
      //If Input System Doesn't Exists Then Don't Process Movement.
      if( !gEnv->pInput )
        return;
    
      //Get This Instance's Entity.
      auto pEntity = GetEntity();
    
      //Don't Process Movement If This Instance's Entity Doesn't Exist.
      if( !pEntity )
        return;
    
      //Get This Instance's Physical Entity.
      auto pPhysEnt = pEntity->GetPhysics();
    
      //Don't Process Movement If This Instance's Physical Entity Doesn't Exist.
      if( !pPhysEnt )
        return;
    
      //We Don't Know Whether We Should Move Or Not Yet So Set This To False.
      bool bMove = false;
    
      //Gets The Current Physics Status/State Of This Entity.
      pe_status_living StatusLiving;
      pPhysEnt->GetStatus( &StatusLiving );
    
      //Setup A "Move" Command.
      pe_action_move Move;
    
      //This Movement Should Add To the Current Velocity.
      Move.iJump = 2;
    
      //We Don't Know Which Direction To Move In Yet.
      Move.dir = Vec3( 0, 0, 0 );
    
      //Specify A Default Run Speed Modifier.
      float fRunModifier = 1.0f;
    
      //If We Are Holding The Left Shift Key.  Set The Run Modifier To 2.
      if( gEnv->pInput->InputState( "lshift", eIS_Down ) )
        fRunModifier = 2.0f;
    
      //If We Are Not In The Air Then Go Ahead And Process Movement Commands.
      if( !StatusLiving.bFlying )
      {
        //We Should Move Left
        if( gEnv->pInput->InputState( "a", eIS_Down ) )
        {
          //We Should Move In The Negative X Direction.
          Move.dir = Vec3( -50, 0, 0 ) * fRunModifier;
    
          //Indicates That We Should In Fact Move This Instance.
          bMove = true;
        }
    
        //We Should Move Right.
        if( gEnv->pInput->InputState( "d", eIS_Down ) )
        {
          //We Should Move In The Positive X Direction.
          Move.dir = Vec3( 50, 0, 0 ) * fRunModifier;
    
          //Indicates That We Should In Fact Move This Instance.
          bMove = true;
        }
    
        //We Should Jump
        if( gEnv->pInput->InputState( "space", eIS_Down ) )
        {
          //We Should Move In The Positive Z Direction. We Add To The Current Move Command (+=) Because We Want To Combine Both Velocities (L/R U/D).
          Move.dir += Vec3( 0, 0, 600 );
    
          //Indicates That We Should In Fact Move This Instance.
          bMove = true;
        }
    
        //If We Should Move, Then Apply The "Move" Command.
        if( bMove )
        {
          //Calculate Final Movement By Factoring In The Delta Time.
          Move.dir = Move.dir * DeltaTime;
          pPhysEnt->Action( &Move );
        }
      }
    }
  2. Implement the private ProcessRotation() method in the CPlayer class so that we can handle rotation logic in every frame. It should look like this:
    void CPlayer::ProcessRotation( float DeltaTime )
    {
      //Get This Instance's Entity.
      auto pEntity = GetEntity();
    
      //Don't Process Rotation If This Instance's Entity Doesn't Exist.
      if( !pEntity )
        return;
    
      //Get This Instance's Physical Entity.
      auto pPhysEnt = pEntity->GetPhysics();
    
      //Don't Process Rotation If This Instance's Physical Entity Doesn't Exist.
      if( !pPhysEnt )
        return;
    
      //Gets The Current Physics Status/State Of This Entity.
      pe_status_dynamics StatusDyn;
      pPhysEnt->GetStatus( &StatusDyn );
    
      //We Are Moving Left (In The Negative X) Direction. Let's Face This Instance In That Direction.
      if( StatusDyn.v.x < -0.1 )
        //Sets This Instance's Rotation Such That It Faces This Instance's Movement Direction On The X Axis.
        pEntity->SetRotation( Quat::CreateRotationVDir( Vec3( 0, 1, 0 ) ), ENTITY_XFORM_ROT );
    
      //We Are Moving Right (In The Positive X) Direction. Let's Face This Instance In That Direction.
      else if( StatusDyn.v.x > 0.1 )
        //Sets This Instance's Rotation Such That It Faces This Instance's Movement Direction On The X Axis.
        pEntity->SetRotation( Quat::CreateRotationVDir( Vec3( 0, -1, 0 ) ), ENTITY_XFORM_ROT );
    }

The big picture

Let's look at the big picture so that we can get a clear view of the full flow and geography of the code. In every frame where the CPlayer class is updated by its Update() method, it makes calls to its ProcessMovement() and ProcessRotation() methods to handle movement and rotation. Inside ProcessMovement(), we get the current keyboard key that is pressed to decide which direction the player should move in. We then tell our player's physics to move us in that direction, making sure not to allow movement when we are in the air. After movement, rotation is processed. Inside ProcessRotation(), we get the player's current movement direction and then set its rotation so that it faces the direction it's moving in. To help visualize this, I have created a simple flowchart explaining the system:

The big picture

This concludes the section on implementing player movement and rotation.