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 Speed loss in loops or pipes though there's no slope physics yet

Discussion in 'Scripting' started by Benji23245, May 23, 2023.

  1. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    When I run in a loop or in a pipe, the player is significantly slowed down. I think it's my player's collider's colliding with the slope that causes the issue but I'm not sure. But the effect is clearly impacted by the smoothness of the curvature.

    Here is a pipe with 20 faces :
    https://gyazo.com/18e2f9c855525630dd5c281993ff6ea3

    And here's one with 100 faces :
    https://gyazo.com/b2d8696c1d55ee1f7522e39673ded194

    There is no slope physics in those videos so accelerating uphill, downhill or on flat ground should make no difference, his speed should increase from 0 to 100. Though in the pipe with 20 faces we see it drop from about 50 to even below 30 at some point.

    On a side note, there's another issue. In the pipe with 100 faces we can see the character clipping a bit through the ground before being corrected out. That seems to disappear when I put the physics functions in Update instead of FixedUpdate. Why ?

    Can you please help ?

    Thanks.


    Code (CSharp):
    1. private void FixedUpdate() {
    2.   OnGround();
    3.   SetRotation();
    4.   Move();
    5. }
    6.  
    7. public void OnGround() {
    8.   float maxLength = 0.5f;
    9.   if (onGround) maxLength *= Mathf.Clamp(RB.velocity.magnitude / 10f, 1f, 5f);
    10.  
    11.   // Get projected velocity for correct setting in Move()
    12.   if (!onGround && Physics.SphereCast(transform.position, playerCollider.radius * 0.9f, -transform.up, out groundHit, maxLength * 2f, collisionMask)) {
    13.     velocityProj = Vector3.ProjectOnPlane(RB.velocity, groundHit.normal);
    14.   } else velocityProj = Vector3.zero;
    15.  
    16.   if (allowGroundRaycast && Physics.Raycast(transform.position, -transform.up, out groundHit, maxLength, collisionMask)) {
    17.     if (RB.velocity.magnitude > 0f) RB.position = groundHit.point + transform.up * 0.25f;
    18.     onGround = true;
    19.     return;
    20.   }
    21.   onGround = false;
    22.   braking = false;
    23. }
    24.  
    25. private void Move() {
    26.   if (onGround) {
    27.     if (velocityProj == Vector3.zero) RB.velocity = transform.forward * RB.velocity.magnitude;
    28.     else RB.velocity = transform.forward * velocityProj.magnitude;
    29.   }
    30.   else RB.velocity = transform.forward.Flatten() * VelocityXZ().magnitude + VelocityY();
    31.  
    32.   float accelAndDragEval = GetEntityOverSpeedEval();
    33.  
    34.   Vector3 accelForce = LeftStickMagnitude() * AccelOverSpeed.Evaluate(accelAndDragEval) * accel * transform.forward;
    35.   Vector3 dragForce = Vector3.zero;
    36.  
    37.   if (LeftStickMagnitude() < 1f && !braking) dragForce = GetDragForce(accelAndDragEval);
    38.  
    39.   Vector3 force = !braking ? accelForce : Vector3.zero;
    40.   force += dragForce;
    41.   RB.AddForce(force, ForceMode.Acceleration);
    42. }
    43.  
    44. private void SetRotation() {
    45.   AlignToGround();
    46.  
    47.   Quaternion camRotation = Quaternion.Euler(Vector3.up * mainCamera.transform.localEulerAngles.y);
    48.   Vector3 camForward = (camRotation * Vector3.forward).Flatten();
    49.   Vector3 camRight = (camRotation * Vector3.right).Flatten();
    50.  
    51.   Vector3 inputDir = (RInput.LeftStick.x * camRight + RInput.LeftStick.y * camForward).normalized;
    52.   Vector3 targetDir = Quaternion.FromToRotation(Vector3.up, transform.up) * inputDir;
    53.  
    54.   float speed = GetVelocity().magnitude;
    55.   float turnAngle = Vector3.Dot(targetDir, transform.forward);
    56.  
    57.   if (LeftStickMagnitude() != 0f) {
    58.     Quaternion targetRotation = Quaternion.LookRotation(targetDir, onGround ? groundHit.normal : Vector3.up);
    59.     float rotSpeed = GetRotSpeed(turnAngle);
    60.     transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotSpeed);
    61.   }
    62. }
    63.  
    64. private void AlignToGround() {
    65.   if (onGround) {
    66.     Quaternion rotationToGround = Quaternion.FromToRotation(transform.up, groundHit.normal);
    67.     transform.rotation = rotationToGround * transform.rotation;
    68.   } else {
    69.     Quaternion upDir = Quaternion.Euler(0f, transform.eulerAngles.y, 0f);
    70.     transform.rotation = Quaternion.Lerp(transform.rotation, upDir, Time.fixedDeltaTime * 8f);
    71.   }
    72. }
    Those are the only ones that should be responsible (if they even are) But I really believe the issue comes from the collider touching the "next slope polygon that has some angle" or something. And I don't know how to deal with that

    Also, the character has a Capsule Collider with center (0, 0.25, 0); radius = 0.25 and height = 1 in Y-axis direction.
     
    Last edited: May 23, 2023
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,718
    You would need to explain exactly how your player is moving for us to have any idea how to help you here.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    What about friction?

    Either way, there will be energy loss / gain, even with 100% bounciness just due to floating point error.

    If you must maintain a steady speed, set it.
     
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Because there are zounds of parameters you ought to set properly. It all varies with your speeds, scales, colliders. And taste!

    Btw what physics functions do you put in Update exactly?
     
  5. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    Sorry about that, I just added the code snippets there. Also, my character has 0 friction/bounciness
     
  6. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    Actually I just removed the Move() and SetRotation() functions and positioned manually my character and applied an instant velocity change and at each curvature change (pipe face) the speed decreased with more or less strength.

    I need to tell the game that velocity should not be lost like this, when encountering such terrain changes but I don't know how :/
    Isn't there a way to ignore those velocity changes or something ?
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Something as simple as this in your FixedUpdate() might serve your purpose:

    Code (csharp):
    1. myRigidbody.velocity = myRigidbody.velocity.normalized * TheSpeedIAlwaysWantNoMatterWhat;
     
  8. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    How do I know what TheSpeedIAlwaysWantNoMatterWhat value is though ? It's impacted by the accelForce every frame. And the drag force too when I don't touch the left stick
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    That would be up to you.

    What does your game design specify?

    You did write this:

    This seems like an incomplete specification because it lacks the where, the why, the when and the amount of time it should take to change.
     
    Bunny83 likes this.
  10. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    I’m making a Sonic Unleashed-like game if that can help ?
     
  11. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    I speak only for myself, but if I was doing Sonic, I would process motion on my own, have forces based on perfect signed distance formulas, my own gravity, my own jump, and absolute control over everything. I'd use physics strictly for detecting collisions and maybe for some other decorative objects, objects you can hit, ragdolls and such. In fact there are very few games that actually use full physics simulation. It is computation-heavy and frankly boring. Not to mention how sensitive it becomes to inaccuracies in mesh quality, frame drops, or high speed situations.
     
  12. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    So you’re saying using AddForce for applying motion is the wrong way of doing and that all I’ve done is bad ?
     
  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Platformer games such as sonic or mario or metroid do not use anything like realistic physics.

    Most fast-twitch games roll their own precisely-tuned physics.

    Some of them may use the underlying Rigidbody physics and build upon it, while others simply do all the work by hand.

    Here's two fine examples from the Very Very Valet game:



     
    orionsyndrome and Bunny83 like this.
  14. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    So I should change all my AddForces to RB.velocity sets ?
     
  15. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    Sorry I’m not sure I really understand what you mean :/
     
  16. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    I prefer setting velocities for these kinds of games. That's what feels right.

    Overall you should engineer a game physics velocity control suitable to what you want for your game.

    Your choices would be to:

    - decompose Sonic and figure out what they are doing and stand up your own solution, either with or without physics

    - find tutorials on making Sonic and get some insights how they did it
     
    orionsyndrome and Yoreki like this.
  17. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,718
    With all due respect this is nonsense.
    • There is nothing in the posted code that allocates any garbage
    • Performance is not really the current concern and while there may be a few repeated or unnecessary calculations here it's hardly the primary concern at the moment.
     
  18. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    I have to say, I’m still I not sure what I should be doing to fix the two issues I stated though ? (The weird speed loss and the little clipping through ground).

    I will see if I change the velocity later, if you say it’s better.

    But I’m not sure it would fix my current issues. Would it ?

    Thanks for your help btw ^^
     
  19. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    Like I said, just using OnGround(), AlignToGround() and setting a velocity directly in play mode (so, setting the RB.velocity directly), shows the issue :/
     
  20. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    Actually I just noticed this : It looks like the AlignToGround() method aligns the player 1 frame too late. This is most certainly the issue :

    In the following images, the character was running down the slope on the left, to the right direction.

    Frame where he should align :
    frameWhereItShouldAlign.png

    Frame where he actually align (1 frame too late) :
    frameWhereItAlignsTooLate.png

    I'm quite confident that this frame, where he's bumping on the ground, is the cause for both the speed loss and the little clipping through ground.

    I tried calling the method first thing in FixedUpdate, and even in Update, but it didn't change anything. How can I make him align on the right frame ?

    Normally, OnGround() runs first, so I should have the groundHit info at the beginning of the frame which I use to rotate the player right after, in AlignToGround(). So why doesn't it work ?
     
  21. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I think what previous posters have been saying is that when dealing with custom physics, you maintain all the values (ergo, the directions, velocities, etc) completely seperate from Unity's physics, and just assign your final value to the rigidbody at the end.

    That way, even if the rigidbody is bumping against things that might slow it dow, you can force it to maintain a specific direction and velocity.
     
    Yoreki likes this.
  22. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    @Benji23245 It's quite obvious that what you're getting is due to how physics works.

    And it's not physics that's faulty, it's just the roughness of your mesh is affecting the virtual object like it would affect a real physical object. However whereas in real world you would have a perfect cylinder, this is not really possible in game engines, at least not in this traditional setup, and so the extremities of your velocities are on the edge of what physics was made for. And imprecisions such as this begin to show up as deep and unsolvable glitches and artifacts.

    I mean people get degree A burns from sliding on the ice-laden bob sleigh track without protection. Who would've thought of that? Well that's physics for you. As I said, it's meant to be realistic, and thus boring for most games. (Disclaimer: I'm not claiming the simulation would produce a high temperature due to friction, that's just an example from the real world.)
     
  23. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    I'm really confused, I removed every AddForce and am just moving my player with this :

    Code (CSharp):
    1. RB.velocity = (RB.velocity.magnitude < maxSpeed ? (RB.velocity.magnitude + accelForceFloat) : maxSpeed) * transform.forward;
    And the problems still stand. I don't know what to do :(
     
  24. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    This is not a problem that's solved by just changing AddForce with .velocity assignments. This is a problem that, as mentioned, requires you to separate your calculations from the rigidbody entirely.

    Super simple example:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [RequireComponent(typeof(Rigidbody))]
    4. public class RigidbodyController : MonoBehaviour
    5. {
    6.     #region Inspector Fields
    7.  
    8.     [SerializeField, Min(0)]
    9.     private float _maxSpeed = 10f;
    10.  
    11.     [SerializeField, Min(0)]
    12.     private float _acceleration = 20f;
    13.  
    14.     #endregion
    15.  
    16.     #region Internal Members
    17.  
    18.     private Rigidbody _rigidbody;
    19.  
    20.     private Vector2 _inputDirection = Vector2.zero;
    21.  
    22.     private Vector3 _velocity = Vector3.zero;
    23.  
    24.     #endregion
    25.  
    26.     #region Unity Callbacks
    27.     private void Awake()
    28.     {
    29.         _rigidbody = GetComponent<Rigidbody>();
    30.     }      
    31.        
    32.     private void Update()
    33.     {
    34.         float xInput = Input.GetAxis("Horizontal");
    35.         float yInput = Input.GetAxis("Vertical");
    36.         _inputDirection = new Vector2(xInput, yInput);
    37.         _inputDirection.Normalize();
    38.     }
    39.  
    40.     private void FixedUpdate()
    41.     {
    42.         bool isInput = _inputDirection != Vector2.zero;
    43.  
    44.         Vector3 targetVelocity;
    45.         if (isInput)
    46.         {
    47.             float x = _inputDirection.x;
    48.             float z = _inputDirection.y; // I hate this
    49.             targetVelocity = new Vector3(x, 0, z);
    50.             targetVelocity *= _maxSpeed;
    51.         }
    52.         else
    53.         {
    54.             targetVelocity = Vector3.zero;
    55.         }
    56.  
    57.         float delta = _acceleration * Time.fixedDeltaTime;
    58.         _velocity = Vector3.MoveTowards(_velocity, targetVelocity, delta);
    59.         Vector3 position = transform.position + _velocity * Time.fixedDeltaTime;
    60.  
    61.         _rigidbody.MovePosition(position);
    62.     }
    63.  
    64.     #endregion
    65. }
    You can throw this on a cube on a plane in a scene and see how it behaves. And you can see that the only point in which we use the rigidbody is when we call
    .MovePosition()
    right at the end. We maintain our own velocity, one that can stay constant, and work with that. Naturally, this cube doesn't slow down when hitting a sharp change in inclination.

    Naturally this can work with
    .velocity
    I just went with MovePosition as its easier to set up in a pinch.

    Again, simple example, but it should show the concept we're trying to illustrate.
     
  25. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    Not sure I understand, I seem to still see speed changes. Do I need to set the object to kinematic maybe ?
     
  26. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    Setting Velocity doesn't work with kinematic rigidbodies, iirc. I believe the accepted workflow for kinematic rigidbodies it to calculate a new position each frame, according to your own code. then call RigidBody.MovePosition with the position that you calculated.

    Edit: in case you were thinking it- Yes, Unity does have a lot of different, confusing options for implementing character movement and it is a big stumbling block for new users. I imagine the ultimate goal is to provide many options for implementing any possible sort of character controller, but it does take a while to learn all the various ins-and-outs.
     
  27. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    You should post a current example of your code.
     
  28. Benji23245

    Benji23245

    Joined:
    May 7, 2020
    Posts:
    127
    Sure, here it is. I had to adapt it a bit because the rotating behavior was just to bad for me to have an idea of the speed loss (didn't turn properly in slopes, and rolled around too (ending up with Y being left or down whatever).


    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class TestMove : MonoBehaviour {
    4.   #region Inspector Fields
    5.  
    6.   [SerializeField, Min(0)]
    7.   private float _maxSpeed = 10f;
    8.  
    9.   [SerializeField, Min(0)]
    10.   private float _acceleration = 20f;
    11.  
    12.   #endregion
    13.  
    14.   #region Internal Members
    15.  
    16.   private Rigidbody RB;
    17.   private RaycastHit groundHit;
    18.   private Camera mainCamera;
    19.  
    20.   private Vector2 _inputDirection = Vector2.zero;
    21.  
    22.   private Vector3 _velocity = Vector3.zero;
    23.  
    24.  
    25.   private bool onGround = false;
    26.  
    27.  
    28.   #endregion
    29.  
    30.   #region Unity Callbacks
    31.   private void Awake() {
    32.     RB = GetComponent<Rigidbody>();
    33.   }
    34.  
    35.   private void Start() {
    36.     mainCamera = Camera.main;
    37.   }
    38.  
    39.   private void Update() {
    40.     float xInput = RInput.LeftStick.x;
    41.     float yInput = RInput.LeftStick.y;
    42.     _inputDirection = new Vector2(xInput, yInput);
    43.     _inputDirection.Normalize();
    44.   }
    45.  
    46.   private void FixedUpdate() {
    47.     OnGround();
    48.     SetRotation();
    49.  
    50.     bool isInput = _inputDirection != Vector2.zero;
    51.  
    52.     Vector3 targetVelocity;
    53.     if (isInput) {
    54.       float x = _inputDirection.x;
    55.       float z = _inputDirection.y; // I hate this
    56.       targetVelocity = new Vector3(x, 0, z);
    57.       targetVelocity *= _maxSpeed;
    58.     } else {
    59.       targetVelocity = Vector3.zero;
    60.     }
    61.  
    62.     float delta = _acceleration * Time.fixedDeltaTime;
    63.     _velocity = Vector3.MoveTowards(_velocity, targetVelocity, delta);
    64.     Vector3 position = transform.position + _velocity * Time.fixedDeltaTime;
    65.  
    66.     RB.MovePosition(position);
    67.   }
    68.  
    69.   public void OnGround() {
    70.     float maxLength = 0.5f;
    71.     if (onGround) maxLength *= Mathf.Clamp(RB.velocity.magnitude / 10f, 1f, 5f);
    72.  
    73.     if (Physics.Raycast(transform.position, -transform.up, out groundHit, maxLength)) {
    74.       onGround = true;
    75.       return;
    76.     }
    77.     onGround = false;
    78.   }
    79.  
    80.   private void SetRotation() {
    81.     AlignToGround();
    82.  
    83.     Quaternion camRotation = Quaternion.Euler(Vector3.up * mainCamera.transform.localEulerAngles.y);
    84.     Vector3 camForward = (camRotation * Vector3.forward).Flatten();
    85.     Vector3 camRight = (camRotation * Vector3.right).Flatten();
    86.  
    87.     Vector3 inputDir = (RInput.LeftStick.x * camRight + RInput.LeftStick.y * camForward).normalized;
    88.     Vector3 targetDir = Quaternion.FromToRotation(Vector3.up, transform.up) * inputDir;
    89.  
    90.     float turnAngle = Vector3.Dot(targetDir, transform.forward);
    91.  
    92.     if (LeftStickMagnitude() != 0f) {
    93.       Quaternion targetRotation = Quaternion.LookRotation(targetDir, onGround ? groundHit.normal : Vector3.up);
    94.       float rotSpeed = 10f;
    95.       transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotSpeed);
    96.     }
    97.   }
    98.  
    99.   private void AlignToGround() {
    100.     if (onGround) {
    101.       Quaternion rotationToGround = Quaternion.FromToRotation(transform.up, groundHit.normal);
    102.       transform.rotation = rotationToGround * transform.rotation;
    103.     } else {
    104.       Quaternion upDir = Quaternion.Euler(0f, transform.eulerAngles.y, 0f);
    105.       transform.rotation = Quaternion.Lerp(transform.rotation, upDir, Time.fixedDeltaTime * 8f);
    106.     }
    107.   }
    108.  
    109.   private float LeftStickMagnitude() {
    110.     return Vector2.ClampMagnitude(RInput.LeftStick, 1f).magnitude;
    111.   }
    112.  
    113.   #endregion
    114. }