Strategy Game Camera: Unity's New Input System

I was working on a paradigm for a potential new project and I needed a photographic camera controller. I was likewise using Unity'southward "new" input system. And I idea, hey, that could exist a good tutorial…

There's too a written mail on the New Input System. Bank check the navigation to the right.

The goal here is to build a camera controller that could be used in a wide variety of strategy games. And to do it using Unity's "New" Input System.

The camera controller will include:

  • Horizontal motion

  • Rotation

  • Zoom/elevate mechanic

  • Dragging the world with the mouse

  • Moving when the mouse is about the screen border

Since I'll be using the New Input Organization, you'll desire to be familiar with that before diving likewise deep into this photographic camera controller. Bank check either the video or the written blog post.

If you're just hither for the lawmaking or want to copy and paste, you tin go the code along with the Input Action Asset on GitHub.

Build the Rig

Camera rig Hierarchy

The first step to getting the camera working is to build the camera rig. For my purposes, I choose to keep it unproblematic with an empty base of operations object that will translate and rotate in the horizontal aeroplane plus a child camera object that volition motion vertically while also zooming in and out.

I'd too recommend adding in something similar a sphere or cube (remove its collider) at the same position as the empty base object. This gives us an idea of what the camera can meet and how and where to position the camera object. It's just easy debugging and once you're happy with the camera you can delete the extra object.

Photographic camera object transform settings

For my setup, my base object is positioned on the origin with no rotation or scaling. I've placed the camera object at (0, eight.3, -eight.8) with no rotation (we'll have the photographic camera "look at" the target in the code).

For your projection, you'll want to play with the location to help tune the experience of your camera.

Input Settings

Input Action Nugget for the Camera Controller

For the camera controller, I used a mix of events and straight polling inputs. Sometimes ane is easier to apply than some other. For many of these inputs, I divers them in an Input Activity Asset. For some mouse events, I simply polled the buttons directly. If that doesn't make sense hopefully information technology will.

In the Input Action Asset, I created an action map for the photographic camera and iii deportment - motility, rotate, and elevate. For the movement action I created two bindings to allow the WASD keys and arrows keys to be used. It's easy, and so why non? Also important, both rotate and elevate have their action type set to Vector2.

Importantly the rotate activity is using the delta of the mouse position not the actual position. This allows for smooth movement and avoids the camera snapping around in a weird way.

We'll be making utilize of the C# events. So brand sure to relieve or have auto-save enabled. We also need to generate the C# code. To practise this select the Input Action Asset in your project folders then in the inspector click the "generate C# course" toggle and press apply.

Variables and More Variables!

Next, nosotros demand to create a camera controller script and adhere information technology to the base object of our camera rig. Then inside of a camera controller course we need to create our variables. And there's a poop ton of them.

The showtime two variables will be used to cache references for use with the input organisation.

The camera transform variable volition cache a reference to the transform with the camera object - equally opposed to the empty object that this class will be attached to.

All of the variables with the BoxGroup attribute volition be used to tune the motion of the camera. Rather than going through them one by 1… I'g hoping the name of the group and the name of the variable clarifies their approximate purpose.

The photographic camera settings I'chiliad using

The last 4 variables are all used to rail various values between functions. Pregnant 1 function might change a value and a 2nd function volition make use of that value. None of these need to have their value fix exterior of the class.

A couple of other bits: Notice that I've too added the UnityEngine.InputSystem namespace. Too, I'm using Odin Inspector to make my inspector a chip prettier and go along information technology organized. If you don't have Odin, you should, but y'all can just delete or ignore the BoxGroup attributes.

Horizontal Motion

I'1000 going to try and build the controller in chunks with each clamper calculation a new mechanic or piece of functionality. This also (roughly) ways y'all can add or not add together any of the chunks and the camera controller won't pause.

The offset chunk is horizontal move. Information technology'due south also the piece that takes the most setup… So bear with me.

First, we demand to gear up our Awake, OnEnable, and OnDisable functions.

In the Awake function, we need to create an example of our CameraControls input activity asset. While nosotros're at it nosotros can also grab a reference to the transform of our camera object.

In the OnEnable function, we first need to make sure our camera is looking in the correct direction - we can practice this with the LookAt function directed towards the camera rig base object (the aforementioned object the code is attached to).

Then we tin can save the current position to our last position variable - this value will go used to help create shine motility.

Side by side, we'll cache a reference to our MoveCamera action - we'll be straight polling the values for motion. We likewise demand to call Enable on the Camera action map.

In OnDisable we'll call Disable on the camera activity map to avoid issues and errors in case this object or component gets turned off.

Helper functions to get camera relative directions

Side by side, we need to create two helper functions. These will return photographic camera relative directions. In detail, we'll exist getting the forwards and right directions. These are all we'll demand since the camera rig base will only movement in the horizontal plane, we'll also squash the y value of these vectors to nix for the same reason.

Kind of yucky. Only gets the job done.

Admittedly I don't love the next function. Information technology feels a bit clumsy, but since I'one thousand not using a rigidbody and I want the camera to smoothly speed up and slow downward I need a way to calculate and rails the velocity (in the horizontal plane). Then thus the Update Velocity function.

Zip too special in the function other than once again squashing the y dimension of the velocity to zero. After calculating the velocity we update the value of the last position for the next frame. This ensures we are calculating the velocity for the frame and not from the commencement.

The side by side part is the poorly named Become Keyboard Motility function. This office polls the Camera Move action to then set the target position.

In guild to translate the input into the move we want we need to be a bit conscientious. We'll accept the ten component of the input and multiply information technology past the Camera Right function and add that to the y component of the input multiplied by the Camera Forwards role. This ensures that the movement is in the horizontal plane and relative to the camera.

We then normalize the resulting vector to keep a compatible length then that the speed volition be abiding fifty-fifty if multiple keys are pressed (up and right for example).

The last footstep is to cheque if the input value's square magnitude is above a threshold, if information technology is we add our input value to our target position.

Annotation that we are NOT moving the object here since eventually there will be multiple ways to move the camera base of operations, we are instead adding the input to a target position vector and our NEXT function will use this target position to actually motility the camera base.

If we were okay with herky-jerky movement the next function would exist much simpler. If we were using the physics engine (rigidbody) to move the camera it would likewise be simpler. But I want polish motion AND I don't desire to tune a rigidbody. Then to create smooth ramping up and down of speed we need to do some piece of work. This work will all happen in the Update Base of operations Position function.

First, we'll bank check if the square magnitude of the target position is greater than a threshold value. If it is this means the player is trying to get the camera to motion. If that'southward the instance we'll lerp our current speed upwards to the max speed. Note that we're also multiplying Time Delta Time by our acceleration. The acceleration allows usa to melody how quickly our camera gets up to speed.

The apply of the threshold value is for two reasons. One so we aren't comparison a float to zero, i.e. asking if a float equals zero can be problematic. Two, if we were using a game controller joystick even if it's at balance the input value may non be zero.

Testing the Code and then far - Smooth Horizontal Motion

We then add together to the transform's position an corporeality equal to the target position multiplied past the current camera speed and time delta time.

While they might look different these 2 lines of code are closely related to the Kinematic equations you may have learned in high school physics.

If the player is not trying to become the photographic camera to move we want the camera to smoothly come to a end. To practise this nosotros want to lerp our horizontal velocity (calculated constantly past the previous part) downward to zippo. Note rather than using our acceleration to control the rate of the slow downwardly, I've used a different variable (damping) to allow separate control.

With the horizontal velocity lerping it's way towards zippo, we then add to the transform'southward position a value equal to the horizontal velocity multiplied by time delta fourth dimension.

The terminal step is to fix the target position to zilch to reset for the next frame'southward input.

Our last step before we tin exam our code is to add our last three functions into the update role.

Camera Rotation

Okay. The hardest parts are over. Now we can add functionality reasonably speedily!

And then let's add the ability to rotate the camera. The rotation will be based on the delta or change in the mouse position and volition but occur when the middle mouse button is pressed.

We'll be using an event to trigger our rotation, so our first addition to our code is in our OnEnable and OnDisable functions. Here we'll subscribe and unsubscribe the (soon to exist created) Rotate Camera function to the performed outcome for the rotate camera action.

If you're new to the input system, you'll discover that the Rotate Camera function takes in a Callback Context object. This contains all the data virtually the action.

Rotating the camera should now be a thing!

Inside the part, we'll first cheque if the centre mouse push button is pressed. This ensures that the rotation doesn't occur constantly simply only when the button is pressed. For readability more than functionality, we'll store the ten value of the mouse delta and utilize information technology in the side by side line of code.

The last piece is to set the rotation of the transform (base object) and only on the y-axis. This is done using the x value of the mouse delta multiplied by the max rotation speed all added to the current y rotation.

And that's it. With the event getting invoked there's no need to add together the role to our update function. Overnice and piece of cake.

Vertical Photographic camera Motion

With horizontal and rotational motion working it would exist nice to motion the camera upwardly and downward to permit the histrion run into more than or less of the world. For controlling the "zooming" we'll exist using the mouse scroll wheel.

This motion, I plant to be one of the more complicated as at that place were several bits I wanted to include. I wanted at that place to be a min and max superlative for the camera - this keeps the thespian from zooming likewise far out or zooming downwards to nothingness - besides while going up and downwardly information technology feels a bit more natural if the photographic camera gets closer or farther away from what it's looking at.

This zoom motion is another good use of events so we need need to make a couple of additions to the OnEnable and OnDisable. Just like we did with the rotation we need to subscribe and unsubscribe to the performed effect for the zoom camera action. We also need to gear up the value of zoom height equal to the local y position of the photographic camera - this gives an initial value and prevents the photographic camera from doing wacky things.

And so inside the Zoom Camera function, we'll cache a reference to the y component of the scroll wheel input and carve up by 100 - this scales the value to something more useful (in my opinion).

