Simple 3rd person camera         A camera system that follows the player like in Skyrim

Introduction

This page will cover the creation of a basic 3rd person camera class. This is the kind of camera used in games like Super Mario 64, GTA, and Skyrim. It allows the player to see their character on screen while controlling it.

You should understand the material from Basic Tutorial 1 and Basic Tutorial 2 before attempting this camera setup. You will need to have a decent understanding of the SceneNode and Entity classes to be able to follow along. You will also have to understand how to gather input events. This is covered in Basic Tutorial 5.

The Plan

We want the camera to rotate around the player when the mouse is moved, and we want the character to walk when the WASD keys are pressed. We are going to use a collection of SceneNodes to position the camera correctly behind the character. We will do this by using the player's SceneNode as a target for the camera's SceneNode. We will also add a child to the camera node that will control the pitch of the camera.

Here is a picture of how all the SceneNodes will be related:
camera_visual.png
The Main Node, Camera Node, and Pitch Node will be part of the Camera class, and the Player Node will be the SceneNode you create for your player. We will attach the actual Ogre::Camera to the Pitch Node.

Starting the Camera Class

Add a new class to your project called Camera. Set up the header like this:

Camera.h
#ifndef CAMERA_H
#define CAMERA_H

#include <OgreCamera.h>
#include <OgreSceneNode.h>

class Camera
{
public:
  Camera();
  virtual ~Camera();

  void update(float dt);

  Ogre::SceneNode* getNode() { return node; }
  Ogre::Camera* getOgreCamera() { return cam; }

private:
  void initViewports();

  float spd, turn_spd, pitch_spd;

  Ogre::Camera* cam;
  Ogre::SceneNode* node;
  Ogre::SceneNode* cam_node;
  Ogre::SceneNode* pitch_node;

};

We have declared our three SceneNodes and given the class an Ogre::Camera member. We have also declared three floats to control the movement and rotation speeds of the camera. In your own project, you may want to bring these values in from outside the Camera class. For instance, you may want to use your player's speed value instead of a speed set by the Camera. For this example, we'll just allow the Camera to set them for simplicity.

Constructing the Camera

Let's begin by writing the Camera's constructor.

Camera::Camera
Camera::Camera()
  : spd(10),
    turn_spd(12),
    pitch_spd(4),
    node(0),
    cam_node(0),
    pitch_node(0)
{
  cam = GameManager::instance().scn_mgr->createCamera("UserCamera");
  cam->setNearClipDistance(.1);

First, we initialize the three speed values and the three SceneNodes. Then we ask the SceneManager to create an Ogre::Camera for us. In this example, we are using a GameManager singleton to get access to the SceneManager. If you don't like this approach, then you can pass your Camera class a reference to the SceneManager. We also set the near clipping distance for the camera. The values used in this example are for a scale where 1 unit = 1 meter. You may have to adjust them to fit the scale of your project.

Now we are going to set up the nodes we need to make our camera work. Continue adding to the constructor where you left off.

^ node = GameManager::instance().scn_mgr->getRootSceneNode()->createChildSceneNode();

  cam_node = node->createChildSceneNode();
  cam_node->setPosition(0, 1.8, 3);

Note: The ^ symbol will be used to indicate a code section continues from a previous one. Don't add it to your code!

We first ask the SceneManager to create a child of the root SceneNode. This will be our Main Node from the diagram. It will be the anchor for all of the other nodes. The next thing we do is create a child of the Main Node to serve as the Camera Node. This will dictate where the camera is placed in relationship to the character. The position of this node will be relative to the Main Node since we have made it a child of that node. So its position will act as an offset from the character's position. Again, the values used here are for a 1 unit = 1 meter scale. They may be too small for your project if it uses a larger scale. These values will place the camera the equivalent of 1.8 meters above the ground (about the average height of a person) and 3 meters behind the character.

^ pitch_node = cam_node->createChildSceneNode();
  pitch_node->attachObject(cam);

  initViewports();

}

The last node we need to create is the Pitch Node. It will control the camera's up and down rotation. We create this node as a child of the Camera Node. We attach the actual Ogre::Camera to this Pitch Node. Since the Pitch Node is a child, we can rotate it without messing up the rotation of our Camera Node, but it will still move along with the Camera Node.

Finally, we call a method that sets up the viewport we will use for our Ogre::Camera. The method looks like this:

void Camera::initViewports()
{
  Ogre::Viewport* vp =
    GameManager::instance().window->addViewport(cam);
  vp->setBackgroundColour(Ogre::ColourValue(0, 0, 0));

  cam->setAspectRatio(
    Ogre::Real(vp->getActualWidth()) /
    Ogre::Real(vp->getActualHeight()));
}

If you did not use a singleton, then you will also have to give this method a reference to the Ogre::RenderWindow you create in your project. The Basic Tutorials cover setting up a viewport if you are confused by any of this code.

Updating the Camera

Now we are going to build the update method.

Camera::update
Ogre::Vector3 movement(0, 0, 0);
Ogre::Vector3 direction = node->getOrientation() * Ogre::Vector3::NEGATIVE_UNIT_Z;
direction.normalise();

We set up two vectors to control the movement of the camera. We will build up the movement vector based on what input events were reported since the last update. The direction vector represents the current direction the entire setup is facing. We build this vector by getting the orientation quaternion from our Main Node and multiplying it by the direction we want to be considered default. When the system is facing directly down the negative z-axis that will be considered a rotation of zero degrees.

Finally, we normalise the direction vector so that its total length is one. This will allow us to use it to move the camera without distorting the speed we wish to use. If you want more information on the use of quaternions, then read through the Quaternion and Rotation Primer. Quaternions are confusing at first, but they are a very powerful tool and well worth learning.

The next thing we will do is disable the character's current animation. This is done through another reference in our GameManager.

GameManager::instance().player->runAnimation(false);

The runAnimation method is in a custom Player class that encapsulates our user's OgreEntity and things like animations. It simply enables/disables the current Ogre::AnimationState of our Player object. Setting up animated entities is covered in Intermediate Tutorial 1.

The next section of the update method will access a struct that was filled with any input events that occurred since the last update. The building of this struct will not be covered here. Basic Tutorial 5 covers buffered input events with Ogre.

const Input input = GameManager::instance().input_system->input;

if (input.up)
  movement += direction;
if (input.down)
  movement -= direction;

First, we get a copy of the Input struct that holds all of the input events. If there was an 'up' or 'down' event, then we simply move the character forward in the direction it was already facing or we move the character directly backwards. Usually, in many PC games these movements will be bound to the W and S keys. The advantage of using generic input events like 'up' and 'down' is that you can let the user decide what keys will generate these events - or whether they should be generated by a joystick instead of the keyboard.

Next we take care of movement to the left or right - often called strafing. To understand this, you need to remember a litle trigonometry. We are basically just adding a rotated direction vector to our movement vector. The direction vector only exists in the x-z plane (the "floor plane" in Ogre). This simplifies things a bit. We only need to perform a 2d rotation.

if (input.right)
{
  movement.x -= direction.z;
  movement.z += direction.x;
}
if (input.left)
{
  movement.x += direction.z;
  movement.z -= direction.x;
}

The 2d rotation matrix looks like this:

[cos(t) -sin(t)]
[sin(t)  cos(t)]

If we plug in a 90 degree turn to the left and right, then we get these values:

Left:
[0 -1]
[1  0]

Right:
[ 0 1]
[-1 0]

Applying these matrices to our direction vector gives us the transformations we're using.

The next thing we will do is use this new movement vector to set the mesh's animation and translate the main node of our camera system.

if (movement.x == 0 && movement.z == 0)
{
  GameManager::instance().player->setAnimation("idle");
}
else
{
  movement.normalise();;
  node->translate(dt * spd * movement);
  GameManager::instance().player->setAnimation("walk");
}

First, we check to see if there were no movement events at all. This would mean our movement vector has a length of zero. In this case, we set an idle animation for the mesh. If there is a non-zero movement vector, then we use it to translate the Main Node and we set the mesh's walk animation.

You may have noticed that our method for building the movement vector would have resulted in a longer vector when the user tried to move forwards and to the side at the same time. This is a classic problem that occurs in many games where the character can increase their speed by running at an angle. To fix this, we normalise our movement vector before applying it to the node. We also factor in the delta time since the last update and the spd value we set earlier.

We've taken care of the camera translation, now we need to set the camera's rotation. This code also relies on input events for the mouse that were captured in the same Input object. The values represent the relative movements of the cursor since the last frame in the x-y plane of the screen.

node->yaw(Ogre::Degree(dt * -input.rot_x * turn_spd));
pitch_node->pitch(Ogre::Degree(dt * -input.rot_y * pitch_spd));

These two calls set the yaw and pitch of the camera. We factor in the delta time, the respective speeds, and the relative motion of the mouse. If you want the pitch to be "inverse" like it is in many first-person shooters, then you would remove the negative sign from the y rotation value in the second call. The same could be done for the yaw call if you wanted to allow your user to decide which axes will be inverse.

The last thing we need to do is restart the animation.

GameManager::instance().player->runAnimation(true);

That completes the update method and the entire Camera class. Now we just have to integrate it into our project.

Using the Camera Class

Now we are going to add the Camera class to our project's startup code. Sometime before we start our rendering loop, we are going to create an instance of our Camera class and save it into our GameManager singleton.

GameManager::instance().cam = new Camera();

Now we need to create the Player Node. This is a little bit trickier than the camera nodes, because you will most likely build your Player instance separately from your camera, but we need to make the Player Node a child of the Main Node. The approach that this example takes is adding a createSceneNode method to our Entity class. This is a class that encapsulates our Ogre::SceneNode and Ogre::Entity. The method looks like this:

bool Entity::createSceneNode(Ogre::SceneNode* parent_node)
{
  if (!node)
  {
    node = parent_node->createChildSceneNode();
    node->attachObject(entity);

    return true;
  }
  
  return false;
}

Now we can use this method when setting up our scene to attach our Player to the camera system. We will retrieve the Main Node of the Camera class and then pass it to the Entity::createSceneNode method to hook our player's Entity into the system. The entity that is attached is the class member that holds a pointer to the Ogre::Entity.

We also have to rotate the character around by 180 degrees. By tradition, Ogre meshes are facing down the positive z-axis (looking out from the screen) and our camera is facing down the negative z-axis. But we want our mesh to have its back facing towards us for this setup. We simply rotate this new SceneNode we've created for the Player. Since it is only a child of the Main Node, its rotation will not screw up the rest of our camera system.

GameManager::instance().player->getSceneNode()->rotate(
  Ogre::Vector3::UNIT_Y, Ogre::Degree(180));

The last thing we need to do is call our Camera's update method during the game loop.

GameManager::instance().cam->update(dt);

That's it! Barring any terrible catastrophes, you should have a working 3rd person camera setup. You may want to start playing around with some of the speed variables or things like the camera near clip distance to make things work better with your own project.

Here's a picture of the camera in action:
3rdpersoncamera_visual.png

Postscript

The 3rd person camera system tutorial provides another method for creating this kind of camera system.