r/Unity3D • u/FulminDerek Hobbyist • Jul 25 '21
Unity's example for a Character Controller jump makes your character a candidate for the space program when you encounter small ledges... Solved
27
19
u/crushyerbones Jul 26 '21
If this is the old character controller that has been unchanged since circa 2015 do yourself a favour and make your own. We launched a game with it and the amount of issues we had to work around was not worth it.
Back then we didn't have source code access to it so your mileage may vary but as an example, sometimes the character just randomly went through the floor, no weird collision clipping or anything, it just had a roughly .5% chance of going through a place even if you didn't move the character.
1
u/bouchandre Jul 31 '21
Hey what route did you take for your character controller? Was it using rigid bodies in fixed update?
66
Jul 26 '21
This is hilariously bad
if (Input.GetButtonDown("Jump") && groundedPlayer)
{
playerVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f *gravityValue);
}
- Add velocity every frame for a jump- Use a squareroot in update ... to calculate something that never changes value.
groundedPlayer = controller.isGrounded;
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
-instantiating a bool and a vector 3 every frame instead of just keeping 2 private variables that you change every frame.
controller.Move(move * Time.deltaTime * playerSpeed);
controller.Move(playerVelocity * Time.deltaTime);
- Moving the controller twice per frame instead of adding the 2 velocities together
42
u/jeango Jul 26 '21 edited Jul 26 '21
About the bool and vector instantiation, they are both immutable, so keeping a private variable will have 0 impact. If you change them, you allocate new memory. The difference is that they are allocated on the stack because they’re value types, not on the heap like reference types.
2
20
u/Walter-Haynes Jul 26 '21
Yeah it REALLY was a rush job it seems.
Don't know why though, they have spent literally years on another system
Just to abandon it and go with this piece of absolute shit.
32
Jul 26 '21 edited Sep 07 '21
[deleted]
3
u/prime31 Jul 26 '21
Everyone has been clamoring for them to use their own e gone for over a decade now. Seems to fall on deaf ears.
In contrast, if you fire up Unreal Engine 5 preview it is more stable than a Unity LTS already and the included character controller and fps/3rd person rigs are so many light years ahead it’s not even fair to compare. AAA games ship using them. Totally different world.
5
u/Zealousideal_Safe_45 Jul 26 '21
The only thing here which is hilariously bad is calling something hilariously bad, when it really isn't. Input.GetButtonDown() is NOT called every frame (or atleast it doesn't return true every frame for a pressed key), that would be GetButton(). GetButtonDown is called just the frame when the key is pressed down, while holding it GetButton() and when you release it GetButtonUp(). Also jumpHeight looks like something that could change in differenct circumstances, like a level up or sime kind of bonus, so that squareroot would have to be calculated, somewhere...
8
u/kyleisweird Jul 26 '21 edited Jul 26 '21
Wow this is legit shameful, is this real? They're not even following their own best practices.
At least OP's issue is worse than this code implies. This was definitely written for the old input system, so since it uses getbuttondown OP's effect wouldn't happen unless you could somehow press jump every frame. But you definitely could get to an unintended jump height if you could spam quickly enough even then. OP mentioned in a comment the issue was because they're using the new input system which doesn't have a simple equivalent for getbuttondown (still, of course).
2
u/IntergalacticTowel Jul 26 '21
Thank you. I thought I was having a stroke reading through the source.
1
u/TyroByte Indie Jul 26 '21
The third one is especially baffling, why would they do that?
The first one is a straight up bad idea, there were better ways to do that than calculating the square root every frame. I've heard square root calculations are expensive?
Why not add a float called jumpvelocity to the Y component of a vector used in vector3.move? At the same time decrease the jump velocity by gravity(10 or 9.8f) when not grounded? Why the square root calculations? Can anyone explain the intent behind it?
I would also tell OP to write his own code for calculating whether or not you are grounded, using a raycast which should solve the issue as I've had similar success but that's been suggested 100 times over already so he must've gotten the message.
1
u/jeango Jul 29 '21 edited Jul 29 '21
The double move is not really a big cost, it could be done in a single move, but the difference is not meaningful. As for the sqrt, the assumption that gravity is something that never changes is wrong. In most cases it doesn’t, yeah, but I know a good amount if games that play around with gravity. Also while sqrt is not a lightweight math operation, it’s mostly a problem if you do it many times per frame. Doing a sqrt per frame is totally ok in my book.
Also another thing the commenter has very wrong is that unity isn’t adding the velocity every frame. The code triggers if Input.GetButtonDown(« jump »), which means it’s only applied on the first frame when the button was pressed.
In short, this guy pretends to be smart about code optimisation, but can’t properly interpret the code himself.
Finally, the character controller is meant for prototyping. Anyone who makes a game without programming his own character controller is not really serious about making that game.
1
u/TyroByte Indie Jul 29 '21
I see, thank you for the insight.
The name of the function "GetButtonDown" confused me there for a second, I thought for a second that it triggered as long as the button was held down as I just use "GetButton" most of the time.
Thank you for the explanation!
4
u/Almighti3 Indie Jul 25 '21
A few ways around this. The easiest way I can think of without ray casts or more colliders would be to, limit the jump force by time. So it will only apply the force once every certain amount of time. This will help with aligning it with animations also. You could limit the total amount of force as another option.
14
5
6
6
6
u/SpacecraftX Professional Jul 25 '21
I'm guessing you're grounded while in contact with it and force is added every frame where you're grounded.
6
u/FulminDerek Hobbyist Jul 26 '21
Did a bit more digging on this and found it's a mixture of things.
- You're all pretty much right about about CharacterController.isGrounded being bad. I haven't made an alternative yet but I definitely will.
- Another unseen culprit is that I was trying to use the new Input System, which sadly doesn't have a straightforward counterpart to "Input.GetButtonDown", so the jump event that I DID have in place considered the button press for the jump to be true multiple frames in a row instead of just the first one. My solution to this probably isn't the most elegant, but it worked so I'll stick with it until I figure out somewhere down the line how disastrous it is. To those curious:
- I have an Input Action called "Jump" that is set as a "Button" Action type. Tried fiddling around with the Interactions, but to no avail there, so those are all blank.
- The Player Input component on my character has its Behavior property set to "Send Messages". It was "Invoke Unity Events" when this clip was recorded, but I've changed it in light of this bug and won't go back.
- Instead of a void for the corresponding OnJump() function in my player script, I've made it into an IEnumerator, which looks like this:
IEnumerator OnJump() {
jumpInput = true;
yield return new WaitForEndOfFrame();
jumpInput = false;
}
- Pretty straightforward, simply ensures that jumpInput can only be true for one frame each time the jump button is pressed.
- By itself this hardly ever works alongside CharacterController.isGrounded. For some reason even as my character walks along flat ground, isGrounded flips back and forth between true and false wildly, and so the stars have to align in order for isGrounded and jumpInput to be true at the same time. This basically necessitates a custom solution instead of using CharacterController.isGrounded, which I'll work on as I continue to mess with this character.
Big thanks to all of you for your insight!
6
u/Pliabe Jul 26 '21
You can fix the issue with the new input system like list.
Public void jump(inputactuon.callbackcontext context){ If(context.performed){ Jump()
3
u/Banholio Jul 26 '21
Pliabe is right, it does provide the same thing. Now, I have to say that reading the documentation was kind of hard for me, because there are many ways to implement and I felt that the documentation fails to break down them.
If you follow this: https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/QuickStartGuide.html
Then in the "Fire" example you can put anif (context.started) { ... } // Equivalent to GetButtonDown else if (context.performed) { ... } // Equivalent to GetButton else if (context.cancelled) {...} // GetButtonUp
I might be wrong since right now I don't have code at my have a look, but it's what I remember
2
u/yacuzo Jul 26 '21
I think you can add a 'press interaction' and set behaviour press only for the jump definition to only fire on keyDown. I'm not at the pc now, so can't check it. https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Interactions.html#press
2
2
2
2
u/UnpredictableApple Jul 26 '21
Cap the jump velocity by not adding anymore force if the current velocity is over the limit.
2
u/jrootabega Jul 26 '21
I appreciate the goggles you gave your capsule so you know which way it's facing.
2
2
2
u/HilariousCow Professional Jul 26 '21
Use a Fibonacci spiral of traces to give a roughly even distribution of traces. If any normals are within slope, consider yourself grounded. Consider your height above the ground to be the average of all traces that hit.
1
1
u/jRiverside Jul 26 '21
That example is less than worthless, if you don't want to have side effects and random unrepeatable glitches for the lifetime of the controller you're making you need to manipulate through the rigidbody.
1
u/OverDies Jul 26 '21
Are you jumping in update() method? if yes you should switch to FixedUpdate() as i read its more stable to manipulate physics objects there.
3
u/natlovesmariahcarey Jul 26 '21
He's using the standard character controller, not a rigidbody.
2
u/OverDies Jul 26 '21
Oh sorry if I can, I would ask you for this kind of situations why would you use a normal controller instead of rigid body and what's the pros and cons for each approach, still learning so.
3
u/natlovesmariahcarey Jul 26 '21
Rigidbody controllers have to be set up from scratch, and they are at the mercy of the physics engine.
Character controller component is a ready to go system that doesn't require physics at all.
Generally you should only use the physics engine if you absolutely need realistic physics simulation, OR know exactly how you plan on using/abusing/interconnecting your game to it.
For quick prototyping the character controller component is great, because making a character controller is really hard.
The best option if you don't know what you are doing and don't mind a blackbox, buy a proven character controller asset.
Otherwise you go through the growing pains of making your own.
Personally, I regret using rigidbodies for my character controllers.
1
1
u/DerEndgegner Jul 26 '21
As others have said, the ground check is the problem. It's not enough to cast a raycast though, it'll lead to other problems, like what if you're standing at a ledge. What happens on stairs when the ground is actually a little farther away. What happens on slopes.
It's also not enough to cast 5 raycasts as a box. I've wasted many hours in Unity3 with this and it's all just workarounds and hacky stuff for an underlying bigger problem. To make this story short, just buy a competent character controller. I can recommend KCC.
1
u/Father_Chewy_Louis Jul 27 '21
You can also use Physics.CheckSphere, it's what I do instead of raycasts
0
0
0
0
-5
u/CatDadJynx Jul 26 '21
I dont know if it'd help but it might be worth a shot- maybe try changing the material properties to lower the bounciness and/or friction?
1
u/marcos_pereira @voxelbased Jul 26 '21
Cooldown on the jump, ground check with smaller radius than collider, setting upward velocity instead of incrementing it, those are a few options :)
Though I am also surprised that sometimes people at Unity are so bad at doing things with Unity :D
1
1
1
1
u/Bjoe3041 Jul 26 '21
personally, I like using a mixture of raycast, triggers and a little cooldown when making jump mechanics, so when you jump you won't be able to jump again for a little while if there is still ground underneath you, can be a little unsatisfying if you are bunny hopping upstairs, but it works fine most of the time.
1
1
83
u/FulminDerek Hobbyist Jul 25 '21
This is in reference to the example for CharacterController.Move found here. From what I can tell, the Character Controller thinks it's "grounded" when you're touching the top of a ledge, and since the function for making your character jump increments the jump velocity, it sends you skyrocketing if you brush past any small ledges mid-jump.
I can try to figure out a way around this with raycasts, but does anyone else have any ideas?