RTS Camera C++

Hi,

this mini tutorial is the extension/rework of the RTS camera code you can find at the Wiki. It contains hopefully all the usual camera and keyboard movement and rotation features you may need.


After you created a new C++ project (call it RTS or whatever), or if you have an existing one, first set up inputs in the editor as follows:

For mouse wheel:

https://i.postimg.cc/44wKMb3y/RTSAction-Mappings.jpg

For keyboard:

https://i.postimg.cc/ZYjccNtF/RTSAxis-Mappings.jpg

Then you need to add some new classes: a Player Controller, and a Spectator Pawn, let’s call them RTSPlayerController and RTSPlayerCameraSpectatorPawn respectively. The player controller will be used only for handling RTS unit selection and orders by mouse clicks (not part of this tutorial), and the spectator pawn will contain a camera and handle its movement. This way you can easily replace your default camera with something else during development e.g. a free camera, or the player can select from different camera styles…

Next, in the Game Mode (in case of a new project called RTS its name is RTSGameMode and automatically created), add a constructor to set your default player controller and pawn:

.h


#pragma once

#include "GameFramework/GameMode.h"
#include "RTSGameMode.generated.h"

/**
 *
 */
UCLASS()
class RTS_API ARTSGameMode : public AGameMode
{
    GENERATED_BODY()

        ARTSGameMode();

};

.cpp


#include "RTS.h"

#include "RTSGameMode.h"

#include "RTSPlayer/RTSPlayerController.h"
#include "RTSPlayer/RTSPlayerCameraSpectatorPawn.h"

ARTSGameMode::ARTSGameMode()
{
    // C++ classes
    PlayerControllerClass = ARTSPlayerController::StaticClass();
    DefaultPawnClass = ARTSPlayerCameraSpectatorPawn::StaticClass();
}

Jump to the Player Controller to set some basic things, most importantly to have a visible mouse pointer:

.h


#pragma once

#include "GameFramework/PlayerController.h"
#include "RTSPlayerController.generated.h"

/**
 *
 */
UCLASS()
class RTS_API ARTSPlayerController : public APlayerController
{
    GENERATED_BODY()

        ARTSPlayerController();

};

.cpp


#include "RTS.h"
#include "RTSPlayerController.h"


ARTSPlayerController::ARTSPlayerController()
{
    bShowMouseCursor = true;
    bEnableClickEvents = true;
    bEnableTouchEvents = true;
}

Now we are ready to set up our camera handling spectator pawn. As you can see mouse wheel and keyboard handling is separated, because keyboard inputs always have to deal with the delta time of the actual tick to set movement amount according to frame rate. It can be Blueprinted where you can set its default parameters from the editor. Please read through the comments above the variables and functions to have an overview what happens in the .cpp.

.h




#pragma once

#include "GameFramework/SpectatorPawn.h"
#include "RTSPlayerCameraSpectatorPawn.generated.h"

/**
 * this is the default RTS camera handling movememnt, rotation, and zoom
 */
UCLASS()
class RTS_API ARTSPlayerCameraSpectatorPawn : public ASpectatorPawn
{
    GENERATED_BODY()

public:

    //------------------------------------

    /** Constructor */
    ARTSPlayerCameraSpectatorPawn(const FObjectInitializer& ObjectInitializer);

    //------------------------------------

    /** Camera Component */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    class UCameraComponent* CameraComponent;

    //------------------------------------

    //** Camera XY limit */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraXYLimit;

    //** Camera height over terrain */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraHeight;

    //** Camera min height over terrain */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraHeightMin;

    //** Camera max height over terrain */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraHeightMax;

    /** Camera Rotation around Axis Z */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraZAnlge;

    /** Camera Height Angle */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraHeightAngle;

    /** Camera Pitch Angle Max */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraHeightAngleMax;

    /** Camera Pitch Angle Min */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraHeightAngleMin;

    /** Camera Radius (Distance) From Pawn Position */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraRadius;

    /** Camera Radius Max */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraRadiusMax;

