Strategy Game Camera: Unity'south New Input System

I was working on a paradigm for a potential new projection and I needed a camera controller. I was also using Unity'due south "new" input system. And I idea, hey, that could be a good tutorial…

There'south as well a written mail on the New Input Arrangement. Check the navigation to the right.

The goal here is to build a photographic camera controller that could be used in a wide variety of strategy games. And to exercise information technology using Unity's "New" Input Arrangement.

The photographic camera controller will include:

  • Horizontal motility

  • Rotation

  • Zoom/elevate mechanic

  • Dragging the globe with the mouse

  • Moving when the mouse is near the screen edge

Since I'll exist using the New Input Arrangement, yous'll want to be familiar with that before diving as well deep into this camera controller. Check either the video or the written weblog mail service.

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

Build the Rig

Camera rig Hierarchy

The showtime step to getting the camera working is to build the camera rig. For my purposes, I choose to continue it simple with an empty base object that will interpret and rotate in the horizontal plane plus a kid camera object that will move vertically while also zooming in and out.

I'd also recommend adding in something like 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 come across and how and where to position the photographic camera object. Information technology's only easy debugging and once you lot're happy with the photographic camera you can delete the extra object.

Camera object transform settings

For my setup, my base of operations object is positioned on the origin with no rotation or scaling. I've placed the camera object at (0, eight.iii, -eight.eight) with no rotation (nosotros'll take the camera "expect at" the target in the code).

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

Input Settings

Input Action Asset for the Camera Controller

For the photographic camera controller, I used a mix of events and directly polling inputs. Sometimes one is easier to use than some other. For many of these inputs, I defined them in an Input Action Asset. For some mouse events, I simply polled the buttons directly. If that doesn't brand sense hopefully it will.

In the Input Action Asset, I created an activeness map for the camera and three actions - movement, rotate, and elevate. For the motility activeness I created two bindings to let the WASD keys and arrows keys to be used. It's like shooting fish in a barrel, then why not? As well important, both rotate and elevate have their activeness type set to Vector2.

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

We'll exist making use of the C# events. Then make certain to save or have auto-salve enabled. We also need to generate the C# code. To do this select the Input Action Asset in your project folders and then in the inspector click the "generate C# class" toggle and press apply.

Variables and More Variables!

Side by side, nosotros need to create a camera controller script and attach it to the base of operations object of our camera rig. Then within of a camera controller class we demand to create our variables. And there'southward a poop ton of them.

The start two variables will exist used to cache references for employ with the input system.

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

All of the variables with the BoxGroup attribute will be used to tune the motility of the camera. Rather than going through them i by one… I'one thousand hoping the name of the grouping and the name of the variable clarifies their gauge purpose.

The photographic camera settings I'm using

The concluding four variables are all used to track various values between functions. Meaning one function might change a value and a 2nd function will make use of that value. None of these demand to have their value ready outside of the grade.

A couple of other bits: Notice that I've likewise added the UnityEngine.InputSystem namespace. Also, I'thou using Odin Inspector to make my inspector a bit prettier and continue it organized. If you lot don't accept Odin, you should, but y'all tin can just delete or ignore the BoxGroup attributes.

Horizontal Motion

I'thousand going to endeavour and build the controller in chunks with each chunk adding a new mechanic or piece of functionality. This likewise (roughly) means you can add together or not add any of the chunks and the photographic camera controller won't interruption.

The first clamper is horizontal motility. It'south also the piece that takes the most setup… And so bear with me.

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

In the Awake part, we demand to create an example of our CameraControls input action asset. While nosotros're at it nosotros can as well 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 right management - we can practice this with the LookAt function directed towards the camera rig base object (the same object the code is attached to).

Then nosotros can save the electric current position to our terminal position variable - this value will get used to help create smooth motion.

Side by side, we'll enshroud a reference to our MoveCamera action - we'll be directly polling the values for move. We also demand to call Enable on the Camera action map.

In OnDisable nosotros'll call Disable on the camera action map to avert bug and errors in case this object or component gets turned off.

Helper functions to get photographic camera relative directions

Side by side, we need to create 2 helper functions. These will return camera relative directions. In particular, we'll exist getting the forrard and correct directions. These are all we'll need since the photographic camera rig base will only motion in the horizontal airplane, we'll as well squash the y value of these vectors to cypher for the same reason.

