1
\$\begingroup\$

I have a game with a player controlled by a CharacterController. I also have a MovingPlatform. When the player lands on the moving platform, it becomes a child object of it, so that it should move with the platform. But it does not move - it just stays in place while the platform keeps moving below it. Here is the MovingPlatform code:

public class MovingPlatform : MonoBehaviour {
    Transform startPoint = null, endPoint = null;
    [SerializeField] float speed = 1f;

    private void Start() {
        startPoint = transform.parent.Find("StartPoint");
        if (!startPoint) throw new Exception("No child with name StartPoint!");
        endPoint = transform.parent.Find("EndPoint");
        if (!endPoint) throw new Exception("No child with name EndPoint!");
        transform.position = startPoint.position;
    }

    bool moveFromStartToEnd = true;

    void Update() {
        float deltaX = speed * Time.deltaTime;
        Transform targetPoint = (moveFromStartToEnd ? endPoint : startPoint);
        transform.position = Vector3.MoveTowards(transform.position, targetPoint.position, deltaX);
   
        if (transform.position == startPoint.position) {
            moveFromStartToEnd = true;
        } else if (transform.position == endPoint.position) {
            moveFromStartToEnd = false;
        }
    }

    private void OnTriggerEnter(Collider other) {
        other.transform.SetParent(this.transform);
    }
    
    private void OnTriggerExit(Collider other) {
            other.transform.SetParent(null);
    }
}

When my player with CharacterController lands on the platform, I see that it indeed becomes a child of the platform object, but it still does not move with it.

Interestingly, if I change the Update to FixedUpdate, it does work - the CharacterController does move with the platform. I do not understand, why?

\$\endgroup\$
3
  • \$\begingroup\$ Keep in mind that absolut comparison against position is not a good idea, you might overshot it slightly and it will never stop moving in that direction. \$\endgroup\$
    – Zibelas
    Commented Dec 3 at 10:17
  • 2
    \$\begingroup\$ @Zibelas that's usually true, but this code has two traits that make it relatively safe: 1. MoveTowards() already contains logic to stop upon reaching the point, so as to not overshoot. 2. The == comparison operator between Unity's Vector3 structs has some built-in tolerance (about 0.00001), so small differences due to taking different routes to compute the number in floating point precision will get swept under the rug. For most cases, this should be enough, so I wouldn't fret unless we have a demonstrated bug to fix. \$\endgroup\$
    – DMGregory
    Commented Dec 3 at 11:49
  • 1
    \$\begingroup\$ Though even the documentation of docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html uses Vector3.Distance. \$\endgroup\$
    – Zibelas
    Commented Dec 3 at 15:12

2 Answers 2

0
\$\begingroup\$

Full Disclosure: I fed the question to ChatGPT and I think this explanation makes the most sense, so I have added this as a new answer (which was already on the right track) with this more correct and better explained version.


The behavior you’re observing is tied to the order of operations in Unity’s update loop and how CharacterController operates. Let’s break it down:

Why doesn’t it work in Update?

1. CharacterController and Parenting:

The CharacterController component does not inherently respond to changes in its parent object’s transform. While other objects would naturally move with a moving parent, the CharacterController bypasses Unity’s physics system to control its position. This means the platform’s movement doesn’t automatically affect the CharacterController unless you handle that explicitly.

2. Timing Mismatch in Update:

The platform moves in Update, but the CharacterController processes its movement and collision later in the same frame. By the time the CharacterController evaluates, it hasn’t had a chance to process the platform’s new position because its physics-like calculations are decoupled from the parent transform changes.

Why Does It Work in FixedUpdate?

When you move the platform logic to FixedUpdate, it aligns with Unity’s physics timestep, which the CharacterController also uses. This ensures:

  1. The platform’s position is updated before the CharacterController performs its collision or movement calculations.

  2. The CharacterController is effectively using the latest position of the platform, so it “rides” the platform correctly.


I hope this makes more sense now. So using FixedUpdate in your case makes sense. Another approach would be to move the Character Controller not by reparenting but offsetting it with the position of the platform.

\$\endgroup\$
0
\$\begingroup\$

FixedUpdate is synced with Unity's Physics system, so that you can overwrite motion caused by Physics in FixedUpdate. That explains why the character moves in your example inside of FixedUpdate. I assume your player character has not only a Character Controller component but also a RigidBody component? If yes, motion of a RigidBody is determined by physical forces, and not from motion of parents in the transform hierarchy. If you want to temporarily pause the physics for an object and move it with its parent you have to set IsKinematic = true in the RigidBody component. In your example:

private void OnTriggerEnter(Collider other) 
{
    other.transform.SetParent(this.transform);
    other.getComponent<RigidBody>().isKinematic = true;

}
    
private void OnTriggerExit(Collider other) 
{
    other.transform.SetParent(null);
    other.getComponent<RigidBody>().isKinematic = false;
}

I have not tested this but from my memory it should work like this.

\$\endgroup\$
1
  • \$\begingroup\$ My player has no RigidBody and no Collider - it only has a CharacterController. \$\endgroup\$ Commented Dec 5 at 1:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .