Encoutered a situation tonight where TheMan would, if dropping from a great height, actually hit the ground but because the physics simulation was a frame behind - would end up falling right through the ground, looking something like this:
The solution for me was to ensure that when we switch from a non-grounded state to a grounded state, use the last known "point of contact" and set the player position to it before exiting the FallingHandler - this cures the problem because it's done in LateUpdate() and will always ensure that BOTH the velocity is Vector3.zero and the position is set above the grounds surface at the end of the game loop and after physics calculations have taken place (so they do not occur and adjust us down again to defeat the guard condition).
End result, looks like this:
One issue i'm seeing still with animations like "LandHard", "LandSoft", etc... is that it can be a real pita to avoid the hands/feet clipping a bit into the ground. At first I took some time to try and get rid of this and was largely successful, but as you can see above not entirely (take a close look at toes and fingers).
In the final analysis though, this is actually not noticeable at all during a regular playback speed (or what you'll be seeing in-game) and frankly the player will have many other effects that are going on to obscure (hopefully) any anomalous behaviour like this. For instance, I plan on having a thin ground fog in most areas, weather effects, dirt/refuse, etc... so when it comes together this will be a non issue, at least that's my thought, and if not i'll just use offsets to account for it.
So I finished up the grounded code and went back for some refactoring to optimize it - thought i'd share my results and if anyone has other suggestions, please advise!
Lets start with the roughed out initial code:
So it looks reasonable but there are several issues with this code that make it fragile, less readable and annoyingly verbose, first lets add some comments to the code here and delineate clearly where things happen and why.
So now looking at the spherecast, i'm creating a ray here to perform the sphere cast but is it necessary to create a new object EVERY game cycle just do define the direction? No, in fact there is an alternative method we can call that takes the origin and direction instead of a Ray object instance, lets do that and eliminate the Ray altogether. It'd be nice to get rid of startPosition as well, but it IS necessary, because it must be calculated each game cycle and the Physics::SphereCast method has no overload for x,y,z instead of a Vector3.
That hitRadius though, it uses the player collider radius .. will that change somehow during a fall? Currently, it does not, so this is something we could move right out of the GroundCheck method and initialize once during startup, saving object churn and processing time. The programming vernacular for analysing where lines of code impact performance the most is "finding the critical path of execution". The GroundCheck() method is run every game cycle and therefore inefficient code here can have a profound impact on the overall performance of your game, so in this case, regardless of readability, i'm going to cut and slash anything possible to make this as fast and efficient as possible, lets look at what we have after this round of refactoring fun:
That is starting to look a lot better, much more readable and concise despite the extra comments. We are not done however, there is a BIG hit here which concerns me in the form of:
The problem here is that we have several operations happening. First, the "Player" static string can mean there is a temporary object created for a string literal, which i know is the case for Java but perhaps C# uses some optimizations like Java's static string pool?
http://stackoverflow.com/questions/4286614/c-sharp-do-string-literals-get-optimised-by-the-compiler
Not too clear to me, and frankly we could be running in a variety of target platforms so why even chance it, besides, there is more. Another issue is that we invoke NameToLayer each game cycle, I'm not sure on the performance of this, but it's not better than avoiding it in the first place that is for certain. Then you have the bitwise shift operation and finally applying ~ to do a bitwise complement on the resulting value. That is what, at least 3 operations and one object creation (potentially). So looking at this, do we even NEED to have this calculated each time? No, because the players layer never changes so lets wrap this in a constant and use that instead.
There is one final optimization to consider, is the following:
But in this case we don't know that the player collider will always be the same HEIGHT, for instance, we could be moving along in a crouched position or doing a dive roll. So this seems a reasonable calculation, using Mathf.Infinity to sphere cast downwards with avoids this calculation at the cost of finding more "hits" due to the distance, so let's adjust that to a smaller value that we still know will be further than what grounded distance should be and call it a day.
The resulting code is like so:
Seems to work well so far!
No comments:
Post a Comment