    /** Camera Radius Min */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraRadiusMin;

    /** Camera Zoom Speed */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraZoomSpeed;

    /** Camera Rotation Speed */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraRotationSpeed;

    /** Camera Movement Speed */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraMovementSpeed;

    /** Camera Scroll Boundary */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    float CameraScrollBoundary;

    /** Should the camera move? */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
    bool bCanMoveCamera;

    //------------------------------------

private:

    /** Sets up player inputs
    *    @param InputComponent - Input Component
    */
    void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent);

    //------------------------------------

public:

    /** Zooms In The Camera */
    void ZoomInByWheel();

    /** Zooms Out The Camera */
    void ZoomOutByWheel();

    /** Rotate The Camera Left */
    void RotateLeftByWheel();

    /** Rotate The Camera Right */
    void RotateRightByWheel();

    /** Rotate The Camera Up */                                
    void RotateUpByWheel();

    /** Rotate The Camera Down */                            
    void RotateDownByWheel();

    //---

    /** Calculates the new Location and Rotation of The Camera */
    void RepositionCamera();

    //------------------------------------

private:

    // set them to +/-1 to get player input from keyboard
    float FastMoveValue;                                            // movement speed multiplier : 1 if shift unpressed, 2 is pressed
    float RotateValue;                                                // turn instead of move camera

    float MoveForwardValue;
    float MoveRightValue;
    float MoveUpValue;
    float ZoomInValue;

    //---

public:

    /** Left or Right Shift is pressed
    * @param direcation - (1.0 for Right, -1.0 for Left)
    */
    void FastMoveInput(float Direction);

    /** Left or Right Ctrl is pressed
    * @param direcation - (1.0 for Right, -1.0 for Left)
    */
    void RotateInput(float Direction);                                

    /** Input recieved to move the camera forward
    * @param direcation - (1.0 for forward, -1.0 for backward)
    */
    void MoveCameraForwardInput(float Direction);

    /** Input recieved to move the camera right
    * @param direcation - (1.0 for right, -1.0 for left)
    */
    void MoveCameraRightInput(float Direction);

    /** Input recieved to move the camera right
    * @param direcation - (1.0 for right, -1.0 for left)
    */
    void MoveCameraUpInput(float Direction);

    /** Input recieved to move the camera right
    * @param direcation - (1.0 for right, -1.0 for left)
    */
    void ZoomCameraInInput(float Direction);

    //---

private:

    /** Moves the camera forward
    * @param direcation - (+ forward, - backward)
    */
    FVector MoveCameraForward(float Direction);

    /** Moves the camera right
    * @param direcation - (+ right, - left)
    */
    FVector MoveCameraRight(float Direction);

    /** Gets the roatation of the camera with only the yaw value
    * @return - returns a rotator that is (0, yaw, 0) of the Camera
    */
    FRotator GetIsolatedCameraYaw();

    //---

    /** Moves the camera up/down
    * @param direcation - (+ up, - down)
    */
    float MoveCameraUp(float Direction);

    //---

    /** Zooms the camera in/out
    * @param direcation - (+ in, - out)
    */
    void ZoomCameraIn(float Direction);

    /** Turns the camera up/down
    * @param direcation - (+ up, - down)
    */
    void TurnCameraUp(float Direction);

    /** Turns the camera right/left
    * @param direcation - (+ right, - left)
    */
    void TurnCameraRight(float Direction);

    //------------------------------------

public:

    /** Tick Function, handles keyboard inputs */
    virtual void Tick(float DeltaSeconds) override;

    //------------------------------------

    // detect landscape and terrain static-mesh
    // usage: RTS Obstacle and RTS Building placement onto landscape, terrain static-mesh
    float    GetLandTerrainSurfaceAtCoord(float XCoord, float YCoord) const;

    //------------------------------------
};


