One way platform using 2D Colliders?

I am currently in the process of building a platform game and I am looking into porting it over to the new 2D physics engine that was added in 4.3. It looks like most of it should port over, but I have no idea how to platforms that can only be collided with from above.

My current solution using the 3D system is a script I altered that converts the current collider into a mesh collider that only has faces pointing upwards. This seems to work really well, but I can’t seem to find a similar solution using the 2D colliders.

I know that Box2D has something like a pre-collision event you can intercept before the physics system handles a collision, effectively giving you the option to ignore the collision, but it doesn’t look like that functionality exists in the Unity release.

Does anyone have a solution? Or perhaps know if an update may include the hooks that I am looking for?

Any help is much appreciated.

Cheers,
Sev

I know this topic is a bit stale and I’m pretty new to Unity and Physics2D.IgnoreLayerCollision probably has been added not so long ago, but I solved one way platform with just one line in FixedUpdate().

Physics2D.IgnoreLayerCollision (playerLayer, platformLayer, rigidbody2D.velocity.y > 0);

I solved it by setting the platform’s collider to trigger and it’s tag to “OneWayPlatform” and then adding the script below to the Player.

What the script does is in OnTriggerEnter/Stay, if the player is above the platform and moving downwards, it stops the player, and stores and disables it’s gravity.

Then, once the player exits this particular collider by going left/right, it sets it’s gravity back.

Edit: I optimized the code a bit and also increased the readability :wink:

Edit 2: Demo: OneWayPlatforms

#pragma strict

public var deadZone : float = 0.1;

private var g = 0;
private var touchingPlatforms : int = 0;
private var body : Rigidbody2D;

function Start () {
	g = rigidbody2D.gravityScale;
	body = rigidbody2D;
}

function OnTriggerEnter2D (c : Collider2D) {
	if(c.CompareTag("OneWayPlatform")) {

		touchingPlatforms++;

		var maxDistance = (c.transform.localScale.y + transform.localScale.y) / 2;
		if(body.velocity.y < 0 && transform.position.y - c.transform.position.y - body.velocity.y * Time.fixedDeltaTime > maxDistance - deadZone) {
			body.gravityScale = 0;
			body.velocity.y = 0;
			transform.position.y = c.transform.position.y + maxDistance;
		}
	}
}

function OnTriggerExit2D (c : Collider2D) {
	if(c.CompareTag("OneWayPlatform") && touchingPlatforms-- == 1)
         body.gravityScale = g;
}

–David

You can use Physics2D.IgnoreLayerCollision and Physics2D.OverlapCircle to achieve one-way platforms.

This script would go on your player game object, with an empty game object placed at the feet of the player, used for the groundCheck transform.

`
//in your player controller script 

/**
 * empty game object used to check for ground, 
 * placed at the bottom of your character, with a small radius
 */
public Transform groundCheck;
public float groundCheckRadius = 0.1; 

/**
 * Layer mask to define what layers are walkable
 */
public LayerMask walkableLayerMask; 

void FixedUpdate()
{
	//first check if the ground is below the player
	bool isOnGround = Physics2D.OverlapCircle( groundCheck.position, 
												groundCheckRadius, 
												walkableLayerMask
											  );
	
	//Now ignore collisions between the Player and OneWayPlatform layers, 
	//unless you're on the ground or travelling up.
	Physics2D.IgnoreLayerCollision( LayerMask.NameToLayer("Player"), 
									LayerMask.NameToLayer("OneWayPlatform"), 
									!isOnGround || rigidbody2D.velocity.y > 0 
								   );
}
`

The condition for IgnoreLayerCollision needs both the ground and velocity conditions to prevent the player’s position from shifting when passing through the platform.

This assumes you have two layers (Player and OneWayPlatform) defined in Project Settings → Tags and Layers. I also use EdgeCollider2D components on the one way platforms.

Also, if you wanted to allow the player to fall through the playform if they are holding a down button, you could change your condition to something like: (!isOnGround || rigidbody2D.velocity.y > 0 || Input.GetButton(“Down”) )

This won’t work yet because there isn’t a Physics2D.IgnoreCollision yet, but it could be done with multiple layers. Add an EdgeCollider2D at the top. Define its height above the transform of the sprite it’s attached to as platformheight.

Then:

void FixedUpdate () {
        foreach (Rigidbody2D Object in FindObjectsOfType(typeof(Rigidbody2D)) as Rigidbody2D[])
        {
            float lowestpoint = Mathf.Infinity;
            foreach (Transform blah in Object.transform)
            {
                if (blah.name == "Edge")
                {
                    if(blah.transform.position.y<lowestpoint) lowestpoint = blah.transform.position.y;  
                }
            }
            if (lowestpoint > transform.position.y + platformheight)
            {
                Physics2D.IgnoreCollision(collider2D, Object.collider2D);
            }
        }
	}

