# Making Space Gal Move - Part 3: It Works

I figured it out. The shitty part is that I don't 100% understand why it works.

I rewrote the collision code from scratch, and discovered, to my chagrin, that the new holistic raycasting method had the exact same problem of getting hung up on corners. What I eventually deduced is that it has to do with the raycasts higher on the bounding box getting snagged on the upper edge, so I just prohibited all the raycasts except the bottom ones from registering a hit on surfaces facing directly up.

Ultimately, I don't have the energy or the expertise to explain how I did it or why, so here's the full source code for my physics system.

```using UnityEngine;
using System;
using System.Collections.Generic;
/// <summary>
/// A custom Rigidbody for platformer physics.
/// </summary>
public class GBody : MonoBehaviour
{
//---------------------
// Static Properties
//---------------------
//---------------------
// Members
//---------------------

public Collider2D myCollider;
public GParticle particle;
[Space]
public bool isSolid;
public bool acceptsInput;
public Verb collisionVerb;
[Space]
public Vector2 input;
public Vector2 velocity;
[Space]
public float gravity;
public float terminalVelocity;
public float hFriction;
public float maxSpeed;
public int maxSlope;
public bool isGrounded;
public bool isWallhanging;
public float wallhangModifier;
/// <summary>
/// Value guide: for x, 1 = blocked to the right, -1 = blocked to the left, 2 is blocked both ways
///              for y, 1 = blocked from above, -1 = blocked from below, 2 is blocked both ways
///              for both, 0 is not blocked at all.
/// </summary>
public Vector2 movementBlock;
// Private Members
private Actor parent;
/// <summary>
/// For gamefeel movement
/// </summary>
public Vector2 overrider;
//---------------------
// Events
//---------------------
//---------------------
// Properties
//---------------------
//---------------------
// Pseudo-Properties
//---------------------

//---------------------
// MonoBehavior Methods
//---------------------
// Use this for initialization
void Start ()
{
parent = GetComponent<actor>();
}

// Update is called once per frame
void FixedUpdate ()
{
Vector2 modifier = new Vector2();
float hDamper = 1;
float vDamper = 1;

if (acceptsInput)
{
Wallhang();
if (input.x == 0)
{
hDamper = hFriction;
}
//Eventually we'll need to check if we're on a ladder, but for now, we'll just always zero out input.y
input.y = 0;
modifier = input;
}

velocity += modifier;
velocity.x = Mathf.Clamp(velocity.x * hDamper,-maxSpeed,maxSpeed);
velocity.y = velocity.y * vDamper;
Gravity();

CheckCollisions();

UpdatePosition();
}

//---------------------
// Public Methods
//---------------------
public void ApplyForce(Vector2 force)
{
velocity += force;
}
//---------------------
// Private Methods
//---------------------

/// <summary>
/// Checks if the Collider is currently touching any other colliders.
/// </summary>
private void CheckCollisions()
{
PreemptiveCollision();
movementBlock = Vector2.zero;

GameObject hit = null;
// Used for adjusting the check location, because of funkiness with how the coordinates work.
// This is basically more shitty number twiddling, but putting it into a holder like this makes it okay /s
Vector2 offset = new Vector2();
if(velocity.y > 0)
{
offset.y = 1;
}
if(velocity.x < 0)
{
offset.x = -1;
}

// Check the space either above or below the collider
for (int i = (int)-myCollider.bounds.extents.x; i < myCollider.bounds.extents.x; ++i)
{
hit = Check(new Vector2(myCollider.transform.position.x + i, myCollider.transform.position.y + offset.y + myCollider.bounds.extents.y * GTools.PosNeg(velocity.y)));
if (hit && hit != myCollider.gameObject)
{
if (isSolid)
{
movementBlock.y = 1 * GTools.PosNeg(velocity.y);
}
parent.Collide(hit);
}
}

// More number finicking but this makes it look less like a kludge!!!
offset.y = 1;
// Check the space either to the left or right of the collider
for (int i = (int)-myCollider.bounds.extents.y; i < myCollider.bounds.extents.y;++i)
{
hit = Check(new Vector2(myCollider.transform.position.x + offset.x + myCollider.bounds.extents.x * GTools.PosNeg(velocity.x), myCollider.transform.position.y + i + offset.y));
if (hit && hit != myCollider.gameObject)
{
if (isSolid)
{
}
parent.Collide(hit);
}
else if (!hit && Mathf.Abs(i - -myCollider.bounds.extents.y) <= maxSlope && madeHit)
{
// This code is responsible for slopes, and as a side-effect, makes movement over edges a little smoother.
break;
}
}
{
movementBlock.x = 1 * GTools.PosNeg(velocity.x);
}
}
/// <summary>
/// Checks for collisions that will occur during the current frame, to prevent interpenetration.
/// </summary>
private void PreemptiveCollision()
{
overrider = Vector2.zero;
Vector2 checkadjuster = new Vector2(0.5f, -0.5f);
// We're going to check points on the corners and middle edges of the collider.
// No really clean way to do this, just gotta enter the values manually.
// It uses the Unity bounding box, so this code does not need to be adjusted
// for different sizes.
// This code is meant to unify the physics systems, so we can collide with 2D or 3D colliders,
// and unfortunately that means twice as many checks because RaycastHit2D and RaycastHit do
// not a common inheritance.
List<tuple<raycasthit2d, vector2="">> hits2D = new List<tuple<raycasthit2d, vector2="">>();
List<tuple<raycasthit, vector2="">> hits3D = new List<tuple<raycasthit, vector2="">>();
Vector2 closestDistance = new Vector2(256,256); //Set very high so that any found distance will be closer.
// Number finicking zone. Watch out!!!
// Top Side Point
Vector2 topSidePoint = new Vector2(myCollider.transform.position.x + (myCollider.bounds.extents.x * GTools.PosNeg(velocity.x)) ,
myCollider.transform.position.y + myCollider.bounds.extents.y);
// Middle Side Point
Vector2 middleSidePoint = new Vector2(myCollider.transform.position.x + (myCollider.bounds.extents.x * GTools.PosNeg(velocity.x)) ,
myCollider.transform.position.y);
// Bottom Side Point
Vector2 bottomSidePoint = new Vector2(myCollider.transform.position.x + (myCollider.bounds.extents.x * GTools.PosNeg(velocity.x)) ,
myCollider.transform.position.y - myCollider.bounds.extents.y);
// Middle Point
Vector2 middlePoint = new Vector2(myCollider.transform.position.x,
myCollider.transform.position.y + (myCollider.bounds.extents.y * GTools.PosNeg(velocity.y)));
RaycastHit hit3d1 = new RaycastHit();
Physics.Raycast(topSidePoint, velocity, out hit3d1, velocity.magnitude, mask);
RaycastHit hit3d2 = new RaycastHit();
Physics.Raycast(middleSidePoint, velocity, out hit3d2, velocity.magnitude, mask);
RaycastHit hit3d3 = new RaycastHit();
Physics.Raycast(bottomSidePoint, velocity, out hit3d3, velocity.magnitude, mask);
RaycastHit hit3d4 = new RaycastHit();
Physics.Raycast(middlePoint, velocity, out hit3d4, velocity.magnitude, mask);
foreach(Tuple<raycasthit2d,vector2> it in hits2D)
{
if (it.Item1.collider)
{
//This code is a little cryptic. Basically we're just trying to find the shortest raycast.
// The business with normals is because the higher-originating raycasts have a bad habit of
// snagging on corners. I don't understand 100% why it works, but it works.
if(it.Item1.distance < closestDistance.magnitude && it.Item1.distance != 0 && !(it.Item1.normal == new Vector2(0,1) && it.Item2.y >= myCollider.transform.position.y ))
{
closestDistance = it.Item1.point - it.Item2;
}
}
}
foreach (Tuple<raycasthit, vector2=""> it in hits3D)
{
if (it.Item1.collider)
{
if (it.Item1.distance < closestDistance.magnitude && it.Item1.distance != 0 && !((Vector2)it.Item1.normal == new Vector2(0, 1) && it.Item2.y >= myCollider.transform.position.y))
{
closestDistance = (Vector2)it.Item1.point - it.Item2;
}
}
}
if(closestDistance != new Vector2(256,256))
{
overrider = closestDistance;
if(overrider.x < Mathf.Round(overrider.x))
{
overrider.x -= (Mathf.Round(overrider.x) - overrider.x);
}
else
{
overrider.x = Mathf.Round(overrider.x);
}
if (overrider.y < Mathf.Round(overrider.y))
{
overrider.y -= (Mathf.Round(overrider.y) - overrider.y);
}
else
{
overrider.y = Mathf.Round(overrider.y);
}
Restrict(ref overrider);
}
}
private void Gravity()
{
{
}
float mod = 1;
if(isWallhanging)
{
velocity.y = Mathf.Clamp(velocity.y, terminalVelocity * wallhangModifier, 0);
}
Gravity(mod);
{
}
}
private void Gravity(float modifier)
{
if (movementBlock.y == -1 || movementBlock.y == 2)
{
//Debug.Log("Grounded");
isGrounded = true;
}
else
{
{
//Debug.Log(gameObject.name + " is airborne");
velocity.y -= gravity * modifier;
velocity.y = Mathf.Clamp(velocity.y, terminalVelocity, 256);
isGrounded = false;
}
}
}
private void Wallhang()
{
GameObject hit = Check(new Vector2(myCollider.transform.position.x + (GTools.PosNeg(input.x) * myCollider.bounds.extents.x) + input.x, myCollider.transform.position.y));
if (!isGrounded && (movementBlock.x == input.x || movementBlock.x == 2) && input.x != 0)
{
if (hit && hit != myCollider)
{
isWallhanging = true;
}
}
else
{
isWallhanging = false;
}

}
private void UpdatePosition()
{
Vector3 oldPosition = transform.localPosition;

Restrict(ref velocity);
float modX = velocity.x;
float modY = velocity.y;
// If the preemptive checking found something, we're going to totally override the velocity.
if(overrider != Vector2.zero)
{
modX = overrider.x;
modY = overrider.y;
}
// The slopes adjustments are applied on top.
{
}

transform.localPosition = new Vector3(transform.localPosition.x + modX, transform.localPosition.y + modY);
// Rounds off the position of the body if it hasn't moved since the last frame.
// This ensures consistency in things like jump height from the body's apparent height.
// Omit this if you're not handling positions in full integers.
if (transform.localPosition.x == oldPosition.x)
{
transform.localPosition = new Vector3(Mathf.Round(transform.localPosition.x), transform.localPosition.y);
}
if (transform.localPosition.y == oldPosition.y)
{
transform.localPosition = new Vector3(transform.localPosition.x, Mathf.Round(transform.localPosition.y));
}
}
//---------------------
// Static Methods
//---------------------
/// <summary>
/// Checks a given pixel coordinate for colliders.
/// </summary>
/// <param name="coords">
/// <returns></returns>
public GameObject Check(Vector2 coords)
{
}
/// <summary>
/// Checks a given pixel coordinate for colliders.
/// </summary>
/// <param name="coords">
/// <returns></returns>
{
// THIS IS A VERY NAUGHTY FIX, BUT LET'S GIVE IT A SHOT!!!
coords.x += 0.5f;
coords.y -= 0.5f;
RaycastHit2D hit2D = Physics2D.Raycast(coords, Vector2.down, 0.01f,layerMask);
if (hit2D.collider != null && hit2D.collider.GetComponentInParent<gbody>() && !hit2D.collider.GetComponentInParent<gbody>().isSolid)
{
return null;
}
Collider[] hits = Physics.OverlapSphere(coords, 0.1f);
foreach (Collider it in hits)
{
if (it != myCollider)
{
return it.gameObject;
}
}
if (hit2D.collider)
{
return hit2D.collider.gameObject;
}
return null;

}
public void Restrict(ref Vector2 input)
{
if (movementBlock.y == -1)
{
input.y = Mathf.Clamp(input.y, 0, 256);
}
if (movementBlock.y == 1)
{
input.y = Mathf.Clamp(input.y, -256, 0);
}
if (movementBlock.x == -1)
{
input.x = Mathf.Clamp(input.x, 0, 256);
}
if (movementBlock.x == 1)
{
input.x = Mathf.Clamp(input.x, -256, 0);
}
}
//---------------------
// Event Methods
//---------------------
}
```

And here's the code for GTools.PosNeg, which the above code will not work very well without:

```public static int PosNeg(float input)
{
if(input > 0)
{
return 1;
}
if (input < 0)
{
return -1;
}
return 0;
}```