In this tutorial, you will be adding the "crouching" functionality to the player character that was built in the first part of this series (Creating a Player using C++).
You will be building upon the Player.cpp and Player.h files built in the Creating a Player using C++ tutorial.
CRYENGINE 5.7 LTS
Visual Studio (2017, 2019 or 2020 work just fine) or any other IDE or code editor of your choice
Refining the Code
As C++ projects get larger, adding more functions, events and general code, you’ll find that the code can quickly become unorganized. Therefore, it’s common practice to periodically refine your code before adding new functions. Your Player.h and Player.cpp, while not incredibly long, could use some refinement and renaming in areas to make better sense of what we currently have. This will make it easier to locate bugs and communicate about your code with others.
Open Game.sln, which can be found within the solution folder of your project's main directory, in Visual Studio (or your editor of choice).
If you have not generated a solution yet, or are unsure about the location of the Game.sln file, follow the first steps of the Creating a Player using C++ tutorial.
- Open Player.h and Player.cpp.
- Move the
enum class EPlayerStatefrom its current position to the
m_cameraDefaultPosmember variables from the
privateclass in Player.h,
along with the corresponding
m_movementSpeedwas replaced with walk and run speed in the sprinting tutorial.
privateclass, add a new
Add the corresponding
cameraOffsetStanding AddMember linesPreviously, the values had to be modified every time you placed a player into your level. However, you can define the variables in advance within the code while still allowing values to be modified within CRYENGINE 5.7 LTS. To achieve this, you must define constant values for each member.
- Create a new
privateclass at the bottom of Player.h.
privateclass, add the following values:
New private class with values
This has defined the values for Schematyc, but now you must update the
AddMembervalues to use these constant values. This is done by changing the end value of each
AddMemberline to the one defined above in the new
privateclass. For example, change
Modified AddMember lines
To finalize the process of setting up default variables within the header, replace the lines
with the lines
The updated public class
Add clarity between the run-time variables and the component properties in the first
privateclass by adding comments defining them.
Any line can be turned into a comment (i.e., completely disregarded when the code is run) by adding
//to the front of the line.
Add the following new runtime variables to the current ones.
New Runtime Variables
Since these runtime variables will be used to refine the camera in Player.cpp, you do not need an
AddMemberline or definitions for these with variables.
Move the following functions into the
Under those functions, still within the
protectedclass, add the following functions that will later be used to clarify code function in Player.cpp.
- The updated Player.h should look like this:
The refined Player.h file
Depending on your character and how you want to implement it, you can also remove any mention of the Animation Component. For a simple first-person capsule player, this component is not needed, but later in the process you may want to implement a model with advanced animations. This can always be added in again later but if you prefer a more refined and less bloated code, its removal will cause no issues.
With the header cleaned up, you can move on to refining Player.cpp by clearly defining your logic by name and implementing some of the new variables added to Player.h.
namespace, create a definition and name for
Beneath that, add the variables declared in Player.h, followed by a function body (defined by a pair of curly brackets):
By modifying your
Resetfrom being an
Event(as it currently is) to a Function, you can call it within other section of the code.
To do this, add the following after the
Copy the movement variables from
EEvent::Resetand paste them into the body of
Add the following variables to
- Add comments to further clarify the purpose of these components of
Reset Function with Comments
case Cry::Entity::EEvent::Resetand all its contents.
You can utilize the new
Resetby adding a line to call it within
Reset Called in Initialize
Currently, your code uses a cylinder as the shape of the player's physics collider. Implementing a capsule-shaped collider provides new benefits such as gliding up stairs and not become snagged on small physics proxies.
To implement the cylinder shape, add a new definition named
RecenterCollideradd the following
Unlike a cylinder, the capsule is a cylinder plus a sphere, cut in half and placed on the top and bottom of the cylinder. This is why the calculation uses half height and radius.
if statements, add the following:
Next, move to the
GetEventMasksection of Player.cpp. Add the following events:
You can place Event lines on their own line to better visualize the list. Each event must be followed by | apart from the last of the list
Reset in GameplayStarted
EEvent::Update, replace the existing logic with the functions you added to Player.h:
Add the following events after
float playerMoveSpeedas a
Completed CPlayerComponent UpdateMovement
Create a definition for
UpdateRotation();by using by adding the following below
This process can also be done by right-clicking on the
void UpdateRotation();line within Player.h, select Quick Actions and Refactorings, then select Create Definition of 'UpdateRotation' in Player.cpp.
Within the new definition, add:
Completed CPlayerComponent UpdateRotation
Create a definition for
UpdateCamera();and fill it with the following:
Completed CPlayerComponent UpdateCamera
- The refined Player.cpp file should look like this:
At the top of Player.h, add an
includethat will be used to determine if the character is currently under a physical object while crouching.
enum class EPlayerState, add an enumerator and name it
Underneath that, define and name the player stances:
The finalized EPlayerStance class
Go to the
privateclass within Player.h and add two new member variables under the
EPlayerStance Runtime Variables
Add the following member variables under the
EPlayerStance Component Properties
Add the following to the
privateclass with the other static
Static value declarations
publicclass, create new
AddMemberlines for each of these new values:
Crouching AddMember lines
protectedclass, add the following functions:
Finally, modify the existing
UpdateCamerafunction by adding
float frametimebetween the brackets.
Updated void UpdateCamera
Within Player.cpp, add the following variables to the list of
CPlayerComponentvariables added during refinement:
Add the following variables to the player state section of the
Player State Reset Variables
Add the following to reset the camera position when a lerp is added later in this tutorial, and add a relevant description:
Adding an Input
Now you will need to define the main logic involved in having your player crouch, which will be controlled by the Left Ctrl key. This tutorial creates a crouch controlled by a hold-key, a key you press continually to activate the effect. Therefore, the code must check for both the key press, the key release, and if the character is already crouching.
void CPlayerComponent::InitializeInput()in Player.cpp, copy and paste an existing
BindActionline and modify it so that you have a unique
eKI(LCtrl) and name for the crouch action:
Crouching Bind and Register Actions
Between the curly brackets of the
RegisterAction, start a new line to input your logic.
Add the following statement to set the crouch function:
ifstatement, add the following else if statement to set the standing function.
- The complete register/bind actions for the crouching should look like this:
Crouch Register and Bind actions
Adding a Lerp
If you used the crouching logic as is, you would have a functional but jarring crouching motion, as pressing crouch in a game with this logic would cause the camera to immediately jump between positions. You can smooth out the camera transition with a lerp.
Lerping (short for Linear Interpolation) is the mathematical interpolation, or smoothing, between two values.
At the top of the
case Cry::Entity::EEvent::Updatewithin Player.cpp, add the following calls:
Still within the
frametimeto the brackets of
float frametimeto the brackets of the initial call for
CPlayerComponent::UpdateCamera, below the lines that modify the camera movement (added in a previous tutorial), add the following line to enable lerping:
Adding the Lerp
Adding a TryUpdateStance
Now it is time to add the crouching logic itself, within the scope of the
TryUpdateStance member variable.
As you did earlier in the tutorial, create a definition of
TryUpdateStanceright before the performance updates to Player.cpp.
Add this definition right before the
UpdateMovementlines added in steps 16, 18, and 20 of "Refining Player.cpp", so that our updates happen only after we have actually pressed the crouch key.
The problem with adding crouching logic after our update is that if we hit the crouch key, the key press is detected on the frame and therefore the movement logic would happen one frame later. We want crouching to happen during the same frame as the update, so we add
TryUpdateStancebefore the updates.
Inside the curly brackets of the newly created
TryUpdateStancedefinition in Player.cpp, add:
Below this add the basic crouch logic:
The crouch switch logic
To create the crouching motion, add the following to the end of
TryUpdateStance(still within the curly brackets):
Continued TryUpdateStance code
- The complete
TryUpdateStanceshould look like this:
The completed TryUpdateStance
Creating the PWI
While crouching under a physical object, you need a way for the entity to check and see if that object would obstruct the character when they attempt to stand. To perform this check, you can use a primitive world intersection (PWI) test. This will create a primitive capsule projection based on the same dimensions as the standing
CharacterController which will detect intersections and prevent the player from standing, even if LCtrl has been released.
- Create a definition for
bool CPlayerComponent, which you added to Player.h earlier in this tutorial.
Add a line between the curly brackets of the new definition added to Player.cpp:
Empty IsCapsuleIntersectingGeometry function
In the space between the brackets, add:
The Capsule Intersection test
- To finish everything off, press Ctrl + Shift + S to save all tabs, and Ctrl + Shift + B to build the completed solution.
Testing the Character
Once the solution is built, you can test your player character in the Sandbox Editor.
- Open the level you created in the Creating a Player using C++ tutorial and select the previously placed Player Entity.
- With the Player Entity selected, open the Properties panel and scroll down to the CPlayerComponent properties, where your defaults should now be pre-set.
Play around with the values in the CPlayerComponent properties or redefine the default values in the code to find a camera and capsule height that best suits your game.
- Press Ctrl + G and test out your character.
You can also follow this tutorial series in video form on our YouTube channel:
- No labels