Here comes the heavy part.

  • In the constructor we set the default values of the public parameters, and add a camera component.
  • Then user inputs are assigned to functions.
  • Mouse wheel actions are called directly.
  • Keyboard inputs first set a corresponding value parameter storing their direction. This way it is easy to handle different key combinations.
  • After the input functions you can find the executive functions resonsible for camera movement and rotation.
  • All of them are handled by the Tick function, where first a screen edge region mouse position is checked, which can result in a movement input similarly to pressing a button, then it calles the executive functions with frame rate corrected parameters.
  • Reworked usage of RepositionCamera() resulting in smoother movement
  • Ray tracing of landscape surface using channel ECC_WorldStatic to disable moving under ground.

.cpp



#include "RTS.h"
#include "RTSPlayerCameraSpectatorPawn.h"

//////////////////////////////////////////////////////////////////


ARTSPlayerCameraSpectatorPawn::ARTSPlayerCameraSpectatorPawn(const FObjectInitializer& ObjectInitializer)
{
    // enable Tick function
    PrimaryActorTick.bCanEverTick = true;

    // disable standard WASD movement
    bAddDefaultMovementBindings = false;

    // not needed Pitch Yaw Roll
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;        
    bUseControllerRotationRoll = false;

    // collision
    GetCollisionComponent()->bGenerateOverlapEvents = false;
    GetCollisionComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    GetCollisionComponent()->SetCollisionProfileName(TEXT("NoCollision"));
    GetCollisionComponent()->SetSimulatePhysics(false);

    // set defaults

    CameraXYLimit = 25000.f;
    CameraHeight = 1000.f;                    
    CameraHeightMin = 300.f;                // 100 for debugging
    CameraHeightMax = 5000.f;

    CameraRadius = 2000.f;                    
    CameraRadiusMin = 1000.f;                // 100 for debugging
    CameraRadiusMax = 8000.f;                

    CameraZAnlge = 0.f;                        // yaw

    CameraHeightAngle = 30.f;                // pitch
    CameraHeightAngleMin = 15.f;
    CameraHeightAngleMax = 60.f;

    CameraZoomSpeed = 200.f;                // wheel
    CameraRotationSpeed = 10.f;                // wheel + ctrl
    CameraMovementSpeed = 3000.f;            // in all directions

    CameraScrollBoundary = 25.f;            // screen edge width

    bCanMoveCamera = true;

    // intialize the camera
    CameraComponent = ObjectInitializer.CreateDefaultSubobject<UCameraComponent>(this, TEXT("RTS Camera"));
    CameraComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
    CameraComponent->bUsePawnControlRotation = false;

    RepositionCamera();
}


//////////////////////////////////////////////////////////////////


void ARTSPlayerCameraSpectatorPawn::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
    if (!PlayerInputComponent) return;

    Super::SetupPlayerInputComponent(PlayerInputComponent);

    // action mappings

    // mouse zoom
    PlayerInputComponent->BindAction("ZoomOutByWheel", IE_Pressed, this, &ARTSPlayerCameraSpectatorPawn::ZoomOutByWheel);
    PlayerInputComponent->BindAction("ZoomInByWheel", IE_Pressed, this, &ARTSPlayerCameraSpectatorPawn::ZoomInByWheel);

    // mouse rotate (+Ctrl or +Alt)
    // unnecessary...
    //PlayerInputComponent->BindAction("RotateLeftByWheel", IE_Pressed, this, &ARTSPlayerCameraSpectatorPawn::RotateLeftByWheel);
    //PlayerInputComponent->BindAction("RotateRightByWheel", IE_Pressed, this, &ARTSPlayerCameraSpectatorPawn::RotateRightByWheel);
    // needed...
    PlayerInputComponent->BindAction("RotateUpByWheel", IE_Pressed, this, &ARTSPlayerCameraSpectatorPawn::RotateUpByWheel);
    PlayerInputComponent->BindAction("RotateDownByWheel", IE_Pressed, this, &ARTSPlayerCameraSpectatorPawn::RotateDownByWheel);

    // axis mappings

    // keyboard move (WASD, Home/End)
    PlayerInputComponent->BindAxis("MoveForward", this, &ARTSPlayerCameraSpectatorPawn::MoveCameraForwardInput);
    PlayerInputComponent->BindAxis("MoveRight", this, &ARTSPlayerCameraSpectatorPawn::MoveCameraRightInput);
    PlayerInputComponent->BindAxis("MoveUp", this, &ARTSPlayerCameraSpectatorPawn::MoveCameraUpInput);
    PlayerInputComponent->BindAxis("ZoomIn", this, &ARTSPlayerCameraSpectatorPawn::ZoomCameraInInput);

    // double speed (WASD +Shift)
    PlayerInputComponent->BindAxis("FastMove", this, &ARTSPlayerCameraSpectatorPawn::FastMoveInput);
    // yaw and pitch (WASD +Ctrl)
    PlayerInputComponent->BindAxis("Rotate", this, &ARTSPlayerCameraSpectatorPawn::RotateInput);
}


