Skip to content

Instantly share code, notes, and snippets.

@grapefrukt
Created April 7, 2021 12:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save grapefrukt/be310f27195d84957dfd1e2e024e7b44 to your computer and use it in GitHub Desktop.
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
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