What could cause this line here to not function properly? Code (csharp): grounded = (controller.Move(totalMovement ) CollisionFlags.Below) !=0; What happens is that when I'm in the air, grounded is false (ok), but when I'm on the ground, instead of being constantly true, it changes it's state every frame.
Are you applying gravity or other movement every frame? If so that's normal; the movement causes the character controller to go slightly below ground level, causing a collision; the collision causes a reaction that pushes the character controller back up, a little above ground level. The cycle repeats frame by frame. You should be able to fix it by not moving the controller down when it's already grounded.
Yes, I have gravity, that's applied, but it's applied only if grounded is false, else it would be: if(grounded) { gravityVar = 0; } But since it's using grounded to check if it should be modified, I assume what happens is some sort of loop. Maybe if((controller.Move(totalMovement ) CollisionFlags.Below) !=0) gravityVar = 0; will fix it. And no, "" is another thing.
Listen to what laurie said, only backwards ; what you're experiencing is completely expected. You need to apply gravity no matter what.
But if I apply gravity all the time, it would result in an unreallistic behavior. The gravity will keep multiplying even if I'm on the ground, so if I try to drop from some place (not with a jump), the player will start falling immediately with a huge speed. That's why my gravity becomes 0 when the player touches the ground. On the other hand, I tried it your way and it worked, but the gravity now is acting strange.
No, you code around that. Yes, there always has to be a nonzero downward velocity when falling (minDownwardVelocity in this code), but it doesn't have to be large enough to be noticeable, I guarantee you. There's no way that I know of to not have to do this kind of thing, when using a Character Controller. The example in the docs doesn't account for it; I'm interested in whether any example projects do, but not interested enough to wade through them, considering I got sick of all the weirdness of Character Controllers and started using Rigidbodies. (This is based on the CharacterController.Move documentation, and worked great for me.) Code (csharp): if (controller.isGrounded moveDirection.y <= 0) moveDirection.y = minDownwardVelocity; else moveDirection.y -= gravity * Time.deltaTime;
Yes, that's what I did after posting. So if you say that's okay, I'm cool with it. I really wanted to use a rigidbody in the beginning, but I couldn't get it to work well. It kept rotating on the Z and X when it was on steep surfaces no matter if I had locked the rotation with a script or with something else. I should try to use a rigidbody for my player again. For example I made my controller slide of steep surfaces, it works great, but still if the controller used the physics, it would be much more reallistic. Sorry I got off topic, just got interested in what you said
I'm having the same problem... I am using Debug.Log to print the "grounded" status of my character. When standing or running, my log keeps printing the status going back and forth between grounded and not grounded. The issue is that I cannot jump reliably. I have an "if (grounded) then perform the jump" logic in my code. Any suggestions for me? Thanks in advance! Jason
Please let me know if anyone has any suggestions on this. I spent all day on this yesterday and still can't find a reasonable solution.
I tried this bit of code and included it in my ThirdPersonController.js script. I am still having the problem where "Not Grounded" is the status more often than "Grounded." The net result is that I cannot jump most of the time because my code thinks that my character is not grounded. If I keep hitting the jump button enough, eventually the player character will jump. I will include my code so maybe your more experienced eye can spot where I am going wrong with this. One moment and I will post the code...
Here you go: Code (csharp): // The smoothing factor for speed settings private var speedSmoothing : float = 10.0; // The speed that the player rotates when turning around private var rotateSpeed : float = 700.0; // Input variables private var inputVAxis : float = 0.0; private var inputHAxis : float = 0.0; private var jumpButtonWasPressed : boolean = false; private var jumpButtonIsPressed : boolean = false; // Store the camera system object into a static variable, since it won't change. private var cameraSystem : CameraSystemScript; // The previous move direction in x-z - We record this to effectively control the // player based on the last "controlling camera" until the timeout occurs for the // "controlling camera" or if the player changes direction. private var previousMoveDirection : Vector3 = Vector3.zero; // The current move direction in x-z private var currentMoveDirection : Vector3 = Vector3.zero; // Stores the "forward" vector for the player, so we always know which way he is facing. private var playerDirection : Vector3; // Is the user pressing any keys? private var isMoving : boolean = false; // Stores the current analog stick magnitude private var currentAnalogStickMagnitude : float = 0.0; // The various move speeds, usage depending on the analog stick magnitude private var moveSpeed1 : float = 0.4; private var moveSpeed2 : float = 0.55; private var moveSpeed3 : float = 0.7; private var moveSpeed4 : float = 0.85; private var moveSpeed5 : float = 1.0; private var moveSpeed6 : float = 1.15; private var moveSpeed7 : float = 1.3; private var moveSpeed8 : float = 1.45; private var moveSpeed9 : float = 1.6; // The gravity for the character private var playerGravity : float = 8.0; // How high do we jump when pressing jump and letting go immediately private var jumpHeight : float = 0.5; // We add extraJumpHeight meters on top when holding the button down longer while jumping private var extraJumpHeight : float = 2.5; // Are we jumping? (Initiated with jump button and not grounded yet) private var isJumping : boolean = false; private var jumpingReachedApex : boolean = false; // The height we jumped from (Used to determine for how long to apply extra jump power after jumping.) private var lastJumpStartHeight : float = 0.0; // Last time the jump button was pressed private var lastJumpButtonTime : float = -10.0; // Last time we performed a jump private var lastJumpTime : float = -1.0; // Jump timeout variable private var jumpTimeout = 0.15; // Jump repeat time variable private var jumpRepeatTime = 0.05; // Last time we were grounded private var lastGroundedTime : float = -1.0; // The last collision flags returned from controller.Move private var collisionFlags : CollisionFlags; // The current vertical speed private var verticalSpeed : float = 0.0; // The player's character controller object private var controller : CharacterController; // If true, player is grounded. private var playerIsGrounded : boolean = false; function Awake() { controller = gameObject.GetComponent(CharacterController) as CharacterController; currentMoveDirection = transform.TransformDirection(Vector3.forward); previousMoveDirection = currentMoveDirection; cameraSystem = GameObject.Find("CameraSystem").GetComponent("CameraSystemScript") as CameraSystemScript; } function Update() { ReadInput(); ReadControlCameraVectors(); MovePlayer(); ApplyGravity(); } function ReadInput() { inputVAxis = Input.GetAxisRaw("Vertical"); inputHAxis = Input.GetAxisRaw("Horizontal"); jumpButtonWasPressed = jumpButtonIsPressed; jumpButtonIsPressed = Input.GetButton("Jump"); } function ReadControlCameraVectors() { var controlCameraObject = GameObject.Find(cameraSystem.GetCurrentControlCameraName()); var cameraTransform : Transform = controlCameraObject.camera.transform; var forwardVector : Vector3 = cameraTransform.TransformDirection(Vector3.forward); forwardVector.y = 0; forwardVector = forwardVector.normalized; var rightVector : Vector3 = Vector3(forwardVector.z, 0, -forwardVector.x); var wasMoving : boolean = isMoving; isMoving = Mathf.Abs(inputHAxis) > 0.1 || Mathf.Abs(inputVAxis) > 0.1; // Store the player's direction, only if the player is actually moving if (isMoving) { // Calculate the direction the player is facing playerDirection = inputHAxis * rightVector + inputVAxis * forwardVector; } } function MovePlayer() { // Variable to store the movement information for each frame. var movement : Vector3 = Vector3.zero; // Make sure the isMoving variable is true before processing player movement. The isMoving variable is set to true // whenever the user is using the movement controls on the joypad or keyboard. if (isMoving) { // Save the previous move direction for the "control camera" functionality to know which camera was last. previousMoveDirection = currentMoveDirection; // The currentMoveDirection variable stores a rotation value. It gets the current rotation and goes to the target // rotation over so many seconds. The third parameter in the RotateTowards method is the rotation speed. // Higher value means faster rotation. currentMoveDirection = Vector3.RotateTowards(currentMoveDirection, playerDirection, rotateSpeed * Mathf.Deg2Rad * Time.deltaTime, 1000); // Rotate player appropriately transform.rotation = Quaternion.LookRotation(currentMoveDirection); // The movement variable takes the direction and multiplies it by Time.deltaTime * 2. This will slow // down the movement to rotate by seconds instead of by frames. The currentMoveDirection is normalized // so we can have direct control over the speed of the character later on. movement = currentMoveDirection.normalized * Time.deltaTime * 2; // Smooth the speed based on the current target direction var curSmooth : float = speedSmoothing * Time.deltaTime; // Choose target speed. We want to support analog input but make sure you cant walk faster diagonally than just forward or sideways var targetSpeed : float = 0.0; // Get the amount the analog stick is pushed. Pushing farther makes player run faster. var analogStickMagnitude : float = Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")).magnitude; // Pick speed modifier if (analogStickMagnitude > 0.9) targetSpeed = moveSpeed9; else if (analogStickMagnitude > 0.8) targetSpeed = moveSpeed8; else if (analogStickMagnitude > 0.7) targetSpeed = moveSpeed7; else if (analogStickMagnitude > 0.6) targetSpeed = moveSpeed6; else if (analogStickMagnitude > 0.5) targetSpeed = moveSpeed5; else if (analogStickMagnitude > 0.4) targetSpeed = moveSpeed4; else if (analogStickMagnitude > 0.3) targetSpeed = moveSpeed3; else if (analogStickMagnitude > 0.2) targetSpeed = moveSpeed2; else if (analogStickMagnitude > 0.1) targetSpeed = moveSpeed1; else targetSpeed = 0.0; // Adjust the movement vector by the player's speed movement *= targetSpeed; // Store the analog stick magnitude so that the animation script can fetch and process with it currentAnalogStickMagnitude = analogStickMagnitude; } else { currentAnalogStickMagnitude = 0.0; } // Update the "control camera" by force if the move direction has changed... if (previousMoveDirection != currentMoveDirection) { cameraSystem.ForceControlCameraUpdate(); } // See if the player is freshly pressing the jump button if (jumpButtonIsPressed !jumpButtonWasPressed) { StartJump(); } // Determine if we are grounded or not if (controller.isGrounded movement.y <= 0) playerIsGrounded = true; else playerIsGrounded = false; if (playerIsGrounded) { lastGroundedTime = Time.time; if (isJumping) { isJumping = false; } } // Make the player fall if there is no floor underneath him. if (!playerIsGrounded) movement = movement + (Vector3(0, verticalSpeed, 0) * Time.deltaTime * 2); controller.Move(movement); if (playerIsGrounded) Debug.Log("GROUNDED"); else Debug.Log("NOT GROUNDED"); } function GetAnalogStickMagnitude() { return currentAnalogStickMagnitude; } function ApplyGravity() { // When we reach the apex of the jump we perform the appropriate operations. if (isJumping !jumpingReachedApex verticalSpeed <= 0.0) { jumpingReachedApex = true; } // When jumping up we don't apply gravity for some time when the user is holding the jump button. // This gives more control over jump height by pressing the button longer. var applyingExtraJumpHeight : boolean = false; if (IsJumping() verticalSpeed > 0.0 jumpButtonIsPressed transform.position.y < lastJumpStartHeight + extraJumpHeight) { applyingExtraJumpHeight = true; } else { applyingExtraJumpHeight = false; } if (applyingExtraJumpHeight) { return; } else if (playerIsGrounded) { verticalSpeed = 0.0; } else { verticalSpeed -= playerGravity * Time.deltaTime; } } function StartJump() { // Prevent jumping too fast after each other if (lastJumpTime + jumpRepeatTime > Time.time) return; if (playerIsGrounded) { verticalSpeed = CalculateJumpVerticalSpeed(); DidJump(); } } function CalculateJumpVerticalSpeed () { // From the jump height and gravity we deduce the upwards speed // for the character to reach at the apex. return Mathf.Sqrt(2 * jumpHeight * playerGravity); } function DidJump() { isJumping = true; jumpingReachedApex = false; lastJumpTime = Time.time; lastJumpStartHeight = transform.position.y; lastJumpButtonTime = -10; } function IsJumping() { return isJumping; }
That's a lot of stuff, but I'd guess that Code (csharp): if (playerIsGrounded) verticalSpeed = 0.0; is the culprit. If not, try whittling that down, and provide an example package/project.
Thank you for your feedback, Jessy. Once again, sorry for the large sized post. If your assessment is that this is the problem... Code (csharp): if (playerIsGrounded) verticalSpeed = 0.0; ...then what is wrong with it? Maybe I'm not seeing what you are seeing, but I'm trying to figure out where you think that is causing the problem. Shouldn't I be setting the vertical gravity to 0.0 when the player is grounded? Thanks in advance!
No. This is what I have posted about in this thread. Please reread, and ask questions to help you understand and internalize if necessary.
Well, the reason I am confused is that I believed I WAS following your advice in this thread by setting downward velocity to 0.0. For example, above in this post Laurie said: And then you said: So I assumed that Laurie was right about "not moving the controller down when it's already grounded"... however, thinking you meant "backwards" to mean don't do it, I tried commenting out "verticalSpeed = 0,0" and even replaced it with code that pulls the character downward... to no avail. The same behavior of "grounded" and "not grounded" still happens. If you need me to post more to get a resolution, please let me know. I am not understanding how other people deal with this kind of problem that use character controllers... :-/
I haven't tried to do this and don't claim to be an expert coder, but after reading the platformer tutorial a few times... I remembered something regarding physics. From the third person platformer tutorial: Key words being "difficult to model". If you have your heart set on physics, why not program some variance or leniency into your IsGrounded function? Say by measuring transform distance from something on a "ground" layer? That way if it's within .5 or .3 or however much of a grace period, it can still be considered "grounded". just my 2c. Hope I didn't derail this.
What I meant was, that should read The ground will push the controller out, but only to a certain threshold, determined by the Skin Width. It will not push the character out enough to cause isGrounded to be false, unless you don't apply a minimum amount of force downward. You'll never achieve an equilibrium where no downward movement is being attempted, and isGrounded is true. If you're working with rigidbodies, then this is not a problem, because you're thinking about applying forces, not "attempting movement in a vector". You've got to adjust your thinking to the oddity of CharacterController.Move, if you don't want to try to make a rigidbody character system.
Okay, well here's the latest: I took out the part where verticalSpeed gets reset to zero. So there is always a downward verticalSpeed present. However, there is no change when it comes to the symptom of "not grounded" being the status over and over per second. My understanding (based on what I've read in this thread and elsewhere) is that "not grounded" will be the status quite a bit if gravity is involved since the player is always getting pushed down (past the plane of collision) and then Unity is pushing the player back up to the floor again. SO........... here's my question. All I want to do is determine if the player is standing on solid ground or not (for the sake of jumping logic). Is there another way you would advise to accomplish this instead of using controller.isgrounded? Many thanks yet again.
Check out this simple package and see what you can get from it. It illustrates what you need to be doing vertically; anything that changes from this is the result of your code not working for you properly. You just need enough downward force to overcome the "force" of being pushed out of the surface.
Thank you, Jessy. I'll check this out (by tomorrow morning) and see how it works out. I truly appreciate the assistance.
Jessy, the unity package you shared made it real to me, and I could see exactly what you were talking about. You have converted me, and turned me into a believer. THAT WORKED!!!!!!!!!!!!!!!!!!!!!!!!!! After a month of not understanding how to fix this, you have become my hero... THANK YOU!!!!!!!
Sorry I didn't think to make it earlier. It didn't take that long, and illustrates the situation better than any forum babble could. Good luck incorporating it into your more complex script. It shouldn't be too difficult; you'll just need to make sure you're putting code in the right order, and branching appropriately, so you always have just enough downward force to make isGrounded true, but not so much that when you step off a cliff, you are moving downward faster than anticipated.
The great part about it is, I have already incorporated it into the script and cleaned up a few loose ends that remained from my crazy tests and attempts to fix this problem. Thank you, thank you, thank you.
Jessy, for what it's worth, I gave you credit for this fix on my Severe Software Facebook page. Thanks again.
Dear all, I'am experiencing the same problem of "alternative" grounded state with character controller. I used the workaround of Jessy which solved a part of my problem . I my game i got a snowboarder riding a slope. When grounded i got moveDirection.y -= theMagicValue However this workaround works only if i go down slope. If my snowboard try to ascend the slope the isGrounded state stay false ! I think the controller don't detect the ground when ascend the slope. Anyone has already experienced that? T
A note to my previous post: I got the problem if my slope angle is too important too. Character Controller is not able to detect grounded state to true even if theorically i'm grounded... Thank you for your assistance
Hi, Been reading this thread, as I too had a object with jitters. After much messing around to fix it (thanks to the above posts by all the smart folks), I see that it has to do with an interaction between either the character controller, mesh coliider and the surface it is resting on. Well, I just made my character controller 'skin width' thicker from 0.001 to 0.007 and the jitters stopped (the unity reference manual actually mentions this). I don't know if that was the best solution or even if it sticks, but it works for me now, and I can move on. Hope it can helps someone. cheers, Pm99
The .unitypackage isn't working anymore for me - using Unity 2019.3.10f1. The scrips aren't getting imported. And after reading a couple of forum posts i am still not sure what the hell is going on.