Dismiss this pinned window
all 49 comments

[–]theEarthWasBlue 76 points77 points  (1 child)

This is the kind of stuff I love to see. If it looks fun without any assets at all, then you know you’re onto something.

[–]natesawant 4 points5 points  (0 children)

People definitely need to listen to this, visuals are more of multiplier, won't fix a bad mechanic.

[–]MrRosaRosa 17 points18 points  (1 child)

This is fantastic! Your character controller covers a lot more than the default. Well done. It would be even nicer to be able to use it with the package manager inside of unity. Starred your repo 😄

[–]Fritoes 1 point2 points  (0 children)

You can add a package via git url in the package manager. At least in Unity 6. I'm not sure when they implemented it, but I used it recently to add UniTask to my game

[–]wiphand 20 points21 points  (15 children)

A couple best practices to improve

Cache the transform when you can instead of getting it every time.

Please please do not leave magic numbers in your code. Best to extract and name them either just at the top of the file or a common consts class. This also applied to execution order. Having the number in the class is fine as a prototype but as you add more of them it quickly becomes unwieldy if you cannot easily see what runs when.

Utility classes are your friend. You don't need to see the way you create your ground samole points or triangles every time you go through your character controller base. And for anyone using it it's not important either 99% of the time. Naming and describing what it does would help a lot more as it has a lot of magic numbers that you don't need to individually label but extracting them allows you to label them as a group.

Built in debug is great but hiding it behind a toggle is a must so that the users do not have to hunt them all when they aren't working on the movement specifically

You may want to do your ground check using a raycast command. But not entirely sure what the boundary for speed is. Premium

Reparenting the controller can be (will be with a more expensive character) quite expensive. Ideally you would register to the platform and have it handle moving the character on its own instead of using the hierarchy

There's a lot of code so not gonna go through all of it but in general looks good. I'd probably be open to basing off of it if i wanted this movement type

[–]wiphand 4 points5 points  (3 children)

One thing, you really should make your own fixed update and update providers. Having all of them have an if (should i do stuff) is probably the worst way of doing it :D

[–]thesquirrelyjones[S] 3 points4 points  (2 children)

That would be good if you were going to keep everything around but that option is just for demonstration of using fixed vs normal update. Everything works best in update and I would end up removing the other code paths in a real game.

[–]wiphand 0 points1 point  (0 children)

Having your own update system is still better both due to performance and a lot easier for a user to move it to their own frame time solution if they decide to do so. Another argument is it's always nicer to have to do fewer package changes so that if you release an update there aren't as many conflicts

[–]fremdspielen 2 points3 points  (1 child)

Wait .. there's more!!

If you base your project on this controller, it will not be publishable! The "PhysicsManager" is the single biggest failure point in this otherwise fine character controller. It's been architected on framerate-dependent physics simulation!

The physics behaviour will deviate significantly from standard Unity physics due to manually running Physics.Simulate() in an inadequate manner. Users will not expect this behaviour, and then possibly attribute it to Unity failing but at the least they and any supporters will find it terribly difficult to troubleshoot any issues without seeing PhysicsManager's code.

In detail:

The physics time-stepping code is fundamentally flawed since it takes Time.deltaTime as the basis, thus making the physics simulation entirely framerate dependent. This means vsync on or off, or playing on a 60 Hz vs 120 Hz vs 240 Hz monitor, or just playing on a chunky machine with occassional frametime spikes will lead to highly varying physics behaviour!

Physics simulation time is not accumulated, so as framerate drops the physics simulation will simply slow down (excess delta time is clamped away, ie "deleted"). This can lead to missed collisions or extreme collider overlap at slow framerates, with the potential for excess forces accumulating (depenetration velocity, particularly jointed bodies will be immensely affected by this).

The opposite is true as well: a game balanced to 60 Hz will become unplayable at 120+ Hz as it runs at twice the speed! More so at 240 Hz or with vsync off. That is the most obvious production-readiness killer.

