Horizontal platform passes through CharacterController

Using the code from the Unity 2D Platform tutorial I am able to get a vertically moving platform working no problem but when I change the platform to move horizontally the following issue is encountered:

  1. When standing still the platform passes through the CharacterController
  2. The character controller is not ‘pushed’ forwards or backwards by the platform

This exact same problem happens in the tutorial example itself if you move the platforms to go in a Horizontal pattern instead of the vertical ‘elevator’ pattern.

The desired behavior is that the CharacerController be pushed by the horizontal platform in either direction.

FACTS:

  1. Unity 3.4
  2. The platform isKinematic = true
  3. The platform has a rigidBody
  4. The platform has a box collider

CODE:
The player controls (attached to PlayerObject)

using UnityEngine;
using System.Collections;

public class CPlayerControls : MonoBehaviour 
{
	//Public
	public float mSpeed = 3.0f;
	public float mJumpSpeed = 8.0f;
	public float mGravity = 20.0f;
	
	//Private
	private Vector3 mMoveDirection = Vector3.zero;
	private CharacterController mController;
	
	
	////////////BEGIN Moving platform support
	private Transform activePlatform;
	private Vector3 activeLocalPlatformPoint;
	private Vector3 activeGlobalPlatformPoint;
	private Vector3 lastPlatformVelocity;
	/////////END Moving platform support
	
	// Use this for initialization
	void Start () 
	{
		this.mController = this.GetComponent<CharacterController>();
	}
	
	// Update is called once per frame
	void Update () 
	{
		// Character is on the ground
		if(this.mController.isGrounded)
		{
			this.mMoveDirection = new Vector3(Input.GetAxis("Horizontal"), 0.0f, 0.0f);
			this.mMoveDirection = this.transform.TransformDirection(this.mMoveDirection);
			this.mMoveDirection *= this.mSpeed;
			
			// Player Jump
			if(Input.GetButton("Jump"))
			{
				this.mMoveDirection.y = this.mJumpSpeed;
			}
			
			//Player Super Jump
			if(Input.GetButtonDown("Jump") && Input.GetButton("Fire1"))
			{
				this.mMoveDirection.y = this.mJumpSpeed + 5.0f;
			}
				
		}
		
		// Character is NOT on the ground
		if(!this.mController.isGrounded)
		{
			this.mMoveDirection.x = Input.GetAxis("Horizontal");
			this.mMoveDirection.x *= this.mSpeed;
		}
		
		/////////BEGIN MOVING PLATFORM SUPPORT
	    if (activePlatform != null) 
		{
	        Vector3 newGlobalPlatformPoint = activePlatform.TransformPoint(activeLocalPlatformPoint);
	        Vector3 moveDistance = (newGlobalPlatformPoint - activeGlobalPlatformPoint);
			
	        if (moveDistance != Vector3.zero)
	                this.mController.Move(moveDistance);
			
	        lastPlatformVelocity = (newGlobalPlatformPoint - activeGlobalPlatformPoint) / Time.deltaTime;
	    }
	    else 
		{
	        lastPlatformVelocity = Vector3.zero;
	    }
	
	    activePlatform = null;
		///////END MOVING PLATFORM SUPPORT
		
		//Moving player
		this.mMoveDirection.y -= this.mGravity * Time.deltaTime;
		this.mController.Move(this.mMoveDirection * Time.deltaTime);
			
		////////BEGIN Moving platform support
	    if (activePlatform != null) 
		{
	        activeGlobalPlatformPoint = transform.position;
	        activeLocalPlatformPoint = activePlatform.InverseTransformPoint (transform.position);
	    }
		///////END Moving platform support
	}
	
	void FixedUpdate () 
	{
		// Make sure we are absolutely always in the 2D plane.
		Vector3 pos = new Vector3(this.transform.position.x, this.transform.position.y, 0.0f);
		this.transform.position = pos;
	}
	
	void OnControllerColliderHit (ControllerColliderHit hit)
	{
		print(hit.moveDirection);
		
	    if (hit.moveDirection.y > 0.01) 
			return;
		
		// 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.9) 
		{
			activePlatform = hit.collider.transform;	
		}
	}
	
}

CODE: The platform controller (attached to PlatformObject)

using UnityEngine;
using System.Collections;

public class CMovingPlatformController : MonoBehaviour 
{
	public GameObject targetA;
	public GameObject targetB;
	
	float speed = 0.05f;
	
	void FixedUpdate () 
	{
		float weight = Mathf.Cos(Time.time * speed * 2 * Mathf.PI) * 0.5f + 0.5f;
		this.transform.position = targetA.transform.position * weight + targetB.transform.position * (1-weight);
	}
}

Any help is greatly appreciated!

3 Answers

3

When moving physics objects, they must be moved within the FixedUpdate loop, because it is synchronous with the physics timestep. Moving in the update means that you might be exceeding tolerances etc inbetween the physics iterations.

Here’s a citation from the unity manual

FixedUpdate should be used instead of
Update when dealing with Rigidbody.
For example when adding a force to a
rigidbody, you have to apply the force
every fixed frame inside FixedUpdate
instead of every frame inside Update.

Also, you should never directly edit the transform of a rigid body, you should only edit its location through rigidbody functions…

Rigidbody.MovePosition

For kinematic rigidbodies it applies
friction based on the motion of the
rigidbody. This lets you simulate
moving platforms with rigidbodies
sitting on top of the elevator. If you
want other rigidbodies to interact
with the kinematic rigidbody you need
to move it in the FixedUpdate
function.

Have you tried increasing the Skin Width?

Thanks for sticking with me :) The character is a 2D sprite made with SpriteManager 2 so there is no skin width as far as I can tell. I think the solution is to apply a force to the character when a collision occurs with the moving platform, but I'm not exactly sure how i go about doing that yet....

Figured it out, it’s not the most elegant solution and in all honesty I still expect there to be a better one but for anyone that falls into a similar snake pit here is my solution:

in the platform controller script I added the following

void OnCollisionStay(Collision collisionInfo) 
	{
		CPlayerControls playerControls = collisionInfo.gameObject.GetComponent<CPlayerControls>();
		
		foreach (ContactPoint contact in collisionInfo.contacts) 
		{
			//Debug.DrawRay(contact.point, -contact.normal * 10, Color.white);
			
			float fDir = contact.normal.x;
			
			if(fDir < 0.0f)
				fDir += 0.6f;
			else
				fDir -= 0.6f;
			
			playerControls.HorizontalPlatformCollision(-fDir);
			
		}
	}

In the character controller script I added the following method:

public void HorizontalPlatformCollision(float pDirection)
	{
		this.mMoveDirection = new Vector3(pDirection, 0.0f, 0.0f);
		this.mMoveDirection = this.transform.TransformDirection(this.mMoveDirection); // Transforms direction from local space to world space
		this.mMoveDirection *= this.mSpeed;
		
		//Moving player
		this.mMoveDirection.y -= this.mGravity * Time.deltaTime;
		this.mController.Move(this.mMoveDirection * Time.deltaTime);
	}

This results in achieving my desired behavior.