//////////////////////////////////////////////////////////////////


void ARTSPlayerCameraSpectatorPawn::ZoomInByWheel()
{
    if (!bCanMoveCamera)    return;

    CameraRadius -= CameraZoomSpeed * FastMoveValue;
    CameraRadius = FMath::Clamp(CameraRadius, CameraRadiusMin, CameraRadiusMax);

    //RepositionCamera();
}


void ARTSPlayerCameraSpectatorPawn::ZoomOutByWheel()
{
    if (!bCanMoveCamera)    return;

    CameraRadius += CameraZoomSpeed * FastMoveValue;
    CameraRadius = FMath::Clamp(CameraRadius, CameraRadiusMin, CameraRadiusMax);

    //RepositionCamera();
}


void ARTSPlayerCameraSpectatorPawn::RotateLeftByWheel()
{
    if (!bCanMoveCamera)    return;

    CameraZAnlge -= CameraRotationSpeed * FastMoveValue;

    //RepositionCamera();
}


void ARTSPlayerCameraSpectatorPawn::RotateRightByWheel()
{
    if (!bCanMoveCamera)    return;

    CameraZAnlge += CameraRotationSpeed * FastMoveValue;

    //RepositionCamera();
}


void ARTSPlayerCameraSpectatorPawn::RotateUpByWheel()
{
    if (!bCanMoveCamera)    return;

    CameraHeightAngle += CameraRotationSpeed * FastMoveValue;
    CameraHeightAngle = FMath::Clamp(CameraHeightAngle, CameraHeightAngleMin, CameraHeightAngleMax);

    //RepositionCamera();
}


void ARTSPlayerCameraSpectatorPawn::RotateDownByWheel()
{
    if (!bCanMoveCamera)    return;

    CameraHeightAngle -= CameraRotationSpeed * FastMoveValue;
    CameraHeightAngle = FMath::Clamp(CameraHeightAngle, CameraHeightAngleMin, CameraHeightAngleMax);

    //RepositionCamera();
}

//---------------

void ARTSPlayerCameraSpectatorPawn::RepositionCamera()
{
    FVector NewLocation(0.f, 0.f, 0.f);
    FRotator NewRotation(0.f, 0.f, 0.f);

    float sinCameraZAngle = FMath::Sin(FMath::DegreesToRadians(CameraZAnlge));
    float cosCameraZAngle = FMath::Cos(FMath::DegreesToRadians(CameraZAnlge));

    float sinCameraHeightAngle = FMath::Sin(FMath::DegreesToRadians(CameraHeightAngle));
    float cosCameraHeightAngle = FMath::Cos(FMath::DegreesToRadians(CameraHeightAngle));

    NewLocation.X = cosCameraZAngle * cosCameraHeightAngle * CameraRadius;
    NewLocation.Y = sinCameraZAngle * cosCameraHeightAngle * CameraRadius;
    NewLocation.Z = sinCameraHeightAngle * CameraRadius;

    // do not allow camera component to go under ground - not enough alone, actor also needed to be limited
    float TerrainSurfaceZ = GetLandTerrainSurfaceAtCoord(GetActorLocation().X + NewLocation.X, GetActorLocation().Y + NewLocation.Y);
    if (GetActorLocation().Z + NewLocation.Z < TerrainSurfaceZ + CameraHeight)
    {
        //FVector NewLocation = CameraComponent->GetComponentLocation();
        NewLocation.Z = TerrainSurfaceZ - GetActorLocation().Z + CameraHeight;
    }

    // new camera location
    CameraComponent->SetRelativeLocation(NewLocation);

    // new camera rotation
    NewRotation = (FVector(0.0f, 0.0f, 0.0f) - NewLocation).Rotation();
    CameraComponent->SetRelativeRotation(NewRotation);
}


//////////////////////////////////////////////////////////////////


void ARTSPlayerCameraSpectatorPawn::FastMoveInput(float Direction)
{
    if (!bCanMoveCamera)    return;

    // left or right does not matter, to set double speed in any direction
    FastMoveValue = FMath::Abs(Direction) * 2.0f;

    // used as multiplier so must be 1 if not pressed
    if (FastMoveValue == 0.0f)
    {
        FastMoveValue = 1.0f;
    }
}


void ARTSPlayerCameraSpectatorPawn::RotateInput(float Direction)
{
    if (!bCanMoveCamera)    return;

    // left or right does not matter
    RotateValue = FMath::Abs(Direction);
}


void ARTSPlayerCameraSpectatorPawn::MoveCameraForwardInput(float Direction)
{
    if (!bCanMoveCamera)    return;

    MoveForwardValue = Direction;
}


void ARTSPlayerCameraSpectatorPawn::MoveCameraRightInput(float Direction)
{
    if (!bCanMoveCamera)    return;

    MoveRightValue = Direction;
}


void ARTSPlayerCameraSpectatorPawn::MoveCameraUpInput(float Direction)
{
    if (!bCanMoveCamera)    return;

    MoveUpValue = Direction;
}


void ARTSPlayerCameraSpectatorPawn::ZoomCameraInInput(float Direction)
{
    if (!bCanMoveCamera)    return;

    ZoomInValue = Direction;
}


//------------------------------------------------------------


FVector ARTSPlayerCameraSpectatorPawn::MoveCameraForward(float Direction)
{
    float MovementValue = Direction * CameraMovementSpeed;
    FVector DeltaMovement = MovementValue * GetIsolatedCameraYaw().Vector();
    //FVector NewLocation = GetActorLocation() + DeltaMovement;
    //SetActorLocation(NewLocation);
    return DeltaMovement;
}


FVector ARTSPlayerCameraSpectatorPawn::MoveCameraRight(float Direction)
{
    float MovementValue = Direction * CameraMovementSpeed;
    FVector DeltaMovement = MovementValue * (FRotator(0.0f, 90.0f, 0.0f) + GetIsolatedCameraYaw()).Vector();
    //FVector NewLocation = GetActorLocation() + DeltaMovement;
    //SetActorLocation(NewLocation);
    return DeltaMovement;
}


FRotator ARTSPlayerCameraSpectatorPawn::GetIsolatedCameraYaw()
{
    // FRotator containing Yaw only
    return FRotator(0.0f, CameraComponent->ComponentToWorld.Rotator().Yaw, 0.0f);
}

//---------------

float ARTSPlayerCameraSpectatorPawn::MoveCameraUp(float Direction)
{
    float MovementValue = Direction * CameraMovementSpeed;
    //FVector DeltaMovement = FVector(0.0f, 0.0f, MovementValue);
    //FVector NewLocation = GetActorLocation() + DeltaMovement;
    //NewLocation.Z = FMath::Clamp(NewLocation.Z, CameraRadiusMin, CameraRadiusMax);
    //SetActorLocation(NewLocation);
    return MovementValue;
}

//---------------

void ARTSPlayerCameraSpectatorPawn::ZoomCameraIn(float Direction)
{
    float MovementValue = Direction * CameraMovementSpeed;                
    CameraRadius += MovementValue;
    CameraRadius = FMath::Clamp(CameraRadius, CameraRadiusMin, CameraRadiusMax);

    //RepositionCamera();
}


