Created
April 7, 2021 12:01
-
-
Save grapefrukt/be310f27195d84957dfd1e2e024e7b44 to your computer and use it in GitHub Desktop.
Makes a CircleCollider2D slide smoothly along the inner edge of a rounded EdgeCollider2D
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
public class CornerCheat : MonoBehaviour { | |
public CircleCollider2D circle; | |
public Rigidbody2D body; | |
EdgeCollider2D edge; | |
[Range(0, .2f)] public float distanceThreshold = .03f; | |
[Range(0, 1)] public float dotThreshold = .96f; | |
[Range(0, 1)] public float lookahead = 1; | |
[Range(0, 1)] public float velocityTweak = .5f; | |
int mask; | |
void Start() { | |
mask = LayerMask.GetMask("Wall"); | |
} | |
void FixedUpdate() { | |
// find any nearby EdgeCollider2D | |
if (edge == null) edge = Physics2D.OverlapCircle(body.position, circle.radius + distanceThreshold, mask) as EdgeCollider2D; | |
if (edge == null) return; | |
// use the current velocity to look ahead where this object will be next frame | |
var center = body.position + body.velocity * (Time.fixedDeltaTime * lookahead); | |
// find the nearest point on the edge collider, unity provides a edge.ClosestPoint() but it was giving inaccurate answers | |
var nearest = FindClosest(center); | |
// work out the distance from the outer radius of the ball to the edge, if it's too far away we bail | |
var distance = Mathf.Abs(Vector2.Distance(nearest, center) - circle.radius); | |
if (distance > distanceThreshold) { | |
edge = null; | |
return; | |
} | |
// figure out the normal | |
var normal = (center - nearest).normalized; | |
// now, we work out if we're at either of the ends | |
// given the way i'm calculating the normal, this breaks once we're "past" the ends | |
var distanceToFirst = Vector2.Distance(edge.points[0], nearest); | |
var distanceToLast = Vector2.Distance(edge.points[edge.pointCount - 1], nearest); | |
var isFirst = distanceToFirst < .01f; | |
var isLast = distanceToLast < .01f; | |
// if we're on the first or last points, round the normal so it's either fully on X or y | |
if (isFirst || isLast) normal = SnapNormal(normal); | |
// get the tangent by rotating the normal 90 degrees | |
var tangent = R90(normal); | |
// calculate the dot product to figure how perpendicular we are to the tangent | |
var dot = Vector2.Dot(tangent, body.velocity.normalized); | |
// if the dot is below some threshold, we're probably just bouncing off the wall not sliding, so we bail | |
if (Mathf.Abs(dot) < dotThreshold) return; | |
// if the dot is negative, we're sliding the "other" way, so we need to flip the tangent | |
if (dot < 0) tangent *= -1; | |
// store the speed we're moving at | |
var speed = body.velocity.magnitude; | |
// then lerp between the current velocity and the corrected velocity scaled by the speed | |
body.velocity = Vector2.Lerp(body.velocity, tangent * speed, velocityTweak); | |
} | |
Vector2 FindClosest(Vector2 center) { | |
var closest = Vector2.zero; | |
var minDistance = float.MaxValue; | |
foreach (var point in edge.points) { | |
var d = Vector2.Distance(point, center); | |
if (d > minDistance) continue; | |
closest = point; | |
minDistance = d; | |
} | |
return closest; | |
} | |
// rotates a vector 90 degrees | |
static Vector2 R90(Vector2 v) { | |
var tmp = v.y; | |
v.y = -v.x; | |
v.x = tmp; | |
return v; | |
} | |
static Vector2 SnapNormal(Vector2 raw) { | |
return Mathf.Abs(raw.x) > Mathf.Abs(raw.y) ? | |
new Vector2(Mathf.Sign(raw.x), 0) : | |
new Vector2(0, Mathf.Sign(raw.y)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment