Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

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

Discussion in 'Input System' started by Yecats, Oct 18, 2019.

  1. Yecats

    Yecats

    Joined:
    Jul 13, 2014
    Posts:
    69
    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:
    1. Camera angle
    2. Zoom min/max
    3. Zoom default
    4. Look offset (where you want the camera to focus on the y axis)
    5. Rotation speed
    Learning Outcomes
    1. Understand key concepts of the new Input System.
    2. Have a configurable camera that can be customized for your game.
    Edit:

    This is now part of a series:
    1. [Part 1] How to make a configurable camera with the new Input System: Learn the new Unity Input System (currently in preview) while creating a configurable camera that can be used in your game.
    2. [Part 2] Listen for the Input System's Action events via C#: Skip the PlayerInput component and register event listeners for Actions directly in code.
    3. [Part 3] Select multiple objects based on the center of a collider: Learn how to create an RTS/RPG "drag-to-select multiple characters" game mechanic without using raycasts to detect collisions.
    4. [Part 4] Challenge Solution - Extending the selection logic: Extend the RTS/RPG “drag-to-select multiple characters” game mechanic by adding additional support for routine player actions.
     
    Last edited: Apr 17, 2021
  2. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Nice work!

    One thing that caught my eye was the rotate code.

    Code (CSharp):
    1.         public void OnRotate(InputAction.CallbackContext context)
    2.         {
    3.             if (!_rightMouseDown)
    4.             {
    5.                 return;
    6.             }
    7.  
    8.             //Read the input value that is being sent by the Input System
    9.             _mouseDelta = context.ReadValue<Vector2>();
    10.  
    11.             //Set the target rotation based on the mouse delta position and our rotation speed
    12.             _rotationTarget *= Quaternion.AngleAxis(_mouseDelta.x * Time.deltaTime * RotationSpeed, Vector3.up);
    13.         }
    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:
    1. Rotate action starts from mouse event #1
    2. Rotate action performs from mouse event #1
    3. Rotate action performs from mouse event #2
    4. Rotate action performs from mouse event #3
    5. Rotate action cancels from delta reset at beginning of frame
    6. Rotate action starts from mouse event #4
    7. Rotate action performs from mouse event #4
    8. 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.
     
    lclemens likes this.
  3. Yecats

    Yecats

    Joined:
    Jul 13, 2014
    Posts:
    69
    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.
     
  4. VenandiVatis

    VenandiVatis

    Joined:
    Dec 5, 2016
    Posts:
    17
    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.
     
    Yecats likes this.
  5. Yecats

    Yecats

    Joined:
    Jul 13, 2014
    Posts:
    69
    @PZAragao - 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. :)
     
  6. Yecats

    Yecats

    Joined:
    Jul 13, 2014
    Posts:
    69
    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:
    1. [Part 1] How to make a configurable camera with the new Input System: Learn the new Unity Input System (currently in preview) while creating a configurable camera that can be used in your game.
    2. [Part 2] Listen for the Input System's Action events via C#: Skip the PlayerInput component and register event listeners for Actions directly in code.
    3. [Part 3] Select multiple objects based on the center of a collider: Learn how to create an RTS/RPG "drag-to-select multiple characters" game mechanic without using raycasts to detect collisions.
    4. [Part 4] Challenge Solution - Extending the selection logic: Extend the RTS/RPG “drag-to-select multiple characters” game mechanic by adding additional support for routine player actions.
     
    Last edited: Apr 17, 2021
    WookieFromAbove and RedEarth like this.
  7. nico_st_29

    nico_st_29

    Joined:
    Mar 14, 2020
    Posts:
    69
    This is excellent work.
     
  8. Yecats

    Yecats

    Joined:
    Jul 13, 2014
    Posts:
    69
    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.
     
    nico_st_29 likes this.
  9. nico_st_29

    nico_st_29

    Joined:
    Mar 14, 2020
    Posts:
    69
    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.
     
  10. RedEarth

    RedEarth

    Joined:
    Nov 4, 2016
    Posts:
    23
    Thanks for writing this up. It was a big help in implementing my own solution. Cheers!
     
    Yecats likes this.
  11. Segment7Software

    Segment7Software

    Joined:
    Aug 5, 2020
    Posts:
    1
    Really nice tutorial, I will the other you did ;-)

    Keep up the good work.
     
    Yecats likes this.
  12. bakelite

    bakelite

    Joined:
    Jan 13, 2016
    Posts:
    7
    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.
     
  13. bakelite

    bakelite

    Joined:
    Jan 13, 2016
    Posts:
    7
    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.
     
  14. itsLevi0sa

    itsLevi0sa

    Joined:
    Oct 26, 2019
    Posts:
    128
    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) 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:
      Code (CSharp):
      1. public void OnΧΥΖMove(InputAction.CallbackContext context)
      2.     {
      3.         //Read the input value that is being sent by the Input System
      4.         Vector3 value = context.ReadValue<Vector3>();
      5.         _moveDirection = new Vector3(value.x, value.y, value.z);
      6.         _moveTarget += (transform.forward * _moveDirection.z +
      7.                         transform.right   * _moveDirection.x +
      8.                         transform.up      * _moveDirection.y)  * Time.fixedDeltaTime * InternalMoveTargetSpeed;
      9.     }
    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.
      Code (CSharp):
      1. 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:
      Code (CSharp):
      1. private void LateUpdate()
      2.     {
      3.         //Lerp  the camera to a new move target position
      4.         transform.position = Vector3.Lerp(transform.position, _moveTarget, Time.deltaTime * InternalMoveSpeed);
      5.  
      6.         //Move the _actualCamera's local position based on the new zoom factor
      7.         _actualCamera.transform.localPosition = Vector3.Lerp(_actualCamera.transform.localPosition, _cameraPositionTarget,Time.deltaTime * _internalZoomSpeed);
      8.  
      9.         //Set the target rotation based on the mouse delta position and our rotation speed
      10.         //Pitch
      11.         transform.rotation *= Quaternion.AngleAxis(_mouseDelta.y * Time.deltaTime * RotationSpeed, Vector3.left);
      12.         //Yaw
      13.         transform.rotation = Quaternion.Euler(
      14.             transform.eulerAngles.x,
      15.             transform.eulerAngles.y + _mouseDelta.x * Time.deltaTime * RotationSpeed,
      16.             transform.eulerAngles.z
      17.         );
      18.     }
    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.
      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.
      Code (CSharp):
      1. public void OnAccelerate(InputAction.CallbackContext context)
      2.     {
      3.         switch (context.phase)
      4.         {
      5.             case InputActionPhase.Performed:
      6.                 isAccelerating = true;
      7.                 break;
      8.             case InputActionPhase.Canceled:
      9.                 isAccelerating = false;
      10.                 break;
      11.         }
      12.     }
    • 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):
      Code (CSharp):
      1. private void FixedUpdate()
      2.     {
      3.         //Sets the move target position based on the move direction.
      4.         //Must be done here as there's no logic for the input system to calculate holding down an input
      5.         if (isAccelerating == false)
      6.         {
      7.             InternalMoveTargetSpeed = 8;
      8.             _moveTarget += (transform.forward * _moveDirection.z +
      9.                             transform.right   * _moveDirection.x +
      10.                             transform.up      * _moveDirection.y)  * Time.fixedDeltaTime * InternalMoveTargetSpeed;
      11.         }
      12.         else
      13.         {
      14.             InternalMoveTargetSpeed = 40;
      15.             _moveTarget += (transform.forward * _moveDirection.z +
      16.                             transform.right   * _moveDirection.x +
      17.                             transform.up      * _moveDirection.y)  * Time.fixedDeltaTime * InternalMoveTargetSpeed;
      18.         }
      19.     }
    • 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.
    • 7.png
    Hope it helps!

     
    Last edited: Apr 2, 2022