Skip to content

Instantly share code, notes, and snippets.

@piedoom
Created July 16, 2016 18:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save piedoom/9cb56b89d5f704a01256c81e4ceab99c to your computer and use it in GitHub Desktop.
Save piedoom/9cb56b89d5f704a01256c81e4ceab99c to your computer and use it in GitHub Desktop.
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