TorqueX/CameraManager

From TDN

Contents

Introduction

The camera manager is a singleton which allows the programmer to queue up commands for multiple cameras to simplify more complex actions. It allows the player to give a camera a series of commands without having to keep track of the time or if the first camera action has finished yet.

While the camera manager was coded for 2d cameras, it is designed to be extensible so it shouldn't be too hard to make it work for 3d cameras.

Download Link

The code for the camera manager class can be downloaded at here.

Basic Information

The camera manager keeps two types of queues for each camera it is managing. The first queue is the movement queue, which holds operations that affect the movement of the camera. The second type of queue is the effect queue, which holds operations that affect the camera that can be run while the camera is performing a movement operation.


The stock operations that the CameraManager class contains are:

Movement Operations

  • Follow an object: this allows the camera to follow an object's position (and optionally its rotation). This is different than mounting the camera to the object because the camera can be put at an offset from the object, and even while rotating the camera will maintain its perspective in relation to the object. A delay can also be added to the follow command, allowing a tiny bit for the camera to get into position.
  • Look at an object: Moves the camera to an object (at a specified offset).
  • Look at a position: Moves the camera to a specific point in 3d space.
  • Shake camera: Performs a camera shake.
  • Stop Movement: Performs one of two functions. Can be used to immediately stop the current movement operation and clear the queue or can be given a duration, which then makes it function as a queued delay operation.

Effect Operations

  • Rotate the camera: allows rotation of the camera while the camera is performing a movement operation
  • Stop Effect: Performs the same function as the stop movement operation, except stop effect functions on the effects queue instead of the movement queue.

Parameter Terminology

When invoking camera operations there are several key things to understand in regards to the terminology

Queue 
A boolean which determines if the requested operation should be put in the queue or not. If the operation being requested is not queued, that means the operation needs to happen immediately. This causes the current operation to stop, the queue to be cleared, then the requested operation to be activated.
Blocking 
This boolean determines if the operation prevents the next operation in the queue from executing. For example, follow object operations always set blocking to false, as the operation never ends and we want it to execute the next operation once it is received. On the other hand, if we are moving the camera along a series of waypoints we want to set blocking to true, so that the camera doesn't go to waypoint #2 until it gets to waypoint #1. In most cases you will want this set as true. Note: Do not use blocking to perform effects simultaneously (i.e. rotate the camera while moving). If the camera is paused it will only resume the last run operation. This is the reason for the effects queue.
Interpolation Mode 
This is the interpolation method the camera should use when non-immediately moving the camera along. This enumeration is provided by the TorqueX engine. By default, TorqueX comes with 4 modes: Linear, EaseIn, EaseOut, and EaseInOut. Linear means it interpolates the position/rotation linearly. The Ease modes slow down/speed up the movement/rotation based on how far along the camera is towards its intended destination. I believe the Ease modes are meant to make camera movement slower, however personally I do not like how the camera looks while using these modes.

Usage

The first thing you must do to use the camera manager is to create a new camera. The camera that you create must have a unique script name, as the camera manager relies on the name to give operations to the correct camera. If you do not know how to create a new camera and assign it to a view you can reference the Airplane tutorial [1]. For the examples given in this section, I am assuming the camera's name is "mainCamera".


Once the camera has been created we need to tell the camera manager to work with it. To do this we just call the AddCamera function.

  // Adds a camera by passing in the camera object
  CameraManager.Instance.AddCamera(cameraObject);
  // Adds a camera by passing in the camera's script name
  CameraManager.Instance.AddCamera("mainCamera");

Note: Just because the camera manager knows about the camera does not mean you cannot use the camera directly. However, it is advisable to mark the camera as paused so the camera manager's operations do not conflict with your direct commands.

By default the camera manager is marked as inactive, so you have to tell the camera manager to start processing operations for the cameras it is managing. This is done by just setting the Active property to true.

  // Activate the camera manager
  CameraManager.Instance.Active = true;

Note: Activating/Deactivating is different then pausing/unpausing in that pausing is done on a per camera basis, and deactivating is done on the camera manager as a whole.


