Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Fixing third person controller

Discussion in 'Scripting' started by pappalardodd, Jun 15, 2020.

  1. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    Hi everyone. I am using this script to move with third person character controller. With this script my character rotates in the direction he walks, and walks in the direction of the camera.
    My problem is that when I rotate the camera, the character moves late to the camera. To fix it I can increase the rotation speed on Quaternion.Lerp, but after that I would have the horizontal and vertical rotations of the character instantaneous and ugly.
    I think I have something wrong with this script or need to add a script to the camera.
    The effect I want is the classic one of many games in which by rotating the camera the character rotates together (and not after) with this one, and after seeing a thousand useless tutorials, I ask the community for help here. Thanks to who will reply.

    my script of the moevement:

    Code (CSharp):
    1. loat Horizontal = Input.GetAxisRaw("Horizontal");
    2. float Vertical = Input.GetAxisRaw("Vertical");
    3.  
    4. var directionRot = movement;
    5. directionRot.y = 0;
    6.  
    7. if (Controller.isGrounded){
    8. movement = (Camera.main.transform.right * Horizontal + Camera.main.transform.forward * Vertical).normalized;
    9. var Rot = Quaternion.LookRotation(directionRot);
    10. transform.rotation = Quaternion.Lerp(transform.rotation, Rot, 0.2f);}
    11.  
    12. var gravity = 30f;
    13. movement.y -= gravity * Time.deltaTime;
    14. Controller.Move(movement * speed * Time.deltaTime)
     
  2. WarmedxMints

    WarmedxMints

    Joined:
    Feb 6, 2017
    Posts:
    1,035
    The first thing I would do is cache the camera transform. Camera.main is a slow lookup.

    Other than that, your lerp call is what determines how quickly the character rotates. I would use Time.deltaTime times a rotation speed variable that you can adjust in the inspector until you are happy with it.

    Although you character is rotating to the directional movement and not the camera movement.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,779
    Traditionally there is a player control script and a separate camera control script.

    It doesn't necessarily have to be this way, but it helps to enforce a clean break between areas of concern. Moving the player and moving the camera are completely different tasks.

    Take a look at some existing third person setups (even the one in the Unity Standard Assets package, for instance), and see how they do it. These scripts are not very complicated to understand when you are running them in the editor, as well as perhaps outputting some of their internal values with Debug.Log() so you can see what's going on.
     
  4. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    thanks for your answer. Can you give me an example of "caching the camera transformation"? .... on Lerp I can also use Time.delta time and a variable to set the rotation speed, but the problem is that if I increase the rotation speed until it is in sync with the camera, its rotations towards the movement become too fast and practically the character teleports instead of rotating.
    I also noticed that if I rotate the camera slowly the character follows it well, but if I rotate quickly there is this delay.
     
  5. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    But I already use a script for movements and one for the camera, this is my camera

    Code (CSharp):
    1. public GameObject player;
    2.      private Camera cam;
    3.      private Transform playerTransform;
    4.    
    5.      public float distanza = 3.0f;      
    6.      public const float Y_ANGOLO_Min = -120.0f;    
    7.      public const float Y_ANGOLO_Max = 80.0f;
    8.      public float sensibilitàX = 4.0f;  
    9.      public float sensibilitàY = 1.0f;
    10.      public float correnteX = 0f;
    11.      public float correnteY = 0f;                
    12.  
    13.     // Start is called before the first frame update
    14.     void Start()
    15.     {
    16.       cam = Camera.main;
    17.     }
    18.  
    19.     // Update is called once per frame
    20.     void Update()
    21.     {
    22.         correnteX += Input.GetAxisRaw("Mouse X") * sensibilitàX;  
    23.         correnteY += Input.GetAxisRaw("Mouse Y") * sensibilitàY;    
    24.  
    25.         correnteY = Mathf.Clamp(correnteY, Y_ANGOLO_Min, Y_ANGOLO_Max);
    26.    
    27.     }
    28.     void LateUpdate()
    29.     {
    30.         Vector3 dir = new Vector3(0, 0, -distanza);                        
    31.  
    32.         Quaternion rotation = Quaternion.Euler(correnteY, correnteX, 0);     //valore rotazione
    33.         cam.transform.position = player.transform.position + rotation * dir;  //posizione camera
    34.         cam.transform.LookAt(player.transform.position);  
    35.  
    36.     }
    37. }
    38.  
     
  6. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    Determine the movement of the player by the rotation of the camera looks strange for me... Normally you move the player by the normal input and then adjust the camera accordingly to the players position or orientation.

    This kind of camera only makes sense for me, if the player sits in some kind of "control unit" (with a camera) and then he controls a missile which has this script attached (so the missile reacts to the camera commands)
     
    Last edited: Jun 15, 2020
  7. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    so, what would you do, can you give me an example with a script?
     
  8. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    For prototyping i would use the third person controller from the old unity standard assets, otherwise i would use a fully implemented controller from the asset store. Developing a good controller is much much work.There are also some good free controllers in the unity example projects or in the asset store. To take these and adapt to your own requirements saves alot of time.

    Here's an updated version of the unity standard asset 3rd person controller:
    https://github.com/Unity-Technologies/Standard-Assets-Characters
     
    Last edited: Jun 15, 2020
  9. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    This is an asset but I would be interested in understanding the mechanism for setting a correct direction of movement in the third person and I was unable to see anything in there regarding the scripts to understand better.

    Unfortunately, I am learning a lot about unity but I can't understand how to create the right movement inputs for a third person.

    For example, you wrote to me that determining the player's movement through the rotation of the camera is strange, and therefore I should move the player with the normal input and then adjust the camera according to the position.
    If I create a normal movement input like:
    Code (CSharp):
    1. movement = new Vector3(Horizontal, 0, Vertical).normalized;
    In a game where I can rotate the camera around the character How can I make sure that the character walks with considering the camera position?
     
  10. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    I looked into some controller code and i must correct my statement a little but. They use the camera rotation to rotate the player in the direction of the camera orientation.

    For the movement vector this code is interesting:
    Code (CSharp):
    1. inputVector = new Vector3(Horizontal, 0, Vertical);
    2. inputVector = inputVector.normalized * Mathf.Max(Mathf.Abs(inputVector.x), Mathf.Abs(inputVector.z)); // Prevent the diagonal from moving faster.
    3. Vector3 eulerAngles = player.transform.rotation.eulerAngles;
    4. eulerAngles.x = eulerAngles.z = 0;
    5. inputVector = Quaternion.Euler(eulerAngles) * inputVector;
    6. // inputVector can be added to the rigidbody.velocity of the player for instance
    For the rotation of the player in relation to the camera there exists many different code and different situtations how the player wants to control the movement. But here's one way to rotate the player accordingly to the camera:
    Code (CSharp):
    1. Vector3 eulerRotation = player.transform.eulerAngles;
    2. Quaternion lookRotation = camera.tranform.rotation;
    3. eulerRotation.y = Quaternion.LookRotation(lookRotation * inputVector.normalized).eulerAngles.y;
    4. eulerRotation.y = Mathf.LerpAngle(player.transform.eulerAngles.y, eulerRotation.y, rotationSpeed * Time.deltaTime);
    5. player.transform.eulerAngles = eulerRotation;
     
    Last edited: Jun 16, 2020
  11. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    I have proved that your script is assuming that the camera is the main room, and that the "player" is the transformation of the character, the character thus rotates on himself.
    Anyway for a simpler configuration I am using this:
    Code (CSharp):
    1. movement = new Vector3(h, 0, v).normalized;
    2. movement = cam.TransformDirection(movement);  // cam is a public transform hooked on camera main.
    3.  
    4. var rot = movement;
    5. rot.y = 0f;
    6. var movemenRot = Quaternion.LookRotation(rot);
    7. transform.rotation = Quaternion.Slerp(transform.rotation, movemenRot, Time.deltaTime * speedRot);
    There is always that little delay but as I wrote I don't think it depends on the rotation speed (which is okay already, and even increasing it, the rotation starts late compared to the rotation chamber only that rotates faster so the problem is not that) . While the effect in games with these inputs is as if the character were the child of the camera (rotation occurs together with the camera, without affecting the normal rotation speed of the character). I post a video to clarify better, watch for example from minute 1.15 to 1.30. The camera rotates and the player rotates at the same time as if he were a child.




    Going forward a few seconds you also see the camera rotating and the character not being affected by the rotation. As if the character, keeps the axis on which it moves (but so I don't understand how, it is oriented).

    Sorry for the long post but, as ignorant on the subject, I would like to understand
     
  12. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    In short are 2 different situations here. In the game from this video and in other games it exists often 2 different modi:
    - mode a) player rotates if the camera rotates
    - mode b) player dont rotate if the camera rotates

    Often these to modes work together and are controled for instance by pressing a key (strg) on the keyboard and rotates the mouse so:
    - mode a) player presses strg + mouse rotate + forward (arrow up)
    -> so the player and run forward and can look around independently with the mouse but the player runs still into the same direction
    - mode b) player only rotates mouse + forward (arrow up)
    -> here if the mouse rotates the player rotates its direction too
    (the code i posted is for mode b)

    If you want that the camera follows the players rotation occur with the camera, then simply set the camera rotation to the rotation of the player in the camera script (but this has some strange side effect if you try to run backwards, so i assume you must handle this separatly then):
    Code (CSharp):
    1. camera.transform.localRotation = player.transform.localRotation;
    I have tested this in the standard asset 3d person controller, it works for "left" "right" and "forward" input but not for backward input ... also i lost here the ability of the camera to rotate around the player with the mouse (because i always synced the camera with the player): Here you must always decide which "case" the camera controller should handle. For instance if the camera is allowed to freely rotate if GetAxis(""Horizontal"") and GetAxis("Vertical") is null etc.
     
    Last edited: Jun 17, 2020
  13. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    Thanks a lot for the answer. For the method (a) how to set it? ... in short, if I wanted to use the combination strg + mouse rotate + forward (arrow up) .. at this point it is a fake orientation, with the camera becoming independent and the character going on with a similar input?

    Code (CSharp):
    1. movement = (transform.forward *  Vertical);
     
  14. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    For method a) the direction of the player is only determined by the players input and the current direction of the player. Here the camera only rotates around the player and not influences the players direction anymore.
     
    Last edited: Jun 18, 2020
  15. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    I have tried today to script a little easy camera and player controller for the CharacterController component (could be used for a Rigidbody component too). After some time its working now.

    I used as little code as possible, for these 2 classes so they are perfect for learning or
    own adjustments.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityStandardAssets.CrossPlatformInput;
    4. using UnityEngine;
    5.  
    6. //[RequireComponent(typeof(Rigidbody))]
    7. [RequireComponent(typeof(CharacterController))]
    8. public class SimplePlayerController : MonoBehaviour
    9. {
    10.  
    11.     public Camera m_camera;
    12.     public float moveSpeed = 0.1f;
    13.     private Vector3 currentInput;
    14.     private Vector3 inputVector;
    15.     private CharacterController characterController;
    16.     private SimpleCameraController cameraController;
    17.  
    18.     void Start()
    19.     {
    20.         characterController = GetComponent<CharacterController>();
    21.         cameraController = m_camera.GetComponent<SimpleCameraController>();
    22.     }
    23.  
    24.     void Update()
    25.     {
    26.         // read inputs
    27.         float h = CrossPlatformInputManager.GetAxis("Horizontal");
    28.         float v = CrossPlatformInputManager.GetAxis("Vertical");
    29.         currentInput = new Vector3(h, 0, v);
    30.     }
    31.  
    32.     public Vector3 GetCurrentInputVector()
    33.     {
    34.         return currentInput;
    35.     }
    36.  
    37.     void FixedUpdate()
    38.     {
    39.         inputVector = currentInput * moveSpeed;
    40.  
    41.         inputVector.x = 0; // no diagonal movement, because player will be driven by the vertical input
    42.  
    43.         Vector3 eulerAngles = this.transform.rotation.eulerAngles;
    44.         eulerAngles.x = eulerAngles.z = 0;
    45.         inputVector = Quaternion.Euler(eulerAngles) * inputVector; // input vector relativ to player and world space
    46.         characterController.Move(inputVector);
    47.         cameraController.RotatePlayer();
    48.         cameraController.PositionCamera();
    49.     }
    50.  
    51. }
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SimpleCameraController : MonoBehaviour
    6. {
    7.     public Transform player;
    8.     public Vector3 cameraOffset = new Vector3(0, -0.9f, 3.8f);
    9.     public float rotationSpeed = 2f;
    10.     public float followSpeed = 25f;
    11.     private SimplePlayerController simplePlayerController;
    12.     private CharacterController charController;
    13.  
    14.     // Start is called before the first frame update
    15.     void Start()
    16.     {
    17.         simplePlayerController = player.gameObject.GetComponent<SimplePlayerController>();
    18.         charController = player.gameObject.GetComponent<CharacterController>();
    19.     }
    20.  
    21.     // Position the camera to match the player
    22.     public void PositionCamera()
    23.     {
    24.         transform.position = Vector3.Lerp(transform.position, player.transform.position - player.transform.TransformDirection(cameraOffset), Time.fixedDeltaTime * followSpeed);
    25.         transform.LookAt(new Vector3(player.transform.position.x, transform.position.y, player.transform.position.z));
    26.     }
    27.  
    28.     // Rotates the player to match the input
    29.     public void RotatePlayer()
    30.     {
    31.         // not rotate the player if no user input
    32.         Vector3 inputVector = simplePlayerController.GetCurrentInputVector();
    33.         if ((inputVector.x != 0) || (inputVector.z != 0))
    34.         {
    35.             // not rotate the player if vertical input is negativ
    36.             // this isn't perfect because the player cant run a backward loop
    37.             // the solution was to flip the vertical input here
    38.             if (inputVector.z < 0) inputVector.z = -inputVector.z;
    39.  
    40.             Vector3 eulerRotation = player.transform.eulerAngles;
    41.             Quaternion lookRotation = this.transform.rotation;
    42.             eulerRotation.y = Quaternion.LookRotation(lookRotation * inputVector.normalized).eulerAngles.y;
    43.             eulerRotation.y = Mathf.LerpAngle(player.transform.eulerAngles.y, eulerRotation.y, rotationSpeed * Time.fixedDeltaTime);
    44.             player.transform.eulerAngles = eulerRotation;
    45.         }
    46.     }
    47. }
     
    Last edited: Jun 18, 2020
  16. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    ok i have try your code. In fact it is a control system that works, but in this way am I wrong, or are the rotations of the character blocked? .. in short, in this way horizontal rotations cannot be implemented.
    Right now I am using a character that has independent rotations (rotate in the walking direction) so I need other settings.
    In any case, it is very useful for genres such as 3D platformers, working on it I think a perfect controller can come out.
    With slightly different settings it would also be perfect for a classic third-person horror.
     
  17. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    Yes the character should only rotate in walking direction with this code (vertical rotation). The horizontal rotation is blocked because in many 3d (or 1st) person controllers the character isnt allow to rotate "forward" or "backward" (bend forward or bend backward).
     
  18. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    Thanks to the replies received, as per advice, I'm trying to make the controller movements independent of the camera, but I don't know how to use multiple rotations together.
    At the moment I have a character that rotates in the direction of movement:

    Code (CSharp):
    1. var rot = (x, y, z); //motion vector
    2.   rot.y = 0f;
    3.   var movementRot = Quaternion.LookRotation(rot);
    4.   transform.rotation = Quaternion.Lerp(transform.rotation, movementRot, Time.deltaTime * speedRot);}
    I would like to rotate its Y rotation axis, also with the mouse X axis (to have those rotations that the character had when the camera rotated)

    Code (CSharp):
    1. float r = Input.GetAxisRaw("Mouse X");
    2.   float Xrot = Input.GetAxisRaw("Mouse X") * speedRotation;
    3.    transform.Rotate(0, Xrot, 0);
    The methods work alone but together they do not. I have already tried converting transform.Rotate into a quaternion to multiply:

    Code (CSharp):
    1. var Xrotation = Quaternion.Euler(0f, Xrot, 0f);
    2. transform.rotation = Xrotation * movementRot;
    The result is a character that does not rotate on itself (it is withheld when it tries). I tried the inverse and converted the quaternion into degrees to add to transform. Rotate. But it didn't help:
    Code (CSharp):
    1. float angle = Quaternion.Angle(transform.rotation, movementRot)//The result is perfect is on its own on transform.Rotate it works
    2.  
    3. transform.Rotate(0f angle + Xrot, 0f); //Together, however, the character rotates at times and turns, on itself only to one side
    Anyone know if it's possible to do it somehow ...?
     
  19. pappalardodd

    pappalardodd

    Joined:
    Apr 13, 2020
    Posts:
    96
    Hi sorry if I answer now, I just wanted to tell you that in the end I solved for my character movements. I did it all over again and based the movement on the rotation of the character (the character goes on in the direction in which the transform is rotating).
    Everything seems to be working perfectly, but actually I'm having trouble inserting the default rotation animations (rotate 90 or 180 degrees). I activate the animation but before activating the character follows its normal rotation and the animation is in the wrong direction. If I can fix this place all, so I have your opinion :)
     
  20. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    Good to hear, but isnt this the solution from the controller i posted too? I think its a valid solution.

    For the animation its important if they have root motions or not. If they have a root motion and you dont disable it then the animation itself drives the player into the direction of this root motion.

    So here you have to decide if the player is driven my script movement or by the movement that comes from the animation. If you only want to move the player by script then you have to "bake" existing root motions into the animations (this is a setting in the unity animation importer)
    Root motions can be a movement in "xz", "y" or a rotation. But perhaps you know all that already.

    Recently i programmed a rotation of a gameobect driven by mouse inputs (by 2 axes) and the above quaternion equations produced the strangest results, so i would use them only with care. Most of the time only the rotation around 1 axes are working as assumed.

    At the end i used this function:
    https://docs.unity3d.com/ScriptReference/Transform.RotateAround.html

    This function produced correct results and if you pass here the player coordinates and call this on the camera transform, then the camera should rotate around the player in the right way.
     
  21. Zer0Cool

    Zer0Cool

    Joined:
    Oct 24, 2014
    Posts:
    203
    The controller i posted was for using without any root animations. If you want to use root animations then the controller shouldnt move or rotate the players transform.

    You simply need 3 root animations for "player forward", "player turn right" and "player turn left" (or you mirror the "player turn right")

    Here's a good and simple tutorial to setup a basic version:


    Simply it does the following:
    The (root) animation "player forward" moves the player transform forward and plays the animation at the same time:
    The (root) animation "player turn right" moves the player to the side and rotates him in the direction to the right.

    By the "blend tree" the animator mixes the forward and sideways movements of these 2 animations so the player moves more in a big circle if the player input is Vertical and Horizontal.

    Of course another way you could go is to "bake" the root animations into your animations at import time. Then the animation wouldnt move or rotate the players transform and in this case the controller script have to drive the players transform again.
    Also you could mix root animations and controller move. For instance if you dont have the "turn left "or "turn right" animations then the controller could do the rotation and the forward root animation could do the forward movement .. so lots of possibilites :)

    Here another vid that shortly explain what is root motion:
     
    Last edited: Jun 27, 2020
  22. NitroZoron

    NitroZoron

    Joined:
    Jun 29, 2020
    Posts:
    42
    Edit: I'm just a Flooging idiot, I was suppose to do
    Code (CSharp):
    1. mathf.Rad2Deg + CamObj
    but accidentaly did * instead of +

    whenever I press w my character goes down towards the X-axis, this is the video I used to follow

    I followed the video step by step but it didn't help with my characters movement...My character doesn't go towards where my camera is pointing is what I'm trying to say I think it may be my code

    Code (CSharp):
    1. public CharacterController Controller;
    2.  
    3. void Update()
    4.     {
    5. float horizontal = Input.GetAxisRaw("Horizontal");
    6.         float vertical = Input.GetAxisRaw("Vertical");
    7.  
    8.         Vector3 direction = new Vector3(horizontal, 0f, vertical).normalized;
    9.  
    10.         if(direction.magnitude >= 0.1f)
    11.         {
    12.             float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg * CamObj.eulerAngles.y;
    13.             float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, turnSmoothTime);
    14.             transform.rotation = Quaternion.Euler(0f, angle, 0f);
    15.  
    16.            Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
    17.             Controller.Move(moveDir.normalized * speed * Time.deltaTime);
    18.  
    19.         }
    20.  
    21.     }
    22.  
    here's an Image of my Hierarchy

    Thanks
     

    Attached Files:

    Last edited: Nov 14, 2020