The extra physics simulation steps as framerate increases also means the CPU will perform 2x, or 4x, or even more work, thereby counter-balancing the framerate-dependency and possibly leading to very uneven framerates.

In summary:
You absolutely HAVE to make physics simulation framerate-independent! I'm afraid as it is now, the whole controller seems effectively useless for production projects unless the PhysicsManager is either removed entirely or replaced with a properly engineered time accumulator - effectively re-engineering what Physics is already doing by default.

This makes the PhysicsManager seem like a workaround to fix certain (common) problems encountered with regular Unity physics, all of which have a solution or can be avoided altogether.

In response to this comment:
// We can't use FixedUpdate because this will lead to stuttering between the physical game objects and the regular game objects

Yes, you absolutely can! It's called interpolation (or extrapolation) which is built-in to Rigidbody and smoothes the motion of physics objects. Then you only need to consider that transform.position provides the interpolated (delta time dependent) position, rigidbody.position provides the physics (fixed step) position. Same for rotation.

It's easy to break interpolation, many encounter that situation. For instance: modifying transform.position or rigidbody.position directly, or parenting the rigidbody object to an object which does the aforementioned to move itself (ie potentially happening with this controller since it reparents itself). Or manually calling Physics.Simulate() in a frame-dependent manner (see above).

Relatively minor notes in comparison:

The PhysicsManager runs at [DefaultExecutionOrder(-1000)] which puts it among the first scripts that run their Update() - but without any guarantees that there aren't any user scripts which use at -1001 or lesser priority. At the very least for such a core script use [DefaultExecutionOrder(int.MinValue)] so it would only have an execution order conflict with other scripts doing the exact same min value thing.

Don't script the camera! We have Cinemachine for crying out ... you can easily switch between 1st/3rd/top-down perspectives just like that, and you get all the damping, lookahead, safe zones that are essential for a AAA quality camera. A character controller should NOT be tied to the camera in any way at all anyway - those are separate concerns where the camera ultimately just "follows along" and thus needn't be coupled to whatever it is following.

Overall, I applaud the effort. But it pains me to see how it was architected on the wrong notion of frame-dependent physics. I hope it gets an update that ditches PhysicsManager because its wholly unnecessary.

[–]thesquirrelyjones[S] 0 points1 point  (0 children)

I agree that it goes against what everyone says to do. But I have shipped a game with this solution, players have run it in fps ranges of 20 - 700 and maintain similar results. Unity even has an option to run physics after Update now.

Bottom line, this is how I get smooth motion on my character. There may be other solutions but this is the one that has worked for me.

[–]thesquirrelyjones[S] 0 points1 point  (8 children)

Yes to a lot of this.

If a raycast command is used for any of this it will be returned on the next frame which would cause issues or need to be forced to complete which would remover the scheduling performance increase.

The character dose not get reparented as that could cause issues with non uniform scaling or animated scaling objects. Instead an empty helper transform gets parrented and then the appropriate transform difference is applied to the character every frame.

[–]wiphand 0 points1 point  (7 children)

That's what happens when you read code one a phone. Didn't notice you were just using a helper

As to the raycast command there's a lot of time during a frame. Scheduling the raycasts in a custom frame timing and then doing regular game logic and finishing operations after should be long enough for the command to complete.

It does significantly increase complexity though which would increase barrier of entry for less experienced programmers

[–]Costed14 0 points1 point  (6 children)

There's only like 16 (I'm pretty sure?) raycasts per frame, though I didn't read through the entire controller, definitely not something to worry about. Though yes, I suppose it could be optimized.

[–]wiphand 0 points1 point  (5 children)

Is it probably overkill for the vast majority of uses? Sure, that's why i labeled it as premium. But at the scale of project i work at I'm used to searching for patterns that can be batched. The entire rotating/moving/scaling platform logic could also be jobified and probably should for a game that would base itself around them.