void ARTSPlayerCameraSpectatorPawn::TurnCameraUp(float Direction)
{
    CameraHeightAngle -= Direction * CameraRotationSpeed * 10.0f;        
    CameraHeightAngle = FMath::Clamp(CameraHeightAngle, CameraHeightAngleMin, CameraHeightAngleMax);

    //RepositionCamera();
}


void ARTSPlayerCameraSpectatorPawn::TurnCameraRight(float Direction)
{
    CameraZAnlge += Direction * CameraRotationSpeed * 10.0f;            

    //RepositionCamera();
}


//////////////////////////////////////////////////////////////////


void ARTSPlayerCameraSpectatorPawn::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    // mouse position and screen size
    FVector2D MousePosition;
    FVector2D ViewportSize;

    UGameViewportClient* GameViewport = GEngine->GameViewport;

    // it is always nullptr on dedicated server
    if (!GameViewport) return;
    GameViewport->GetViewportSize(ViewportSize);

    // if viewport is focused, contains the mouse, and camera movement is allowed
    if (GameViewport->IsFocused(GameViewport->Viewport)
        && GameViewport->GetMousePosition(MousePosition) && bCanMoveCamera)
    {
        //-------------------
        // movement direction by mouse at screen edge

        if (MousePosition.X < CameraScrollBoundary)
        {
            MoveRightValue = -1.0f;
        }
        else if (ViewportSize.X - MousePosition.X < CameraScrollBoundary)
        {
            MoveRightValue = 1.0f;
        }

        if (MousePosition.Y < CameraScrollBoundary)
        {
            MoveForwardValue = 1.0f;
        }
        else if (ViewportSize.Y - MousePosition.Y < CameraScrollBoundary)
        {
            MoveForwardValue = -1.0f;
        }

        //-------------------
        // tweak camera actor position

        FVector ActualLocation = GetActorLocation();
        FVector ActualMovement = FVector::ZeroVector;        

        // horizontal movement
        if (RotateValue == 0.f)
        {
            ActualMovement += MoveCameraForward(MoveForwardValue * FastMoveValue * DeltaSeconds);
            ActualMovement += MoveCameraRight(MoveRightValue * FastMoveValue * DeltaSeconds);
        }
        ActualLocation += ActualMovement;

        // vertical movement
        CameraHeight += MoveCameraUp(MoveUpValue * FastMoveValue * DeltaSeconds);
        CameraHeight = FMath::Clamp(CameraHeight, CameraHeightMin, CameraHeightMax);

        // adjust actor height to surface
        float TerrainSurfaceZ = GetLandTerrainSurfaceAtCoord(ActualLocation.X, ActualLocation.Y);
        ActualLocation.Z = TerrainSurfaceZ + CameraHeight;

        // limit movement area
        ActualLocation.X = FMath::Clamp(ActualLocation.X, -CameraXYLimit, CameraXYLimit);
        ActualLocation.Y = FMath::Clamp(ActualLocation.Y, -CameraXYLimit, CameraXYLimit);

        // move actor
        SetActorLocation(ActualLocation);

        //-------------------
        // tweak camera component relative transform

        // set rotation parameters
        if (RotateValue != 0.f)
        {
            TurnCameraUp(MoveForwardValue * FastMoveValue * DeltaSeconds);
            TurnCameraRight(MoveRightValue * FastMoveValue * DeltaSeconds);
        }

        // set zoom distance
        ZoomCameraIn(ZoomInValue * FastMoveValue * DeltaSeconds);

        // adjust camera component relative location and rotation
        RepositionCamera();

        //-------------------
        // debug

        //DrawDebugSphere(    
        //                    GetWorld(),
        //                    GetCollisionComponent()->GetComponentLocation(),
        //                    GetCollisionComponent()->GetScaledSphereRadius(),
        //                    8,
        //                    FColor::White,
        //                    false,
        //                    -1.f
        //                );

        //-------------------
    }
}


