Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by OrdinaryDev83 · Oct 16, 2016 at 10:49 PM · rigidbodies

Rigidbody FPS Controller using hit.normal problem

Hello Unity users, I'm making an FPS so I would like to achieve the best FPS controller that handles smooth slopes limit, anti-slope sliding, ect... So I wrote this script : (see gifs below) using UnityEngine; using System.Collections;

 [RequireComponent (typeof (Rigidbody))]
 public class Player_Movement: MonoBehaviour {
     [Header("Components")]
     public Rigidbody rb;
     [Header("Stats")]
     public float speed;
     public float runFactor;
     public bool grounded;
     [Header("Offsets")]
     public LayerMask layers;
     public float speedScale;
     [Header("Run-Time values")]
     public Vector2 rawAxis;
     public Vector3 normalizedAxis;
     public Vector3 smoothedAxis;
     public Vector3 localAxis;
     RaycastHit hit;
     public Vector3 force;
 
 
     void Start (){
         
     }
 
     void Update (){
         rawAxis = new Vector2 (Input.GetAxisRaw ("Horizontal"), Input.GetAxisRaw ("Vertical"));
         normalizedAxis = new Vector3 (rawAxis.x, 0f, rawAxis.y).normalized;
         smoothedAxis = new Vector3 (Mathf.SmoothStep (smoothedAxis.x, normalizedAxis.x, Time.deltaTime * 15f), 0f, Mathf.SmoothStep (smoothedAxis.z, normalizedAxis.z, Time.deltaTime * 15f));
         localAxis = transform.TransformDirection (smoothedAxis);
     }
 
     void FixedUpdate () {
         float totalSpeed = speed * speedScale;
 
         var draggedVel = Vector3.zero;
         draggedVel.x = rb.velocity.x * 0.75f;
         draggedVel.y = rb.velocity.y;
         draggedVel.z = rb.velocity.z * 0.75f;
 
         rb.velocity = draggedVel;
 
         Debug.DrawRay (transform.position, Vector3.down * 1f);
         if(Physics.Raycast(transform.position, Vector3.down, out hit, 100f, layers)){
             Debug.DrawRay (hit.point, hit.normal * 0.5f, Color.magenta, 2f);
         }
 
         force = Vector3.Cross (Vector3.Cross (hit.normal, localAxis), hit.normal) * totalSpeed;
 
         rb.AddForce (force, ForceMode.VelocityChange);
     }
 
     void OnDrawGizmos(){
         Gizmos.color = Color.green;
         Gizmos.DrawSphere (hit.point + force, 0.3f);
     }
 }

When I'm walking down slopes, it's working like a charm, but I go up a slope it's doing a weird thing, and I don't know why. If you don't see me right click on me then press open in another tab If you don't see me right click on me then press open in another tab_ The green sphere is the result of "hit.point + force".

If someone could help me it would be appreciable, yours sincerely, Tom.

Comment
Add comment · Show 1
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image oswin_c · Oct 17, 2016 at 02:52 AM 0
Share

I replied, but the moderation queue means you won't see it for a while. It seems that comments on a user's post don't have that queue, so I will give a similar answer here.

I tested this code in game, found the problem, and fixed it. The problem is actually a subtle one. Your math is fine, your method is fine. But you apply the force even when the character is airborne (which is half of the frames or something, knowing PhysX), you will simply keep going up forever and ever because you are getting the same global force value forever and ever.

Your rigid body constantly loses contact with the ground, but it doesn't know that, so it applies whatever the last successful force was, and it keeps doing it because the hit.normal is valid. Even when you lose contact with the ground (y > 100f), the character continues flying, because your hit.normal still exists, it is just using the last one that existed.

I have two suggestions here. First, a suggested workaround. Then, a suggestion about coding these kinds of things.

$$anonymous$$gested workaround: To fix this, I did the following. -Created a global boolean airborne = false. -Set, in FixedUpdate, raycast distance to 2f. -In FixedUpdate(), added: if (!airborne) rb.AddForce(...); -Used OnCollisionEnter() and OnCollisionEnd() to set airborne to false and true respectively.

$$anonymous$$gestion about coding: Don't make the variables you are using in an update calculation globals unless you have a good reason, because it makes debugging harder. You can have global variables be set by the ones you're using so you can view them normally (e.g inspectorForce = force). Nothing will help you find a problem faster than a wall of null reference exceptions.

1 Reply

· Add your reply
  • Sort: 
avatar image
1
Best Answer

Answer by oswin_c · Oct 17, 2016 at 05:00 AM

I tested it in my Unity and fixed it.

The problem is actually a subtle one. Your math is fine (I think -- I replaced the vector triple product with a quaternion.fromtorotation), your work is fine. Your mistake is subtle -- your force vector is a global variable, and so is your raycasthit.

As you move up a slope, any time when your rigidbody loses connection with the terrain (This will happen CONSTANTLY, because of computational errors) , you just keep applying that force. You keep applying the same force vector again and again forever, causing your character to fly in the direction of the last force vector it had when it lost contact with the ground.