Now everything is ready! Now lets send some commands to our "mainCamera" camera. Let's assume that right once the scene loads we want the camera to go to 5 waypoints in order, taking about 2 seconds to get to each, stop at the last waypoint for 3 seconds, then go to and follow our controllable character. For this example we will assume that we are storing the T2DSceneObjects into the variable (not script) names of waypoint1, waypoint2, waypoint3, waypoint4 ,waypoint5, player.

In the BeginRun() function, right after the function to load the level, add the code to retrieve all the T2DSceneObjects from the TorqueObjectDatabase. Then add the following code:

 // Move "mainCamera" to waypoint1 in the span of 2 seconds, at offset 0,0 in relation to the object, using interpolation mode of Linear,
 //     queue this operation and set it to blocking.
 CameraManager.Instance.MoveCameraTo2DObject("mainCamera", waypoint1,2,0,0,InterpolationMode.Linear,true,true);
 CameraManager.Instance.MoveCameraTo2DObject("mainCamera", waypoint2,2,0,0,InterpolationMode.Linear,true,true);
 CameraManager.Instance.MoveCameraTo2DObject("mainCamera", waypoint3,2,0,0,InterpolationMode.Linear,true,true);
 CameraManager.Instance.MoveCameraTo2DObject("mainCamera", waypoint4,2,0,0,InterpolationMode.Linear,true,true);
 CameraManager.Instance.MoveCameraTo2DObject("mainCamera", waypoint5,2,0,0,InterpolationMode.Linear,true,true);
 
 // Delay the next action by 3 seconds
 CameraManager.Instance.StopMovement("mainCamera", 3, true);

 // Go to the player object then follow it
 CameraManager.Instance.MoveCameraTo2DObject("mainCamera", player, 1, 0, 0, InterpolationMode.Linear, true, true);

 // With "mainCamera" follow the player object with a delay of 0, follow the object's rotation, go to offset 0,0 from the object, 
 //     use InterpolationMode.Linear, and queue the operation.
 CameraManager.Instance.FollowObject("mainCamera", player, 0, true, 0, 0, InterpolationMode.Linear, true);

And that's it! Now the camera manager will handle all the timings of the movement and you never need to worry about it again. Just sit back and watch the camera move on its own!

Extending the Camera Manager

Adding new operations for the camera is real easy. To show this I will go through the process of how I added the shake operation.


Recognizing the New Operation

The first action is to add an operation name to the OperationTypes enumeration (I called it just "Shake").

Next you have to think about if this operation should go into the movement queue or the effects queue. The decision usually comes down to if you want the operation to execute while the camera is moving or not. While testing I found that moving the camera and shaking didn't work too well together, so I decided to put the shake operation into the movement queue.

The CameraManager maintains two List<OperationTypes> variables to make it easy to determine if an operation is a movement or effect operation. These lists are populated in the constructor. To add the shake operation to the movement queue I added the following code after the list initialization:

_moveOperations.Add(CameraOperationTypes.Shake); 

Calling the New Operation

Now the CameraManager needs a public function to pass in the shake camera requests. All that is required for these functions is that you have the user pass in the required information and pass that information right to the AddOperation() function. The Camera.StartShake() function takes two variables, duration and magnitude. Beyond that we will also need to know the camera name for the camera we want to shake as well as if we want the shake operation to be blocking and if the shake operation should be queued or act immediately. With this in mind I created the following function:

        public void ShakeCamera(string cameraName, float duration, float magnitude, bool queue, bool blocking)
        {
            // Last variable determines if we have started the shake or not
            AddOperation(cameraName, queue, blocking, null, CameraOperationTypes.Shake, duration, magnitude, 0);
        }

Note: The AddOperation() function takes an unlimited number of floats at the end of the parameter list. Notice how the duration parameter is the first of the float parameters. If your operation blocks then it MUST have the time variable located as the first float parameter. This is because the CamerManager's ProcessTick() function uses the first parameter in the parameter list to determine if the current operation's time limit is up and to go to the next queue.

Note2: The time variable should always be in seconds, so the ProcessTick() function decreases it correctly!

