Search Unity

Help with collision detection on One-Way Platforms.

Discussion in 'Physics' started by JustGrant2009, Mar 8, 2021.

  1. JustGrant2009

    JustGrant2009

    Joined:
    Nov 8, 2017
    Posts:
    5
    I'm trying to figure out what is happening to my player character in this 2D platformer. I want the collision detection for when my player is through a one-way platform to just be based of the bottom of the sprite. If they jump and only get halfway through a one-way-platform, they should continue to fall back down. Only when the player's feet get through the platform (100% clearance of jumping through the platform) should they be allowed to land on it from the top.

    I'm using the "Platform Effector 2D" on my One-Way Platforms Tilemap layer, and I have them set to "one-way" and I've tried changing the arc from 1-180 and still no success. I've moved the Pivot point on my character to the bottom center of the sprite and still having no success with that attempt either.

    I took a video of this in action to better help explain what I'm finding. If there is anything else I can provide to get help solving this, please let me know.

     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,474
    Why are you using a composite in outline mode? I would suggest not using that or change outline mode to Polygon mode. Outlines are very useful but they also don't have any interior (they are outlines after all) so you can end up sitting on the lower edge inside it.

    The surface angle is the angle of the normal so on perfectly horizontal platforms, the surface is always straight up so a surface arc of 1 degree is more than enough. As you contact the corners this angle veers off towards the side edges so you can control how much of that you use but 1 degree is more than enough for surface = top only.
     
    JustGrant2009 likes this.
  3. JustGrant2009

    JustGrant2009

    Joined:
    Nov 8, 2017
    Posts:
    5
    Thanks Melv,

    I did that, but it still has the same problem. Here's what I've now changed it to.

    Any ideas?

     
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,474
    No way I can tell by just seeing that you've changed it to polygon mode. I've no idea about your movement controller and if it's completely physics driven i.e. you're not modifying Transform and only going via the Rigidbody2D or if you've got multiple colliders or not that are contacting etc. If you've got multiple colliders on your player then use One Way Grouping so they all act the same.

    Take a look at these example scenes in my GitHub repo and experiment there:

    OneWay
    OneWayGrouping

    You can see one-way grouping here in the original dev video:
     
    JustGrant2009 likes this.
  5. JustGrant2009

    JustGrant2009

    Joined:
    Nov 8, 2017
    Posts:
    5
    I am going to try to provide a bit more information then.

    I've since dropped the composite collider and just using the Tilemap collider (as you recommended). I'm also going to include another video, a couple screenshots showing the setup I have on my Player, and last, I'm including the code for my player controller script.

    I do a groundCheck by checking a Transform that's a child of my player. This "groundCheck" is only to determine if my player can jump, nothing more is done with it. Other than that, I think it's entirely physics driven.








    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using TMPro;
    6.  
    7. public class PlayerController : MonoBehaviour
    8. {
    9.     private Rigidbody2D rb;
    10.     private Animator anim;
    11.     private Collider2D coll;
    12.  
    13.     //For Ground Checking Purposes
    14.     bool grounded = false;
    15.     [SerializeField] public Transform groundCheck;
    16.     float groundRadius = 0.2f;
    17.  
    18.  
    19.  
    20.     [SerializeField] private LayerMask ground;
    21.     [SerializeField] private float jumpHeight = 25;
    22.     [SerializeField] private float moveSpeed = 8;
    23.     [SerializeField] private int cherries = 0;
    24.     //[SerializeField] private Text cherryText;
    25.     [SerializeField] private TextMeshProUGUI cherryText;
    26.     [SerializeField] private float hurtForce = 15f;
    27.     [SerializeField] private AudioSource cherry;
    28.     [SerializeField] private AudioSource footstep;
    29.     [SerializeField] private AudioSource ouch;
    30.     [SerializeField] private AudioSource jumpSound;
    31.  
    32.     private enum State { idle, running, jumping, falling, hurt }
    33.     private State state = State.idle;
    34.  
    35.     // Start is called before the first frame update
    36.     void Start()
    37.     {
    38.         rb = GetComponent<Rigidbody2D>();
    39.         anim = GetComponent<Animator>();
    40.         coll = GetComponent<Collider2D>();
    41.      
    42.     }
    43.  
    44.     // Update is called once per frame
    45.     private void Update()
    46.     {
    47.         //Don't move if you're hurt!
    48.         if(state != State.hurt)
    49.         {
    50.             Movement();
    51.         }
    52.  
    53.         //Check what animation state we should be in
    54.         AnimationState();
    55.  
    56.         //Sets animation based on enumerator state
    57.         anim.SetInteger("state", (int)state);
    58.  
    59.     }
    60.  
    61.     //FixedUpdate is called every physics frame (more than FPS)
    62.     private void FixedUpdate()
    63.     {
    64.         grounded = Physics2D.OverlapCircle(groundCheck.position, groundRadius, ground);
    65.     }
    66.  
    67.  
    68.  
    69.     //Interacting with Collectables
    70.     private void OnTriggerEnter2D(Collider2D collision)
    71.     {
    72.         if(collision.tag == "Collectable")
    73.         {
    74.             cherry.Play();
    75.             Destroy(collision.gameObject);
    76.             cherries += 1;
    77.             cherryText.text = cherries.ToString();
    78.         }
    79.     }
    80.  
    81.  
    82.     //Colliding with Enemies
    83.     private void OnCollisionEnter2D(Collision2D other)
    84.     {
    85.         if(other.gameObject.tag == "Enemy")
    86.         {
    87.             Enemy enemy = other.gameObject.GetComponent<Enemy>();
    88.             if(state == State.falling)
    89.             {
    90.              
    91.                 enemy.JumpedOn();
    92.                 Jump();
    93.             }
    94.             else
    95.             {
    96.                 state = State.hurt;
    97.                 ouch.Play();
    98.                 if(other.gameObject.transform.position.x > transform.position.x)
    99.                 {
    100.                     //Enemy to my right therefore I should be damaged and move left
    101.                     rb.velocity = new Vector2(-hurtForce, rb.velocity.y);
    102.                 }
    103.                 else
    104.                 {
    105.                     //Enemy is to my left therefore I should be damaged and move right
    106.                     rb.velocity = new Vector2(hurtForce, rb.velocity.y);
    107.                 }
    108.             }
    109.         }
    110.      
    111.     }
    112.  
    113.  
    114.  
    115.  
    116.     //Handing how the player moves about
    117.     private void Movement()
    118.     {
    119.         float hDirection = Input.GetAxisRaw("Horizontal");
    120.  
    121.         //Running Left
    122.         if (hDirection < 0)
    123.         {
    124.             rb.velocity = new Vector2(-moveSpeed, rb.velocity.y);
    125.             transform.localScale = new Vector2(-1, 1);
    126.          
    127.         }
    128.         //Running Right
    129.         else if (hDirection > 0)
    130.         {
    131.             rb.velocity = new Vector2(moveSpeed, rb.velocity.y);
    132.             transform.localScale = new Vector2(1, 1);
    133.          
    134.         }
    135.  
    136.         else
    137.         {
    138.          
    139.         }
    140.  
    141.         //Jumping
    142.         if (Input.GetButtonDown("Jump") && grounded)
    143.         //if (Input.GetButtonDown("Jump") && coll.IsTouchingLayers(ground))
    144.         {
    145.             grounded = false;
    146.             Jump();
    147.         }
    148.     }
    149.  
    150.  
    151.     //Switching what animation the player should show
    152.     private void AnimationState()
    153.     {
    154.  
    155.         if(state == State.jumping)
    156.         {
    157.             if(rb.velocity.y < .1f)
    158.             {
    159.                 state = State.falling;
    160.             }
    161.          
    162.         }
    163.         else if(state == State.falling)
    164.         {
    165.             if(coll.IsTouchingLayers(ground))
    166.             {
    167.                 state = State.idle;
    168.             }
    169.         }
    170.         else if(state == State.hurt)
    171.         {
    172.             if(Mathf.Abs(rb.velocity.x) < .1f)
    173.             {
    174.                 state = State.idle;
    175.             }
    176.         }
    177.  
    178.         else if(Mathf.Abs(rb.velocity.x) > 2f)
    179.         {
    180.             //Moving
    181.             state = State.running;
    182.         }
    183.         else if(rb.velocity.y < -0.8f)
    184.         {
    185.             state = State.falling;
    186.         }
    187.         else
    188.         {
    189.             state = State.idle;
    190.         }
    191.  
    192.  
    193.  
    194.     }
    195.  
    196.     private void Jump()
    197.     {
    198.         //isGrounded = false;
    199.         rb.velocity = new Vector2(rb.velocity.x, jumpHeight);
    200.         state = State.jumping;
    201.         jumpSound.Play();
    202.  
    203.     }
    204.  
    205.  
    206.     private void Footstep()
    207.     {
    208.         footstep.Play();
    209.     }
    210.  
    211.  
    212.  
    213.  
    214.  
    215.  
    216. }
    217.  
     
    Last edited: Mar 9, 2021
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,474
    Don't change tha transform unless you absolutely have to and certainly don't do it continually as you are with the local-scale. This can cause the colliders to be recreated along with their contacts which causes issues.
     
    JustGrant2009 likes this.
  7. JustGrant2009

    JustGrant2009

    Joined:
    Nov 8, 2017
    Posts:
    5
    I'm sorry, where am I changing the transform? I've followed a tutorial for 95% of my code, so I'm sorry I don't know where I took a wrong turn.

    EDIT: Oh! From what I see, the only place I do it is in the turning left/right to swap the direction of my sprites. I can fix that by adding more sprites/animation states, and get rid of that. However, the issue with the one-way platforms still has issue with jumping UP through one platform, colliding with a second (separate) one-way platform, and thus no longer being able to collide with the first one that I've successful passed up and through.
     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,474
    You can do it but doing it continually means it can cause issues. Only change the local-scale if you actually change direction; don't change it continually.

    Right so this is because it's the SAME collider (TilemapCollider2D). Effectors work per-collider and effectors were around a long time before tilemaps. Typically platforms were defined using primitive colliders.

    If you can set-up a small reproduction project which makes it super easy to reproduce and get it to me, I can take a look and verify the behaviour I'm describing for you. Either send it here or DM me with your email and I can set-up a private workspace for you to upload it to.
     
    JustGrant2009 likes this.
  9. JustGrant2009

    JustGrant2009

    Joined:
    Nov 8, 2017
    Posts:
    5
    Thank you!

    I've tried stripping most of the assets out and commenting out any code that would use them. It's not just a scene with the main player and some platforms for testing on.

    It's about 150 MB.

    I'll DM you shortly.
     
    MelvMay likes this.
  10. emredesu

    emredesu

    Joined:
    Aug 12, 2021
    Posts:
    55
    Soooooo have you been able to solve this? I am having the same issue at the moment, and I'd like to know the solution haha.
     
  11. IWillCreateGames

    IWillCreateGames

    Joined:
    Jul 11, 2022
    Posts:
    1
    I'm not sure of this'll help for you, but it worked for my case, so I'll share it.

    At first the components were set like this:

    Initially, I had:
    -composite collider enabled
    -platformer effector 2d enabled
    -Use one way on
    -Use one way grouping on
    -Surface arc 180

    ~The result was that I could not go through the platform from the side.

    So I changed it to:
    -composite collider enabled
    -platformer effector 2d enabled
    -Use one way on
    -Use one way grouping on
    -Surface arc 90*

    ~Changing the surface arc allowed me to go through the side. But I had another issue where if my player collider was touching 2 vertically adjacent platforms, it would fall through both despite being above the bottom platform.*

    And then:
    -composite collider enabled
    -platformer effector 2d enabled
    -Use one way on
    -Use one way grouping off*
    -Surface arc 90

    ~Turning one way grouping off had it's own issue where I could jump on the bottom of the tile collider where my player would be able to go inside the tile. I assume this is the issue you're talking about? *

    To fix this, I simply removed the composite collider which I don't think is needed for a one way platform:
    -composite collider disabled*
    -platformer effector 2d enabled
    -Use one way on
    -Use one way grouping off
    -Surface arc 90

    With these configurations or whatever, I could get through the side of the platform if I was underneath it, I could place 2 vertically adjacent platforms and have the player jump on both without falling through and I didn't have the issue of being able go inside and land on the bottom of the collider.

    Also yes, I know this was several months late haha...

    That aside, I'm actually experiencing another separate issue relating to this actually. My ground check returns true while my player is going through the platform which gives my player an infinite amount of jumps while in the platform. I don't know how to fix this. Do you have any tips or suggestions?

    This has been a tough nut to crack for me tbh.
     
    Last edited: Dec 17, 2023
  12. emredesu

    emredesu

    Joined:
    Aug 12, 2021
    Posts:
    55
    Hey! No worries, I had found my own solution too, and thanks for actually taking your time to respond :D

    About your issue, how do you do your ground check? I do mine with a boxcast and check for the "Ground" layer, and since both normal ground and one way platforms have the "Ground" layer it works without an issue for me.
     
  13. germza123

    germza123

    Joined:
    Feb 26, 2022
    Posts:
    2
    Hey, sorry if my format is not the best ( this is my first comment ever), I designed a workaround. There is no way that just the effector is going to work well with the tilemap composite collider but i really didnt want to make prefabs or different objects for the 2 way plartforms, so i came up with this:

    First, i made two different layers:

    the ground layer:
    upload_2024-4-19_12-21-9.png

    and the twowayground:
    upload_2024-4-19_12-20-44.png

    each with it's collisionscript.

    Then in the scripts that handles the player's collisions i check where am i standing on or if im inside the twoway layer:
    Code (CSharp):
    1.  
    2.  
    3.         public bool CheckIfOnGround()
    4.         {
    5.             return Physics2D.OverlapCircleAll(_groundCheck.position, _groundCheckRadius, _whatIsGround)
    6.                 .Any(collider => !collider.isTrigger);
    7.         }
    8.         public bool CheckIfInsideTwoWayGround()
    9.         {
    10.             return Physics2D.OverlapCapsuleAll((Vector2)transform.position + _playerRectCollider.position, new Vector2(_playerRectCollider.size.x / 2f, _playerRectCollider.size.y), CapsuleDirection2D.Vertical, 0, _whatIsTwoWayGround)
    11.                 .Any(collider => !collider.isTrigger);
    12.         }
    13.         public bool CheckIfOnTopOfTwoWayGround()
    14.         {
    15.             return Physics2D.OverlapCircleAll(_groundCheck.position, _groundCheckRadius, _whatIsTwoWayGround)
    16.                 .Any(collider => !collider.isTrigger);
    17.         }
    18.  
    19.  
    -------------------

    so now i now when im in contact with that layer, now the player script has a method to ignore the collisions with the twoway layer:

    Code (CSharp):
    1.  
    2.     public void IgnoreTwoWayCollision(bool value)
    3.     {
    4.         switch (value)
    5.         {
    6.             case true:
    7.                 SpriteRenderer.color = Color.yellow;
    8.                 IgnoreLayerMask(Core.CollisionSenses.whatIsTwoWayGround, true);
    9.                 break;
    10.             case false:
    11.                 SpriteRenderer.color = Color.white;
    12.                 IgnoreLayerMask(Core.CollisionSenses.whatIsTwoWayGround, false);
    13.                 break;
    14.         }
    15.     }
    16.  
    17.  
    ------------------
    so now i place the conditions on each player state:

    Code (CSharp):
    1.  
    2. OnTheGroundedState:
    3.     public override void LogicUpdate()
    4.     {
    5.         base.LogicUpdate();
    6.  
    7.         if (IsGrounded == false && IsOnTwoWayGround == false && JumpInput == false)
    8.         {
    9.             player.PlayerStates.FallingState.StartCoyoteTime();
    10.             stateMachine.ChangeState(player.PlayerStates.FallingState);
    11.         }
    12.  
    13. if (JumpInput)
    14.         {
    15.             if(IsOnTwoWayGround && YInput < 0)
    16.             {
    17.                 player.IgnoreTwoWayCollision(true);
    18.                 stateMachine.ChangeState(player.PlayerStates.FallFromTwoWay);
    19.             }
    20.             else if(playerData.currentJumpsLeft > 0 && IsTouchingCeiling == false)
    21.             {
    22.                 core.Movement.CheckIfShouldFlip(XInput);
    23.                 stateMachine.ChangeState(player.PlayerStates.JumpState);
    24.             }
    25.         }
    26. }
    27.  
    28.  
    --------------------------

    in my Jumping state:
    public override void Enter()
    {
    base.Enter();
    player.IgnoreTwoWayCollision(true);
    }

    -------------------------

    In my InFallingState:

    Code (CSharp):
    1.  
    2.   public override void Enter()
    3.   {
    4.       base.Enter();
    5.       player.IgnoreTwoWayCollision(core.CollisionSenses.CheckIfInsideTwoWayGround());
    6.   }
    7.     public override void LogicUpdate()
    8.     {
    9.         base.LogicUpdate();
    10.          if(IsOnTwoWayGround)
    11.                 {
    12.                     if(YInput < 0)
    13.                     {
    14.                         stateMachine.ChangeState(player.PlayerStates.FallFromTwoWay);
    15.                     }
    16.                     else
    17.                     {
    18.                         if (Mathf.Abs(core.Movement.RB.velocity.y) < 0.1)
    19.                         {
    20.                             player.IgnoreTwoWayCollision(false);
    21.                             stateMachine.ChangeState(player.PlayerStates.LandState);
    22.                         }
    23.                     }                  
    24.                 }
    25. }
    26.  
    27.  
    and the result is this: ( i added the yellow color to know when i'm ignoring the two way layer)


    upload_2024-4-19_12-46-5.gif

    Hope it helps you

    PD: If you have any advice on how can i improve my posting, i would be very thankful
     
    Last edited: Apr 19, 2024 at 9:36 PM
  14. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,474
    germza123 likes this.