Search Unity

Rotate Quadruped Character based on terrain angle

Discussion in 'Physics' started by HappySylveon, Aug 21, 2017.

  1. HappySylveon

    HappySylveon

    Joined:
    Aug 10, 2017
    Posts:
    11
    Hello,

    I have a four legged character that I'm trying to make it rotate depending on the terrain (like when going up a hill the character should face up), see image below:



    Here is my current code, I am using Character Controller component.

    Code (CSharp):
    1. public class PlayerController1 : MonoBehaviour
    2. {
    3.      [System.Serializable]
    4.      public class MoveSettings
    5.      {
    6.          public float forwardVel = 10f;      // walk speed
    7.          public float rotateVel = 100;       // character rotation speed, character can walk 360 degree
    8.          public float jumpVel = 25f;
    9.          public LayerMask ground;
    10.          public Transform backLeft;   // back left feet
    11.          public Transform backRight;  // back right feet
    12.          public Transform frontLeft;  // front left feet
    13.          public Transform frontRight; // front left feet
    14.      }
    15.      [System.Serializable]
    16.      public class PhysicsSettings
    17.      {
    18.          public float downAccel = 0.75f;     // down speed when not grounded
    19.      }
    20.      public MoveSettings moveSettings = new MoveSettings();
    21.      public PhysicsSettings physicsSettings = new PhysicsSettings();
    22.      private Vector3 velocity = Vector3.zero;
    23.      private Quaternion targetRotation;
    24.      private CharacterController cc;
    25.      private float forwardInput, turnInput, jumpInput = 0;
    26.      private RaycastHit lr;
    27.      private RaycastHit rr;
    28.      private RaycastHit lf;
    29.      private RaycastHit rf;
    30.      private Vector3 upDir;
    31.      /// <summary>
    32.      /// Rotate towards terrain slope
    33.      /// </summary>
    34.      public void RotateTowardsGround()
    35.      {
    36.          // we have four feet
    37.          Physics.Raycast(moveSettings.backLeft.position + Vector3.up, Vector3.down, out lr);
    38.          Physics.Raycast(moveSettings.backRight.position + Vector3.up, Vector3.down, out rr);
    39.          Physics.Raycast(moveSettings.frontLeft.position + Vector3.up, Vector3.down, out lf);
    40.          Physics.Raycast(moveSettings.frontRight.position + Vector3.up, Vector3.down, out rf);
    41.          upDir = (Vector3.Cross(rr.point - Vector3.up, lr.point - Vector3.up) +
    42.                   Vector3.Cross(lr.point - Vector3.up, lf.point - Vector3.up) +
    43.                   Vector3.Cross(lf.point - Vector3.up, rf.point - Vector3.up) +
    44.                   Vector3.Cross(rf.point - Vector3.up, rr.point - Vector3.up)
    45.                  ).normalized;
    46.          Debug.DrawRay(rr.point, Vector3.up);
    47.          Debug.DrawRay(lr.point, Vector3.up);
    48.          Debug.DrawRay(lf.point, Vector3.up);
    49.          Debug.DrawRay(rf.point, Vector3.up);
    50.          transform.forward = upDir;
    51.      }
    52.      public bool Grounded()
    53.      {
    54.          return cc.isGrounded;
    55.      }
    56.      private void Start()
    57.      {
    58.          targetRotation = transform.rotation;
    59.          cc = GetComponent<CharacterController>();
    60.      }
    61.      private void Update()
    62.      {
    63.          GetInput();     // movement input keys
    64.          Turn();         // character movement direction input
    65.      }
    66.      private void FixedUpdate()
    67.      {
    68.          Run();  // calculate the velocity to be applied on character controller, stored in the velocity variable
    69.          Jump(); // code for jumping
    70.        
    71.          cc.Move(transform.TransformDirection(velocity) * Time.deltaTime);
    72.          RotateTowardsGround();
    73.      }
    74.      private void GetInput()
    75.      {
    76.          forwardInput = Input.GetAxis("Vertical");
    77.          turnInput = Input.GetAxis("Horizontal");
    78.          jumpInput = Input.GetAxisRaw("Jump");
    79.      }
    80.      private void Turn()
    81.      {
    82.          targetRotation *= Quaternion.AngleAxis(moveSettings.rotateVel * turnInput * Time.deltaTime, Vector3.up);
    83.          transform.rotation = targetRotation;
    84.      }
    85.      public void Jump()
    86.      {
    87.          if (jumpInput > 0 && Grounded())
    88.          {
    89.              velocity.y = moveSettings.jumpVel;
    90.          }
    91.          else if (jumpInput == 0 && Grounded())
    92.          {
    93.              velocity.y = 0;
    94.          }
    95.          else
    96.          {
    97.              velocity.y -= physicsSettings.downAccel;
    98.          }
    99.      }
    100.      private void Run()
    101.      {
    102.          velocity.z = moveSettings.forwardVel * forwardInput;
    103.      }
    Currently the RotateTowardsGround() is not working correctly or is conflicting with the movement code, the character stutters and doesn't move correctly, here are my values for the public variables and character controller:

     
  2. BakeMyCake

    BakeMyCake

    Joined:
    May 8, 2017
    Posts:
    175
    Transform.forward will make an object face the upDir, are you sure it's what you want? If UpDir is the normal of the surface then setting Transform.forward this way would make the character look up.

    You're doing crossproducts when you can get the normal of the surface from RaycastHit.normal.
    https://docs.unity3d.com/ScriptReference/RaycastHit-normal.html

    Are you using readings from 4 appendages because you want the character to not only remain upright relative to the surface, but also take into account cases where not all of the feet are on the same surface? Have you tried doing a simple scalar average of the four normals?
     
  3. HappySylveon

    HappySylveon

    Joined:
    Aug 10, 2017
    Posts:
    11
    Well its conflicting with my other movement code, the Y-axis turning in particular, I'm trying to achieve this: http://answers.unity3d.com/questions/168097/orient-vehicle-to-ground-normal.html

    I could make it into one ray cast only but I thought one on each feet would be more accurate, however not sure if I want this anymore, I mean it would work good for a vehicle, but for a character it seems unfitting, but at the same time appearing clipped into terrain or floating mid-air a bit is also not good...