Search Unity

Recommended way to handle getting onto ledges whose collider2D is at an angle?

Discussion in 'Scripting' started by tfishell, Aug 1, 2018.

  1. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    (if this is tl;dr search for "possible solution?" to help me decide if the following would be a solution)

    I'm experimenting with a 2D platformer, and (while it's not crucial for the type of game I'm planning on making) am trying to figure out a good way to handle the player getting onto ledges.

    Currently I have 2 rays sticking on from the player to detect horizontal collisions, an upper one and a lower one. (The only reason for two would be the top one will play an animation of the player climbing up onto the ledge, while the bottom one will just move the player up.) I wonder though if it'd be better to have a multitude of rays sticking out horizontally rather than two vertically? Would that allow for better collision detection? (collision based on a small horizontal ray instead of some point on a long vertical ray, if the second option is even possible accurately.) picture related:

    I figured out pretty easily how to do ledges whose collider2D is at a 0 degree angle like a box or rectangle - just move the player to the max Y of the box.

    However I'm not sure how to so easily do that for a ledge whose collider2D that is at an angle, whether the z-rotation of a boxcollider2d is not 0 or a polygon collider is used (but not rotated), because the max Y isn't all the same and upon collision, the player will appear at the max Y point above the point of collision (before dropping back down to the collider and point of collision).

    Something that sometimes works okay for the second image is to get the hit.point.y and move the player to that point, but (from what I can tell) it only works if the frame of the collision detection gets the first pixel or two of the boxcollider2d, otherwise the player doesn't necessarily get sent high up enough to keep moving on the platform.


    possible solution? Anyway, I'm wondering if something that might work is to, upon collision, shoot out a raycast up from the point of collision, and once that ray stops colliding, have the character move to that point (or slightly above to avoid getting stuck):

    Or perhaps there's some geometry math I can use. (I'm trying to study game-related math here and there but still don't really know enough to be effective) Or just a better way to handle this.

    Any help would be appreciated. Code is below for what I'm currently doing (though I edited out some of it, the code below only handles the character moving right) though I'm not sure its inclusion is necessary here.

    Code (CSharp):
    1.  
    2. void Update()
    3. {
    4.     LedgeRays();
    5. }
    6. void LedgeRays()
    7. {
    8.     //half the box collider height
    9.     float halfBoxCollideHeight = boxCollide.bounds.size.y / 2;
    10.  
    11.     //store min and max x and y to shorten lines below (slightly)
    12.     float boxCollideBoundsMaxX = boxCollide.bounds.max.x;
    13.     float boxCollideBoundsMaxY = boxCollide.bounds.max.y;
    14.     float boxCollideBoundsMinX = boxCollide.bounds.min.x;
    15.     float boxCollideBoundsMinY = boxCollide.bounds.min.y;
    16.  
    17.     //create the ledgechecks
    18.     topLedgeCheckRaycast = Physics2D.Raycast(new Vector2(boxCollideBoundsMaxX + amountFromCollider, boxCollideBoundsMaxY), Vector2.down, halfBoxCollideHeight, whatIsGround);
    19.     bottomLedgeCheckRaycast = Physics2D.Raycast(new Vector2(boxCollideBoundsMaxX + amountFromCollider, boxCollideBoundsMinY), Vector2.up, halfBoxCollideHeight, whatIsGround);
    20.  
    21.     if (isFacingRight)
    22.     {
    23.         //face the rays to the right
    24.         topLedgeCheckRaycast = Physics2D.Raycast(new Vector2(boxCollideBoundsMaxX + amountFromCollider, boxCollideBoundsMaxY),
    25.             Vector2.down,
    26.             halfBoxCollideHeight,
    27.             whatIsGround);
    28.  
    29.         bottomLedgeCheckRaycast = Physics2D.Raycast(new Vector2(boxCollideBoundsMaxX + amountFromCollider, boxCollideBoundsMinY),
    30.             Vector2.up,
    31.             halfBoxCollideHeight,
    32.             whatIsGround);
    33.  
    34.         //draw the rays to the right
    35.         Debug.DrawLine(new Vector2(boxCollideBoundsMaxX + amountFromCollider, boxCollideBoundsMaxY),
    36.             new Vector2(boxCollideBoundsMaxX + amountFromCollider, boxCollideBoundsMaxY - halfBoxCollideHeight),
    37.             Color.green);
    38.  
    39.         Debug.DrawLine(new Vector2(boxCollideBoundsMaxX + amountFromCollider, boxCollideBoundsMinY),
    40.             new Vector2(boxCollideBoundsMaxX + amountFromCollider, boxCollideBoundsMinY + halfBoxCollideHeight),
    41.             Color.yellow);
    42.     }
    43.  
    44.     //store rays in array
    45.     RaycastHit2D[] ledgeColliders = new RaycastHit2D[2];
    46.     ledgeColliders[1] = topLedgeCheckRaycast;
    47.     ledgeColliders[0] = bottomLedgeCheckRaycast;
    48.  
    49.     //if the player velocity, collision, etc. was turned off for previous frame, turn it back on
    50.     TurnOffForLedge(false);
    51.  
    52.     //for the ledge ray collider array
    53.     for (int i = 0; i < ledgeColliders.Length; i++)
    54.     {
    55.         //if top one is null but bottom one isn't and colliding with ground
    56.         if (ledgeColliders[1].collider == null && ledgeColliders[0].collider != null
    57.             && ledgeColliders[0].collider.tag == "Ground") //&& ledgeColliders[i].collider.tag == "Ground" && !crouch && boxCollide.max.y >= topLedgeCheckRaycast.collider.GetComponent<Collider2D>().bounds.max.y)
    58.         {
    59.             //the obj being collided with
    60.             GameObject otherObj = ledgeColliders[0].collider.gameObject;
    61.  
    62.             //and if player is facing right, colliding with ground, the right hit collider isn't null but the two aren't hitting the same thing
    63.             if (isFacingRight && rightUpOrDownRaycast.collider != null //right hit collider
    64.                 && rightUpOrDownRaycast.collider.gameObject != otherObj) //if the hit and ledge don't strike the same thing
    65.             {
    66.                 //turn off gravity and velocity and collision for the remainder of the frame
    67.                 TurnOffForLedge(true);
    68.                 //move player to right point
    69.                 gameObject.transform.position = new Vector2(ledgeColliders[0].point.x - boxCollideBounds.x / 2 + skinWidth, ledgeColliders[0].point.y + amountFromCollider * 2);
    70.                 rb.velocity = new Vector2(0, 0);
    71.                 /*gameObject.transform.position = new Vector2(
    72.                     ledgeColliders[0].collider.GetComponent<Collider2D>().bounds.min.x - boxCollideBounds.x/2 + amountFromCollider,
    73.                     ledgeColliders[0].collider.GetComponent<Collider2D>().bounds.max.y);*/
    74.             }
    75.         }
    76.     }
    77. }
    78.  
    79. void TurnOffForLedge(bool _playerMovedOntoLedge)
    80. {
    81.     if (_playerMovedOntoLedge == true)
    82.     {
    83.         rb.velocity = new Vector2(0, 0);
    84.         rb.gravityScale = 0;
    85.         boxCollide.enabled = false;
    86.         Debug.Log("get here?");
    87.     }
    88.     else
    89.     {
    90.         rb.gravityScale = 8;
    91.         boxCollide.enabled = true;
    92.     }
    93.     return;
    94. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,747
    You've done a lot of legwork here and I think you're going to have a feel for what works best as far as your specific level designs, etc. When you say raycast I assume you're actually raycasting in 2D from your toe up to your belt or so.

    Another way is to do a series of calls to Physics2D.OverlapPoint once you determined that either the lower or upper raycast hits something. Or you could skip the raycasts and go with your "better option?" image at the top and just do a series of calls to OverlapPoint to test points right in front of you to find when you hit. I can't imagine you would need more than 8 or perhaps 12 discrete points to check for OverlapPoint, so you could probably do it every frame without too much problem.

    I've done that "better option?" multi-check way before to find if the player is standing on the verge of falling off something, and then they start to slide towards the edge slowly, giving the player a chance to push back onto the ledge or press jump.
     
    tfishell likes this.
  3. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    Thanks man, I'll be looking over the post more thoroughly tonight and take it into consideration (b/c I'll have time to work on my game).
     
  4. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    I don't suppose you happen to have any idea of how to find the point on a ray (raycasthit2d) where a collision stops? I may create a separate thread for this question, but if this isn't possible to get that point, I might try what worked for you.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,747
    In your specific use case, is there any reason you can't cast from belt buckle down instead of up from toes? If you hit something then, the player hops onto that spot.
     
    tfishell likes this.
  6. tfishell

    tfishell

    Joined:
    Nov 20, 2017
    Posts:
    97
    tbh I never thought of that, that might work. However I think part of the problem may depend on where the ray is when the contact point/collision info is captured; I'm concerned if the ray may send the player box collider2d inside the ground boxcollider2d depending on when the actual collision info is captured. (though I am turning off box collider2d for the frame of the collision and reinstating it right after)

    But here's what I presume the logic is, drawn up; I'll try it tomorrow or Sunday.

    (Also just FYI, my platformer is meant to be semi-realistic so probably no "floating platforms" or huge jumps); I'm planning on horror themes and monsters as enemies but set in our world (inner city [streets, offices, etc.] or forest, I'll play around with the setting and visuals once I have the game code pretty solid))