Skating physics problem

Started by
4 comments, last by lawnjelly 5 years, 8 months ago

Hello y'all

I'm making a small skating game, mostly because I'm interested in mechanics but I'm stuck with an orientation problem I can't figure out.

To adapt the rotation of the skater with respect to its environment, I break the logic into several components:
 

  • A PhysicRotation : Returns the Quaternion to go from the skater's transform.up to the normal of the point he's at

    Quaternion GetPhysicsRotation()
        {
            Vector3 target_vec = Vector3.up;
            Ray ray = new Ray(transform.position, Vector3.down);
            RaycastHit hit;
     
            if(Physics.Raycast(ray, out hit, 1.05f*height))
            {
                target_vec = hit.normal;
            }
     
            return Quaternion.FromToRotation(transform.up, target_vec);
        }
  • A velocity rotation: So that the character faces the direction he's going. This rotation is contrained to a plane

    Quaternion GetVelocityRot()
        {
            Vector3 vel = rb.velocity;
            if(vel.magnitude > 0.2f)
            {
                vel.y = 0;
                Vector3 dir = transform.forward;
                dir.y = 0;
                Quaternion vel_rot = Quaternion.FromToRotation(dir.normalized, vel.normalized);
                return vel_rot;
            }
            else
                return Quaternion.identity;
        }
  • And finally, I an InputRotation that rotates the force applied for moving
  • All of this is then tied in this function:

    void SkaterMove(Vector2 inputs)
        {
       
     
            Quaternion PhysicsRotation = aerial ? Quaternion.identity : GetPhysicsRotation(); // Rotation according to ground normal
            Quaternion VelocityRotation = GetVelocityRot();
            Quaternion InputRotation = Quaternion.identity;
            Quaternion ComputedRotation = Quaternion.identity;
     
     
            if(inputs.magnitude > 0.1f)
            {
                Vector3 adapted_direction = CamToPlayer(inputs);
                Vector3 planar_direction = transform.forward;
                planar_direction.y = 0;
                InputRotation = Quaternion.FromToRotation(planar_direction, adapted_direction);
     
                if(!aerial)
                {
                    Vector3 Direction = InputRotation*transform.forward*Speed;
                    rb.AddForce(Direction);
                }
            }
     
            ComputedRotation = PhysicsRotation*VelocityRotation*transform.rotation;
            transform.rotation = Quaternion.Lerp(transform.rotation, ComputedRotation, RotatingSpeed*Time.deltaTime);
     
     
     
        }

The thing works rather well, but always stumbles on one specific case. Here's a video I made the explain it better:

Basically, when in the quarter, if the character's speed carries him in the air, he does a strange spin and and breaks its flow. I can't figure out where in the code I'm allowing it to do this. I've tried various tests, but I can't figure out where the problem is.

Could anyone point me in the good direction ?

Thanks !

Advertisement

Am not sure exactly what you meant as the particular problem (maybe you can tell us which seconds in the video), but the behaviour at the top of the ramp is sometimes a bit snappy. At the top of the ramps I would be suspecting GetPhysicsRotation() isn't getting a hit and it is jumping to using Vector3.up as target_vec, so it seems to be flipping between orientating straight up, and orientating to match the ramp. There is also this variable 'aerial' which doesn't seem clear where it is from.

Hey, thanks for answering. At 0:30, I demonstrate that with slow speed, everything's fine. However, slighthly before at 0:25, there's the problem I'm talking about. Also happens at 0:39.

Aerial is just a boolean. Here's the function:


void CheckPhysics()
	{
		Ray ray = new Ray(transform.position, -transform.up); 
		RaycastHit hit; 

		if(Physics.Raycast(ray, out hit, 1.05f*height))
		{
			if(aerial)
			{
				VelocityOnLanding(); 
			}
			aerial = false; 
		}
		else
		{
			aerial = true; 
			rb.velocity += Vector3.down*AdditionalGravity; 
		}

	}

 

1 hour ago, MehdiUBP said:

dir.y = 0; Quaternion vel_rot = Quaternion.FromToRotation(dir.normalized, vel.normalized);

Notice that if dir = (0,+/-1,0), the normalization would fail. (but this does not cause your problems.)

 

1 hour ago, MehdiUBP said:

if(Physics.Raycast(ray, out hit, 1.05f*height)) { target_vec = hit.normal; }

This is a problem, because the result can change widely in an instant. You can prevent this by a simple temporal filter:

static target_vec(0,1,0);

target_vec = target_vec * 0.98 + hit.normal * 0.02;

target_vec.Normalize();

so the vector changes smoothly. You should get rid of all discontinuities by using this practice everywhere. 

But be warned that this simple lerp bevhaviour depends on the timestep. If you have variable timesteps, you need something more advanced.

 

To get on overall better alignment of the player, i'd try to keep it more upwards even at the ramp, and i would make it lean against acceleration. (It's not velocity that matters here, but acceleration.)

This would look more like natural balancing. So you would use this for the upper body target, and the legs should be set by IK.

 

Aside from just reading the code and trying to work out what is going wrong (and especially when your logic gets more complex), this is something I personally would probably be doing by the numbers: I.e. lots of debugging output, so you can pinpoint exactly when and why this is occurring.

As JoeJ says there is also the problem of variable timesteps, my preferred solution to this is to fix your timestep.

There are a number of sub-systems here which go to determine the final orientation, it can be a good idea to test these independently and make sure they are all working 'as expected' (especially the ray test), rather than trying to test the system as a whole.

This topic is closed to new replies.

Advertisement