- Home /
How to add platform motion to player controller without parenting? - Mostly working code
Hi! I am currently playing around with character controllers in unity and I wanted to try make a 3d platformer. Everything is going well except I am struggling with the code for adding a platforms velocity to the players movement.
On another post I found this code here, which is apparently from an old unity tutorial that no longer exists:
void GetEnvInfluence()
{
if (activePlatform != null )
{
newPlayerGlobalPosition = activePlatform.TransformPoint(playerLocalPosition);
envVelocity = (newPlayerGlobalPosition - playerGlobalPosition);
if (envVelocity != Vector3.zero)
{
playerController.Move(envVelocity);
}
previousPlatformVelocity = (newPlayerGlobalPosition - playerGlobalPosition) / Time.deltaTime;
newGlobalPlatformRotation = activePlatform.rotation * activeLocalPlatformRotation;
rotationDiff = newGlobalPlatformRotation * Quaternion.Inverse(activeGlobalPlatformRotation);
rotationDiff = Quaternion.FromToRotation(rotationDiff * transform.up, transform.up) * rotationDiff;
transform.rotation = rotationDiff * transform.rotation;
}
else
{
previousPlatformVelocity = Vector3.zero;
}
activePlatform = null;
Move();
if (activePlatform != null)
{
playerGlobalPosition = transform.position;
playerLocalPosition = activePlatform.InverseTransformPoint(transform.position);
activeGlobalPlatformRotation = transform.rotation;
activeLocalPlatformRotation = Quaternion.Inverse(activePlatform.rotation) * transform.rotation;
}
}
I have managed to get this code working within my own script, and everything is working pretty great! I only have two issues that I am stuck on.
Currently this code uses the "onControllerColliderHit" function to determine the platform transform. This is fine, but I am using my own spherecast for other ground detection systems and I would like to use it in this case too. For some reason, everything works fine when using the controller collision, but using the raycast to determine the platform transform leads to the character flying off. Using the collision isnt that big of a deal, but it would be preffered if I could use my raycast too. .
My other problem is when the platform changes direction, the player does not register this for at least one frame, causing the player to move a small increment in the old direction of the platform after it has already changed direction, causing these little slips each time this happens.
Is this code just outdated? Is there a better solution to this now? I would not like to use the parenting method as I think this way will be better for my game. Any help would be greatly appreciated :)
Answer by lgarczyn · Dec 21, 2019 at 07:31 PM
This is an unfortunately pretty complex subject.
Parenting is often the best option for a physics-based character (using a non-kinematic rigidbody). Simply set the parent on collision enter, and leave on collision exit. This breaks the "don't have rigidbodies as parents to each other" rule, but it's a pretty outdated rule.
For kinematic rigidbodies and character controllers, the best way is to modify your Platform script. Simply use your Raycast to detect if the player is on a collider, and if it is, use GetComponent to obtain the Platform script. You may want to use RaycastAll if you want to be able to be pulled by the platform if you're only partially on it.
Your Platform script should run first (use the script order tool), and always store the difference between the next position and the previous position. When you detect if your character is on the ground, try to get that difference, and add it to your movement script offset.
Do note that both the platform script and the player script need to run on FixedUpdate, otherwise just make the the platform store its last velocity in a frame-agnostic manner (divide the delta post by fixedDeltaTime).
Hey thanks for your reply! Im glad its not just me, ive tried to come up with different solutions over the last few days with nothing working perfectly hahah.
I did consider deriving the extra velocity from the platform script, but I was hoping I could figure out a way that my player script could do this independently, so that anything that is moving (platforms, enemies, objects etc) could have its velocity obtained without the need for extra code on each of the objects.
Am I just over complicating it? If i cant find another solution then I think your proposed one sounds like the best thing for now.
I think the problem with my current code is that the player gets the velocity from the previous frame, which leads to the little "nudge" when the platform changes direction.
Again, thanks for your time. I really appreciate the advice
Any non-kinematic rigidbody can have its velocity obtained, however it will only ever be the last frame's velocity. That is because the next physics update might have collisions that you couldn't predict. You can limit that with raycasts, but it's complicated.
Any kinematic rigidbody needs a script to move, and all these scripts will always inherently be able to calculate their velocity.
In general, yes, you are overcomplicating things. All objects should implement their own behaviors and provide information about themselves, ins$$anonymous$$d of being moved or analyzed by others.
The clean way to do it would be yo create a Smart$$anonymous$$inematic class inheriting from $$anonymous$$onoBehaviour, that requires a rigidbody component. That class will have a:
GetVelocity function that returns its speed
(currentPos - lastPos) / Time.fixedDeltaTime
$$anonymous$$ovePosition function that calls rigidbody.$$anonymous$$ovePosition, but also sets lastPos
GetRigidbody function that returns the rigidbody
private bool has$$anonymous$$oved to check that GetVelocity is not called before $$anonymous$$ovePosition
Simply make all your platforms and kinematic objects inherit from that class to be able the get the velocity of all of them easily.
You're right, that is a good solution! thank you for pointing me in the right direction, I dont know why I thought I could get it to work from the previous frame. I appreciate the help :)