Download

[REQUEST] Fully flexible RTS camera

On Unity3D there is a RTS Pro camera asset which you can control in many ways. Can someone make a camera like this for UE4? Heres the camera:

Hi there, I’m pretty new myself and one of my first learning projects is a RTS. I have pretty much replicated most most of this Cameras functions.

If you’re interested in doing it yourself, this is how I did it.

DISCLAIMER:
As said I’m still a beginner and so my Blueprints probably will contain bad implentations or un-performant solutions that maybe can and probably will lead to errors, so I’m kinda posting this in the hope to get some feedback on optimisations aswell :stuck_out_tongue:

Lets begin with the Setup:

As Root I use a Sphere, which is set to Visible for Control purposes.
Attached to this Sphere is a Spring Arm which is angled,
and attached to that Spring Arm is my Camera.

First lets go through moving the Camera in the 4 directions. For that I defined 2 axis’ mapped to WS and AD (for test purposes, later on it’ll probably be the Arrow Keys)

The upper axis is for moving the Camera Forward and Backward. Im using the “Add Actor Local Transform”-function for that. The Axis Value is either +1 or -1 multiplied by a number depending on desired movement speed.
The value is now fed into a vector as the x-component, which then is fed into the transform of the function.

Now I’ve repeated the same for moving Left and Right. This time feeding the Axis Value into the y-component of the vector

That’s all that’s needed for controlling the Camera via Keyboard.

Notice how the Trigger Pins of the two functions Continue? I gonna use them later on.

62a4baa332fc1e2b7a4479a14792ec21601e9482.png

Now lets go for Rotating the Camera via Keyboard

Again I defined an axis for the Rotation mapped to Q and E

Im Using “Add Actor World Rotation”. As Rotation values I take the Axis value, multiply it so it moves a little bit faster, and create a Rotation. The value goes into the Yaw Pin.

e048cf7b951cdf7f8a206325dd89908b11d9dee9.png

Now it gets a little bit messy, there probably is an easier way to do this, I may have missed a handy function or something, but anyways:

First I used the Event Tick which triggers a Sequence.

The first part of the Sequence Compares to values.

As Input Value I use the Mouse x-position from “Get Mouse Position” for my Player Controller. The X Axis of course is the value of the mouse cursors horizontal position.

We compare the x value with 10 first (this value can be smaller, but for testing I made it a bit larger). Now If the Mouse X Value is smaller than 10 that means the Mouse is on the far left. Thats when we want to trigger “Add Actor Local Transform” and move the Camera left.
If Mouse X is larger than 10 we go to the next Compare. Now we have a little problem though, because the screen resolution is variable, so we don’t have a fixed Value of when the Mouse reaches the right edge of the screen.
Luckily there is a function called “Get Viewport Size” which gives us a 2D Vector with the Game Window Size. We Break this Vector into his X and Y components, and from those values we subtract 10, so again we now have a value of 10 pixels from the right edge. The Second Compare now looks if our Mouse X is bigger than this value, and if it is, it moves the Camera to the right.

We do the same compares for the Mouse Y values and transform the Camera Position in the same way as above, only of course this time we move it up and down.

0bb6f0b2546bba3332bd7d2a2f7afe7c3a115284.png

Rotating the View with the Mouse luckily is pretty chill compared to scrolling the screen via the screen borders.

So I wanted to be able to rotate the mouse via holding the Middle Mouse Button and then moving the Mouse horizontally.
For that I took the Event “Mouse X” this event gives me the value of the horizontal Mouse movement and it triggers on most Mouse Events.
Now If i would have connected this directly, I would rotate the mouse with every button I click, so to stop that I added a Gate in between.
The Gate is controlled by the Middle Mouse. It opens when I press Middle Mouse down, and closes when I release the button. This way movement only happens when im holding the middle mouse button.
For rotating I used “Add Actor World Rotation” again. This time as Rotation I used the Axis value that Event “Mouse X” gave me, multiplied it so it goes a little faster, and again made a Rotation from that value.
The movement this time though was the wrong way round. When I moved the Mouse Left, the screen rotated right. To fix this I simply negated the Rotator, before I inserted it into “Add Actor World Rotation”

b30be32cf84e9fabe6efb0885bde946e5ff8bedd.png

Now lets go for zooming in and out.
I defined two Actions for that: Zoom in and zoom out, bound to Mouse Wheel Up and Down.

In my Setup I used a Spring Arm for the Camera, now you can change the lenght of that Spring arm via Getters and Setters for “zooming” in and out.

If the “Zoom in” Event is triggered it starts a Compare. Here I take the current Lenght of the Spring arm, and subtract 100 from that then I set the result as the new value for the Spring Arm lenght.
The Compare is used to limit how far one can zoom in. It compares the lenght to 800, and if it gets any shorter than that, it wont set the new value. It only goes through it the value is above or equal 800