Making the problem worse, is the choice of 100f as a raycast distance, causing your raycast to keep updating as if you were still on the ground. I cannot think of a good reason to have a raycast to get a floor normal be greater than your character's height.

To fix it, I did the following:

D

efined a boolean airborne, defaulted to false. In FixedUpdate(), only add a force to the rigidbody if airborne is false. Also, set the raycasthit to 2f. Added the following below FixedUpdate():

 void OnCollisionEnter(Collision asdf)
 {
     airborne = false;
 }
 
 void OnCollisionEnd(Collision asdf)
 {
     airborne = true;
 }

This causes the rigidbody to stop applying a force, so the player moves up the slope just fine.

Word of warning: You are going down a dark path with a rigid body character controller. The Unity character controller can be extended to work with most games. If you MUST use a rigid body character controller (I had to make one because, and only because, my game is set on a space station and gravity is not a constant, but changes based on position), be wary. First, your method of dealing with slopes works great for terrain, but test it on hard edges (e.g ramps) before you get too excited. Second... steps. Steps. With rigid bodies. Should be easy, right?

Welcome to hell.

Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image OrdinaryDev83 · Oct 17, 2016 at 06:12 AM 0
Share

Thanks for your anwser! I'll read it completely when i'll get back from school.

avatar image OrdinaryDev83 · Oct 17, 2016 at 11:09 AM 0
Share

Thank you so much for your anwser, It might be my only answer I got from my Unity Answer experience :'(, anyway I didn't decide to choose the dark path, I chose the Character Controller method, it needs more vector knowledge, but I can do it. Here is my actual code :

 using UnityEngine;
 using System.Collections;
 
 [RequireComponent (typeof (CharacterController))]
 public class Player_$$anonymous$$ovement: $$anonymous$$onoBehaviour {
     [Header("Components")]
     public CharacterController cc;
     [Header("Stats")]
     public float speed;
     public float runFactor;
     public bool grounded;
     [Header("Offsets")]
     public Layer$$anonymous$$ask layers;
     public float speedScale;
     public float jumpForce;
     [Header("Run-Time values")]
     public Vector2 rawAxis;
     public Vector3 normalizedAxis;
     public Vector3 smoothedAxis;
     public Vector3 localAxis;
     RaycastHit hit;
     public Vector3 force;
     public float verticalForce;
 
 
     void Start (){
         
     }
 
     void Update (){
         rawAxis = new Vector2 (Input.GetAxisRaw ("Horizontal"), Input.GetAxisRaw ("Vertical"));
         normalizedAxis = new Vector3 (rawAxis.x, 0f, rawAxis.y).normalized;
         smoothedAxis = new Vector3 ($$anonymous$$athf.SmoothStep (smoothedAxis.x, normalizedAxis.x, Time.deltaTime * 15f), 0f, $$anonymous$$athf.SmoothStep (smoothedAxis.z, normalizedAxis.z, Time.deltaTime * 15f));
         localAxis = transform.TransformDirection (smoothedAxis);
 
         grounded = cc.isGrounded;
         float totalSpeed = speed * speedScale;
 
         Debug.DrawRay (transform.position, Vector3.down * 1f);
         if (Physics.Raycast (transform.position, Vector3.down, out hit, 2f, layers)) {
             Debug.DrawRay (hit.point, hit.normal * 0.5f, Color.magenta, 2f);
         }
 
         force = Vector3.Cross (Vector3.Cross (hit.normal, localAxis), hit.normal) * totalSpeed;
 
         if (grounded) {
             verticalForce = 0f;
             if (Input.Get$$anonymous$$eyDown ($$anonymous$$eyCode.Space)) {
                 verticalForce = jumpForce;
             }
         }
 
         verticalForce += $$anonymous$$athf.Sqrt (Time.deltaTime) * Physics.gravity.y / 2f; //2f is a scale
         var forceWithGravity = new Vector3(force.x, verticalForce, force.z);
 
         cc.$$anonymous$$ove (forceWithGravity * Time.deltaTime);
     }
 
     void OnDrawGizmos(){
         Gizmos.color = Color.green;
         Gizmos.DrawSphere (hit.point + force, 0.3f);
         Gizmos.DrawCube (hit.point + force, new Vector3 (0.05f, 5f, 0.05f));
     }
 }

I just changed the method "FixedUpdate" to "Update", I learnt that it's more efficient.

avatar image oswin_c · Oct 17, 2016 at 08:31 PM 0
Share

If you are using a character controller, you do not need to use vector triple products to deal with slopes. All of that math is already encoded in the character controller.

All you need to do is use: cc.$$anonymous$$ove(localAxis*TotalSpeed*Time.deltaTime). No fussing with vectors, no vector triple product, no nothing.

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

58 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Simplistic Colliders for enemies 1 Answer

Do you know how to move rigidbodies like they are children, whilst allowing them to collide with the level? 2 Answers

Simulating wheels without a wheel collider? 1 Answer

How do I apply drag rigidbody? 2 Answers

Collision and 2D 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges