layers, collision and one-way platforms (a question)

Hi

I’m trying to make a 2D platformer, and a feature I want is one-way collisions. Basically this means that a creature (player, enemy, powerup, whatever) can pass through a platform from below without any interference, but when it collides from above the platform is rock solid.
if you don’t know what i mean, look at this video.

At 1:05 he jumps though the grass but then he stands safely on top of it.

Here is what it looks like in my project:

The capsule is the player, and the sphere will be an enemy.

There is a trigger slightly lower than the platform and it is a child object of the platform. The idea is to alter something about the platform before the player touches the collider.
So here I jump, enter the trigger, which lets me pass the collider and once I leave the trigger, the collider is restored to its default and becomes solid.

The obvoius thing to do would be to deactivate the collider somehow. However, this would also make the enemy fall right through it. If I had a separate collider for enemies and players, it would prevent bouncing enemies (such as the flying turtles from Mario) from passing the platform.

So instead of altering the collider, I woud have to alter the object.

Then I remembered a feature from Torque: Collision groups and layers.
What does this mean? Each object has a layer (for rendering order) and a group. Any object could be made to interact only with specific groups and layers. So I could for example have the first ten groups only for stuff in the foreground, the last ten for collisions in the background and only a handful of groups for stuff in between, and nothing would interfere with each other.

So here is my idea: Once the player, or any other object with such an ability, enters the trigger, its collision group is set to a certain group. The platform will never be able to have any collision with that group (by default, and nothing about it should be changed), so it should just ignore the object. Once the object leaves the trigger its group is reset to something normal again. So if I jumped onto the platform i can stand on it, and if I jumped back down I will safely land on the platform below, even if it is one-way as well.
It is necessary to be able to only make specific platforms one-way, as there are actual ceilings supposed to exist as well.

The big question is of course whether there is such a feature as collision groups and how to change them.

The trigger itself works, I use a pretty simple script and get my response:

function OnTriggerEnter (jumper: Collider) {
	//set jumper's layer to something that can pass through the platform
	Debug.Log ("ping");
}

function OnTriggerExit (jumper: Collider) {
	//reset jumper's layer to something that the platform collides with
	Debug.Log ("pong");
}

In your modeling program, create a copy of the mesh and do not provide a downward-facing face. You might also omit sideways faces. Use that copy as the collision mesh.

Collision faces only block movement coming from the direction of their normal, so an upward-pointed face will prevent things from moving downward, but allow them to move upwards.

So you are suggesting, to use a custom mesh, that only detects collision from above, instead of the box collider, on the platform? Okay, that would be even easier.
Only problem: How do I get the Box Collider’s mash out and inside Blender? And then make it like that? Well, I can ask that last question on some Blender forum, but I’d still need the mesh.

I’m sorry, all I was doing with Unity so far only involved scripting and using the regular primitive shapes that come with the editor. The art (either 2D with Sprite Manager or 3D models) was supposed to come from an artist, after I got the gameplay skeleton finished.

EDIT:
There is still a problem with that technique: In games like Contra on NES the player can duck, then press down, and he’ll fall through the one-way platform.

at 3:08

My idea looks like it would handle both jumping and falling through these platforms. When the player ducks, his collision group is set to the special one, and once he passes the platform, the trigger automatically restores it back to normal.

PUSH PLZ HELP!!111!!!

Nah, just kidding, I figured it out :wink:

So here is what I did, just in case someone digs up this thread:
The setup is the same as above, except I adjusted the size of the trigger a bit:

It goes so much to the side to make sure the player does not hit the edge. It’s also higher to give the trigger enough time to react.

Of course this leads to another problem: If the platform below is a one-way platform as well, the player falls right through that one even if not jumping.
So intead of messing with layers, I used the Physics.IgnoreCollision() function which will only affect the trigger’s parent, nothing else.

For jumping down I wrote a second script which will set the player’s layer to the special one and the trigger below will restore it back to normal. So the trigger can only restore layers, but not deviate from the default.

So, here are the scripts:

function OnTriggerEnter (jumper: Collider) {
	//make the parent platform ignore the jumper
	var platform = transform.parent;
	Physics.IgnoreCollision(jumper.GetComponent(CharacterController), platform.GetComponent(BoxCollider));
}

function OnTriggerExit (jumper: Collider) {
	//reset jumper's layer to something that the platform collides with
	//just in case we wanted to jump throgh this one
	jumper.gameObject.layer = 0;
	
	//re-enable collision between jumper and parent platform, so we can stand on top again
	var platform = transform.parent;
	Physics.IgnoreCollision(jumper.GetComponent(CharacterController), platform.GetComponent(BoxCollider), false);
}

This one goes onto the trigger. The trigger MUST be the platform’s child and will ONLY affect its parent platform.

function Update () {
	//tracks if the button combo for falling through is pressed
	//usually in video games this is down + jump
	if(Input.GetAxis("Vertical") == -1){
		 //the layer moving platforms cannot collide with
		gameObject.layer = 9;
	}
	else{
		gameObject.layer = 0; //default layer
	}
}

This one is attached to the player, if such an ability is even desired (e. g. Mario cannot jump down, so no need there). It’s just a proof of concept and it would probably need more refinement to implement it into a proper control scheme. Also the moving platforms MUST have a special layer for this and the collision under Edit-> Project Setting-> Physics must be deactivated for these layers.

Messing with layers is only necessary if the player is supposed to be able to jump down.

2 Likes

Just wanted to say thanks hiphish! This thread is 2 years old, but this solution helped me a ton! Thanks for sharing!

Probably one of the better solutions I’ve found on this.

I however went a much “lazier” route, as my situation is not as complex. Hehe.

I simply check if the character’s velocity is greater than 0(he is jumping) and use IgnoreLayerCollision on the platforms accordingly.

                    // pretty dirty way to do this, hehe
                    Physics.IgnoreLayerCollision(8, 12, (m_myRB.velocity.y > 0.0f));
6 Likes

Lazy or not, that is a great solution :slight_smile: Thanks JG!

1 Like

in the prototype of the other brothers it was simple : there’s pass through platforms and there’s solid platforms. whenever the player jumps, the player’s collision layer changes to solid only, and when the player is coming back down, the collision layer is is changed to pass through platforms. This is elegant, simple and always works with just 2 lines of code for everything. No special meshes needed.

However, in the end I wrote my own custom collision and physics system (mostly because I wanted to).

3 Likes

I’ve been messing around with this too. I just made it if the player is higher than the platform, activate the platform’s collider, if not, he can jump through it. Using tags it works well. I did this for a different reason, though… It was because when my character would jump and hit the side of a platform, it would “grab” onto it, not just fall… So I needed to turn the platform off. Added bonus (or negative side-effect) is that the player can jump up through the platform.

I wrote a pretty simple script that does the same thing - http://pastebin.com/KArtkV1E

/*********************************************************

  • Author: Justin Williamson
  • CubeScript for simple pass through script.
  • Create your character two emptygameObjects and tag them (“TopTrigger”)(“BottomTrigger”)
  • getComponent these emptygameObjects box colliders
  • check the isTrigger box for both box colliders
  • TopTrigger Should be the length of the gameObject “player” and the BottomTrigger
  • should be small and at the bottom of the gameObject.
  • getComponent your ground blocks rigidbodies and uncheck useGravity
  • drop this script into your ground blocks and poof, jump through blocks.
  • only error, hits his head on second jump first time. Small Glitch.
  • ********************************************************/

using UnityEngine;
using System.Collections;

public class CubeTriggerScript : MonoBehaviour {

void OnTriggerStay(Collider hit){
if(hit.gameObject.tag == “TopTrigger”){
rigidbody.collider.isTrigger = true;
rigidbody.isKinematic = true;
}
}
void OnTriggerExit(Collider hit){
if(hit.gameObject.tag == “BottomTrigger”){
rigidbody.collider.isTrigger = false;
}
}
// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

}
}

hello, I dealt with this problem recently and I came up with a simple solution. Just move the player collider when velocity.y is greater than zero in a position where not collide with the platform, and when velocity.y is equal or less than zero move the collider back in its original position. I illustrate this in a draw, “is side view”. The enemy collider need to be larger than the platform collider if the player hits the enemy when is jumping.

2 Likes

Justmail’s approach is easy and nice, problem is if you’ve got an enemy right in the platform you are jumping to. He wont then kill you =) I’ll go to hiphish’s IgnoreCollision() to false platform-player. That way enemies can still kill you. Layers won’t do when you have more than one player in scene but this Physics.IgnoreCollision( player, platform ) system seems to be 100% usable for having 2 players right?

I love this post, so many interesting workarounds!!!

2 Likes

this solution is brilliant!!! You are a genius!!!

1 Like

Hi, I know this post is old but I found a solution to justmail0116’s glitch.

so first change

void OnTriggerExit(Collider hit){
if(hit.gameObject.tag == “GroundCheck”){
rigidbody.collider.isTrigger = false;
}
}

To

void OnTriggerEnter(Collider hit){
if(hit.gameObject.tag == “GroundCheck”){
rigidbody.collider.isTrigger = false;
}
}

then add this after the OnTriggerEnter method.

void OnTriggerExit(Collider hit){
if(hit.gameObject.tag == “GroundCheck”){
rigidbody.collider.isTrigger = true;
}
}

This prevents your characters head from “bumping” into the platform the first time you try to jump through.
This also resets the platform so you can jump through it as many times as you want without “bumping” into it.
Hope this helps :slight_smile:

Not bad for a novice programmer eh? :]

Well, this problem is really difficult as far as i can see. I have a few requirements for the algorithm:

  1. Platform is a near flat BoxCollider in any angle. You can’t just analyze the Y axis, but treat the issue as Vector2D if necessary.
  2. All moving entities must be able to hit and pass through the platforms, not just player.

I used to use simple dotproduct calculation to set collider active when player is above the platform with script in question, but then they may shut off when monsters are walking over them.

This is becoming a big mess overtime, and i think i may have to rethink the approach entirely. Might have to make 2 subobjects inside the main one, invisible at runtime but mark the surface level vector with 2 “points”. It’s rather difficult to retrieve from box collider, although possible too.

Random code i got so far, it’s not working:

public class Platform : MonoBehaviour {

    BoxCollider2D thisColl;
    Vector2 platformAxis;
    Vector2 frontAxis;

    void Start () {
        thisColl = GetComponent<BoxCollider2D>();
        thisColl.isTrigger = true;

        float angle = (transform.rotation.eulerAngles.z+90) * Mathf.Deg2Rad;
        platformAxis = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
        frontAxis = new Vector2(platformAxis.y, platformAxis.x);
    }

    void OnTriggerEnter2D(Collider2D coll) {
        GameObject entity = coll.transform.parent.gameObject;
        Vector2 velocity = entity.rigidbody2D.velocity;

        //if (Vector2.Dot(coll.transform.position - transform.position, platformAxis) >= 0) {
        if (Vector2.Dot(entity.rigidbody2D.velocity, platformAxis) <= 0) {
            Debug.Log("hit");
            //Debug.Log(entity.name);
            Debug.Log(velocity);

            //entity.transform.position -= (Vector3)velocity;
            //entity.transform.position += (Vector3)(platformAxis *
            //    ( Vector3.Distance(entity.transform.position, transform.position) ));
            //entity.transform.position -= new Vector3(0, velocity.y, 0);

            //entity.rigidbody2D.velocity += //new Vector2(velocity.x, 0);
            //    platformAxis * velocity.magnitude*0.1f;

        } else {
            Debug.Log("pass through");
        }
    }

    void OnCollisionEnter2D(Collision2D coll) {
        //coll.
        /*if (Vector2.Dot(coll.transform.position - transform.position, platformAxis) < 0) {
            Physics2D.IgnoreCollision(thisColl,    coll.collider, true);
            coll.gameObject.rigidbody2D.velocity -= 2*coll.relativeVelocity;
            Debug.Log("pass through");
        } else
            Debug.Log("hit");*/
    }

    void OnCollisionExit2D(Collision2D coll) {
        Physics2D.IgnoreCollision(thisColl,    coll.collider, false);
    }

    /*Transform player;
    BoxCollider2D coll;
    
    void Start () {
        GameObject playerObj = GameObject.Find("Monty");
        if (playerObj != null) player = playerObj.transform;
        coll = GetComponent<BoxCollider2D>();
    }

    void Update () {
        if ((player != null) && (coll != null)) {

            float angle = (transform.rotation.eulerAngles.z+90) * Mathf.Deg2Rad;
            Vector2 axis = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));

            coll.enabled = Vector2.Dot(player.transform.position - transform.position, axis) >= 0;

            // DEBUGGING
            //Debug.DrawLine(transform.position, transform.position+
            //               new Vector3(axis.x, axis.y, 0));
            //GetComponent<SpriteRenderer>().enabled = coll.enabled;
        }
    }*/
}

While I like the simplicity of this solution, is not true that if your jump peaks while you are inside the platform and velocity.y changes to 0, won’t the collision turning back on mean that the player will get stuck inside the platform (or, well, pushed up out of it if that’s how your collision works)?

Player will most likely be pushed up.

I have another code here, which is simple in theory. Have 1 trigger and 1 solid collider for platform. Trigger is a bit bigger box placed under the main collider. Too bad it’s not working… player still hits the platform for some reason. Next i might look into collision layers.

    Collider2D thisColl;

    void Start () {
        Component[] colliders = GetComponents<BoxCollider2D>();
        // Find the solid collider within gameobject
        foreach (BoxCollider2D coll in colliders) {
            if (!coll.isTrigger) thisColl = coll.collider2D;
        }
    }

    void OnTriggerEnter2D(Collider2D coll) {
        Physics2D.IgnoreCollision(thisColl, coll.collider2D, true);
    }

    void OnTriggerExit2D(Collider2D coll) {
        Physics2D.IgnoreCollision(thisColl, coll.collider2D, false);
    }

Reason this is not working is very likely that my player is trying to simulate capsule shape with 2 circles and 1 box colliders. So when the first circle at head turns off it bounces back off.

Ok, the end solution is finally found that meets all the demands. Use default layer (0) for normal walls, and 8 for platforms, i name it Platform. Layer 9 called GoThrough. Basically, when entity velocity.y < 0 (going down), set layer 0, and if it’s > 0, set it 9. That’s all fine with players and monsters, with platforms in almost any angle from -45 to 45 degrees.

But then you discover that when entity start going down while it’s in the middle of platform, the collider solidifies and player suddenly jumps to either nearby side, up or down of platform. To combat that i make a new subobject for platform object and give it a trigger box collider, slightly under the platform. Set this trigger in GoThrough layer! This is to check that layer value is not changed while trigger is collided with. It has to be in subobject, because if it was in platform object, having platform layer would make trigger never activate.

Long story short, here is code to place for all entities like players and monster scripts. I am assuming that entity may be combination of multiple colliders, not just 1 capsule (which i hope will someday be implemented for 2D aswell):

int inPlatform = 0;
GameObject colliders;

void Start () {
    // Colliders are in childobject named Colliders
    colliders = GameObject.Find(gameObject.name+"/Colliders");
}

void Update () {
    Vector2 vel = rigidbody2D.velocity;
    // Change collision layer for go-through platforms
    if (inPlatform <= 0) {
        inPlatform = 0;
        int newLayer = colliders.layer;
        if (vel.y >= 0) {
            // Only set GoThrough layer if upwards speed is slightly under jump speed
            // Prevents bug where player drops through angled platform when walking over it
            if (vel.y > 3.0f) newLayer = 9;
        } else newLayer = 0;
        if (colliders.layer != newLayer) colliders.layer = newLayer;
    }
}

void OnTriggerEnter2D(Collider2D coll) {
    if ((coll.gameObject.layer == 9) && (colliders.layer == 9)) {
        inPlatform++;
    }
}

void OnTriggerExit2D(Collider2D coll) {
    if (coll.gameObject.layer == 9) {
        inPlatform--;
    }
}

And finally go to Edit → Project settings → Physics 2D, and disable collision between layers GoThrough and Platform.

I’m just starting out w/ Unity, but tell me if this concept is workable:

  • All “normal” fully-solid scenery colliders are on layer 1

  • Jumpthrough colliders are on layer 2

  • Player ignores ALL layer colliders on layer 2 (by default) while in the air

  • While in air, player raycasts from his feet upward to see if he’s colliding with a collider from Layer 2, and if he is, he does nothing else but continue jumping/falling and ignoring colliders from layer 2 (to prevent the head from getting stuck in geometry for layer 2)

  • However, if while in the air, and he’s NOT colliding with anything from layer 2, while jumping up we cast rays from his feet to down below his feet a short distance (i.e. the length of his current falling speed ) checking for layer 2 colliders (perhaps with multiple rays casting downward from his feet, consisting of rays across the width of his body)

  • if a collider is found on layer 2 colliding with those downward rays from his feet at that position, stop ignoring collisions w/layer 2

  • start ignoring collisions with layer 2 again when jumping up OR when no collider from layer 2 is found beneath the player’s feet anymore (i.e. since the player is moving upwards and only the upward rays are being cast atm, which disables the need to cast downward rays until the player is falling again – but once the player starts falling again, it checks once more w/upward rays from feet to top of player’s head to see if the player is colliding w/layer 2 colliders, and if not, it checks below for a collider – otherwise, once again, the player ignores layer 2 colliders)

  • also, if the player is “onGround” the vertical rays are disabled when upward speed is 0 to prevent falling through jumpthrough/one-way platforms you’re already on

Do you think this is workable for jumpthrough geometry without the need to setup special trigger areas?