Created
July 16, 2016 18:08
-
-
Save piedoom/9cb56b89d5f704a01256c81e4ceab99c to your computer and use it in GitHub Desktop.
This file contains hidden or 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 Microsoft.Xna.Framework; | |
using Nez; | |
using Nez.Sprites; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Vapor.Code.Utility; | |
namespace Vapor.Code.Player | |
{ | |
/// <summary> | |
/// I hate myself. | |
/// </summary> | |
public class BoxController2D : Component | |
{ | |
private int skinWidth = 1; | |
private float maxClimbAngle = 60; | |
private Sprite sprite; | |
private Collider collider; | |
public CollisionData collisions = new CollisionData(); | |
public BoundData bounds = new BoundData(); | |
public BoxController2D(Sprite _sprite, BoxCollider _collider) | |
{ | |
sprite = _sprite; | |
collider = _collider; | |
} | |
public void Move(Vector2 velocity) | |
{ | |
// reset some data that we use for determining collisions | |
collisions.Reset(); | |
bounds.Set(sprite.bounds, skinWidth); | |
// update to see if we are ascending or descending any slopes | |
UpdateRaycasts(velocity); | |
// if we're moving left or right, modify our velocity with possible slope collisions | |
if (velocity.X != 0) | |
{ | |
// we modify slope velocity first since we'll check for collisions afterwards | |
CheckSlope(ref velocity); | |
CheckCollisions(ref velocity, Direction.Horizontal); | |
} | |
// if we are moving downwards | |
if (velocity.Y != 0) | |
CheckCollisions(ref velocity, Direction.Vertical); | |
// actually move the thing | |
entity.colliders.unregisterAllCollidersWithPhysicsSystem(); | |
this.transform.position += velocity; | |
entity.colliders.registerAllCollidersWithPhysicsSystem(); | |
} | |
private void UpdateRaycasts(Vector2 velocity) | |
{ | |
// ascending slope check | |
Vector2 ascendingOrigin = (Math.Sign(velocity.X) == 1 ? bounds.bottomRight : bounds.bottomLeft); | |
Vector2 ascendingCheckTo = ascendingOrigin + (Vector2.UnitX * velocity.X); | |
// performa linecast outward to the left or right | |
RaycastHit ascendingHit = Physics.linecast(ascendingOrigin, ascendingCheckTo); | |
Debug.drawLine(ascendingOrigin, ascendingCheckTo, Color.Orange); | |
// if we hit something... | |
if (ascendingHit.collider != null) | |
{ | |
// set some values that we'll use if the slope is climable | |
collisions.ascendingSlopeNormal = ascendingHit.normal; | |
collisions.ascendingSlopeHit = ascendingHit; | |
// if this slope is a climable angle, we are ascending the slope! | |
if (collisions.ascendingSlopeAngle <= maxClimbAngle) | |
{ | |
collisions.ascendingSlope = true; | |
collisions.below = true; | |
} | |
} | |
// descending slope | |
Vector2 descendingOrigin = (Math.Sign(velocity.X) == 1 ? bounds.bottomLeft : bounds.bottomRight); | |
Vector2 descendingCheckTo = descendingOrigin + (Vector2.UnitY * 500); | |
// perform a raycast downwards | |
RaycastHit descendingHit = Physics.linecast(descendingOrigin, descendingCheckTo); | |
Debug.drawLine(descendingOrigin, descendingCheckTo, Color.Orange); | |
// if we hit something... | |
if (descendingHit.collider != null) | |
{ | |
// store the values of the hit | |
collisions.descendingSlopeNormal = descendingHit.normal; | |
collisions.descendingSlopeHit = descendingHit; | |
// get if we are going positive or negative X | |
var directionX = Math.Sign(velocity.X); | |
// check if this angle is just flat ground - if it isn't make sure that it's within our maxClimbAngle range. | |
if (collisions.descendingSlopeAngle != 0 && collisions.descendingSlopeAngle <= maxClimbAngle) | |
{ | |
// I don't actually know why we need this | |
if (Math.Sign(collisions.descendingSlopeNormal.X) == directionX) | |
{ | |
// since we did a huge cast (500 px in length) we need to make sure we are in range to contact | |
// skinWidth * 3 is so we have a little buffer (like the platform is magnetic) | |
if (collisions.descendingSlopeHit.distance - (skinWidth * 3) <= Math.Tan(CustomMath.DegToRad((float)collisions.descendingSlopeAngle)) * Math.Abs(velocity.X)) | |
{ | |
// if everything is good, we are descending the slope! | |
collisions.descendingSlope = true; | |
collisions.below = true; | |
} | |
} | |
} | |
} | |
} | |
private void CheckSlope(ref Vector2 velocity) | |
{ | |
// modify our velocity if we are going up or down a slope | |
var directionX = Math.Sign(velocity.X); | |
// the absolute X distance we are moving | |
float moveDistance = Math.Abs(velocity.X); | |
// we determined if we are ascending a slope earlier in UpdateRaycasts(). If we did determine we are ascending a slope, | |
// we need to modify our velocity to scale it. | |
if (collisions.ascendingSlope) | |
{ | |
// determine the Y velocity we will need to scale the slope correctly | |
float climbVelocityY = (float)Math.Sin(CustomMath.DegToRad(collisions.ascendingSlopeAngle)) * moveDistance; | |
// if we are not already at or greater than the necessary Y velocity to scale the slope properly, set it now. | |
if (velocity.Y < climbVelocityY) | |
{ | |
velocity.Y = -climbVelocityY; | |
velocity.X = (float)Math.Cos(CustomMath.DegToRad(collisions.ascendingSlopeAngle)) * moveDistance * Math.Sign(velocity.X); | |
} | |
} | |
// if we are descending | |
if (collisions.descendingSlope) { | |
// modify our velocity once again (much like ascending the slope). | |
float descendVelocityY = (float)Math.Sin(CustomMath.DegToRad((float)collisions.descendingSlopeAngle)) * moveDistance; | |
velocity.X = (float)Math.Cos(CustomMath.DegToRad((float)collisions.descendingSlopeAngle)) * moveDistance * Math.Sign(velocity.X); | |
velocity.Y += descendVelocityY; | |
} | |
} | |
// performing our boxcasting here to correct any overlapping movement | |
private void CheckCollisions(ref Vector2 velocity, Direction direction) | |
{ | |
// set this variable beforehand as it is an out parameter for the .collidesWith method for some reason. | |
// probably because it would be inefficient to return a new object each time | |
var collisionResult = new CollisionResult(); | |
// make sure we don't take into account any collider currently on our player entity | |
entity.colliders.unregisterAllCollidersWithPhysicsSystem(); | |
// for each collider, check for collisions. This usually won't loop more than once | |
for(var i = 0; i < entity.colliders.Count; i++) | |
{ | |
// get our current collider | |
var col = entity.colliders[i]; | |
// if the collider we're using is a trigger, don't react | |
if (col.isTrigger) | |
continue; | |
// get the bounds of our collider for use in our boxcast | |
RectangleF bounds = col.bounds; | |
// update our bounds for the boxcast. We do a boxcast per axis so we can determine collisions | |
if (direction == Direction.Horizontal) | |
bounds.x += velocity.X; | |
else if (direction == Direction.Vertical) | |
bounds.y += velocity.Y; | |
// for each collision... | |
var neighbors = Physics.boxcastBroadphase(ref bounds, col.collidesWithLayers); | |
foreach (var neighbor in neighbors) | |
{ | |
// skip if it's a trigger | |
if (neighbor.isTrigger) | |
continue; | |
// if our collider collides | |
if (col.collidesWith(neighbor, velocity, out collisionResult)) | |
{ | |
if (direction == Direction.Horizontal) | |
{ | |
// don't set horizontal collisions if we're ascending a slope | |
// this will prevent us from doing a wall jump while traveling upwards | |
if (!collisions.ascendingSlope) | |
{ | |
// set our collision data | |
collisions.right = Math.Sign(velocity.X) == 1; | |
collisions.left = Math.Sign(velocity.X) == -1; | |
} | |
// modify our direction X | |
velocity.X -= collisionResult.minimumTranslationVector.X; | |
} | |
if (direction == Direction.Vertical) | |
{ | |
// we cannot reliably ascertain collisions above + below if we are climbing a slope, | |
// e.g. if we are climbing upwards, our Y velocity is negative but we have collisions below | |
if (!collisions.ascendingSlope && !collisions.descendingSlope) | |
{ | |
collisions.below = Math.Sign(velocity.Y) == 1; | |
collisions.above = Math.Sign(velocity.Y) == -1; | |
} | |
//collisionResult.removeHorizontalTranslation(velocity); | |
velocity.Y -= collisionResult.minimumTranslationVector.Y; | |
} | |
Debug.log(collisionResult); | |
} | |
} | |
} | |
entity.colliders.registerAllCollidersWithPhysicsSystem(); | |
} | |
public struct CollisionData { | |
public bool above, below, right, left; | |
public bool ascendingSlope, descendingSlope; | |
public bool isInJump; | |
public RaycastHit ascendingSlopeHit, descendingSlopeHit; | |
public Vector2 ascendingSlopeNormal, descendingSlopeNormal; | |
public float ascendingSlopeAngle { | |
get | |
{ | |
return (float)CustomMath.NormalToDeg(ascendingSlopeNormal); | |
} | |
} | |
public float descendingSlopeAngle | |
{ | |
get | |
{ | |
return (float)CustomMath.NormalToDeg(descendingSlopeNormal); | |
} | |
} | |
public void Reset() | |
{ | |
above = below = right = left = ascendingSlope = descendingSlope = isInJump = false; | |
} | |
} | |
public struct BoundData | |
{ | |
public Vector2 topRight, topLeft, bottomRight, bottomLeft; | |
public void Set(Rectangle b, int skinWidth) | |
{ | |
b.Inflate(-skinWidth, -skinWidth); | |
topRight = new Vector2(b.Right, b.Top); | |
topLeft = new Vector2(b.Left, b.Top); | |
bottomLeft = new Vector2(b.Left, b.Bottom); | |
bottomRight = new Vector2(b.Right, b.Bottom); | |
} | |
} | |
public enum Direction | |
{ | |
Horizontal, | |
Vertical | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment