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.

<HR>
Creative Commons Copyright -- Some rights reserved.


THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.

BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.

1. Definitions

  • "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License.
  • "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
  • "Licensor" means the individual or entity that offers the Work under the terms of this License.
  • "Original Author" means the individual or entity who created the Work.
  • "Work" means the copyrightable work of authorship offered under the terms of this License.
  • "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
  • "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.

2. Fair Use Rights

Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.

3. License Grant

Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:

  • to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works;
  • to create and reproduce Derivative Works;
  • to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works;
  • to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works.
  • For the avoidance of doubt, where the work is a musical composition:
    • Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work.
    • Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights society or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions).
    • Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions).


The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved.

4. Restrictions

The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:

  • You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(c), as requested.
  • You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License.
  • If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit.

5. Representations, Warranties and Disclaimer

UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.

6. Limitation on Liability.

EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

7. Termination

  • This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
  • Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.

8. Miscellaneous

  • Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
  • Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
  • If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
  • No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
  • This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.