//////////////////////////////////////////////////////////////////


float    ARTSPlayerCameraSpectatorPawn::GetLandTerrainSurfaceAtCoord(float XCoord, float YCoord) const
{
    FCollisionQueryParams TraceParams(FName(TEXT("LandTerrain")), false, this);        // TraceTag (info for debugging), bTraceComplex, AddIgnoredActor
    TraceParams.bFindInitialOverlaps = false;                                        // needed

    FHitResult Hit;

    FVector Start = FVector(XCoord, YCoord, GetActorLocation().Z + CameraRadius);
    FVector End = FVector(XCoord, YCoord, -500.f);

    // ECC_ channels should be set properly !!!
    bool bHit = GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECollisionChannel::ECC_WorldStatic, TraceParams);

    if (bHit)
    {
        return Hit.ImpactPoint.Z;    // for shape trace it differs from Location
    }

    return 0.f;        // water level
}

//////////////////////////////////////////////////////////////////


I hope it was detailed enough (anyway the in-code comments should help), ask me if something unclear, or scream if you found a bug/mistake.

Maybe later I will add unit selection to the player controller…

Have fun with it! :slight_smile:

I made a little cosmetics on this old stuff, removed some UFUNCTION() macros, as they are not needed…

Nice, thank you! :slight_smile:

Hi,

first off all +rep to you as well as to Connor, really good work. I tried to get it work in UE 4.16 and there are just two things to fix in order to get it work.

Both at RTSPawn.cpp (or whatever your pawn is named):

  1. Replacement of this line of code:
    CameraComponent->AttachParent = GetRootComponent(); //AttachParent is private
    to
    CameraComponent->SetupAttachment(GetRootComponent());

  2. For some reason you also double initialized GameViewport:
    UGameViewportClient* GameViewport = GameViewport;
    UGameViewportClient* GameViewport = GEngine->GameViewport;
    While the first line of code should be removed. I believe you just forget to delete it guys.

Once again, nice work! (btw your RTS project looks awesome !)

Thanks! I have made a slightly better version having smoother moves, I will update and correct it soon. And if there is interest, I can share my single click and box unit selection stuff too.

Hey,

the RTSPlayerCameraSpectatorPawn.h/.cpp codes are updated to UE4.15.

Moreover, there are two improvements:

  • The movement is really smoother by a different usage of RepositionCamera(), which is now only called once per tick.
  • Vertical ray tracing is introduced to deny the camera to move under ground or inside static meshes. You can set ECollisionChannel::ECC_WorldStatic to a custom channel, e.g. to sense only landscape instead of all static geometries, or as you desire.

Have fun!

My engine version is 4.17.

I have a problem with the following line in the RTSPlayerCameraSpectatorPawn class.
GetCollisionComponent()->bGenerateOverlapEvents = false; Looks like RTSPlayerCameraSpectatorPawn does not have a collision component.

Is this an engine quirk in 4.17?

it’s inherited from ASpectatorPawn which has a collision component, namely a sphere. only the mesh is omitted by .DoNotCreateDefaultSubobject(Super::MeshComponentName)
I use it with 4.17 and fine here.

, hi!

I created an RTS project in 4.17 with the bare minimum code to illustrate the issue that I am facing. See code below.

RTSGameModeBase.h



#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "RTSGameModeBase.generated.h"

/**
 * 
 */
UCLASS()
class RTS_API ARTSGameModeBase : public AGameModeBase
{
    GENERATED_BODY()

        ARTSGameModeBase(); //Constructor



};

RTSGameModeBase.cpp


#include "RTSGameModeBase.h"
#include "RTS.h"
#include "RTSPlayerController.h"
#include "RTSPlayerCameraSpectatorPawn.h"


ARTSGameModeBase::ARTSGameModeBase()
{
    PlayerControllerClass = ARTSPlayerController::StaticClass();
    DefaultPawnClass = ARTSPlayerCameraSpectatorPawn::StaticClass();
}


RTSPlayerController.h


#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "RTSPlayerController.generated.h"