The same goes for “Zoom Out” but this time I compare it 3000. Because I want this to be my upper limit, the value only goes through if the number is below or equal 3000, everything else gets thrown out.

Now we can move our camera in any direction and rotate it any way we want. Of course my maps wont be completely flat, so I want to keep a fixed distance from the ground.
Remeber those two Trigger Pins from earlier? We use them now.

So first I have to check my current distance from the ground. I want this to happen everytime I move the on the XY plane, that’s why I use the Trigger Pins from earlier.

I use a function called “Line Trace by Channel”. This function cast a ray from a Start to an End Point, when this ray collides with something it gives you results.

I want this ray to start above my sphere, and end below it. I use the function “Get World Location” to get infos about my current World Position.
For the Start Point I take my current World Position and add 5000 to that (or any other high number)
For the End Point I subtract 5000 from my current position, this gives me a long perpendicular ray. At some point this ray probably will collide with something. We have to break this hit result into its components.
What’s interesting for us is the Location of the Hit. This is provided as a Vector. I wanted my sphere to stay a bit above the ground, so i broke this Vector again and made a new Vector from that. X and Y stays the same, but for Z, which will be on ground level, I added 50 before I fed it into the new Vector. This Vector is the Location for the function “Set World Location” at the end. This makes my sphere follow the ground in a fixed distance of 50 units.

And here is a little demonstration:

Sorry for the potato-esque Video quality, but I wanted to do this fast, so I rendered the video on pretty low settings.

If you have any questions feel free to ask. And again, I really would like some feedback from the experts around here. I guess there is a lot of room for improvements :smiley:

Looks very nice :slight_smile: Can you add this camera-function as well?
Poorly Rated: Hospital Tycoon | WoWcrendor - YouTube (the Hospital Tycoon camera, starts at 7:34)

And if you could upload the blueprint somewhere it would be most appreciated. And welcome to the forums by the way!

Thank you Maggifixxx, it’s a great base for an RTS camera :slight_smile:

A couple of suggestions though:

  • It is best practice to make variables out of all the values scattered around your blueprint. It will be easier to modify later, and other people can then easily find them as well.
  • For beginners: Make sure your camera blueprint is a pawn, and is auto possesed by player 0 (unless it’s a muliplayer game, then it’s a bit more complicated).

Movement with keyboard

  • The movement of the camera with the keyboard can be simplified by using the Add Actor Local Offset, eliminating the need to convert the location to a transform. Then you could even split that pin and input the x or y directly.
  • Since the inputaxis events are once per frame, your camera movement is dependent on framerate. The way to solve this is to multiply your value with delta time, which you can get from GetWorldDeltaSeconds. The value is now in centimeter per second.
  • I also added scaling depending on zoom distance. Movement will go faster if you are zoomed out more.

Rotation with keyboard
Rotating using Q and E has the same modifications (except zoom scaling):

Rotation with mouse
I added rotating up and down using the mouse. Note that rotating the whole actor up and down results in undesirable effects when moving forward/backward, so this vertical rotation is best done in the spring arm.

Zoom

  • I modified the zooming amount to be a percentage of the current distance. This way even at larger distances from the sphere the amount is substantial.
  • The minimal and maximal distance can be reached exactly, by checking if the new value exceeds them, and if so, overriding the value with that minimal or maximal distance.

Movement with mouse

  • I added movement with the mouse, while holding the right mouse button.
  • When the right mouse button is pressed, the position of the mouse at that time is saved. Using that stored position you can calculate the relative movement, and use that to move the actor. Moving the mouse further away from that initial position will result in a faster movement in that direction.

Height Correction

  • I moved it to a function, because there were a couple of places within the event graph that needed to execute this sequence of nodes.

  • Instead of adding a z offset here, I set the offset inside the SpringArm component. The SpringArm has offset options for target as well as socket, and those are preferable above relative offsets.

  • I added a check to see if the linetrace does actually hit the ground. It only corrects the position if it does have a hit. This allows the sphere to float in the case there is no ground beneath. Invisible walls are probably still useful to limit the movement to within the level.

  • While the linetrace in your blueprint seems to not ignore anything, your video shows it ignores those factories. I guess you modified it after making the picture?
    Anyway, here is a way to make the linetrace ignore everything but the ground:

    • In your project settings: Under collision settings make a new object channel. Set the default response to block and the name to whatever you want (I named mine Ground).
    • In your landscape (or any other object that represents the ground) collision settings: Set the Preset to custom and the Object Type to your newly made type (Ground). (And usually you want your ground to block everything, so set all collision responses to block).
    • Now do a LineTraceForObjects and for the object types make an array and choose Ground as only element in the array.

b0455d1d69a67dba683dc9844c764fe32c7c1b9a.png

EdgeScrolling
This one is pretty much the same, with the exception of the usage of custom events to make it look better and a boolean to turn it off if preferred.