Skip to main content

In search of the perfect RTS Camera

In search of the perfect RTS Camera

This blog entry is going to be pretty technical, and possibly over-specific. For various reasons I have been working on an RTS/TBS/God Mode style camera (the sort one uses in things like Sim City, Warcraft 2, or Command and Conquer) and I realized that none of the various tutorials or pre-made examples for UE4 were quite right. So I built a new one, and I thought I'd share how I did it so others in the same spot can benefit.

The code, screenshots, and steps here are going to be specific to Unreal Engine 4. The general methods and concepts can be adapted to several other engines. After all, I adapted methods and concepts from 30 years of games to UE4, right?


  • First off: What exactly is a God Mode or RTS Camera?

To put it simply, this is a camera view where you see the map from a high angle, as opposed to first person/shoulder height, or third person/right behind the character. There is generally no character focal point, though sometimes there is a cursor, pointer, or other indicator. It is often used in building games or strategy games. The camera can be moved (often very quickly) around the play field.


  • Ok, so what's wrong with what's out there?

Here's where it comes down to personal taste and preference. There are plenty of tutorials and examples on the marketplace, youtube, you name it. Most of them have one of a few major shortcomings for me. Firstly, I like to be able to move with gamepad, WASD/Arrow keys, or mouse clicks for big jumps. I find that just to be faster, since it's often the case that you want to cover very long distances very rapidly. The camera also needs to be able to zoom in and out and rotate, all without leaving the bounds of the map.


  • For your consideration, my solution.

Here's what I did. In UE4, start from the basic third-person C++ template. I will not be including that code, which also allows me to skirt those touchy bits of the UE4 license. Then we will add several pieces. Let's walk through them.


  • Step by step:
  • Create a new third-person mode game in C++ using the UE4 template.
  • Open the (yourgame)Character.h file. Remember to replace (yourgame)Character with the appropriate class name.

In the "public" section (so that we can easily access it to change it from other code or blueprints) add these UPROPERTY values which will determine the characteristics of your zoom functionality:


/** CameraBoom max length */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (Tooltip = "Furthest camera can get from board"))
    float BoomMaxLength = 2000.0f;
/** CameraBoom min length */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (Tooltip = "Closest camera can get to the board"))
    float BoomMinLength = 500.0f;
/** CameraBoom movement per tick */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (Tooltip = "Distance to move camera on mouse wheel"))
    float BoomLengthScrollDelta = 50.0f;

In the "protected" section, add these internal camera controls.


/** Called for camera rotation */
void RotateCamera(float Value);
/** Called for camera zoom */
void ZoomIn();
/** Called for camera zoom */
void ZoomOut();
  • Save and then open the (yourgame)Character.cpp file. Here we'll add the code to make that new functionality work.

Include the header for math:

#include "Math/UnrealMathUtility.h"

Then update the CameraBoom->TargetArmLength appropriately for your settings (this may be called something different in your version of the template):

CameraBoom->TargetArmLength = 800.0f;

Add the playerinputcomponent bindings for zoom and rotate to match what you set in the project settings input config:


PlayerInputComponent->BindAxis("RotateCameraRight", this, &(yourgame)Character::RotateCamera);
PlayerInputComponent->BindAction("ZoomIn", IE_Pressed, this, &(yourgame)Character::ZoomIn);
PlayerInputComponent->BindAction("ZoomOut", IE_Pressed, this, &(yourgame)Character::ZoomOut);

Of course there's only one Axis binding, because it has a range of -1 to 1, but two scroll bindings because they are Actions rather than Axis.

Now we are going to make them work properly by adding the code we just defined.


void (yourgame)Character::RotateCamera(float Value)
{
    // Rotate Right or Left. Adjust sensitivity by preference.
    if (abs(Value) > 0.75f)
    {
        CameraBoom->AddWorldRotation(FRotator(0.f, Value, 0.f));
    }
}

void (yourgame)Character::ZoomIn() {     // Retract CameraBoom     CameraBoom->TargetArmLength = FMath::Clamp(CameraBoom->TargetArmLength - BoomLengthScrollDelta, BoomMinLength, BoomMaxLength); }
void (yourgame)Character::ZoomOut() {     // Extend CameraBoom     CameraBoom->TargetArmLength = FMath::Clamp(CameraBoom->TargetArmLength + BoomLengthScrollDelta, BoomMinLength, BoomMaxLength); }

Now save, build, and then add these input methods to your project settings. RotateCameraRight under Axis, and ZoomIn/ZoomOut under Actions. Recompile, and restart your game.

When you go to playtest, you'll see that while zoom works, and rotate moves the camera, rotating causes the movement to be based on your original rotation. That's because by default, the rotation is relative to the Controller, not the Camera.

Let's go fix that.

  • Re-open (yourgame)Character.cpp and jump to MoveForward

Comment or replace

const FRotator Rotation = Controller->GetRelativeRotation();

with:


// Set motion relative to the camera at all times
const FRotator Rotation = CameraBoom->GetRelativeRotation();

Then jump to MoveRight and repeat the same step. Save, build, recompile, and relaunch.

  • That's it for the camera controls! Time to move on.

Now that handles the camera work, but what about jumping around to the point on the map based on click?

That's actually really pretty simple. Just for fun, let's switch to Blueprint for it. This time, it will go in controller blueprint.

  • Create a Blueprint controller from the default CPP controller, and save it to a new folder in your project.
  • Set this as the controller to use in your GameMode.
  • Add a new input method "CursorSelect", binding it to any mouse key you like. Left mouse button, for example.
  • Open the new Blueprint controller, and add CursorSelect input handler, and let's get the hit result of that click.
Get hit result
  • Now we'll pull the class of the actor we hit, and make sure we're clicking on the landscape (or whatever your playing field is)
Verify that we're hitting the right thing
  • Grab the hit location vector, establish your baseline (so that the camera doesn't bounce unexpectedly) and confirm you're inside your safe zone for the map.
Confirm the hit location is valid
  • Finally, set actor location - I find sweep and teleport both have some minor issues, so work with them to find what works best for you.
Move to the new coordinates if they're valid

Obviously, you could do this same thing in C++ as well, using FHitResult. GetHitResultUnderCursor(ECC_Visibility, false, Hit); and apply your logic there instead.


Hope this was at all helpful.

See you next time!