Then stick four empty GameObjects called “Edge” on each side of your objects. If you’re just using square hitboxes for everything though you can just use lossyscale to work out whether they’re above or below. This will make the colliders only work if the object is above the platform.

But, again, Physics2D.IgnoreCollision isn’t an option yet, so Physics2D.IgnoreLayer could be used as a temporary fix.

e.g. You could put different platforms on different layers, and then snap the player’s height to the layer of the platform he’s in, and forbid collisions on the same layer. Which is a pain for now, and a bad idea in general.

But I would also say that just simply using triggers and killing velocity and gravity is a bad idea too - very fast objects will be able to bury themselves into walls and objects will float slightly above your platforms instead of resting on them.

Here is my workaround, because of bug (OnTriggerEnter2D call multiple times, i use 2 triggers, one for OnTriggerEnter, another for OnTriggerExit.

For triggers, i use EdgeColliders and on scene it looks like this
[23716-screen+shot+2014-03-17+at+16.35.53.png|23716]

Here is a full prefab structure

alt text

This code for enter Trigger

public class OneWayPlatformTriggerEnterController : MonoBehaviour {
	public int playerLayerId = 8;
	public int enabledPlatformId = 9;
	public int disabledPlatformId = 10;
	GameObject parentGO;

	void Start(){
		parentGO = gameObject.transform.parent.gameObject;
	}

	void OnTriggerEnter2D(Collider2D other){
		if (other.gameObject.layer == playerLayerId && parentGO.layer == enabledPlatformId){

			parentGO.layer = disabledPlatformId;
		}
	}
}

and this for exit

public class OneWayPlatformTriggerExitController : MonoBehaviour {
	public int playerLayerId = 8;
	public int enabledPlatformId = 9;
	public int disabledPlatformId = 10;
	GameObject parentGO;
	
	void Start(){
		parentGO = gameObject.transform.parent.gameObject;
	}

	void OnTriggerExit2D(Collider2D other){
		if (other.gameObject.layer == playerLayerId){
			parentGO.layer = enabledPlatformId;
		}
	}
}

As you can guess, my player has one layer Id, and oneWayPlatoform has 2 layer, one Layer when platform active and player can walk on it, and another layer which does not interact with player because of switched off in LayerCollision Matrix (Kevin is my player layer)

alt text

Since my character has the ability to grow and shrink I couldn’t really use many of these solutions. Here’s the one that I used.

First create 3 layers in your editor

  • Player
  • AbovePlatform
  • BelowPlatform

Next make sure that “BelowPlatform” Layer ignores the Player Layer in your Physics2D settings

[26682-screen+shot+2014-05-20+at+2.39.14+am.png|26682]

Add this code to your platform

//Platform.cs

using System.Collections;
using System.Collections.Generic;
	
public class Platform : MonoBehaviour {
	public bool passThrough;

	void Start (){
		    //Set the layer based on whether the platform is solid or passthrough
	        if(passThrough){
                  gameObject.layer = LayerMask.NameToLayer("BelowPlatform");
            }else{
                  gameObject.layer = LayerMask.NameToLayer("AbovePlatform");
            }
	}

	void FixedUpdate(){
            if(passThrough){
                    //Set the layer based whether they are above or below the platform on the y coordinate
	 	        bool ignore =  Player.FootPosition < transform.position.y;
                
                    if(ignore){
		                 gameObject.layer = LayerMask.NameToLayer("BelowPlatform");  
                    }else{
                         gameObject.layer =  LayerMask.NameToLayer("AbovePlatform");
                    }
            }
	}   
}

Add this code to your Player character

//Player.cs

using UnityEngine;
using System.Collections;


public class Player : MonoBehaviour {
        public BoxCollider2D boxCollider;

        public static float FootPosition{
                //We want to return a foot position slightly lower than our characters actutal 
                //foot position to avoid jerkiness when passing through the platform
		get{return transform.position.y- (boxCollider.bounds* 0.75f);}
	}

}

I hope this will help some of you till Unity releases version 5.

Try making a collider just below the platform and deactivating the platform collider whenever the player collides with that collider. Try out the link For the Tutorial Newtonians' Blog 3D: One Way Platform : Unity Tutorial

I ended up writing a script that projects a ray up and down from the character. If it collised with a platform that is marked as one way, it uses IgnoreCollisions to update teh physics items.

It took a bit of tweaking to get it working, but it seems to work pretty well.

I think the best method to achieve the one way platform is to make a edge collider around the part you want things to collide with and a polygon collider for the area under and in the middle of the object as a trigger with this script attached.

#pragma strict
var PutTheEdgeColliderHere : Collider2D;

function OnTriggerEnter2D (other : Collider2D)
{
	if (other.gameObject.tag == "TheTagThePlayerUses")
	{
		other.gameObject.tag = "ThroughOneWay";
		Physics2D.IgnoreCollision(defaultcollider,other.gameObject.collider2D,true);
	}
	
}

function OnTriggerExit2D (other : Collider2D)
{
	Physics2D.IgnoreCollision(defaultcollider,other.gameObject.collider2D,false);
	other.gameObject.tag = "TheTagThePlayerUses";
}

Do get this system wotking you need to:

1st Make a EdgeCollider2D and set it around the whole object.
alt text

2nd Then make a PolygonCollider2D a bit smaller but longer at the bottom.
http://i.gyazo.com/0278a84b5024acf36aeff9d696d627e6.png

3rd Assign the script and drag the EdgeCollider2D into the defaultcollider slot.
alt text

4th The last thing you need to do is make a tag named “ThroughOneWay” or something, just be sure to name it the same in both script and in unity editor.

alt text

The simplest method I found is similar to nicloay’s but I set the platforms collider to trigger.

public class PlatformDetector : MonoBehaviour
{
	EdgeCollider2D colPlatform;

	void OnTriggerEnter2D(Collider2D col)
	{
		if (col.gameObject.tag == "Platform")
		{
			col.gameObject.GetComponent<EdgeCollider2D>().isTrigger = false;
		}
	}

	void OnTriggerExit2D(Collider2D col)
	{
		if (col.gameObject.tag == "Platform")
		{
			col.gameObject.GetComponent<EdgeCollider2D>().isTrigger = true;
		}
	}
}

This code is placed on a platform check that is beneath the player. Hope this helps!

I’ve composed a solution that supports edge cases for ground checks, fallthrough platforms, multiple colliders on a player, and is even optimized to prevent performance issues. This can easily be expanded to work with any nearby entity by wrapping the platform with a simple trigger and applying the appropriate ignore collision logic.

using UnityEngine;
using System.Collections;

public class Platform : MonoBehaviour {
	SpriteRenderer playerGraphic;
	Rigidbody2D playerBody;
	BoxCollider2D playerBox;
	CircleCollider2D playerCircle;
	
	float padTop = -0.05f; // Padding applied to the top of feet platform checks (prevents player from barely standing on platform)
	bool visible; // Is the player able to interact with the platform
	
	[SerializeField] bool enableDrop;
	
	void Start () {
		GameObject player = PlayerAPI.current.gameObject;
		playerGraphic = player.GetComponent<SpriteRenderer>();
		playerBody = player.GetComponent<Rigidbody2D>();
		playerBox = player.GetComponent<BoxCollider2D>();
		playerCircle = player.GetComponent<CircleCollider2D>();
		
		ToggleCollision(playerGraphic.bounds.min.y > collider2D.bounds.max.y + padTop, playerBody.velocity.y, true);
	}
	
	void Update () {
		if (!IsDropping()) {
			ToggleCollision(playerGraphic.bounds.min.y > collider2D.bounds.max.y + padTop, playerBody.velocity.y);
		} else {
			ToggleCollision(false);
		}
	}
	
	bool IsDropping () {
		if (!enableDrop) return false;
		return Input.GetAxis("Vertical") < -0.1f && Input.GetButton("Jump");
	}
	
	void ToggleCollision (bool isEnabled) {
		// Fake a submission to get the values toggled
		if (isEnabled) {
			ToggleCollision(true, -5f);
		} else {
			ToggleCollision(false, 0f);
		}
	}
	
	void ToggleCollision (bool above, float velY, bool force = false) {
		if (above == visible && !force) return;
		
		// Check if falling and and above the platform
		if (above && velY < 0.1f) {
			ToggleColliders(false);
			SetDepth(0f);
			visible = true;
		} else {
			ToggleColliders(true);
			SetDepth(-5f);
			visible = false;
		}
	}
	
	void ToggleColliders (bool ignore) {
		Physics2D.IgnoreCollision(collider2D, playerBox, ignore);
		Physics2D.IgnoreCollision(collider2D, playerCircle, ignore);
	}

	// With setting the depth you can prevent ground checks from firing false positives
	void SetDepth (float depth) {
		Vector3 pos = transform.position;
		pos.z = depth;
		transform.position = pos;
		
	}
}