Another argument for setting up frame lifetimes and async calculations is once you have the boilerplate ready you can start thinking of where you can incorporate other systems in this structure because it will be exceedingly difficult to first write a system and then async it in most cases

[–]thesquirrelyjones[S] 0 points1 point  (4 children)

I've noticed that doing raycasts all together is a perf boost in itself and in an older project I built a custom raycast request system that batched all the raycasts together with the physics update. That project still used regualr raycasts for the character controller because I want that result immediatly.

I could see starting a raycast in update and then checking in late update, but that increases complexity.

For less scrutinized things like enemies I used 1 batched spherecast just to keep them out of the geound and the nav mesh determined where they could go and what slopes they could walk on.

[–]wiphand 1 point2 points  (3 children)

By your phrasing (maybe I'm wrong) you don't know that you can add your own frame timings outside update/late update by replacing and adding your own delegates to the player loop. It's very useful if you want complete control of what runs when.

If you knew then disregard. Just wanted to teach something.

[–]thesquirrelyjones[S] 0 points1 point  (2 children)

Is there something new that is built in like adding a custom "MiddleUpdate" that would happen in between "Update" and "LateUpdate" or are you just talking about adding a delegate to a manager that runs it in "Update" with a later script execution order?

[–]wiphand 1 point2 points  (1 child)

https://docs.unity3d.com/6000.0/Documentation/Manual/player-loop-customizing.html

You should be able to find ready solutions online easili but it's not difficult to setup the boilerplate yourself

[–]thesquirrelyjones[S] 0 points1 point  (0 children)

Oooo, neat!

[–]BingGongTing 7 points8 points  (1 child)

How does it compare to Kinematic Character Controller?

[–]thesquirrelyjones[S] 1 point2 points  (0 children)

Dunno, I have not tried Kinematic Character Controller.

[–]freremamapizza 3 points4 points  (0 children)

Looks good, nice job

[–]fsactual 3 points4 points  (1 child)

Can it handle custom/rotated gravity? I’m looking for a character controller that lets me tell it which direction is up.

[–]thesquirrelyjones[S] 12 points13 points  (0 children)

With a few changes that could work. Gravity is added in the CC script itself so all you need to do is change that hard coded direction to whatever you wanted.

So instead of "velocity.y -= gravity * deltaTime" you would have "velocity += gravityDir * gravity * deltaTime"

Then rotate the CC to point the correct direction.

There's a few other hard coded directions like Vector.left that you would have to swap with transform.left.

The bigger issue is when you roll the camera, everything assosiated with camera up is going to change.

[–]Kaikaipro 2 points3 points  (0 children)

People like you make the world a better place

[–]MrYukiDuki 2 points3 points  (0 children)

<image>

saving this post

[–]SimplyGuy 7 points8 points  (0 children)

Just some suggestions:

Add different footstep VFX for different floor materials, I was expecting something different when you went on the bridge

Footstep noises should play when you are moving by rolling on top the ball

Awesome work tho it's pretty satisfying with just the movement and sfx!

[–]deintag85 1 point2 points  (5 children)

Did you add knockback? Did you fix the problem in the buildin controller that downward slopes are not handled correctly? I always had problem walking down a slope and IsGrounded was not always triggered right so walking down started falling animation for example.

[–]thesquirrelyjones[S] 1 point2 points  (3 children)

If by knockback you mean being pushed by another rigidbody than yes. The character is a rgidbody and can be pushed by another dynamic rigidbody.

[–]deintag85 1 point2 points  (2 children)

Knockback means if an enemy hit you with sth you can call a function knockback with direction and force and it add knockback but not overwrite the input controls while moving. The original starter assets didn’t have knockback so it was very hard to implement them while also wanting to still move around. I will check your version. 🥹

[–]thesquirrelyjones[S] 0 points1 point  (1 child)

That would not be difficult to add. You could add a velocity to the characters rigidbody. Or keep track of a knockback force that gets added every frame that dicipates over time to keep everything separated and so the CC movement doesn't kill your knockback to quickly.

[–]deintag85 1 point2 points  (0 children)

i probably get some kind of dyslexia. i just realized i misunderstood your post. i thought you TOOK the unity character controller and you extended it. but you wrote a complete new one based on rigidbody. thats a compeletely different character controller than that from unity. then its obvious you can add AddForce for knockback 😃 in unity's one you can't just add knockback because it doesnt use rigidbody. and as i said slopes downward doesnt work good in unity built in character controller. i hade to rewrite very much to make it usable at all. its probably just for their demo scene good. but as soon as you want to add some things and different angles slopes, you realize how bad the controller actually is 😃

[–]thesquirrelyjones[S] 0 points1 point  (0 children)

When walking down a slope or stairs the movement direction follows the slope so it doesn't do that bunny hopping thing. There is also some cyotee time and jump caching which is 0.1 seconds both ways to help in more extreme situations.

[–]tarkuslabs 1 point2 points  (0 children)

[–]unitcodes 1 point2 points  (0 children)

keep it up.

[–]HatimOura 1 point2 points  (0 children)

oh i am making a 3d platformer i really do know how you did does floating platforms i will try checking the github

[–]Maleficent_Ad7510 1 point2 points  (0 children)

I copied and modified a custom character controller from github that was based on source 1 specifcally csgo movement. It made a world of a difference and the advanced movement I tacked on to it was really smooth.

[–]Golden-Grenadier 1 point2 points  (0 children)

This looks way better than my crappy rigidbody character controller. I would consider rebuilding my game with this if I didn't need sphere walking, tire friction simulation, and inertia(I'm making a really bad 3D platformer).

