Search Unity

New / Different Character Controller Method

Discussion in 'General Discussion' started by Deleted User, Apr 4, 2019.

  1. Deleted User

    Deleted User

    Guest

    Hi Everybody-

    I'm curious if anyone is interested in this besides me. I'd gotten frustrated with using capsule colliders to handle gravity and testing if I'm grounded on my character controller. Last night I had a crazy idea, and low and behold, I implemented it this morning and it works!

    So I have a player prefab with no collider, no character controller. Instead of doing that, I define the load bearing limbs (so for a biped, this would probably be the ankle/foot joint at the end of your leg IK chain), and add an offset to accommodate the distance between the joint center and the skin. Then, I raycast down from each of those joints the distance of my predicted gravity for the next frame. If I collide, I find the lowest point out of those collisions (presumably the lowest limb would be the one holding the lion's share of the character's weight) and translate my root transform down that height.

    As kind of a last step, I can apply the collision points to the IK targets for those limbs to make them stick to uneven surfaces, that way you get climbing of stairs and sloped surfaces with a procedural center of mass.

    Has anyone else played around with something like this? Am I just re-inventing the wheel again for no good reason? Here is the code that does most of the work, minus the IK. I need to add horizontal movement and game logic, and it works like a charm....even with my character when animated.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Kinematica : MonoBehaviour
    6. {
    7.     public Transform[] LoadPoints;
    8.     public float gravity = 8f;
    9.     public float offset = 0.01f;
    10.  
    11.     private Vector3 moveVector = Vector3.zero;
    12.     private float foreGravity = 0f;
    13.     private bool grounded = false;
    14.  
    15.     void Start()
    16.     {
    17.  
    18.     }
    19.  
    20.     // Update is called once per frame
    21.     void Update()
    22.     {
    23.         ForecastGravity();
    24.         TestGrounding();
    25.  
    26.         Move();
    27.     }
    28.  
    29.     void ForecastGravity()
    30.     {
    31.         foreGravity = gravity * Time.deltaTime;
    32.     }
    33.  
    34.     void TestGrounding()
    35.     {
    36.         float lowestPoint = 0f;
    37.         float predictedDist = 0f;
    38.         grounded = false;
    39.         foreach(Transform t in LoadPoints)
    40.         {
    41.             Vector3 testOrigin = t.position;
    42.             testOrigin.y -= offset;
    43.             RaycastHit hit;
    44.             //Add layermask for optimization layer
    45.             if (Physics.Raycast(testOrigin, Vector3.down, out hit, foreGravity))
    46.             {
    47.                 float dist = testOrigin.y - hit.point.y;
    48.                 if (dist <= foreGravity && hit.point.y < lowestPoint)
    49.                 {
    50.                     lowestPoint = hit.point.y;
    51.                     predictedDist = dist;
    52.                 }
    53.                 grounded = true;
    54.             }
    55.         }
    56.  
    57.         if (!grounded)
    58.         {
    59.             moveVector.y -= foreGravity;
    60.         } else
    61.         {
    62.             moveVector.y = -(predictedDist - offset);
    63.         }
    64.     }
    65.  
    66.     void Move()
    67.     {
    68.         transform.Translate(moveVector * Time.deltaTime);
    69.     }
    70. }
    71.  
    Hope that's useful to anyone else who wants to experiment with my implementation. If anyone is at all interested in how the controller turns out, drop me a line and I'd be happy to share.
     
  2. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,634
    Cool. Have you tested it against moving objects? One thing I realized when trying to make my own character controller is that it's probably the most difficult-to-implement and the most important feature in any sort of action game.

    Edit: I ended up using raycasts in my character controller too. At first I was trying to infer whether character landed on the floor, or a slope, or a wall using collision.contacts. Never could get that approach to work well.
     
    Last edited: Apr 4, 2019
  3. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    I haven't made a character controller for ages, but last time I did I believe I also found a casting-based controller to work best, at least for movement purposes.

    I didn't break it up into individual limbs, though. For visuals that makes sense, planting feet and depenetrating arms from walls and stuff like that is great.. For general movement I don't think I'd do that, though. You can often get what you need out of something simpler than you may think.