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. Dismiss Notice

Question smoothness of movement when changing direction (front - back / left - right)

Discussion in 'Scripting' started by foxyyhappyw, Jul 15, 2023.

  1. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59
    this is my script for movement (I reworked it based on this: https://assetstore.unity.com/packag...-first-person-character-controller-urp-196525), generally everything works fine about accelerations and decelerations,
    but I still have one problem. when the player quickly clicks W and S or A and D alternately - the movement is very unnatural, the deceleration of the player does not work (the player runs at the same speed right and left or forward and backward.). Can it be fixed somehow? I've tried many methods but I'm out of ideas.

    Movement section from the "FirstPersonController" script:
    Code (CSharp):
    1.         private void Movement()
    2.         {
    3.             float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;
    4.  
    5.             if (_input.move == Vector2.zero)
    6.             {
    7.                 targetSpeed = 0.0f;
    8.             }
    9.  
    10.             float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;
    11.             float speedOffset = 0.1f;
    12.  
    13.             if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
    14.             {
    15.                 _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed, Time.deltaTime * SpeedChangeRate);
    16.                 _speed = Mathf.Round(_speed * 1000f) / 1000f;
    17.             }
    18.             else
    19.             {
    20.                 _speed = targetSpeed;
    21.             }
    22.  
    23.             moveX = Mathf.Lerp(moveX, _input.move.x, Time.deltaTime * InputValueRate);
    24.             moveY = Mathf.Lerp(moveY, _input.move.y, Time.deltaTime * InputValueRate);
    25.  
    26.          
    27.             if (Mathf.Abs(moveX) < 0.001f)
    28.             {
    29.                 moveX = 0.0f;
    30.             }
    31.             if (Mathf.Abs(moveY) < 0.001f)
    32.             {
    33.                 moveY = 0.0f;
    34.             }
    35.  
    36.             Vector3 inputDirection = new Vector3(moveX, 0.0f, moveY).normalized;
    37.  
    38.             if (_input.move != Vector2.zero)
    39.             {
    40.                 inputDirection = transform.right * moveX + transform.forward * moveY;
    41.             }
    42.             else if (currentHorizontalSpeed > 0)
    43.             {
    44.                 _speed -= SpeedChangeRate * Time.deltaTime;
    45.                 _speed = Mathf.Max(_speed, 0);
    46.                 inputDirection = _controller.velocity.normalized;
    47.             }
    48.  
    49.             _controller.Move(inputDirection.normalized * (_speed * Time.deltaTime) + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
    50.         }
    StarterAssetsInputs:
    Code (CSharp):
    1. using UnityEngine;
    2. #if ENABLE_INPUT_SYSTEM
    3. using UnityEngine.InputSystem;
    4. #endif
    5.  
    6. namespace StarterAssets
    7. {
    8.     public class StarterAssetsInputs : MonoBehaviour
    9.     {
    10.         [Header("Character Input Values")]
    11.         public Vector2 move;
    12.         public Vector2 look;
    13.         public bool jump;
    14.         public bool sprint;
    15.  
    16.         [Header("Movement Settings")]
    17.         public bool analogMovement;
    18.  
    19.         [Header("Mouse Cursor Settings")]
    20.         public bool cursorLocked = true;
    21.         public bool cursorInputForLook = true;
    22.  
    23. #if ENABLE_INPUT_SYSTEM
    24.         public void OnMove(InputValue value)
    25.         {
    26.             MoveInput(value.Get<Vector2>());
    27.         }
    28.  
    29.         public void OnLook(InputValue value)
    30.         {
    31.             if(cursorInputForLook)
    32.             {
    33.                 LookInput(value.Get<Vector2>());
    34.             }
    35.         }
    36.  
    37.         public void OnJump(InputValue value)
    38.         {
    39.             JumpInput(value.isPressed);
    40.         }
    41.  
    42.         public void OnSprint(InputValue value)
    43.         {
    44.             SprintInput(value.isPressed);
    45.         }
    46. #endif
    47.  
    48.  
    49.         public void MoveInput(Vector2 newMoveDirection)
    50.         {
    51.             move = newMoveDirection;
    52.         }
    53.  
    54.         public void LookInput(Vector2 newLookDirection)
    55.         {
    56.             look = newLookDirection;
    57.         }
    58.  
    59.         public void JumpInput(bool newJumpState)
    60.         {
    61.             jump = newJumpState;
    62.         }
    63.  
    64.         public void SprintInput(bool newSprintState)
    65.         {
    66.             sprint = newSprintState;
    67.         }
    68.      
    69.         /*
    70.         private void OnApplicationFocus(bool hasFocus)
    71.         {
    72.             SetCursorState(cursorLocked);
    73.         }
    74.         */
    75.  
    76.         public void SetCursorState(bool newState)
    77.         {
    78.             Cursor.lockState = newState ? CursorLockMode.Locked : CursorLockMode.None;
    79.         }
    80.     }
    81.  
    82. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    What on earth is going on there in the first script... in line 36 you're assigning a normalized value to the input direcition, then you're either assigning the motion inputs (apparently not normalized) or else the existing velocity, normalized... back to the ... inputs?!

    I cannot even reason about that... It's Friday.

    All I can say is I know that the following BasicFPCC has the important advantage that it Just Works(tm).

    https://forum.unity.com/threads/a-basic-first-person-character-controller-for-prototyping.1169491/

    That one has run, walk, jump, slide, crouch... it's crazy-nutty!!

    For smoothing it just relies on the old input system properly set up.

    If that's not settable uppable by you for any reason, here is how to smoothly change values, ANY values:

    Smoothing movement between any two particular values:

    https://forum.unity.com/threads/beginner-need-help-with-smoothdamp.988959/#post-6430100

    You have currentQuantity and desiredQuantity.
    - only set desiredQuantity
    - the code always moves currentQuantity towards desiredQuantity
    - read currentQuantity for the smoothed value

    Works for floats, Vectors, Colors, Quaternions, anything continuous or lerp-able.

    The code: https://gist.github.com/kurtdekker/fb3c33ec6911a1d9bfcb23e9f62adac4
     
  3. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    510
    Input.GetAxis() is good for giving smooth/gradual acceleration. And in the Input Manager there is a 'Snap' setting that can be disabled and then gives a smooth gradual transition when switching from left to right or forward and back.
     
    foxyyhappyw and Kurt-Dekker like this.
  4. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    620
    The code is extremely messy but that aside, the main problem is that you smooth out the Speed but not the Direction.

    If you are accelerating forwards and decide that you want to go to the right, mid-speed, it will keep the speed and go to the right at 0.5 speed, continuing the acceleration.
    Like with a car - just because you turn the steering wheel, you are not losing any speed.
    Driving a car is different to walking.

    So what you need to do is to remove 99% of your code and replace it with:

    Code (CSharp):
    1.  
    2. Vector3 inputXZ = new Vector3(_input.move.x, 0, _input.move.y);
    3. if (inputXZ.magnitude > 1) inputXZ.Normalize();
    4. Vector3 targetVelocity = inputXZ * targetSpeed;
    5. Vector3 currentVelocity = _controller.velocity;
    6. Vector3 resultVelocity = Vector3.MoveTowards(currentVelocity, targetVelocity, acceleration * Time.deltaTime);
    7. _controller.Move(resultVelocity);
    8.  
    You define a target velocity based on your input and then lerp the current velocity of your character to this one using "MoveTowards". (you could also use Vector3.Lerp but then the acceleration starts high and gets less and less which is not how changing walking movement works).

    Also be careful with naming things - MoveSpeed should be called WalkSpeed, because running is also a way to "move".

    "InputValueRate" should not be required at all.
    The whole IF is not required.
    Also when you normalize the input, you can't walk slow with a controller, you should avoid that and write it like in my example above instead. I am normalizing the value if it's greater than 1 to prevent a player to move faster when moving diagonally.
     
    Kurt-Dekker likes this.
  5. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59


    I don't like BasicFPCC, movement works very unnaturally. My movement script, even if it is badly written, has a better end result.
     
  6. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59
    Well, when I tried to use your code, nothing works at all lol

    as for the variable names, most of them are from the asset
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Then debug it and find out why!!

    And if you can't get Hannibal's code working, go debug your own code!

    Debugging is not this "Oh gosh, I hope I don't ever have to debug!" type of thing.

    Debugging is part and parcel of software engineering. You WILL learn how to do it and you WILL do it or you won't be a software engineer.

    Time to start debugging! Here is how you can begin your exciting new debugging adventures:

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the names of the GameObjects or Components involved?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer for iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    "When in doubt, print it out!(tm)" - Kurt Dekker (and many others)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.
     
  8. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    620
    "Nothing works at all" ... uhmmm

    You know the basics right? Variables, functions, brackets, ...
    And you know that code runs line by line.
    And you have set up your IDE correctly so it shows compile errors?
    Reading your own code line by line, then reading my code line by line should be enough to understand which lines you have to replace. It's the same as reading the text-lines of this comment.
    Each line, each sentence, says something, in code, each line does something, but ultimately it's the same thing.

    I don't know what you expect from these forums - we're not gonna code your game, we will just help with issues.
     
  9. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59
    I finally got something like this and everything works fine

    Code (CSharp):
    1. void UpdateMove()
    2.         {
    3.             isGrounded = Physics.CheckSphere(groundCheck.position, 0.2f, ground);
    4.  
    5.             Vector2 targetDir = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
    6.             targetDir.Normalize();
    7.  
    8.             currentDir = Vector2.SmoothDamp(currentDir, targetDir, ref currentDirVelocity, moveSmoothTime);
    9.  
    10.             velocityY += gravity * 2f * Time.deltaTime;
    11.  
    12.             Vector3 velocity = (transform.forward * currentDir.y + transform.right * currentDir.x) * Speed + Vector3.up * velocityY;
    13.  
    14.             _controller.Move(velocity * Time.deltaTime);
    15.  
    16.             if (isGrounded! && _controller.velocity.y < -1f)
    17.             {
    18.                 velocityY = -8f;
    19.             }
    20.         }