/**
 * 
 */
UCLASS()
class RTS_API ARTSPlayerController : public APlayerController
{
    GENERATED_BODY()

        ARTSPlayerController(); //Constructor


};


RTSPlayerController.cpp


#include "RTSPlayerController.h"


ARTSPlayerController::ARTSPlayerController()
{
    bShowMouseCursor = true;
    bEnableClickEvents = true;
    bEnableTouchEvents = true;
}



RTSCameraSpectatorPawn.h


#include "CoreMinimal.h"
#include "GameFramework/SpectatorPawn.h"
#include "RTSPlayerCameraSpectatorPawn.generated.h"

/**
 * 
 */
UCLASS()
class RTS_API ARTSPlayerCameraSpectatorPawn : public ASpectatorPawn
{
    GENERATED_BODY()

public:

    /** Constructor */
    ARTSPlayerCameraSpectatorPawn(const FObjectInitializer& ObjectInitializer);


};

RTSCameraSpectatorPawn.cpp


#include "RTSPlayerCameraSpectatorPawn.h"

ARTSPlayerCameraSpectatorPawn::ARTSPlayerCameraSpectatorPawn(const FObjectInitializer& ObjectInitializer) //Constructor
{

    // collision
    GetCollisionComponent()->bGenerateOverlapEvents = false;

}



Compile fails on this line at RTSCameraSpectatorPawn.cpp:

GetCollisionComponent()->bGenerateOverlapEvents = false;

Looks like the method is not able to get the CollisionComponent. See compile error message from the compiler:
error C2027 : use of undefined type ‘USphereComponent’.

Intellisense too gives an error with the following message: Pointer to incomplete class type is not allowed.

See if anything obvious jumps out at you.

If not could you please compile the above simple code on your machine and see if it works?

Thanks.
​​​​​​​

thanks, it is probably because of the default project includes were changed a few engine versions ago (as I remember in 4.15, include what you use), but my project still includes #include “Engine.h” in RTS.h, which could be not the case in new projects, I have to check it.
adding #include “Components/SphereComponent.h” to RTSCameraSpectatorPawn.cpp should solve the problem.

Well, thanks are due to you. Excellent tut. BTW, I included #include “Components/SphereComponent.h” and my test code above compiles. I will test the rest of the camera stuff in 4.17 and post my results. Then you can probably make the changes needed for 4,17 compilation perhaps?

I will test if there any other additional dependencies like #include “Components/SphereComponent.h” above and post my results soon.

, I had to add the following includes to the RTSPlayerCameraSpectatorPawn.cpp in order for the code to compile in 4.17.



#include "RTSPlayerCameraSpectatorPawn.h"
#include "Components/SphereComponent.h" //Needed for the Collision Component to work
#include "Camera/CameraComponent.h" //Needed for the Camera Component to work
#include "Components/InputComponent.h" //Needed for the Player Input Component to work
#include "Classes/Engine/GameViewportClient.h" //Needed for the GameViewport to work
#include "Engine.h" //Needed for Gengine to work

Do you want to make the changes yourself in your code above or do you want me to post the whole code?

Also, I had to change the Default Game Mode in the Project Settings -> Maps and Modes section:

Default_Game_Mode.png

thanks! I will add these details.

Hello,

i have tried setting up your tutorial and everything works except for the player input. For some reason, the SpectatorPawn::SetupPlayerInputComponents() method is not run. The code is compiling, i made sure all includes are set and even tried calling CreatePlayerInputComponent(); in the SpectatorPawn Constructor. Nothing works tho. I can not get player inputs on my pawn.

You have any idea what that could be?

Thx anyway for the awesome tut

I had to change the GetIsolatedCameraYaw function:

FRotator ACameraPawn::GetIsolatedCameraYaw()
{
//FRotator contains Yaw only
return FRotator(0.0f, Camera->GetComponentTransform().Rotator().Yaw, 0.0f);
}

I’m sorry but both images are not available anymore.

Can you reattach it again, please?

thanks, done! :slight_smile: