Tutorial: How to make a configurable camera with the new Unity Input System

I've been working on a new prototype game and started to learn the new Input System. I thought it might be helpful to create a tutorial that covers creating a configurable camera that works with the input system. The goal is to give foundational knowledge for beginners to get going.

Right now it's just a written tutorial, but if there's interest I may also record a video.

Introduction
In this tutorial, you’re going to build a configurable camera that handles moving, zooming and rotating. This design works great for games that do not want an attached 3rd person camera, but instead want freedom to move around a scene. Instead of using the old input system, we’ll be hooking it up to the new one and will review key concepts as we go.

Configuration features of the camera are:

  • Camera angle
  • Zoom min/max
  • Zoom default
  • Look offset (where you want the camera to focus on the y axis)
  • Rotation speed

Learning Outcomes

  • Understand key concepts of the new Input System.
  • Have a configurable camera that can be customized for your game.

Edit:

This is now part of a series:

6 Likes

Nice work!

One thing that caught my eye was the rotate code.

        public void OnRotate(InputAction.CallbackContext context)
        {
            if (!_rightMouseDown)
            {
                return;
            }

            //Read the input value that is being sent by the Input System
            _mouseDelta = context.ReadValue<Vector2>();

            //Set the target rotation based on the mouse delta position and our rotation speed
            _rotationTarget *= Quaternion.AngleAxis(_mouseDelta.x * Time.deltaTime * RotationSpeed, Vector3.up);
        }

Note that this will run too often and produce exaggerated mouse motion as the delta control both accumulates and resets. Let's say you have two frames with 3 mouse motion events in the first and two in the second. What you'll see is the following call sequence:

  • Rotate action starts from mouse event #1
  • Rotate action performs from mouse event #1
  • Rotate action performs from mouse event #2
  • Rotate action performs from mouse event #3
  • Rotate action cancels from delta reset at beginning of frame
  • Rotate action starts from mouse event #4
  • Rotate action performs from mouse event #4
  • Rotate action performs from mouse event #5

So 1 and 2 basically apply the same delta twice. 3 applies the delta of events #1 + #2. 4 applies the delta of events #1, #2, and #3. And so on.

Overall, the recommendation is to never apply deltas successfully but only apply final deltas once per frame.

1 Like

Thanks for the compliment and the insight! I've updated the rotation section of the tutorial to better illustrate this behavior for others. Please let me know if I've missed the mark at all, and I'd be happy to try again. :)

You can see the changes here.

I cannot praise enough your tutorial! It's amazing and worked 100% from start to finish, explained clearly each step and provided links for further knowledge.
One can use it as a step-by-step guide and have a running input system in minutes, or use it as a learning tool and invest a few more hours and end up with an input system that fit to his/her needs!

Thank you.

1 Like

@VenandiVatis - Thank you for the kind words! This is exactly what I was hoping to achieve with the tutorial. I'm very glad it hit the mark. :)

1 Like

I decided to turn this into a series of posts and just released the last one yesterday. If you are interested, you can find them here:

2 Likes

This is excellent work.

Thank you! I'm wrapping up another one that teaches the EnhancedTouch API. Next up after that will about enabling players to set their own key mappings.

Let me know if there's any areas you think would be great for a tutorial.

1 Like

You're very welcome.
That EnhancedTouch API tutorial will certainly prove useful once we start adapting the game for smartphone/tablet control schemes.

In your new Input System tutorial, we found interesting that you show a way of selecting units without RayCasts.

You also show a workaround for the lack of continuous action support through the various update methods. It would be useful to have some sort of robust framework for dealing with those, perhaps as an additional chapter in the above tutorial?
For example I added panning when the mouse is on the edge of the screen but the way I'm implementing it within these update methods is relatively messy.

Thanks for writing this up. It was a big help in implementing my own solution. Cheers!

1 Like

Really nice tutorial, I will the other you did ;-)

Keep up the good work.

1 Like

Thanks so much for this, super helpful and well written. Is there a good way of adding vertical rotation using mouse Y without messing up the movement direction? The most obvious thing I could think of was adding a second empty object as an "arm" for the camera but rotating that somehow blocks the rotation of the base object.

Update: I tried binding the camera angle variable to mouse Y and updating the camera target and rotation in LateUpdate(), but that produced some very strange behavior where the camera was moving in an arc above the base. As someone who's mainly a UE4 user I'm totally lost here.

Thank you so much for the great work and the documentation! Learned a lot. I wanted to try to make a FlyCamera, using WASDQE keys, with pitch & yaw rotation that could also accelerate movement, and I managed to do this thanks to you. I decided to document my additions/changes on the initial Github tutorial you posted here, if anyone might be struggling to do the same! I am using Unity 2021.2.0f1.

1. Making the camera move in all 3 directions

  • I wanted to add an E/Q input for up/down movement. So in Part2 where we set up the Actions of our ActionMap, I changed the Camera_Move Action from a Value2 to a Value3 instead. Then chose the option to add an Up/Down/Left/Right/Forward/Backward Composite, and chose the preferred keyboard keys to do the movement (WASDQE)8013542--1031405--1.png
  • Then in Part3 I corrected the OnMove function to read a Vector3 value instead of Vector2 and changed the parameters of the _moveDirection and _moveTarget accordingly:
public void OnΧΥΖMove(InputAction.CallbackContext context)
    {
        //Read the input value that is being sent by the Input System
        Vector3 value = context.ReadValue<Vector3>();
        _moveDirection = new Vector3(value.x, value.y, value.z);
        _moveTarget += (transform.forward * _moveDirection.z +
                        transform.right   * _moveDirection.x +
                        transform.up      * _moveDirection.y)  * Time.fixedDeltaTime * InternalMoveTargetSpeed;
    }

2. Give the camera a Pitch and Yaw rotation

  • In Part7 I changed the _rotationTarget line of the LateUpdate() method, to take the mouseDelta.y as an input to correspond to the Pitch movement.transform.rotation *= Quaternion.AngleAxis(_mouseDelta.y * Time.deltaTime * RotationSpeed, Vector3.left); Make sure you use transform.rotation instead of _rotationTarget otherwise it won't work (not really sure why?not a pro here)
  • For the Yaw, use Quaternion.Euler instead of Slerp, so now the whole LateUpdate() should look like this:
private void LateUpdate()
    {
        //Lerp  the camera to a new move target position
        transform.position = Vector3.Lerp(transform.position, _moveTarget, Time.deltaTime * InternalMoveSpeed);

        //Move the _actualCamera's local position based on the new zoom factor
        _actualCamera.transform.localPosition = Vector3.Lerp(_actualCamera.transform.localPosition, _cameraPositionTarget,Time.deltaTime * _internalZoomSpeed);

        //Set the target rotation based on the mouse delta position and our rotation speed
        //Pitch
        transform.rotation *= Quaternion.AngleAxis(_mouseDelta.y * Time.deltaTime * RotationSpeed, Vector3.left);
        //Yaw
        transform.rotation = Quaternion.Euler(
            transform.eulerAngles.x,
            transform.eulerAngles.y + _mouseDelta.x * Time.deltaTime * RotationSpeed,
            transform.eulerAngles.z
        );
    }

3. Adding acceleration to the camera movement
I wanted to add an acceleration option as well, by holding Shift. To do so:

  • I created a boolean named isAccelerating and I’ve set it to false.

  • I created an extra Action on our InputActionMap called Camera_Acceleration that is a Button, and I've set the path to be the keyboard’s Shift button.
    8013542--1031420--4.png

  • To know when we have pushed the Shift button I created a new method called OnAccelerate(), that only sets isAccelerating to true, for as long as the player is pressing the Shift key.

public void OnAccelerate(InputAction.CallbackContext context)
    {
        switch (context.phase)
        {
            case InputActionPhase.Performed:
                isAccelerating = true;
                break;
            case InputActionPhase.Canceled:
                isAccelerating = false;
                break;
        }
    }
  • In the FixedUpdate() I used a condition to define whether or not we are accelerating and use the appropriate InternalMoveTargetSpeed on each occasion (I’ve set the speed when accelerating to be 40):
private void FixedUpdate()
    {
        //Sets the move target position based on the move direction.
        //Must be done here as there's no logic for the input system to calculate holding down an input
        if (isAccelerating == false)
        {
            InternalMoveTargetSpeed = 8;
            _moveTarget += (transform.forward * _moveDirection.z +
                            transform.right   * _moveDirection.x +
                            transform.up      * _moveDirection.y)  * Time.fixedDeltaTime * InternalMoveTargetSpeed;
        }
        else
        {
            InternalMoveTargetSpeed = 40;
            _moveTarget += (transform.forward * _moveDirection.z +
                            transform.right   * _moveDirection.x +
                            transform.up      * _moveDirection.y)  * Time.fixedDeltaTime * InternalMoveTargetSpeed;
        }
    }
  • I then hooked up the logic to the Input system: Under the Camera_Acceleration event, reference the CameraController GameObject and set the event to CameraController.OnAccelerate.
  • 8013542--1031423--7.png

Hope it helps!