- Home /
CharacterController falls through or slips off moving platforms
I made an animated elevator/platform by creating an animation in Maya and then attached a box collider as a child of the animation so that they move together. However my player uses the CharacterController and it falls thru the box collider (floor) as soon as the elevator starts animating*.
There are a couple of threads on the unity forum (here and here), but one of them is very old and neither of them has current or clear explanations of why the CharacterController has issues with attaching to moving objects and how best to solve the problem.
*note if I keep the player moving while the elevator is in motion, he does not fall thru the floor - he only falls thru when standing still. Also he's more likely to fall thru while the elevator is moving up than down.
I found this video helpful https://www.youtube.com/watch?v=U1Uk$$anonymous$$w12pQo
Hey guys, that simple JS parenting script worked in my case, thanks!!! :D
However, I have noticed that it works much better with my animations imported from $$anonymous$$aya (or perhaps it's the mesh itself,and the trigger box generated from that mesh (it's just a duplicate)). The collider is set to convex,otherwise it wouldn't let me check 'is trigger' box.
Another moving platform was created with a Unity's cube and the trigger cube was done the same way,uses animations created with $$anonymous$$echanim, and whenever my character steps inside the trigger zone, its feet start sinking in a bit... I'm using that standard Ethan char to run around the level.
$$anonymous$$aybe this observation can help somebody out with the similar issue. I guess I need to test with another object imported from $$anonymous$$aya.
A programmer friend is going to help me with this in the next couple of days,I know parenting is not the best solution,but the other ones are too complicated for me (game artist here xD). If he does something cool,I'll post it here!
Answer by runevision · Nov 24, 2009 at 10:37 AM
The 2D Gameplay Tutorial has a character that can stay perfectly on platforms. The needed code, derived from that tutorial, is below.
This solution is rather flexible.
It supports moving in any direction (vertically, horizontally, diagonally, you name it).
It works no matter if the platforms are moving through scripting, or through animation. If done by animation, the Animation component of the platform must have the checkbox "Animate Physics" turned on to avoid glitches.
I have enhanced it here so that it also supports rotating platforms, but you can leave out that part if you don't need or want it.
It does not rely on making the platform a parent to the character, as that can have certain disadvantages.
Note that the below is NOT a complete script. It's only some code fragments needed for making the character stay on platforms. For a complete script, see the 2D Gameplay Tutorial.
// Moving platform support private var activePlatform : Transform; private var activeLocalPlatformPoint : Vector3; private var activeGlobalPlatformPoint : Vector3; private var lastPlatformVelocity : Vector3;
// If you want to support moving platform rotation as well: private var activeLocalPlatformRotation : Quaternion; private var activeGlobalPlatformRotation : Quaternion;
function Update () {
// Perform gravity and jumping calculations here
...
// Moving platform support
if (activePlatform != null) {
var newGlobalPlatformPoint = activePlatform.TransformPoint(activeLocalPlatformPoint);
var moveDistance = (newGlobalPlatformPoint - activeGlobalPlatformPoint);
if (moveDistance != Vector3.zero)
controller.Move(moveDistance);
lastPlatformVelocity = (newGlobalPlatformPoint - activeGlobalPlatformPoint) / Time.deltaTime;
// If you want to support moving platform rotation as well:
var newGlobalPlatformRotation = activePlatform.rotation * activeLocalPlatformRotation;
var rotationDiff = newGlobalPlatformRotation * Quaternion.Inverse(activeGlobalPlatformRotation);
// Prevent rotation of the local up vector
rotationDiff = Quaternion.FromToRotation(rotationDiff * transform.up, transform.up) * rotationDiff;
transform.rotation = rotationDiff * transform.rotation;
}
else {
lastPlatformVelocity = Vector3.zero;
}
activePlatform = null;
// Actual movement logic here
...
collisionFlags = myCharacterController.Move (calculatedMovement);
...
// Moving platforms support
if (activePlatform != null) {
activeGlobalPlatformPoint = transform.position;
activeLocalPlatformPoint = activePlatform.InverseTransformPoint (transform.position);
// If you want to support moving platform rotation as well:
activeGlobalPlatformRotation = transform.rotation;
activeLocalPlatformRotation = Quaternion.Inverse(activePlatform.rotation) * transform.rotation;
}
}
function OnControllerColliderHit (hit : ControllerColliderHit) { // Make sure we are really standing on a straight platform // Not on the underside of one and not falling down from it either! if (hit.moveDirection.y < -0.9 && hit.normal.y > 0.5) { activePlatform = hit.collider.transform;
} }
Notes:
The lastPlatformVelocity variable is not used for anything in the code above, but you can use it in the jumping logic to make the jumping speed get the inertia from the moving platform, such that the character is jumping relative to the platform reference and not to the global reference. You could also calculate a lastPlatformRotationalVelocity to get the rotational inertia from the platform when jumping, but for most games this would be overkill and only confuse the user.
I added the code from above, and so far I'm still falling thru the platform - same result as without the code. I also tried a code driven platform (moving the transform in FixedUpdate) but he falls thru that too exactly the same.
Perhaps there's a problem with the way my platform is setup? In both setups, I'm simply using a Box Collider for collision (the player collides just fine so long as the box isn't moving).
Or perhaps the code is fighting with my gravity code?
if (!controller.isGrounded) moveDirection.y -= gravity * Time.deltaTime;
So after working on this for several hours today, I finally tried commenting out the activePlatform = null; line. And would you believe, without that line my character no longer falls thru the platform? I can't understand it -- that line is clearly in the 2D Gameplay Tutorial in the same location as where I had it. Weird.
It's hard to say why it doesn't work in your case. All I can say is that it is very important that the .$$anonymous$$ove function on the CharacterController is called in the location shown in the code above. That is, after activePlatform is set to null and before the last code block with the $$anonymous$$oving Platform Support comment.
Well, like I said, it works fine if I remove the activePlatform = null; line. $$anonymous$$aybe someone can chime in on what purpose that line has in the script? In any case, this appears to be the most useable solution at this point.
If you don't set activePlatform = null; then the character should still be affected by the platform movement after having jumped off it, until you land on a new surface.
Answer by Jaap Kreijkamp · Nov 24, 2009 at 07:33 AM
Connect a script to the platform that detects if someone is on it, in LateUpdate adjust the y pos to the new y pos of the platform.
EDIT: I added an example implementation that should keep any character controller happily on a platform. It's a bit more complex than just adjusting the y position as it also handles horizontal movement and works with more than one char on the platform (so it maintains a list of all players on the platforms).
The platform probably has already a collider so you can't walk through it and stuff, to use the script, add a child GameNode to the platform, give it a trigger collider that's slightly smaller as the platform and extends a bit above the platform. Add the script and adjust the Vertical Offset var. If you need an example project, let me know.
EDIT2: Made a few modifications to handle jumping :-)
using UnityEngine; using System.Collections;
/** Helps keeping charactercontroller entities nicely on the platform Needs a Collider set as trigger in the gameobject this script is added to works best if collider is bit smaller as platform but extends quite a lot (say .5m or so) above the platform, As the platform possibly already has a normal collider the easiest way is to add a GameObject to the platform, give it a trigger collider and add this script. The yOffset is the vertical offset the character should have above the platform (a good value to start with is half the y value of the Collider size). */ public class JKeepCharOnPlatform : MonoBehaviour {
// helper struct to contain the transform of the player and the
// vertical offset of the player (how high the center of the
// charcontroller must be above the center of the platform)
public struct Data {
public Data(CharacterController ctrl, Transform t, float yOffset) {
this.ctrl = ctrl;
this.t = t;
this.yOffset = yOffset;
}
public CharacterController ctrl; // the char controller
public Transform t; // transform of char
public float yOffset; // y offset of char above platform center
};
public float verticalOffset = 0.25f; // height above the center of object the char must be kept
// store all playercontrollers currently on platform
private Hashtable onPlatform = new Hashtable();
// used to calculate horizontal movement
private Vector3 lastPos;
void OnTriggerEnter(Collider other) {
CharacterController ctrl = other.GetComponent(typeof(CharacterController)) as CharacterController;
// make sure we only move objects that are rigidbodies or charactercontrollers.
// this to prevent we move elements of the level itself
if (ctrl == null) return;
Transform t = other.transform; // transform of character
// we calculate the yOffset from the character height and center
float yOffset = ctrl.height / 2f - ctrl.center.y + verticalOffset;
Data data = new Data(ctrl, t, yOffset);
// add it to table of characters on this platform
// we use the transform as key
onPlatform.Add(other.transform, data);
}
void OnTriggerExit(Collider other) {
// remove (if in table) the uncollided transform
onPlatform.Remove(other.transform);
}
void Start() {
lastPos = transform.position;
}
void Update () {
Vector3 curPos = transform.position;
float y = curPos.y; // current y pos of platform
// we calculate the delta
Vector3 delta = curPos - lastPos;
float yVelocity = delta.y;
// remove y component of delta (as we use delta only for correcting
// horizontal movement now...
delta.y = 0f;
lastPos =curPos;
// let's loop over all characters in the table
foreach (DictionaryEntry d in onPlatform) {
Data data = (Data) d.Value; // get the data
float charYVelocity = data.ctrl.velocity.y;
// check if char seems to be jumping
if ((charYVelocity <= 0f) || (charYVelocity <= yVelocity)) {
// no, lets do our trick!
Vector3 pos = data.t.position; // current charactercontroller position
pos.y = y + data.yOffset; // adjust to new platform height
pos += delta; // adjust to horizontal movement
data.t.position = pos; // and write it back!
}
}
}
}
This sounds like a promising solution, but could you provide a more detailed example? Even some psudo code would be helpful.
This works great for keeping the player from falling thru an animated platform (I haven't tried it with a code driven platform yet). I had to find the right balance between the verticalOffset, gravity, and even the height of the trigger to keep the player from bouncing as the platform moved up/down. Thanks!
While it does keep the player from falling thru, there's an issue: If I adjust the verticalOffset so that the character doesn't "bounce" when the platform is moving UP, then he cannot jump from the platform. $$anonymous$$y jump button checks to see if isGrounded = true, if it's not then he can't jump. There's something about being pinned exactly to the center of the trigger that makes the character think it's not grounded. $$anonymous$$aybe the collider is being pulled slightly thru the elevator collision? If I set verticalOffset so that the character "bounces" a little when the platform is moving he can jump off.
Ah, you want to jump! How eighties! winks at DragonAge where your char can't even jump a 10 cm ledge. Edited the code in my post to handle jumping, just copy/paste over old code and it should work. Note that the y offset must be high enough that the char's collider isn't through the platform, but preferably low enough that the colliders skin is. So try adding a cm of 2 if jumping doesn't work when platform is going up.
Hi, I am using a script based on ThirdPersonController script, I tried to add this solution but failed miserably, something worked but the character appeared in a place that wasnt the place I seted, and he was sliding the platform. $$anonymous$$y original solution was to parent it, worked very well but I am afraid of the physical consequences of such act. Please, can you give me a hand? to improve the ThirdPersonController with this platform solution?
Sorry about my noobish and my terrible english!
Answer by Codayus · Mar 16, 2010 at 12:59 AM
There's several ways to do this of various complexity, depending on what you need. For a simple elevator, you probably want the simplest and easiest way - which would be as follows:
Create a new .js script with the following code in it and add it to the trigger collider:
function OnTriggerEnter (other : Collider) { other.transform.parent = gameObject.transform; } function OnTriggerExit (other : Collider) { other.transform.parent = null; }
What's happening here is fairly obvious. When anything enters your trigger (like, for example, a CharacterController), it becomes parented to it. Anything parented to the trigger will move when the elevator does. When you step out of the trigger, the parent link is removed. Simple. :)
thank you! This way is simple but very effective. It is ideal for me because I did not have to change anything else.
Thank you very much for the advise, ALTHOUGH I have encountered a problem. I'm not sure if you are an expert at unity3d but I have a firstpersonplayer with weapons. The weapons are attached to the main camera but when I step on the platform, it does not move correctly with the camera. It will not stay in an exact position relative to the camera. Do you have any ways of fixing this kind of problem?
I looked forever to find how to do this!!! You made it so simple! Thanks!
problem !! my 3d player looks liked he has been just came out from toilet or been squeezed but a roller truck.
in the script make sure you reset the players transform.localScale to 1,1,1 or else it will be squished
Answer by Brian-Kehrer · Nov 24, 2009 at 02:45 AM
The CharacterController is very simple in function.
Every FixedUpdate it applies the Move() function to determine where it should go. Typically, gravity is assigned, so it always attempts to move down. The Move() function detects objects between the controllers current and intended positions, and will stop moving when a collision is detected. Of course it's much more complex than that, but that is the idea.
However, if between calls to Move() in FixedUpdate, you move the ground above the current position of the CharacterController, the check the CharacterController performs will say "There is nothing below me" and so it will fall. Makes sense.
Now, onto fixing it.
First, to make things behave well, one trick is to make sure all objects the CharacterController interact with are also being adjusted in FixedUpdate. Animations will run in Update. I would suggest animating the platform in code rather than in Maya, and also in the FixedUpdate loop. While there are ways to make what you suggest work, I think it will be far less complicated in this manner.
Depending on your game logic, there are a few options. Personally I dislike parenting the CharacterController to other objects because it can result in some strange behavior. Instead, create some code to tell the CharacterController when it is interacting with a moving platform, and allow the moving platform to tell the CharacterController how far it should move this frame, and then reposition the CharacterController manually.
I have code that moves a Character Controller to another place on the map, and it may be inside of a collider that it should ins$$anonymous$$d be walking on, afterwards, so it can then fall through. The teleportation happens in OnTriggerStay(), so I don't think there's anything to be helped in terms of FixedUpdate(). Any tips? You think the solution is an OnTriggerStay() in the floors that it can teleport into, which pushes the Controller out far enough so that it's on top again?
Is OnTriggerStay even being called? If you teleport in, it might get messed up. What I finally ended up doing was very complicated. I used a dummy rigidbody specifically to catch that case. Obviously if you are very far off, it will still fail, but it might catch close cases.
Answer by venkspower · May 04, 2012 at 04:17 AM
Why don't you just make the CharacterController as the child to the floor of the elevator. That solves it. Simple!
No, it doesn't. Perhaps actually testing your solution ins$$anonymous$$d of blurting out the first thing that pops into your head would be more useful.
Your answer
Follow this Question
Related Questions
Character Contoller with Ragdoll 1 Answer
Make a Platform push a character controller? 2 Answers
how can i make my character run up ramps like in sonic, fancy pants or even line rider? 1 Answer
Character Controller + Animation Not Working As It Should 0 Answers
What is the easiest way to push a character controller with a moving platform? 0 Answers