The Impossible Odds Tactical Camera package provides a plugin camera system for smoothly navigating your environments in both a top-down tactical view as well as up-close action scenes.
You can expect to find the following set of features in this plugin:
- Move the camera using the keyboard or screen edge detection, or double click to move to a target position.
- Zoom in & out with a dynamic field of view to get a greater sense of scale.
- Look around with restricted tilt angles, and orbit around a focus point.
- Smooth collision detection with terrain and objects in your world.
- Restricted area-of-operation to keep the camera inside the map boundaries.
- Height-based parameters where the camera's behaviour changes based on its altitude.
- Extensive customization of behaviour through simple animation curves.
- Minimal setup and easy integration.
You can view a quick demo video of this plugin below.
This package was developed on Unity 2020 LTS. So make sure you're on this version of Unity, or newer.
Open Unity's package manager and add the following git-tracked URLs to your project:
- C# Toolkit:
- Tactical Camera:
If you want to get started quickly without being bothered with all the details, then follow these few steps:
- Add the
TacticalCamera
component to yourCamera
game object. This will also add aCharacterController
component to it. - Create a
TacticalCameraSettings
data object (Assets → Create → Impossible Odds → Tactical Camera → new Tactical Camera Settings) and assign it to theSettings
property. You can play around with the different values to make it feel/behave differently. - Finally, add the
TacticalCameraInputProvider
component to the game object and assign it to theInput Provider
property. Feel free to adjust the key bindings in a way that you see fit. - Optionally, add some bounds in which the camera is allowed to operate and assign it to the
Operational bounds
property. TheTacticalCameraBoxBounds
component restricts it to operate within the defined outline of the box.
That's it!
Note: remember to check out the tooltips in the inspector if something is not clear!
The Tactical Camera plugin has a central component that does all the heavy lifting: TacticalCamera
. It will also add a CharacterController
component on there which it uses to detect collisions as well as move smoothly over any terrain and objects.
The component requires a few additional data objects to operate correctly:
- An input provider to move and rotate in and around the environment, and
- A settings object that defines how it operates/behaves, e.g. movement and rotation speed, tilt angles, etc.
Optionally, the camera can also be equipped with a component that restricts its area of operation so that it can't move outside of your game world.
The input provider for the Tactical Camera instructs where the camera should move to and where to look at. Input is very project-dependent and there is no "one size fits all"-solution, e.g. mouse & keyboard versus gamepad versus touch screen, etc. That's why this camera is designed to be driven using an interface-component rather than forcing a single solution on you. However, a sample implementation of an input provider is given by the TacticalCameraInputProvider
component. It's designed for use with mouse and keyboard and allows you to assign which keys and mouse axes should be used to control the movement and rotation.
Note: remember to check out the tooltips in the inspector if something is not clear!
The ITacticalCameraInputProvider
interface is the key to hooking the Tactical Camera system in your custom input code. Either have your input object implement this interface as well to pass it along to the Tactical Camera directly, or provide an abstraction layer for this interface in your input code.
The interface expects you to implement the following features:
- Moving forward, sideways and upwards through the
MoveForward
,MoveSideways
andMoveUp
properties. These should return a value in the range of [-1, 1]. - Moving to a target position through the
MoveToTarget
andCancelMoveToTarget
properties. These should returntrue
when the action is requested, i.e. when a button is pressed down that frame. - Rotating around its pivot or focus point through the
TiltDelta
andRotationDelta
properties. TheOrbitAroundTarget
is the switch between rotating around the pivot or focus point. As long as this last one keeps returningtrue
, the Tactical Camera will orbit around the focal point rather than rotate around its pivot point.
Note: the ITacticalCameraInputProvider
interface in itself does not define which particular input method is used, e.g. moving using the keyboard versus the mouse cursor against the edge of the screen. The implementing class should define which methods are supported by way of returning appropriate values.
The Tactical Camera's behaviour is driven by a data object that tells it how fast it can move and rotate, as well as how smooth it comes to a standstill and how its movement is affected by altitude. The TacticalCameraSettings
is the ScriptableObject that holds this data. It can be created through the Unity create menu: Assets → Create → Impossible Odds → Tactical Camera → new Tactical Camera Settings.
You can adjust the values in this object to suit the environment the camera will operate in, but sensible default values have already been set for a small-to-medium sized map. This object operates using value ranges and animation curves. The choice for using animation curves allows to visually define how a specific value should fade out, or how a value should transition from one end to the other, e.g. the movement speed versus the altitude of the camera.
The settings object also allows to set some world-interaction values which might be of particular interest for performance reasons:
- The World interaction mask (
InteractionMask
) property defines which collision layers are interacted with during raycast operations. This is best set to only layers that are of interest to the camera to interact with, i.e. your terrain and some world-objects. - The Max ray casting distance (
InteractionDistance
) property defines the maximum length of raycasts used for interacting with your world. The larger this value, the further it has to check. Reduce this to a value that makes sense for your world setup. - The Collision radius (
InteractionBubbleRadius
) defines the sphere in which the camera collides with objects in your world. This adjusts theradius
property on the attachedCharacterController
component.
Note: remember to check out the tooltips in the inspector if something is not clear!
Compared to the input provision, this implementation of settings is immediately usable in a broad set of projects. Nonetheless, if you want to further alter or customize the behaviour, the ITacticalCameraSettings
interface is what you should look into.
This interface will ask you to implement the following:
- The height limits in which the Tactical Camera is allowed to operate in through the
AbsoluteHeightRange
property. This is a range in which the camera will always be restricted to, even if there are operational bounds assigned with higher ceiling or lower floor values defined. - Movement behaviour through
MovementSpeedRange
,MovementFadeTime
andMoveToTargetSmoothingTime
. - Rotational behaviour through
MaxRotationalSpeed
,RotationalFadeTime
,TiltRangeLow
andTiltRangeHigh
. These last two define what the angle ranges are for the Tactical Camera when its at its lowest and highest point, respectively. - Field of view settings through
UseDynamicFieldOfView
andDynamicFieldOfViewRange
. - World interation settings through
InteractionMask
,InteractionDistance
andInteractionBubbleRadius
. - Several evaluation methods for custom fade-outs and transition curves.
Most of these properties are self-explanatory, but the purpose of the evaluation methods might not be immediately clear. Each of these methods take a float
value as input in the range of [0, 1]. The output of these methods can be exactly the same as their input value in case a linear transition is desirable, but it can also be transformed to create a more pleasant looking/feeling result, e.g. a smooth roll-off in case of fading, or an ease-in, ease-out in case of transitions. In the implementation of TacticalCameraSettings
, this is done by evaluating animation curves which naturally lend themselves to this.
EvaluateMovementFadeOut
andEvaluateRotationFadeOut
are fading methods that determine how the movement and rotation of the camera fade out when no more input is given.EvaluateMovementTransition
,EvaluateTiltTransition
andEvaluateFieldOfViewTransition
methods determine how the camera reacts to transitions in altitude. When the camera moves around, several values or value ranges may change in response to that, e.g. when higher up, the faster the camera can move around.
In most situations it's desirable to restrict the camera to a specific area so that it can't wander off in areas not meant for the player to visit. The Tactical Camera can be equipped with some bounds that force it to be in a particular area.
How such an area is defined is very project-dependent and could become a complex matter. For example, polygonal level bounds in case the map is not just a square, or a fog-of-war restriction where undiscovered pieces of the map may not be accessible yet.
To give a basic form of restriction already, the TacticalCameraBoxBounds
component can restrict the Tactical Camera's position to a simple box. Whenever it tries to leave the area, its position is reset to the nearest valid location inside the box. The box bounds can be made to align and scale with the game object it is put on by ticking the Follow Game Object toggle.
The TacticalCameraCompositeBounds
component can stitch together multiple bounds objects and make them act like a single one. You can include custom bounds implementations to the list as well!
To have the Tactical Camera be restricted to more complex areas, have your restriction tool implement the ITacticalCameraBounds
interface with a couple of straightforward calls:
void Apply(TacticalCamera)
should check on the camera's current position and place it back within the bounds if the camera is currently outside of them.Vector3 Apply(Vector3)
should check on the provided position and either return the same value when aleady within the bounds, or the closest point that lies within.bool IsWithinBounds(Vector3)
should simply check whether the provided position is within the bounds.
The external resources the Tactical Camera system requires to operate can all be assigned through Unity's inspector view or through your own scripts. Additionally, you can also have them delivered by the Dependency Injection framework from the Impossible Odds C# Toolkit. It allows you to inject resources and values into objects that require them to operate, which makes managing resources in your project easier.
The TacticalCamera
component's properties for the input provider, settings and bounds are all marked for injection in case you prefer to work using this system as well.
When using the Tactical Camera system you might run into a few limits of the system as well. You'll find them listed here:
- The
z
-value of the local Euler rotation angle is always set to 0 at the end of itsLateUpdate
phase. This is to prevent drift and keeps the camera straight up. - The operating range of the tilt angle in the
TacticalCameraSettings
objects can be no larger than [-90, 90] degrees, with 0 degrees being level with the horizon. This range is defined to prevent flipping over. If you make a custom implementation throughITacticalCameraSettings
, best to keep this in mind.
Check out the demo scene for a complete setup and demonstration of how the camera behaves in different circumstances. This scene is setup using the dependency injection method, i.e. the resources the camera needs are injected upon Start from the TacticalCameraDemo
component.
Developed and tested on Unity 2020.3 LTS.
This package is provided under the MIT license.