That's all the code that is needed to add an operation to the queue. The reason for the 0 at the end is so we have a flag which can tell us if we already called the function to do the action in the processTick() function. This is because when shaking the camera (or even animating the camera's movement or rotation) we are calling the camera's own functions which does all the processing themselves and thus we have no need to constantly call the camera's function every tick.

Processing the New Operation

So now everything is all setup and code can add the new operation to a camera's movement queue. However, nothing will happen because we haven't created any code to process the camera's operation.

Note: At of this point if you call the ShakeCam() function and set blocking to true it will prevent any camera operation from happening, because we have no code implemented to decrease the time parameter. The only way to remove this from the queue is to call an operation that is set not to queue up. If your new operation is not leaving the queue, you should look to make sure the time parameter (first parameter in the parameter list) is decreasing correctly.

Now open up the CameraManager's ProcessTick() function. The first thing we need to decide is if we need to decrease the time parameter for this operation. It is very rare that you will not need to, for reasons given in past notes. The only operation that doesn't need its time parameter decreased (so far) is the FollowObject operation, because it doesn't have a time limit.

If your operation does use a time limit, you do not need to modify the first block of code in the for loop. If your operation does not use a time limit and does not block, then you must add a code to bypass the time check or you risk stopping the operation prematurely. The code which prevents the system from checking the time limit on the FollowObject operation is:

 if (Cameras[x].CurMovOperation.OperationType != CameraOperationTypes.FollowObject)
                    {
                        // Time/duration is the first parameter
                        if (Cameras[x].CurMovOperation.Parameters[0] < 0)
                            Cameras[x].CurMovOperation = null;
                    }

Since ShakeCamera does use a time limit (i.e. duration), we do not need to touch this part of the code. At the end of the ProcessTick() function you will see the switch statements used to do the actual processing of the operations. Since the operation I am adding is a movement operation, I need to add a new case statement in the block which switches between OperationTypes for CurMoveOperation.

case CameraOperationTypes.Shake:
                            ProcessShakeOperation(Cameras[x], dt);
                            break;

The ProcessShakeOperation() function has not been created yet, so lets add that. I am calling a new function to keep the switch statements organized and clean, but if you wanted you can add your code block right there instead of creating a new function (see the StopMove and StopEffect operation types to see me do that).

Here is the ProcessShakeOperation function I created to handle camera shakes:

 protected void ProcessShakeOperation(CameraData cam, float dt)
        {
            if (cam == null)
                return;

            T2DSceneCamera Camera = cam.Camera;
            float[] mParams = cam.CurMovOperation.Parameters;
            CameraOperation CurMovOperation = cam.CurMovOperation;

            // Check to make sure we haven't already told the camera to shake
            if (mParams[2] == 0)
            {
                mParams[2] = 1;
                Camera.StartShake(mParams[1], mParams[0] * 1000);
            }
            mParams[0] -= dt;
        }

This function accepts two parameters, the CameraData structure for the camera we are modifying as well as the difference in time since the last tick. The first thing we do is check if the last parameter (parameter #3) is 0 or 1 (remember, this is the flag we passed in to the AddOperation() function). If it is 0 that means we have not called the StartShake() function yet, so we set the flag to 1 and call the StartShake() function. Finally, we take the duration parameter (the first parameter in the parameter list) and subtract it by the time since last the last tick.

And now we have fully functioning camera shake!

Stopping the Camera Operation

There is only one step left. Allowing the pausing and immediate stopping of the camera operation. To do this we need to go into the correct stop function based on if the operation is a movement operation or an effect operation.

Since the Shake operations is a movement operation the stopping code needs to go into the StopCameraMovements() function. Inside the switch statement I added the following code:

case CameraOperationTypes.Shake:
                        cam.CurMovOperation.Parameters[2] = 0;
                        cam.Camera.StartShake(0, 0);
                        break;

The first statement resets the "have we called the function yet" flag. This means that once the camera is unpaused (if it is only paused) the next tick it will call the StartShake() function with the decreased duration. The last function actually stops the camera from shaking.

Conclusion

That's all it takes to create a new camera operation!

Updates

3/29/2008 - Updated and re-uploaded code to make the ProcessTick() function cleaner, by moving out the processing code into separate functions