• Create Account

Like
3Likes
Dislike

# Building a First-Person Shooter Part 1.2: The Player Class UNDER REVIEW

By Chris Vossen | Published May 22 2013 07:38 AM in Game Programming

It's finally time to begin fleshing out our player class. This class will manage the first-person user controls. We will start by setting up player.h. This header file will contain the declarations of the player class as well as the various variables and functions that will be held within the player class. At this time copy and paste the following code into player.h:

#pragma once
#include "MyGame.h"

class Player: public Node
{
private:
Camera* camera;
float standheight;
float crouchheight;
float cameraheight;
float move;
float strafe;
float movementspeed;
float maxacceleleration;
float sensitivity;
float smoothedcamerapositiony;
float cameraypositionsmoothing;
float cameralooksmoothing;
float runboost;
float jump;
float jumpforce;
float jumpboost;
int footstepwalkfrequency;
int footsteprunfrequency;
long footsteptimer;
bool running;
bool crouched;
bool landing;
Vec2 mousespeed;
Vec3 normalizedmovement;
Vec3 cameraposition;
Vec3 playerrotation;
Sound* footstepsound[4];
Sound* landsound[4];
Sound* jumpsound[1];

public:
Player();
virtual ~Player();

virtual void UpdateControls();
virtual void Update();
};


## Setting Up the Player Class

The player.cpp file will contain all the logic and code for setting up FPS player mechanics. We start with a basic player class that contains a constructor, destructor, and two empty functions:

#include "MyGame.h"

Player::Player()
{
}

Player::~Player()
{
}

void Player::UpdateControls()
{
}

void Player::Update()
{
}


Since the player class is a child of the node class it will inherit an entity member from its parent. In the player constructor we assign a value to this entity member with a call to Pivot::Create(). A pivot is an invisible entity with no special properties, it is essentially an instantiation of an empty entity:

entity = Pivot::Create();

We now want to setup the player physics properties for the entity:

entity->SetPhysicsMode(Entity::CharacterPhysics);
entity->SetCollisionType(Collision::Character);
entity->SetMass(10.0);


And finally position the player at the origin:

entity->SetPosition(0,0,0,true);

With the code additions our player class will now look as such:

#include "MyGame.h"

Player::Player()
{
//Create the entity
entity = Pivot::Create();
//Set up player physics
entity->SetPhysicsMode(Entity::CharacterPhysics);
entity->SetCollisionType(Collision::Character);
entity->SetMass(10.0);
//Player position
entity->SetPosition(0,0,0,true);
}

Player::~Player()
{
}

void Player::UpdateControls()
{
}

//Update function
void Player::Update()
{
}


In a FPS the player’s camera acts as the player’s head, in that it should be positioned at a height directly above the player’s shoulders and be restricted to normal human movements. For the player’s height we will create and initialize three separate variables in the constructor:

standheight=1.7;
crouchheight=1.2;
cameraheight = standheight;


We then create the camera, position it to the height of a standing player, and narrow the camera’s field of view:

camera = Camera::Create();
camera->SetPosition(0,entity->GetPosition().y + cameraheight,0,true);
camera->SetFOV(70);


We also don’t want to forget to deal with the camera when an instance of the player class gets deleted. so in the destructor we add in:

if (camera)
{
camera->Release();
camera = NULL;
}


After these changes the player class will now look like this:

#include "MyGame.h"

Player::Player()
{
//Create the entity
entity = Pivot::Create();

//Initialize values
standheight=1.7;
crouchheight=1.2;
cameraheight = standheight;
//Create the player camera
camera = Camera::Create();
camera->SetPosition(0,entity->GetPosition().y + cameraheight,0,true);
camera->SetFOV(70);
//Set up player physics
entity->SetPhysicsMode(Entity::CharacterPhysics);
entity->SetCollisionType(Collision::Character);
entity->SetMass(10.0);
//Player position
entity->SetPosition(0,0,0,true);
}

Player::~Player()
{
if (camera)
{
camera->Release();
camera = NULL;
}
}

void Player::UpdateControls()
{
}

//Update function
void Player::Update()
{
}


Up next, we'll talk about movement with keyboard input.

Chris Vossen is a developer at Leadwerks Software. A few of his contributions to the Leadwerks 3 Engine include designing and creating the LE3 particle system as well as building the example game Darkness Awaits. Areas of knowledge include:C++, Lua, Leadwerks Engine 3, Gameplay programming,and Particle systems.

I see several problem with the Player class:

1) The class is too big. It has 29 members including arrays! It has too many responsibilities: mouse speed, camera, physics, sound, etc.

2) Manual memory management. std::unique_ptr (or boost::scoped_ptr) would be a lot better.

3) It creates its own dependencies instead of using Dependency Injection. It creates coupling an block the possibility of unit testing the class.

4) The meaning of the last parameter of entity->SetPosition is not clear.

5) Not all members are initialized in the constructor.

6) Constructor is not exception safe (assuming ::Create can throw std::bad_alloc).

Such code smells in an article can teach people really bad habbits.

I see several problem with the Player class:

1) The class is too big. It has 29 members including arrays! It has too many responsibilities: mouse speed, camera, physics, sound, etc.

2) Manual memory management. std::unique_ptr (or boost::scoped_ptr) would be a lot better.

3) It creates its own dependencies instead of using Dependency Injection. It creates coupling an block the possibility of unit testing the class.

4) The meaning of the last parameter of entity->SetPosition is not clear.

5) Not all members are initialized in the constructor.

6) Constructor is not exception safe (assuming ::Create can throw std::bad_alloc).

Such code smells in an article can teach people really bad habbits.

I agree with most of this, and there's other issues with the above code... starting with the fact that anyone who doesnt use camelCase is not really interested in code readability, which makes me not interested in reading their code.

Dont really care about the exception safety though.  I've never worked at a game studio where exception handling was even considered.

The class is quite large, but when the goal is to teach the basics of FPS mechanics I'd rather spend the majority of the tutorial inside 1 class rather than jumping between multiple classes and interfaces.

entity-SetPosition explicitly sets the players position to the origin, which happens to be the center of the room created earlier.

Not having all members initialized in the constructor is extremely poor programming. When making these tutorials I started with the complete game, then worked my way backwards. My guess is that the un-initialized members are actually initialized in one of the later tutorials. (My bad)

The class is quite large, but when the goal is to teach the basics of FPS mechanics I'd rather spend the majority of the tutorial inside 1 class rather than jumping between multiple classes and interfaces.

I can definitely understand the motivation to make the tutorial easier to understand by coalescing code into a single object, but at some point it goes too far.  For example, having all the camera stuff in the player class is really bad form, teaches bad practices, and will lead to dirtier code in your tutorial later on.  It could be fixed by adding a separate class to control the camera without really much total added complexity.  The complexity you add by having one more class gets balanced by reducing the complexity of the player class.  Remember that smaller, more focused classes are easier to explain and understand.

Right now when someone looks at your player class they see a dauntingly large and complex monolith that incorporates all kinds of functionality.  You cant just tell someone "the Player class represents a player"... because it doesnt.  Now you've made more work for yourself in the tutorial because you have to explain why a thing doesnt do what it claims to do.  And, when you add more stuff, it will presumably go right into the Player class, making things even worse.

Since subsequent articles that make use of the Player class have been approved, I've decided to mark this one as peer reviewed as well.

I would like to see some future attention to better separation of functionality, but any changes to the Player class would likely have large impacts to subsequent articles that have already been approved.

Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.

PARTNERS