Kind of yucky. Merely gets the task done.

Admittedly I don't love the next function. Information technology feels a bit clumsy, but since I'm non using a rigidbody and I want the camera to smoothly speed up and slow downwards I need a manner to summate and rails the velocity (in the horizontal plane). So thus the Update Velocity part.

Cipher likewise special in the role other than once over again squashing the y dimension of the velocity to zero. Later on calculating the velocity we update the value of the final position for the next frame. This ensures nosotros are calculating the velocity for the frame and non from the start.

The next part is the poorly named Go Keyboard Movement part. This function polls the Photographic camera Movement action to then ready the target position.

In club to translate the input into the motion we want we need to be a bit careful. Nosotros'll accept the x component of the input and multiply it by the Camera Right function and add that to the y component of the input multiplied past the Camera Frontward role. This ensures that the motility is in the horizontal plane and relative to the camera.

We then normalize the resulting vector to keep a uniform length and then that the speed volition exist constant even if multiple keys are pressed (up and correct for example).

The terminal step 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.

Note that we are Not moving the object here since somewhen there will be multiple ways to motion the camera base, we are instead adding the input to a target position vector and our NEXT function volition employ this target position to actually movement the camera base.

If we were okay with herky-jerky move the adjacent role would be much simpler. If we were using the physics engine (rigidbody) to move the photographic camera it would also be simpler. But I desire smooth motility AND I don't want to tune a rigidbody. So to create polish ramping up and downward of speed we need to exercise some work. This work will all happen in the Update Base Position function.

First, we'll bank check if the foursquare 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 move. If that's the case nosotros'll lerp our current speed up to the max speed. Note that nosotros're also multiplying Time Delta Fourth dimension past our acceleration. The acceleration allows us to tune how quickly our photographic camera gets up to speed.

The use of the threshold value is for 2 reasons. 1 then we aren't comparing a bladder to zero, i.e. asking if a float equals zero can exist problematic. Two, if nosotros were using a game controller joystick even if it's at residuum the input value may non be zero.

Testing the Lawmaking then far - Smooth Horizontal Motion

We so add to the transform's position an amount equal to the target position multiplied by the current camera speed and time delta time.

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

If the actor is not trying to get the photographic camera to motion we want the camera to smoothly come up to a cease. To do this we want to lerp our horizontal velocity (calculated constantly past the previous role) downwards to naught. Notation rather than using our acceleration to control the charge per unit of the slow downwards, I've used a different variable (damping) to allow separate control.

With the horizontal velocity lerping information technology's way towards zero, we then add to the transform's position a value equal to the horizontal velocity multiplied by fourth dimension delta fourth dimension.

The final stride is to fix the target position to zero to reset for the next frame's input.

Our last step before we tin can test our code is to add our last 3 functions into the update function.

Camera Rotation

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

So allow's add the ability to rotate the camera. The rotation will be based on the delta or alter in the mouse position and will just occur when the center mouse push button is pressed.

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

If y'all're new to the input system, yous'll find that the Rotate Camera role takes in a Callback Context object. This contains all the information virtually the action.

Rotating the camera should at present be a thing!

Within the function, we'll first check if the middle mouse push button is pressed. This ensures that the rotation doesn't occur constantly simply only when the button is pressed. For readability more functionality, we'll store the x value of the mouse delta and use it in the adjacent line of lawmaking.

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

And that's information technology. With the event getting invoked at that place's no demand to add the function to our update function. Nice and like shooting fish in a barrel.

Vertical Photographic camera Move

With horizontal and rotational motion working it would exist squeamish to move the photographic camera up and down to let the player see more or less of the globe. For controlling the "zooming" we'll exist using the mouse whorl cycle.

This move, I found to exist one of the more complicated equally there were several $.25 I wanted to include. I wanted there to exist a min and max summit for the photographic camera - this keeps the player from zooming too far out or zooming down to nothingness - besides while going upwardly and down it feels a bit more natural if the camera gets closer or further away from what information technology'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. Simply similar we did with the rotation we need to subscribe and unsubscribe to the performed event for the zoom camera action. We as well demand to set the value of zoom height equal to the local y position of the photographic camera - this gives an initial value and prevents the camera from doing wacky things.

Then inside the Zoom Camera office, we'll cache a reference to the y component of the scroll bike input and divide by 100 - this scales the value to something more useful (in my stance).

If the accented value of the input value is greater than a threshold, pregnant the actor has moved the ringlet 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 elevation to the min and max peak. If the target height is exterior of the allowed limits we set up our height to the min or max height respectively.

Once once more this function isn't doing the actual moving it's just setting a target of sorts. The Update Camera Position office will do the actual moving of the camera.

The first step to move the camera is to utilize the value of the zoom superlative variable to create a Vector3 target for the photographic camera to move towards.

Zooming in action

The next line is admittedly a bit confusing and is my endeavour to create a zoom frontwards/backward motion while going up and down. Here nosotros decrease a vector from our target location. The subtracted vector is a product of our zoom speed and the difference betwixt the current acme and the target elevation All of which is multiplied by the vector (0, 0, one). This creates a vector proportional to how much we are moving vertically, but in the camera's local forrard/astern direction.

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

Finally, we too take the camera look at the base to ensure we are nonetheless looking in the correct direction.

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

If you are having weird zooming behavior it's worth double-checking the initial position of the photographic camera object. My values are shown at the peak of the page. In my testing if the 10 position is non zip, some odd twisting motion occurs.

Mouse at Screen Edges

At this bespeak, we have a pretty functional photographic camera, but there's yet a bit more than polish we can add together. Many games allow the player to motion the camera when the mouse is well-nigh the edges of the screen. Personally, I like this when playing games, just I exercise find it frustrating when working in Unity equally the "screen edges" are defined by the game view…

To create this motion with the mouse all nosotros need to practise is check if the mouse is well-nigh the edge of the screen.

Nosotros practise this by using Mouse.current.position.ReadValue(). This is very similar to the "one-time" input arrangement where we could only phone call Input.MousePosition.

We likewise need a vector to track the motion that should occur - this allows the mouse to be in the corner and have the photographic camera motility in a diagonal direction.

Screen edge motion

Next, nosotros just check if the mouse x and y positions are less than or not bad than threshold values. The edge tolerance variable allows fine tuning of how shut to the border the cursor needs to exist - in my case I'thou using 0.05.

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

The concluding step inside the role is to add our motion management vector to the target position.

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

Dragging the Globe

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

The terminal slice of polish I'm calculation is the ability to click and drag the world. This makes for very fast motion and more often than not feels good. All the same, a notation of caution when implementing this. Since we are using a mouse button to elevate this can quickly interfere with other player actions such as placing units or buildings. For this reason, I've chosen to apply the correct mouse button for dragging. If you desire to utilise the left mouse button y'all'll need to check if you lot CAN or SHOULD elevate - i.e. are you placing an object or doing something else with your left mouse push. In the past I take used a elevate handler… so maybe that's a better route, just it's non the direction I choose to become at this indicate.

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

Since dragging is an every frame type of thing, I'one thousand once again going to direct poll to determine whether the right mouse button is downwardly and to go the current position of the mouse…

This could probably be down with events, but that seems contrived and I'1000 not sure I actually run into the benefit. Perhaps I'thou wrong.

Inside the Drag Camera role, we can starting time check if the right button is pressed. If it's not we don't want to go whatever further.

If the button is pressed, nosotros're going to create a plane (I learned almost this in the Game Dev Guide video) and a ray from the camera to the mouse cursor. The plane is aligned with the world XZ aeroplane and is facing upward. When creating the airplane the first parameter defines the normal and the 2nd defines a point on the plane - which for the non-math nerds is all you need.

Adjacent, we'll raycast to the plane. So absurd. I totally didn't know this was a affair!

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

Dragging the world

If the right mouse button was pressed this frame (learned about this cheers to a YouTube comment) we'll enshroud the bespeak on the aeroplane that we hit. And we get that point, by using the Get Point part on our ray.

If the correct mouse button wasn't pressed this frame, meaning we are actively dragging, we can 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 yous go. The basics of a strategy camera for Unity using the New Input System. Hopefully, this gives you lot a jumping off point to refine and perchance add features to your ain camera controller.