x


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.

more ▼

asked Nov 24, 2009 at 01:27 AM

rocket5tim gravatar image

rocket5tim
1k 23 30 56

(comments are locked)
10|3000 characters needed characters left

14 answers: sort voted first

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.

more ▼

answered Nov 24, 2009 at 10:37 AM

runevision gravatar image

runevision ♦♦
9.2k 54 110 173

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;

Nov 24, 2009 at 05:07 PM rocket5tim

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.

Nov 25, 2009 at 04:48 AM rocket5tim

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 .Move 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 Moving Platform Support comment.

Nov 25, 2009 at 09:01 AM runevision ♦♦

Well, like I said, it works fine if I remove the activePlatform = null; line. Maybe 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.

Nov 25, 2009 at 07:05 PM rocket5tim

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.

Nov 26, 2009 at 09:03 AM runevision ♦♦
(comments are locked)
10|3000 characters needed characters left

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 &lt;= 0f) || (charYVelocity &lt;= 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!
         }
     }
 }

}

more ▼

answered Nov 24, 2009 at 07:33 AM

Jaap Kreijkamp gravatar image

Jaap Kreijkamp
7k 44 54 99

This sounds like a promising solution, but could you provide a more detailed example? Even some psudo code would be helpful.

Nov 24, 2009 at 05:09 PM rocket5tim

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!

Nov 25, 2009 at 04:14 PM rocket5tim

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. My 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. Maybe 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.

Nov 25, 2009 at 06:37 PM rocket5tim

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. My 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!

Aug 06, 2011 at 02:12 PM Fubiou

@Fubiou You should open a new question, link to this page and give a more detailed description of your problem. You can also post a link to that question in here. Helping you in the comments to this answer would most likely get very messy.

Greetz, Ky.

Aug 06, 2011 at 03:02 PM SisterKy
(comments are locked)
10|3000 characters needed characters left

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 cube game object which is a child of your elevator platform.
  • Give the cube a collider component and set it as a trigger
  • Adjust the size of the cube so that it more-or-less covers the entire floor of your elevator. Y-axis doesn't much matter, but it's important that anyone standing on the platform must be inside the trigger.
  • Disable the mesh renderer component.
  • 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. :)

    more ▼

    answered Mar 16, 2010 at 12:59 AM

    Codayus gravatar image

    Codayus
    173 4 9 11

    thank you! This way is simple but very effective. It is ideal for me because I did not have to change anything else.

    May 12, 2010 at 01:32 PM Deise

    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?

    May 29, 2010 at 04:57 AM 0V3RWR173D

    I looked forever to find how to do this!!! You made it so simple! Thanks!

    Jul 27, 2010 at 10:52 PM James 2

    problem !! my 3d player looks liked he has been just came out from toilet or been squeezed but a roller truck.

    Nov 10, 2012 at 09:36 PM munaeem

    A late response, but when I add this code, it never seems to exit the trigger when I jump off. So when the platform begins to go downwards again, it pushes my player through the floor.

    Mar 27, 2013 at 02:48 PM junkrar
    (comments are locked)
    10|3000 characters needed characters left

    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.

    more ▼

    answered Nov 24, 2009 at 02:45 AM

    Brian Kehrer gravatar image

    Brian Kehrer
    3k 21 29 71

    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 instead 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?

    May 15, 2010 at 04:09 AM Jessy

    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.

    May 17, 2010 at 04:30 PM Brian Kehrer
    (comments are locked)
    10|3000 characters needed characters left

    Why don't you just make the CharacterController as the child to the floor of the elevator. That solves it. Simple!

    more ▼

    answered May 04, 2012 at 04:17 AM

    venkspower gravatar image

    venkspower
    319 66 49 56

    (comments are locked)
    10|3000 characters needed characters left
    Your answer
    toggle preview:

    Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

    Follow this question

    By Email:

    Once you sign in you will be able to subscribe for any updates here

    By RSS:

    Answers

    Answers and Comments

    Topics:

    x6816
    x3181
    x1171
    x34
    x18

    asked: Nov 24, 2009 at 01:27 AM

    Seen: 37030 times

    Last Updated: Aug 22 at 12:34 PM