If the absolute value of the input value is greater than a threshold, meaning the player has moved the scroll wheel, we'll set the zoom height to the local y position plus the input value multiplied by the footstep size. We then compare the predicted height to the min and max height. If the target height is outside of the allowed limits we set our summit to the min or max acme respectively.

Once over again this part isn't doing the actual moving it'south merely setting a target of sorts. The Update Photographic camera Position role volition practice the actual moving of the camera.

The first footstep to move the camera is to use the value of the zoom summit variable to create a Vector3 target for the camera to motion towards.

Zooming in action

The next line is admittedly a bit confusing and is my endeavour to create a zoom forrad/astern motion while going up and down. Hither we subtract a vector from our target location. The subtracted vector is a product of our zoom speed and the departure between the electric current height and the target height All of which is multiplied by the vector (0, 0, 1). This creates a vector proportional to how much we are moving vertically, only in the camera'south local frontward/backward direction.

Our terminal steps are to lerp the photographic camera's position from its current position to the target location. Nosotros utilize our zoom damping variable to control the speed of the lerp.

Finally, we too take the camera wait at the base of operations to ensure we are withal looking in the correct direction.

Before our zoom will work we need to add both functions to our update role.

If y'all are having weird zooming behavior it's worth double-checking the initial position of the photographic camera object. My values are shown at the top of the page. In my testing if the x position is not aught, some odd twisting motion occurs.

Mouse at Screen Edges

At this betoken, we have a pretty functional photographic camera, but there'due south still a flake more polish we can add. Many games let the player to movement the camera when the mouse is near the edges of the screen. Personally, I like this when playing games, but I exercise detect information technology frustrating when working in Unity as the "screen edges" are divers by the game view…

To create this motility with the mouse all we need to do is check if the mouse is most the edge of the screen.

We do this by using Mouse.current.position.ReadValue(). This is very similar to the "old" input system where we could only call Input.MousePosition.

We also demand a vector to rails the motion that should occur - this allows the mouse to be in the corner and have the camera move in a diagonal direction.

Screen edge motility

Next, nosotros simply bank check if the mouse ten and y positions are less than or slap-up than threshold values. The border tolerance variable allows fine tuning of how close to the edge the cursor needs to be - in my case I'm using 0.05.

The mouse position is given to usa in pixels not in screenspace coordinates so it's important that we multiply past the screen width and superlative respectively. Notice that we are again making use of the GetCameraRight and GetCameraForward functions.

The last step inside the function is to add our move direction vector to the target position.

Since we are non using events this function also needs to get added to our update function.

Dragging the Earth

I stole and adapted the drag functionality from Game Dev Guide.

The final piece of polish I'chiliad adding is the power to click and drag the world. This makes for very fast motion and generally feels good. Nonetheless, a note of caution when implementing this. Since we are using a mouse button to elevate this can rapidly interfere with other player actions such as placing units or buildings. For this reason, I've chosen to utilize the right mouse button for dragging. If you want to use the left mouse push button you'll need to check if you lot Can or SHOULD drag - i.e. are y'all placing an object or doing something else with your left mouse button. In the past I have used a drag handler… and then maybe that's a better route, simply it's not the direction I choose to go at this point.

I should besides admit that I stole and adapted much of the dragging code from a Game Dev Guide video which used the old input system.

Since dragging is an every frame type of thing, I'm in one case once more going to directly poll to determine whether the right mouse push is downwardly and to get the current position of the mouse…

This could probably be downward with events, but that seems contrived and I'm not sure I really run across the do good. Possibly I'm wrong.

Inside the Drag Photographic camera office, nosotros can outset check if the right button is pressed. If it's not we don't desire to go any further.

If the button is pressed, nosotros're going to create a aeroplane (I learned about this in the Game Dev Guide video) and a ray from the photographic camera to the mouse cursor. The airplane is aligned with the globe XZ aeroplane and is facing upward. When creating the plane the first parameter defines the normal and the second defines a indicate on the aeroplane - which for the not-math nerds is all yous need.

Next, nosotros'll raycast to the plane. Then cool. I totally didn't know this was a thing!

The out variable of altitude tells united states how far the ray went before it hit the aeroplane, assuming it hit the aeroplane. If information technology did hit the plane we're going to do 2 different things - depending on whether nosotros but started dragging or if we are continuing to elevate.

Dragging the globe

If the right mouse button was pressed this frame (learned well-nigh this thanks to a YouTube comment) we'll cache the point on the plane that we hit. And nosotros get that indicate, by using the Get Bespeak function on our ray.

If the right mouse button wasn't pressed this frame, meaning nosotros are actively dragging, nosotros tin update the target position variable with the vector from where dragging started to where it currently is.

The final step is to add together the drag function to our update part.

That's It!

There you go. The basics of a strategy camera for Unity using the New Input Arrangement. Hopefully, this gives you a jumping off point to refine and maybe add features to your own camera controller.