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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Rigidbody FPS Controller

Discussion in 'Scripting' started by cranky, Jul 16, 2014.

  1. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    Hello guys! I am currently working on a rigidbody FPS controller for a game. I plan to release it for free when it's finished, but I am having a few problems maybe some of you can help with.

    First of all, why use a rigidbody over a character controller? Well, it just feels better. When you play a game like Halo, how do the physics feel? Jumping and moving around feels great and very natural! You feel like part of the world. How about games like CoD? Very basic. No physical interaction. Hey, that's great and all, but what if we want a very physical feel? Then rigidbody it is!

    Before embarking on creating my own rigidbody controller, I tested out the community's ordinary character controllers and the assets that come with Unity. They worked great, but each had a few problems. Off the top of my head, a lot did not support slight air control. They either supported none, or had full speed air control. This wasn't too big of a deal, as I could modify the code to support it. The main problem was sliding down surfaces. If I jumped off a cliff, I would fall at the speed of gravity. However, when I slid down a hill which was too steep to stand on, my character would slide much faster than gravity. Almost twice as fast. Not cool.

    The first big problem I encountered with my rigidbody controller stems from the physical nature of it. For example, if you run up a ramp, you will be launched up into the air when you stop, or reach the top. Or when you run from a flat surface onto a ramp, you will be launched forwards and possibly miss the ramp entirely! So I implemented what I call "fudging".

    Code (CSharp):
    1. // fudging
    2. if (groundedLastFrame && doJump != 2)
    3. {
    4.     RaycastHit hit;
    5.     if (Physics.Raycast(transform.position, Vector3.down, out hit, halfPlayerHeight + (rigidbody.velocity.magnitude * Time.deltaTime), ~0)
    6.         && Vector3.Dot(hit.normal, Vector3.up) > 0.5f)
    7.     {
    8.         rigidbody.AddForce(new Vector3(0.0f, -hit.distance, 0.0f), ForceMode.VelocityChange);
    9.         groundedLastFrame = true;
    10.     }
    11. }
    So this basically says, "if I was grounded last frame and I didn't just jump, raycast downwards and see if there's a surface I can stand on beneath me." The distance of the raycast is calculated by the player's velocity. So essentially, this is an extra downward force keeping the player on the ground. It works decently -- there is some jittering when stopping on a slope, but it works nicely. It's a bit ugly, though. Anyone have any better ideas?

    Another problem: running into an angled wall which is too steep to climb causes jitter from the player bouncing up and down against it :S. This should be fixed once I finish acceleration

    Anyone can take this code and do with as they please. If you make some changes, you should post them here so the code gets better for us all! :)

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. [RequireComponent(typeof(CapsuleCollider))]
    6. [RequireComponent(typeof(Rigidbody))]
    7. public class CrankyRigidBodyController : MonoBehaviour
    8. {
    9.     [Tooltip("How fast the player moves.")]
    10.     public float MovementSpeed = 7.0f;
    11.     [Tooltip("Units per second acceleration")]
    12.     public float AccelRate = 20.0f;
    13.     [Tooltip("Units per second deceleration")]
    14.     public float DecelRate = 20.0f;
    15.     [Tooltip("Acceleration the player has in mid-air")]
    16.     public float AirborneAccel = 5.0f;
    17.     [Tooltip("The velocity applied to the player when the jump button is pressed")]
    18.     public float JumpSpeed = 7.0f;
    19.     [Tooltip("Extra units added to the player's fudge height... if you're rocketting off ramps or feeling too loosely attached to the ground, increase this. If you're being yanked down to stuff too far beneath you, lower this.")]
    20.     public float FudgeExtra = 0.5f; // extra height, can't modify this during runtime
    21.     [Tooltip("Maximum slope the player can walk up")]
    22.     public float MaximumSlope = 45.0f;
    23.  
    24.     private bool grounded = false;
    25.  
    26.     // temp vars
    27.     private float inputX;
    28.     private float inputY;
    29.     private Vector3 movement;
    30.     private float acceleration; // or deceleration
    31.  
    32.     // keep track of falling
    33.     private bool falling;
    34.     private float fallSpeed;
    35.  
    36.     // jump state var:
    37.     // 0 = hit ground since last jump, can jump if grounded = true
    38.     // 1 = jump button pressed, try to jump during fixedupdate
    39.     // 2 = jump force applied, waiting to leave the ground
    40.     // 3 = jump was successful, haven't hit the ground yet (this state is to ignore fudging)
    41.     private byte doJump;
    42.  
    43.     private Vector3 groundNormal; // average normal of the ground i'm standing on
    44.     private bool touchingDynamic; // if we're touching a dynamic object, don't prevent idle sliding
    45.     private bool groundedLastFrame; // was i grounded last frame? used for fudging
    46.     private List<GameObject> collisions; // the objects i'm colliding with
    47.     private Dictionary<int, ContactPoint[]> contactPoints; // all of the collision contact points
    48.  
    49.     // temporary calculations
    50.     private float halfPlayerHeight;
    51.     private float fudgeCheck;
    52.     private float bottomCapsuleSphereOrigin; // transform.position.y - this variable = the y coord for the origin of the capsule's bottom sphere
    53.     private float capsuleRadius;
    54.  
    55.     void Awake()
    56.     {  
    57.         movement = Vector3.zero;
    58.  
    59.         grounded = false;
    60.         groundNormal = Vector3.zero;
    61.         touchingDynamic = false;
    62.         groundedLastFrame = false;
    63.  
    64.         collisions = new List<GameObject>();
    65.         contactPoints = new Dictionary<int, ContactPoint[]>();
    66.  
    67.         // do our calculations so we don't have to do them every frame
    68.         CapsuleCollider capsule = (CapsuleCollider)collider;
    69.         halfPlayerHeight = capsule.height * 0.5f;
    70.         fudgeCheck = halfPlayerHeight + FudgeExtra;
    71.         bottomCapsuleSphereOrigin = halfPlayerHeight - capsule.radius;
    72.         capsuleRadius = capsule.radius;
    73.  
    74.         PhysicMaterial controllerMat = new PhysicMaterial();
    75.         controllerMat.bounciness = 0.0f;
    76.         controllerMat.dynamicFriction = 0.0f;
    77.         controllerMat.staticFriction = 0.0f;
    78.         controllerMat.bounceCombine = PhysicMaterialCombine.Minimum;
    79.         controllerMat.frictionCombine = PhysicMaterialCombine.Minimum;
    80.         capsule.material = controllerMat;
    81.  
    82.         // just in case this wasn't set in the inspector
    83.         rigidbody.freezeRotation = true;
    84.     }
    85.  
    86.     void FixedUpdate()
    87.     {
    88.         // check if we're grounded
    89.         RaycastHit hit;
    90.         grounded = false;
    91.         groundNormal = Vector3.zero;
    92.  
    93.         foreach (ContactPoint[] contacts in contactPoints.Values)
    94.             for (int i = 0; i < contacts.Length; i++)
    95.                 if (contacts[i].point.y <= rigidbody.position.y - bottomCapsuleSphereOrigin && Physics.Raycast(contacts[i].point + Vector3.up, Vector3.down, out hit, 1.1f, ~0) && Vector3.Angle(hit.normal, Vector3.up) <= MaximumSlope)
    96.                 {
    97.                     grounded = true;
    98.                     groundNormal += hit.normal;
    99.  
    100.                 }
    101.  
    102.         if (grounded)
    103.         {
    104.             // average the summed normals
    105.             groundNormal.Normalize();
    106.  
    107.             if (doJump == 3)
    108.                 doJump = 0;
    109.         }
    110.  
    111.         else if (doJump == 2)
    112.             doJump = 3;
    113.  
    114.         // get player input
    115.         inputX = Input.GetAxis("Horizontal");
    116.         inputY = Input.GetAxis("Vertical");
    117.  
    118.         // limit the length to 1.0f
    119.         float length = Mathf.Sqrt(inputX * inputX + inputY * inputY);
    120.  
    121.         if (length > 1.0f)
    122.         {
    123.             inputX /= length;
    124.             inputY /= length;
    125.         }
    126.  
    127.         if (grounded && doJump != 3)
    128.         {
    129.             if (falling)
    130.             {
    131.                 // we just landed from a fall
    132.                 falling = false;
    133.                 this.DoFallDamage(Mathf.Abs(fallSpeed));
    134.             }
    135.  
    136.             // align our movement vectors with the ground normal (ground normal = up)
    137.             Vector3 newForward = transform.forward;
    138.             Vector3.OrthoNormalize(ref groundNormal, ref newForward);
    139.  
    140.             Vector3 targetSpeed = Vector3.Cross(groundNormal, newForward) * inputX * MovementSpeed + newForward * inputY * MovementSpeed;
    141.  
    142.             length = targetSpeed.magnitude;
    143.             float difference = length - rigidbody.velocity.magnitude;
    144.  
    145.             // avoid divide by zero
    146.             if (Mathf.Approximately(difference, 0.0f))
    147.                 movement = Vector3.zero;
    148.  
    149.             else
    150.             {
    151.                 // determine if we should accelerate or decelerate
    152.                 if (difference > 0.0f)
    153.                     acceleration = Mathf.Min(AccelRate * Time.deltaTime, difference);
    154.  
    155.                 else
    156.                     acceleration = Mathf.Max(-DecelRate * Time.deltaTime, difference);
    157.  
    158.                 // normalize the difference vector and store it in movement
    159.                 difference = 1.0f / difference;
    160.                 movement = new Vector3((targetSpeed.x - rigidbody.velocity.x) * difference * acceleration, (targetSpeed.y - rigidbody.velocity.y) * difference * acceleration, (targetSpeed.z - rigidbody.velocity.z) * difference * acceleration);
    161.             }
    162.  
    163.             if (doJump == 1)
    164.             {
    165.                 // jump button was pressed, do jump      
    166.                 movement.y = JumpSpeed - rigidbody.velocity.y;
    167.                 doJump = 2;
    168.             }
    169.  
    170.             else if (!touchingDynamic && Mathf.Approximately(inputX + inputY, 0.0f) && doJump < 2)
    171.                 // prevent sliding by countering gravity... this may be dangerous
    172.                 movement.y -= Physics.gravity.y * Time.deltaTime;
    173.  
    174.             rigidbody.AddForce(new Vector3(movement.x, movement.y, movement.z), ForceMode.VelocityChange);
    175.             groundedLastFrame = true;
    176.         }
    177.  
    178.         else
    179.         {
    180.             // not grounded, so check if we need to fudge and do air accel
    181.  
    182.             // fudging
    183.             if (groundedLastFrame && doJump != 3 && !falling)
    184.             {
    185.                 // see if there's a surface we can stand on beneath us within fudgeCheck range
    186.                 if (Physics.Raycast(transform.position, Vector3.down, out hit, fudgeCheck + (rigidbody.velocity.magnitude * Time.deltaTime), ~0) && Vector3.Angle(hit.normal, Vector3.up) <= MaximumSlope)
    187.                 {
    188.                     groundedLastFrame = true;
    189.  
    190.                     // catches jump attempts that would have been missed if we weren't fudging
    191.                     if (doJump == 1)
    192.                     {
    193.                         movement.y += JumpSpeed;
    194.                         doJump = 2;
    195.                         return;
    196.                     }
    197.  
    198.                     // we can't go straight down, so do another raycast for the exact distance towards the surface
    199.                     // i tried doing exsec and excsc to avoid doing another raycast, but my math sucks and it failed horribly
    200.                     // if anyone else knows a reasonable way to implement a simple trig function to bypass this raycast, please contribute to the thead!
    201.                     if (Physics.Raycast(new Vector3(transform.position.x, transform.position.y - bottomCapsuleSphereOrigin, transform.position.z), -hit.normal, out hit, hit.distance, ~0))
    202.                     {
    203.                         rigidbody.AddForce(hit.normal * -hit.distance, ForceMode.VelocityChange);
    204.                         return; // skip air accel because we should be grounded
    205.                     }
    206.                 }
    207.             }
    208.  
    209.             // if we're here, we're not fudging so we're defintiely airborne
    210.             // thus, if falling isn't set, set it
    211.             if (!falling)
    212.                 falling = true;
    213.  
    214.             fallSpeed = rigidbody.velocity.y;
    215.  
    216.             // air accel
    217.             if (!Mathf.Approximately(inputX + inputY, 0.0f))
    218.             {
    219.                 // note, this will probably malfunction if you set the air accel too high... this code should be rewritten if you intend to do so
    220.  
    221.                 // get direction vector
    222.                 movement = transform.TransformDirection(new Vector3(inputX * AirborneAccel * Time.deltaTime, 0.0f, inputY * AirborneAccel * Time.deltaTime));
    223.  
    224.                 // add up our accel to the current velocity to check if it's too fast
    225.                 float a = movement.x + rigidbody.velocity.x;
    226.                 float b = movement.z + rigidbody.velocity.z;
    227.  
    228.                 // check if our new velocity will be too fast
    229.                 length = Mathf.Sqrt(a * a + b * b);
    230.                 if (length > 0.0f)
    231.                 {
    232.                     if (length > MovementSpeed)
    233.                     {
    234.                         // normalize the new movement vector
    235.                         length = 1.0f / Mathf.Sqrt(movement.x * movement.x + movement.z * movement.z);
    236.                         movement.x *= length;
    237.                         movement.z *= length;
    238.  
    239.                         // normalize our current velocity (before accel)
    240.                         length = 1.0f / Mathf.Sqrt(rigidbody.velocity.x * rigidbody.velocity.x + rigidbody.velocity.z * rigidbody.velocity.z);
    241.                         Vector3 rigidbodyDirection = new Vector3(rigidbody.velocity.x * length, 0.0f, rigidbody.velocity.z * length);
    242.  
    243.                         // dot product of accel unit vector and velocity unit vector, clamped above 0 and inverted (1-x)
    244.                         length = (1.0f - Mathf.Max(movement.x * rigidbodyDirection.x + movement.z * rigidbodyDirection.z, 0.0f)) * AirborneAccel * Time.deltaTime;
    245.                         movement.x *= length;
    246.                         movement.z *= length;
    247.                     }
    248.  
    249.                     // and finally, add our force
    250.                     rigidbody.AddForce(new Vector3(movement.x, 0.0f, movement.z), ForceMode.VelocityChange);
    251.                 }
    252.             }
    253.  
    254.             groundedLastFrame = false;
    255.         }
    256.     }
    257.  
    258.     void Update()
    259.     {
    260.         // check for input here
    261.         if (groundedLastFrame && Input.GetButtonDown("Jump"))
    262.             doJump = 1;
    263.     }
    264.  
    265.     void DoFallDamage(float fallSpeed) // fallSpeed will be positive
    266.     {
    267.         // do your fall logic here using fallSpeed to determine how hard we hit the ground
    268.         Debug.Log("Hit the ground at " + fallSpeed.ToString() + " units per second");
    269.     }
    270.  
    271.     void OnCollisionEnter(Collision collision)
    272.     {
    273.         // keep track of collision objects and contact points
    274.         collisions.Add(collision.gameObject);
    275.         contactPoints.Add(collision.gameObject.GetInstanceID(), collision.contacts);
    276.  
    277.         // check if this object is dynamic
    278.         if (!collision.gameObject.isStatic)
    279.             touchingDynamic = true;
    280.  
    281.         // reset the jump state if able
    282.         if (doJump == 3)
    283.             doJump = 0;
    284.     }
    285.  
    286.     void OnCollisionStay(Collision collision)
    287.     {
    288.         // update contact points
    289.         contactPoints[collision.gameObject.GetInstanceID()] = collision.contacts;
    290.     }
    291.  
    292.     void OnCollisionExit(Collision collision)
    293.     {
    294.         touchingDynamic = false;
    295.  
    296.         // remove this collision and its associated contact points from the list
    297.         // don't break from the list once we find it because we might somehow have duplicate entries, and we need to recheck groundedOnDynamic anyways
    298.         for (int i = 0; i < collisions.Count; i++)
    299.         {
    300.             if (collisions[i] == collision.gameObject)
    301.                 collisions.RemoveAt(i--);
    302.              
    303.             else if (!collisions[i].isStatic)
    304.                 touchingDynamic = true;
    305.         }
    306.  
    307.         contactPoints.Remove(collision.gameObject.GetInstanceID());
    308.     }
    309.  
    310.     public bool Grounded
    311.     {
    312.         get
    313.         {
    314.             return grounded;
    315.         }
    316.     }
    317.  
    318.     public bool Falling
    319.     {
    320.         get
    321.         {
    322.             return falling;
    323.         }
    324.     }
    325.  
    326.     public float FallSpeed
    327.     {
    328.         get
    329.         {
    330.             return fallSpeed;
    331.         }
    332.     }
    333.  
    334.     public Vector3 GroundNormal
    335.     {
    336.         get
    337.         {
    338.             return groundNormal;
    339.         }
    340.     }
    341. }
     
    Last edited: Jul 21, 2014
    DawnosaurDev, Roz1x and RelayRay like this.
  2. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    Edited the code in the original post. Air acceleration is working, so long as you don't use a huge value for it. I will eventually correct my math (probably tomorrow), but regardless -- the feature is working correctly.

    I fixed the old issue where the player would slowlllly slide down a slope while idle on it. If you use this, make sure you mark static geometry as static, or else my code will have to be modified. To prevent sliding, the controller essentially cancels gravity while you are idle on static geometry. On non-static geometry, it is not canceled so it interacts correctly with other rigidbodies. I understand this is kind of a dirty hack, but hey, works perfect for my purpose.

    Next: I half implemented acceleration. It still builds up even while running into a wall. I'll fix this either tomorrow or in the next few days.

    It feels totally usable now at least! Once I fully implement acceleration, jittering against unwalkable slopes will be significantly reduced if not eliminated.

    Please give me feedback and let me know what you guys think!

    EDIT: Air acceleration is almost done. Going to bed now -- will work on it tomorrow.
     
    Last edited: Jul 17, 2014
  3. outtoplay

    outtoplay

    Joined:
    Apr 29, 2009
    Posts:
    741
    Very cool. going for a run, wanna check this out when I get back. Thanks for the post, well commented & clean.
     
  4. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    Alright, this thing just got crazy! Fully implemented air acceleration, ground acceleration, fixed jittering against slopes and a lot of jittering related to fudging, added fall damage and fixed a few bugs and mistakes from earlier versions. Added some public properties to expose stuff you may need for animation. Probably forgetting a few things, but that's the jist of it anyways.

    After a headache inducing day, I realized ContactPoint.normal is not the surface normal, but the normal of contact. This required a hefty dosage of Raycasts which makes me really unhappy with the code, but alas, it still works and with, in my opinion, great performance.

    This controller feels awesome right now! I am extremely happy with it. If you tested my earlier version, make sure you test the new one. It is a million times smoother. In my tests, I am using the following settings:

    Capsule Collider
    -------
    Radius: 0.5
    Height: 2
    Direction: Y-Axis

    Rigidbody
    -------
    Mass: 150
    Drag: 0
    Angular Drag: 0.05
    Use Gravity: yes
    Is Kinematic: no
    Interpolate: None
    Collision Detection: Discrete

    Cranky Rigid Body Controller
    -------
    Movement Speed: 7
    Accel Rate: 20
    Decel Rate: 20
    Airborne Accel: 5
    Jump Speed: 7
    Fudge Extra: 0.5
    Maximum Slope: 50

    Fudge Extra is really important to the feel of your game, so make sure you set it up!

    Anyways, I updated the code in the original post, so you can grab it there. Sorry if my code is a mess! It's been a long day :S. Let me know if you have any questions.
     
  5. outtoplay

    outtoplay

    Joined:
    Apr 29, 2009
    Posts:
    741
    You sir are a madman... got to test this out a bit later. You got a really great handle on this stuff. Props and respect.:cool:
     
  6. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    Glad it looks like I know what I'm doing, haha!
     
  7. Zorranco

    Zorranco

    Joined:
    Jul 15, 2014
    Posts:
    23
    Hello, I am still new to Unity for some things...I think the controller is great, just wondering if it is possible to make a controller like this, but with instant air control, so you can jump and in the middle of the air turn, say 90º degrees instantly (or almost instantly).

    I know the way rigidbody works, via acceleration, makes this a special work... I just wonder if it's possible
     
  8. passerbycmc

    passerbycmc

    Joined:
    Feb 12, 2015
    Posts:
    1,739
    You can directly set velocity with RigidBody.velocity as opposed to using addForce()
     
  9. cranky

    cranky

    Joined:
    Jun 11, 2014
    Posts:
    180
    Just change the airborne acceleration inspector value to the same as the acceleration value. Have both set really high.

    Note that this is an old version of my controller. It has bugs. I plan to sell my new version on the asset store eventually.
     
  10. nbg_yalta

    nbg_yalta

    Joined:
    Oct 3, 2012
    Posts:
    378
    Very nice, thanks! Any updates there?
     
    Last edited: Apr 15, 2016
  11. NightWarrior

    NightWarrior

    Joined:
    Apr 28, 2014
    Posts:
    2
    I have updated the script to the newest version of unity(5.6) for those who still want to use it.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.  
    6. [RequireComponent(typeof(CapsuleCollider))]
    7. [RequireComponent(typeof(Rigidbody))]
    8.  
    9. public class PlayerMover : MonoBehaviour {
    10.  
    11.     [Tooltip("How fast the player moves.")]
    12.     public float MovementSpeed = 7.0f;
    13.     [Tooltip("Units per second acceleration")]
    14.     public float AccelRate = 20.0f;
    15.     [Tooltip("Units per second deceleration")]
    16.     public float DecelRate = 20.0f;
    17.     [Tooltip("Acceleration the player has in mid-air")]
    18.     public float AirborneAccel = 5.0f;
    19.     [Tooltip("The velocity applied to the player when the jump button is pressed")]
    20.     public float JumpSpeed = 7.0f;
    21.     [Tooltip("Extra units added to the player's fudge height... if you're rocketting off ramps or feeling too loosely attached to the ground, increase this. If you're being yanked down to stuff too far beneath you, lower this.")]
    22.     // Extra height, can't modify this during runtime
    23.     public float FudgeExtra = 0.5f;
    24.     [Tooltip("Maximum slope the player can walk up")]
    25.     public float MaximumSlope = 45.0f;
    26.  
    27.     private bool grounded = false;
    28.  
    29.     //Unity Components
    30.     private Rigidbody rb;
    31.     private Collider coll;
    32.  
    33.     // Temp vars
    34.     private float inputX;
    35.     private float inputY;
    36.     private Vector3 movement;
    37.  
    38.     // Acceleration or deceleration
    39.     private float acceleration;
    40.  
    41.     /*
    42.      * Keep track of falling
    43.      */
    44.     private bool falling;
    45.     private float fallSpeed;
    46.  
    47.     /*
    48.      * Jump state var:
    49.      * 0 = hit ground since last jump, can jump if grounded = true
    50.      * 1 = jump button pressed, try to jump during fixedupdate
    51.      * 2 = jump force applied, waiting to leave the ground
    52.      * 3 = jump was successful, haven't hit the ground yet (this state is to ignore fudging)
    53.     */
    54.     private byte doJump;
    55.  
    56.     // Average normal of the ground i'm standing on
    57.     private Vector3 groundNormal;
    58.  
    59.     // If we're touching a dynamic object, don't prevent idle sliding
    60.     private bool touchingDynamic;
    61.  
    62.     // Was i grounded last frame? used for fudging
    63.     private bool groundedLastFrame;
    64.  
    65.     // The objects i'm colliding with
    66.     private List<GameObject> collisions;
    67.  
    68.     // All of the collision contact points
    69.     private Dictionary<int, ContactPoint[]> contactPoints;
    70.  
    71.     /*
    72.      * Temporary calculations
    73.      */
    74.     private float halfPlayerHeight;
    75.     private float fudgeCheck;
    76.     private float bottomCapsuleSphereOrigin; // transform.position.y - this variable = the y coord for the origin of the capsule's bottom sphere
    77.     private float capsuleRadius;
    78.  
    79.     void Awake()
    80.     {
    81.         rb = GetComponent<Rigidbody>();
    82.         coll = GetComponent<Collider>();
    83.  
    84.         movement = Vector3.zero;
    85.  
    86.         grounded = false;
    87.         groundNormal = Vector3.zero;
    88.         touchingDynamic = false;
    89.         groundedLastFrame = false;
    90.  
    91.         collisions = new List<GameObject>();
    92.         contactPoints = new Dictionary<int, ContactPoint[]>();
    93.  
    94.         // do our calculations so we don't have to do them every frame
    95.         CapsuleCollider capsule = (CapsuleCollider)coll;
    96.         Debug.Log(capsule);
    97.         halfPlayerHeight = capsule.height * 0.5f;
    98.         fudgeCheck = halfPlayerHeight + FudgeExtra;
    99.         bottomCapsuleSphereOrigin = halfPlayerHeight - capsule.radius;
    100.         capsuleRadius = capsule.radius;
    101.  
    102.         PhysicMaterial controllerMat = new PhysicMaterial();
    103.         controllerMat.bounciness = 0.0f;
    104.         controllerMat.dynamicFriction = 0.0f;
    105.         controllerMat.staticFriction = 0.0f;
    106.         controllerMat.bounceCombine = PhysicMaterialCombine.Minimum;
    107.         controllerMat.frictionCombine = PhysicMaterialCombine.Minimum;
    108.         capsule.material = controllerMat;
    109.  
    110.         // just in case this wasn't set in the inspector
    111.         rb.freezeRotation = true;
    112.     }
    113.  
    114.     void FixedUpdate()
    115.     {
    116.         // check if we're grounded
    117.         RaycastHit hit;
    118.         grounded = false;
    119.         groundNormal = Vector3.zero;
    120.  
    121.         foreach (ContactPoint[] contacts in contactPoints.Values)
    122.             for (int i = 0; i < contacts.Length; i++)
    123.                 if (contacts[i].point.y <= rb.position.y - bottomCapsuleSphereOrigin && Physics.Raycast(contacts[i].point + Vector3.up, Vector3.down, out hit, 1.1f, ~0) && Vector3.Angle(hit.normal, Vector3.up) <= MaximumSlope) {
    124.                     grounded = true;
    125.                     groundNormal += hit.normal;
    126.  
    127.                 }
    128.  
    129.         if (grounded) {
    130.             // average the summed normals
    131.             groundNormal.Normalize();
    132.  
    133.             if (doJump == 3)
    134.                 doJump = 0;
    135.         } else if (doJump == 2)
    136.             doJump = 3;
    137.  
    138.         // get player input
    139.         inputX = Input.GetAxis("Horizontal");
    140.         inputY = Input.GetAxis("Vertical");
    141.  
    142.         // limit the length to 1.0f
    143.         float length = Mathf.Sqrt(inputX * inputX + inputY * inputY);
    144.  
    145.         if (length > 1.0f) {
    146.             inputX /= length;
    147.             inputY /= length;
    148.         }
    149.  
    150.         if (grounded && doJump != 3) {
    151.             if (falling) {
    152.                 // we just landed from a fall
    153.                 falling = false;
    154.                 this.DoFallDamage(Mathf.Abs(fallSpeed));
    155.             }
    156.  
    157.             // align our movement vectors with the ground normal (ground normal = up)
    158.             Vector3 newForward = transform.forward;
    159.             Vector3.OrthoNormalize(ref groundNormal, ref newForward);
    160.  
    161.             Vector3 targetSpeed = Vector3.Cross(groundNormal, newForward) * inputX * MovementSpeed + newForward * inputY * MovementSpeed;
    162.  
    163.             length = targetSpeed.magnitude;
    164.             float difference = length - rb.velocity.magnitude;
    165.  
    166.             // avoid divide by zero
    167.             if (Mathf.Approximately(difference, 0.0f))
    168.                 movement = Vector3.zero;
    169.  
    170.             else {
    171.                 // determine if we should accelerate or decelerate
    172.                 if (difference > 0.0f)
    173.                     acceleration = Mathf.Min(AccelRate * Time.deltaTime, difference);
    174.  
    175.                 else
    176.                     acceleration = Mathf.Max(-DecelRate * Time.deltaTime, difference);
    177.  
    178.                 // normalize the difference vector and store it in movement
    179.                 difference = 1.0f / difference;
    180.                 movement = new Vector3((targetSpeed.x - rb.velocity.x) * difference * acceleration, (targetSpeed.y - rb.velocity.y) * difference * acceleration, (targetSpeed.z - rb.velocity.z) * difference * acceleration);
    181.             }
    182.  
    183.             if (doJump == 1) {
    184.                 // jump button was pressed, do jump    
    185.                 movement.y = JumpSpeed - rb.velocity.y;
    186.                 doJump = 2;
    187.             } else if (!touchingDynamic && Mathf.Approximately(inputX + inputY, 0.0f) && doJump < 2)
    188.                 // prevent sliding by countering gravity... this may be dangerous
    189.                 movement.y -= Physics.gravity.y * Time.deltaTime;
    190.  
    191.             rb.AddForce(new Vector3(movement.x, movement.y, movement.z), ForceMode.VelocityChange);
    192.             groundedLastFrame = true;
    193.         } else {
    194.             // not grounded, so check if we need to fudge and do air accel
    195.  
    196.             // fudging
    197.             if (groundedLastFrame && doJump != 3 && !falling) {
    198.                 // see if there's a surface we can stand on beneath us within fudgeCheck range
    199.                 if (Physics.Raycast(transform.position, Vector3.down, out hit, fudgeCheck + (rb.velocity.magnitude * Time.deltaTime), ~0) && Vector3.Angle(hit.normal, Vector3.up) <= MaximumSlope) {
    200.                     groundedLastFrame = true;
    201.  
    202.                     // catches jump attempts that would have been missed if we weren't fudging
    203.                     if (doJump == 1) {
    204.                         movement.y += JumpSpeed;
    205.                         doJump = 2;
    206.                         return;
    207.                     }
    208.  
    209.                     // we can't go straight down, so do another raycast for the exact distance towards the surface
    210.                     // i tried doing exsec and excsc to avoid doing another raycast, but my math sucks and it failed horribly
    211.                     // if anyone else knows a reasonable way to implement a simple trig function to bypass this raycast, please contribute to the thead!
    212.                     if (Physics.Raycast(new Vector3(transform.position.x, transform.position.y - bottomCapsuleSphereOrigin, transform.position.z), -hit.normal, out hit, hit.distance, ~0)) {
    213.                         rb.AddForce(hit.normal * -hit.distance, ForceMode.VelocityChange);
    214.                         return; // skip air accel because we should be grounded
    215.                     }
    216.                 }
    217.             }
    218.  
    219.             // if we're here, we're not fudging so we're defintiely airborne
    220.             // thus, if falling isn't set, set it
    221.             if (!falling)
    222.                 falling = true;
    223.  
    224.             fallSpeed = rb.velocity.y;
    225.  
    226.             // air accel
    227.             if (!Mathf.Approximately(inputX + inputY, 0.0f)) {
    228.                 // note, this will probably malfunction if you set the air accel too high... this code should be rewritten if you intend to do so
    229.  
    230.                 // get direction vector
    231.                 movement = transform.TransformDirection(new Vector3(inputX * AirborneAccel * Time.deltaTime, 0.0f, inputY * AirborneAccel * Time.deltaTime));
    232.  
    233.                 // add up our accel to the current velocity to check if it's too fast
    234.                 float a = movement.x + rb.velocity.x;
    235.                 float b = movement.z + rb.velocity.z;
    236.  
    237.                 // check if our new velocity will be too fast
    238.                 length = Mathf.Sqrt(a * a + b * b);
    239.                 if (length > 0.0f) {
    240.                     if (length > MovementSpeed) {
    241.                         // normalize the new movement vector
    242.                         length = 1.0f / Mathf.Sqrt(movement.x * movement.x + movement.z * movement.z);
    243.                         movement.x *= length;
    244.                         movement.z *= length;
    245.  
    246.                         // normalize our current velocity (before accel)
    247.                         length = 1.0f / Mathf.Sqrt(rb.velocity.x * rb.velocity.x + rb.velocity.z * rb.velocity.z);
    248.                         Vector3 rigidbodyDirection = new Vector3(rb.velocity.x * length, 0.0f, rb.velocity.z * length);
    249.  
    250.                         // dot product of accel unit vector and velocity unit vector, clamped above 0 and inverted (1-x)
    251.                         length = (1.0f - Mathf.Max(movement.x * rigidbodyDirection.x + movement.z * rigidbodyDirection.z, 0.0f)) * AirborneAccel * Time.deltaTime;
    252.                         movement.x *= length;
    253.                         movement.z *= length;
    254.                     }
    255.  
    256.                     // and finally, add our force
    257.                     rb.AddForce(new Vector3(movement.x, 0.0f, movement.z), ForceMode.VelocityChange);
    258.                 }
    259.             }
    260.  
    261.             groundedLastFrame = false;
    262.         }
    263.     }
    264.  
    265.     void Update()
    266.     {
    267.         // check for input here
    268.         if (groundedLastFrame && Input.GetButtonDown("Jump"))
    269.             doJump = 1;
    270.     }
    271.  
    272.     void DoFallDamage(float fallSpeed) // fallSpeed will be positive
    273.     {
    274.         // do your fall logic here using fallSpeed to determine how hard we hit the ground
    275.         Debug.Log("Hit the ground at " + fallSpeed.ToString() + " units per second");
    276.     }
    277.  
    278.     void OnCollisionEnter(Collision collision)
    279.     {
    280.         // keep track of collision objects and contact points
    281.         collisions.Add(collision.gameObject);
    282.         contactPoints.Add(collision.gameObject.GetInstanceID(), collision.contacts);
    283.  
    284.         // check if this object is dynamic
    285.         if (!collision.gameObject.isStatic)
    286.             touchingDynamic = true;
    287.  
    288.         // reset the jump state if able
    289.         if (doJump == 3)
    290.             doJump = 0;
    291.     }
    292.  
    293.     void OnCollisionStay(Collision collision)
    294.     {
    295.         // update contact points
    296.         contactPoints[collision.gameObject.GetInstanceID()] = collision.contacts;
    297.     }
    298.  
    299.     void OnCollisionExit(Collision collision)
    300.     {
    301.         touchingDynamic = false;
    302.  
    303.         // remove this collision and its associated contact points from the list
    304.         // don't break from the list once we find it because we might somehow have duplicate entries, and we need to recheck groundedOnDynamic anyways
    305.         for (int i = 0; i < collisions.Count; i++) {
    306.             if (collisions[i] == collision.gameObject)
    307.                 collisions.RemoveAt(i--);
    308.  
    309.             else if (!collisions[i].isStatic)
    310.                 touchingDynamic = true;
    311.         }
    312.  
    313.         contactPoints.Remove(collision.gameObject.GetInstanceID());
    314.     }
    315.  
    316.     public bool Grounded {
    317.         get {
    318.             return grounded;
    319.         }
    320.     }
    321.  
    322.     public bool Falling {
    323.         get {
    324.             return falling;
    325.         }
    326.     }
    327.  
    328.     public float FallSpeed {
    329.         get {
    330.             return fallSpeed;
    331.         }
    332.     }
    333.  
    334.     public Vector3 GroundNormal {
    335.         get {
    336.             return groundNormal;
    337.         }
    338.     }
    339. }
     
  12. unity_z-_IOCddVYbRsA

    unity_z-_IOCddVYbRsA

    Joined:
    Aug 7, 2020
    Posts:
    1
    Thank you for this, the only thing I need is the name of the script to attach it to my player, please respond and thank you. P.S. I'm using the first one posted. Thanks again.
     
  13. Donse10

    Donse10

    Joined:
    May 23, 2019
    Posts:
    2
    The name is CrankyRigidBodyController, but you can edit the name.
    And cranky, thanks for this, you are a madman.
     
  14. SandVampire

    SandVampire

    Joined:
    Oct 5, 2018
    Posts:
    3
    I modified the code to make it work with the new input system (with a PlayerInput component).
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.InputSystem;
    5.  
    6. [RequireComponent(typeof(CapsuleCollider))]
    7. [RequireComponent(typeof(Rigidbody))]
    8. [RequireComponent(typeof(PlayerInput))]
    9.  
    10. public class PlayerMover : MonoBehaviour
    11. {
    12.  
    13.     [Tooltip("How fast the player moves.")]
    14.     public float MovementSpeed = 7.0f;
    15.     [Tooltip("Units per second acceleration")]
    16.     public float AccelRate = 20.0f;
    17.     [Tooltip("Units per second deceleration")]
    18.     public float DecelRate = 20.0f;
    19.     [Tooltip("Acceleration the player has in mid-air")]
    20.     public float AirborneAccel = 5.0f;
    21.     [Tooltip("The velocity applied to the player when the jump button is pressed")]
    22.     public float JumpSpeed = 7.0f;
    23.     [Tooltip("Extra units added to the player's fudge height")]
    24.     // Extra units added to the player's fudge height... if you're rocketting off
    25.     // ramps or feeling too loosely attached to the ground, increase this.
    26.     // If you're being yanked down to stuff too far beneath you, lower this.
    27.     // Thid can't be modified during runtime
    28.     public float FudgeExtra = 0.5f;
    29.     [Tooltip("Maximum slope the player can walk up")]
    30.     public float MaximumSlope = 45.0f;
    31.  
    32.     private bool _isGrounded = false;
    33.     public bool IsGrounded { get => _isGrounded; }
    34.  
    35.     //Unity Components
    36.     private Rigidbody _rigidbody;
    37.     private CapsuleCollider _capsuleCollider;
    38.  
    39.     // Temp vars
    40.     private float _inputX;
    41.     private float _inputY;
    42.     private Vector2 _movementInput;
    43.     private Vector3 _movementVector;
    44.  
    45.     // Acceleration or deceleration
    46.     private float _acceleration;
    47.  
    48.     /*
    49.      * Keep track of falling
    50.      */
    51.     private bool _isFalling;
    52.     public bool IsFalling { get => _isFalling; }
    53.  
    54.     private float _fallSpeed;
    55.     public float FallSpeed { get => _fallSpeed; }
    56.  
    57.     /*
    58.      * Jump state var:
    59.      * 0 = hit ground since last jump, can jump if grounded = true
    60.      * 1 = jump button pressed, try to jump during fixedupdate
    61.      * 2 = jump force applied, waiting to leave the ground
    62.      * 3 = jump was successful, haven't hit the ground yet (this state is to ignore fudging)
    63.     */
    64.     private byte _jumpState;
    65.  
    66.     // Average normal of the ground i'm standing on
    67.     private Vector3 _groundNormal;
    68.     public Vector3 GroundNormal { get => _groundNormal; }
    69.  
    70.     // If we're touching a dynamic object, don't prevent idle sliding
    71.     private bool _touchingDynamic;
    72.  
    73.     // Was i grounded last frame? used for fudging
    74.     private bool _groundedLastFrame;
    75.  
    76.     // The objects i'm colliding with
    77.     private List<GameObject> _collisions;
    78.  
    79.     // All of the collision contact points
    80.     private Dictionary<int, ContactPoint[]> _contactPoints;
    81.  
    82.     /*
    83.      * Temporary calculations
    84.      */
    85.     private float _halfPlayerHeight;
    86.     private float _fudgeCheck;
    87.     private float _bottomCapsuleSphereOrigin; // transform.position.y - this variable = the y coord for the origin of the capsule's bottom sphere
    88.     private float _capsuleRadius;
    89.  
    90.     void Awake()
    91.     {
    92.         _rigidbody = GetComponent<Rigidbody>();
    93.         _capsuleCollider = GetComponent<CapsuleCollider>();
    94.  
    95.         _movementVector = Vector3.zero;
    96.  
    97.         _isGrounded = false;
    98.         _groundNormal = Vector3.zero;
    99.         _touchingDynamic = false;
    100.         _groundedLastFrame = false;
    101.  
    102.         _collisions = new List<GameObject>();
    103.         _contactPoints = new Dictionary<int, ContactPoint[]>();
    104.  
    105.         // do our calculations so we don't have to do them every frame
    106.         Debug.Log(_capsuleCollider);
    107.         _halfPlayerHeight = _capsuleCollider.height * 0.5f;
    108.         _fudgeCheck = _halfPlayerHeight + FudgeExtra;
    109.         _bottomCapsuleSphereOrigin = _halfPlayerHeight - _capsuleCollider.radius;
    110.         _capsuleRadius = _capsuleCollider.radius;
    111.  
    112.         PhysicMaterial controllerMat = new PhysicMaterial();
    113.         controllerMat.bounciness = 0.0f;
    114.         controllerMat.dynamicFriction = 0.0f;
    115.         controllerMat.staticFriction = 0.0f;
    116.         controllerMat.bounceCombine = PhysicMaterialCombine.Minimum;
    117.         controllerMat.frictionCombine = PhysicMaterialCombine.Minimum;
    118.         _capsuleCollider.material = controllerMat;
    119.  
    120.         // just in case this wasn't set in the inspector
    121.         _rigidbody.freezeRotation = true;
    122.     }
    123.  
    124.     void FixedUpdate()
    125.     {
    126.         // check if we're grounded
    127.         RaycastHit hit;
    128.         _isGrounded = false;
    129.         _groundNormal = Vector3.zero;
    130.  
    131.         foreach (ContactPoint[] contacts in _contactPoints.Values)
    132.         {
    133.             for (int i = 0; i < contacts.Length; i++)
    134.             {
    135.                 if (contacts[i].point.y <= _rigidbody.position.y - _bottomCapsuleSphereOrigin &&
    136.                     Physics.Raycast(contacts[i].point + Vector3.up, Vector3.down, out hit, 1.1f, ~0) &&
    137.                     Vector3.Angle(hit.normal, Vector3.up) <= MaximumSlope)
    138.                 {
    139.                     _isGrounded = true;
    140.                     _groundNormal += hit.normal;
    141.  
    142.                 }
    143.             }
    144.         }
    145.  
    146.         if (_isGrounded)
    147.         {
    148.             // average the summed normals
    149.             _groundNormal.Normalize();
    150.  
    151.             if (_jumpState == 3)
    152.                 _jumpState = 0;
    153.         }
    154.         else if (_jumpState == 2)
    155.             _jumpState = 3;
    156.  
    157.         // get player input
    158.         _inputX = _movementInput.x;
    159.         _inputY = _movementInput.y;
    160.  
    161.         // limit the length to 1.0f
    162.         float length = 0;
    163.  
    164.         if (_isGrounded && _jumpState != 3)
    165.         {
    166.             if (_isFalling)
    167.             {
    168.                 // we just landed from a fall
    169.                 _isFalling = false;
    170.                 this.DoFallDamage(Mathf.Abs(_fallSpeed));
    171.             }
    172.  
    173.             // align our movement vectors with the ground normal (ground normal = up)
    174.             Vector3 newForward = transform.forward;
    175.             Vector3.OrthoNormalize(ref _groundNormal, ref newForward);
    176.  
    177.             Vector3 targetSpeed = Vector3.Cross(_groundNormal, newForward) * _inputX * MovementSpeed +
    178.                 newForward * _inputY * MovementSpeed;
    179.  
    180.             length = targetSpeed.magnitude;
    181.             float difference = length - _rigidbody.velocity.magnitude;
    182.  
    183.             // avoid divide by zero
    184.             if (Mathf.Approximately(difference, 0.0f))
    185.                 _movementVector = Vector3.zero;
    186.  
    187.             else
    188.             {
    189.                 // determine if we should accelerate or decelerate
    190.                 if (difference > 0.0f)
    191.                     _acceleration = Mathf.Min(AccelRate * Time.deltaTime, difference);
    192.  
    193.                 else
    194.                     _acceleration = Mathf.Max(-DecelRate * Time.deltaTime, difference);
    195.  
    196.                 // normalize the difference vector and store it in movement
    197.                 difference = 1.0f / difference;
    198.                 _movementVector = (targetSpeed - _rigidbody.velocity) * difference * _acceleration;
    199.             }
    200.  
    201.             if (_jumpState == 1)
    202.             {
    203.                 // jump button was pressed, do jump  
    204.                 _movementVector.y = JumpSpeed - _rigidbody.velocity.y;
    205.                 _jumpState = 2;
    206.             }
    207.             else if (!_touchingDynamic && Mathf.Approximately(_inputX + _inputY, 0.0f) && _jumpState < 2)
    208.                 // prevent sliding by countering gravity... this may be dangerous
    209.                 _movementVector.y -= Physics.gravity.y * Time.deltaTime;
    210.  
    211.             _rigidbody.AddForce(_movementVector, ForceMode.VelocityChange);
    212.             _groundedLastFrame = true;
    213.         }
    214.         else
    215.         {
    216.             // not grounded, so check if we need to fudge and do air accel
    217.  
    218.             // fudging
    219.             if (_groundedLastFrame && _jumpState != 3 && !_isFalling)
    220.             {
    221.                 // see if there's a surface we can stand on beneath us within fudgeCheck range
    222.                 if (Physics.Raycast(transform.position, Vector3.down, out hit, _fudgeCheck +
    223.                     (_rigidbody.velocity.magnitude * Time.deltaTime), ~0) &&
    224.                     Vector3.Angle(hit.normal, Vector3.up) <= MaximumSlope)
    225.                 {
    226.                     _groundedLastFrame = true;
    227.  
    228.                     // catches jump attempts that would have been missed if we weren't fudging
    229.                     if (_jumpState == 1)
    230.                     {
    231.                         _movementVector.y += JumpSpeed;
    232.                         _jumpState = 2;
    233.                         return;
    234.                     }
    235.  
    236.                     // we can't go straight down, so do another raycast for the exact distance towards the surface
    237.                     // i tried doing exsec and excsc to avoid doing another raycast, but my math sucks and it failed
    238.                     // horribly. if anyone else knows a reasonable way to implement a simple trig function to bypass
    239.                     // this raycast, please contribute to the thread!
    240.                     if (Physics.Raycast(new Vector3(transform.position.x,
    241.                         transform.position.y - _bottomCapsuleSphereOrigin,
    242.                         transform.position.z), -hit.normal, out hit, hit.distance, ~0))
    243.                     {
    244.                         _rigidbody.AddForce(hit.normal * -hit.distance, ForceMode.VelocityChange);
    245.                         return; // skip air accel because we should be grounded
    246.                     }
    247.                 }
    248.             }
    249.  
    250.             // if we're here, we're not fudging so we're defintiely airborne
    251.             // thus, if falling isn't set, set it
    252.             if (!_isFalling)
    253.                 _isFalling = true;
    254.  
    255.             _fallSpeed = _rigidbody.velocity.y;
    256.  
    257.             // air accel
    258.             if (!Mathf.Approximately(_inputX + _inputY, 0.0f))
    259.             {
    260.                 // note, this will probably malfunction if you set the air accel too high...
    261.                 // this code should be rewritten if you intend to do so
    262.  
    263.                 // get direction vector
    264.                 _movementVector = transform.TransformDirection(new Vector3(_inputX * AirborneAccel * Time.deltaTime,
    265.                     0.0f, _inputY * AirborneAccel * Time.deltaTime));
    266.  
    267.                 // add up our accel to the current velocity to check if it's too fast
    268.                 float a = _movementVector.x + _rigidbody.velocity.x;
    269.                 float b = _movementVector.z + _rigidbody.velocity.z;
    270.  
    271.                 // check if our new velocity will be too fast
    272.                 length = Mathf.Sqrt(a * a + b * b);
    273.                 if (length > 0.0f)
    274.                 {
    275.                     if (length > MovementSpeed)
    276.                     {
    277.                         // normalize the new movement vector
    278.                         length = 1.0f / Mathf.Sqrt(_movementVector.x * _movementVector.x +
    279.                             _movementVector.z * _movementVector.z);
    280.                         _movementVector.x *= length;
    281.                         _movementVector.z *= length;
    282.  
    283.                         // normalize our current velocity (before accel)
    284.                         length = 1.0f / Mathf.Sqrt(_rigidbody.velocity.x * _rigidbody.velocity.x +
    285.                             _rigidbody.velocity.z * _rigidbody.velocity.z);
    286.                         Vector3 rigidbodyDirection = new Vector3(_rigidbody.velocity.x * length, 0.0f,
    287.                             _rigidbody.velocity.z * length);
    288.  
    289.                         // dot product of accel unit vector and velocity unit vector, clamped above 0 and inverted (1-x)
    290.                         length = (1.0f - Mathf.Max(_movementVector.x * rigidbodyDirection.x +
    291.                             _movementVector.z * rigidbodyDirection.z, 0.0f)) * AirborneAccel * Time.deltaTime;
    292.                         _movementVector.x *= length;
    293.                         _movementVector.z *= length;
    294.                     }
    295.  
    296.                     // and finally, add our force
    297.                     _rigidbody.AddForce(new Vector3(_movementVector.x, 0.0f, _movementVector.z),
    298.                         ForceMode.VelocityChange);
    299.                 }
    300.             }
    301.  
    302.             _groundedLastFrame = false;
    303.         }
    304.     }
    305.  
    306.     void DoFallDamage(float fallSpeed) // fallSpeed will be positive
    307.     {
    308.         // do your fall logic here using fallSpeed to determine how hard we hit the ground
    309.         Debug.Log("Hit the ground at " + fallSpeed.ToString() + " units per second");
    310.     }
    311.  
    312.     void OnCollisionEnter(Collision collision)
    313.     {
    314.         // keep track of collision objects and contact points
    315.         _collisions.Add(collision.gameObject);
    316.         _contactPoints.Add(collision.gameObject.GetInstanceID(), collision.contacts);
    317.  
    318.         // check if this object is dynamic
    319.         if (!collision.gameObject.isStatic)
    320.             _touchingDynamic = true;
    321.  
    322.         // reset the jump state if able
    323.         if (_jumpState == 3)
    324.             _jumpState = 0;
    325.     }
    326.  
    327.     void OnCollisionStay(Collision collision)
    328.     {
    329.         // update contact points
    330.         _contactPoints[collision.gameObject.GetInstanceID()] = collision.contacts;
    331.     }
    332.  
    333.     void OnCollisionExit(Collision collision)
    334.     {
    335.         _touchingDynamic = false;
    336.  
    337.         // remove this collision and its associated contact points from the list
    338.         // don't break from the list once we find it because we might somehow have duplicate entries,
    339.         // and we need to recheck groundedOnDynamic anyways
    340.         for (int i = 0; i < _collisions.Count; i++)
    341.         {
    342.             if (_collisions[i] == collision.gameObject)
    343.                 _collisions.RemoveAt(i--);
    344.  
    345.             else if (!_collisions[i].isStatic)
    346.                 _touchingDynamic = true;
    347.         }
    348.  
    349.         _contactPoints.Remove(collision.gameObject.GetInstanceID());
    350.     }
    351.  
    352.     public void OnMove(InputAction.CallbackContext context)
    353.     {
    354.         _movementInput = context.ReadValue<Vector2>();
    355.     }
    356.  
    357.     public void OnJump(InputAction.CallbackContext context)
    358.     {
    359.         if (_groundedLastFrame)
    360.             _jumpState = 1;
    361.     }
    362. }
     
    DawnosaurDev likes this.
  15. MrKory

    MrKory

    Joined:
    Sep 20, 2013
    Posts:
    66
    Line #316 generates errors if instanced gameObjects are encountered. I am not an adept enough programmer to resolve this... My thoughts to fix it are as follows, but I don't know how to implement it.

    Before the contactPoints.Add... should there be code to determine if this collision has already been added, maybe based off of it's position in the world.

    My errors state... Argument Exception: An item with the same key has already been added.

    Example: I have a terrain with trees and their colliders enabled. I also have a prefab gameObject called rock. I have multiple trees and rocks place in the scene. The first tree I contact generates an error. The first rock does not, however the second rock will.

    Hopefully someone can help me get this fixed. This controller is solid, and fast. Very nice work!!

    Thanks,
     
  16. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,780
    Please don't necro-post to dead old threads.

    Instead, start your own... it's FREE and that's what the forum rules say you must do.

    Before you post, read this:

    How to report your problem productively in the Unity3D forums:

    http://plbm.com/?p=220

    How to understand errors in general:

    https://forum.unity.com/threads/ass...3-syntax-error-expected.1039702/#post-6730855

    If you post a code snippet, ALWAYS USE CODE TAGS:

    How to use code tags: https://forum.unity.com/threads/using-code-tags-properly.143875/
     
    Joe-Censored likes this.
  17. Oskarrrrrr

    Oskarrrrrr

    Joined:
    Aug 8, 2021
    Posts:
    1
    Thx to the guy that made the script BIG thx it works very good