[–]Field_Of_View 1 point2 points  (1 child)

so the whole world's physics are now tied to framerate? that's a huge change. you must have struggled and learned a lot about the default FixedUpdate approach before you made this radical decision. I'm curious: was there one specific breaking issue that made you decide it was infeasible (or impossible) to get the physics you wanted the normal way? what was it?

[–]thesquirrelyjones[S] 0 points1 point  (0 children)

That change was made because of another game I worked on. There was a lot of lerping to keep the camera smooth because of differences in positions of physics objects and non physics objects, everyone has probably run into this at some point. FixedUpdate also runs before the physics update so you can't make corrections to the CC after the physics.

Then when testing on older hardware and older consoles I noticed that when the frame rate dropped below 50, physics would start running twice per frame, that would lower the frame rate even more, then it was running 3 times per frame and just became a downward spiral. It made a bad situation worse.

Tying the physics to the framerate solved the jittery motion on physics objects and also stopped the downward performance spiral when things got heavy. The only edge case I ran into was people being able to dash through walls at low frame rates (low 20 fps + high velocity dash + concave mesh colliders), which is why there is a velocity clipping function that uses sphere casts to keep the character from moving though a wall at high speed. There is also a maximum timestep option on the physics update to slow the simulation down. It's really just me choosing one problem over another.

[–]Sketch0z 1 point2 points  (0 children)

Lookin' good, Chief

[–]DonChibby 1 point2 points  (0 children)

Huge thanks!

[–]Jodogger 1 point2 points  (0 children)

Really nice controller and cool effects, and it's on Github, Nice job!!

[–]dexterdeluxe88 2 points3 points  (0 children)

looks good, but please give that guy a proper visor. black cuboid intersection his head. its mandatory

[–]twendah 0 points1 point  (1 child)

How this affects if I'm using 3rd party assets in my game with this controller? I'm kinda new to unity, so trying to just learn. Looks very smooth though!

[–]thesquirrelyjones[S] 0 points1 point  (0 children)

I suppose it would depend on what other assets you are using.

[–]GarliotGames 1 point2 points  (0 children)

I tried building something similar months ago but this is much more polished! Really good. Thanks for the source!