Skip to content

Instantly share code, notes, and snippets.

Created August 1, 2014 00:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/66af1f2bc24b5b4451e8 to your computer and use it in GitHub Desktop.
Save anonymous/66af1f2bc24b5b4451e8 to your computer and use it in GitHub Desktop.
using UnityEngine;
using System.Collections;
public class CharacterController2D : MonoBehaviour
{
private const float SkinWidth = 0.02f;
private const int TotalHorizontalRays = 8;
private const int TotalVerticalRays = 4;
private static readonly float SlopeLimitTangant = Mathf.Tan(75f * Mathf.Deg2Rad);
public LayerMask PlatformMask;
public ControllerParameters2D DefaultParameters;
public ControllerState2D State { get; private set; }
public Vector2 Velocity { get {return _velocity; } }
public bool HandleCollisions {get; set; }
public ControllerParameters2D Parameters {get {return _overrideParameters ?? DefaultParameters; }}
// ?? is called a null override, and will return DefaultParameters, if overrideParameters happens to be null
public GameObject StandingOn {get; private set; }
public bool CanJump
{
get
{
if (Parameters.JumpRestrictions == ControllerParameters2D.JumpBehaviour.CanJumpAnywhere)
return _jumpIn <= 0;
if (Parameters.JumpRestrictions == ControllerParameters2D.JumpBehaviour.CanJumpOnGround)
return State.IsGrounded;
return false;
}
}
private Vector2 _velocity;
private Transform _transform;
private Vector3 _localScale;
private BoxCollider2D _boxCollider;
private ControllerParameters2D _overrideParameters;
private float _jumpIn;
private Vector3
_raycastTopLeft,
_raycastBottomRight,
_raycastBottomLeft;
private float
_verticalDistanceBetweenRays,
_horizontalDistanceBetweenRays;
public void Awake()
{
HandleCollisions = true;
State = new ControllerState2D();
_transform = transform;
_localScale = transform.localScale;
_boxCollider = GetComponent<BoxCollider2D>();
var colliderWidth = _boxCollider.size.x * Mathf.Abs(transform.localScale.x) - (2 * SkinWidth);
_horizontalDistanceBetweenRays = colliderWidth / (TotalVerticalRays - 1);
var colliderHeight = _boxCollider.size.y * Mathf.Abs (transform.localScale.y) - (2 * SkinWidth);
_verticalDistanceBetweenRays = colliderHeight / (TotalHorizontalRays - 1);
}
public void AddForce(Vector2 force)
{
_velocity = force;
}
public void SetForce(Vector2 force)
{
_velocity += force;
}
public void SetHorizontalForce(float x)
{
_velocity.x = x;
}
public void SetVerticalForce(float y)
{
_velocity.y = y;
}
public void Jump()
{
// TODO: Moving platform support
AddForce (new Vector2(0, Parameters.JumpMagnitude));
_jumpIn = Parameters.JumpFrequency;
}
public void LateUpdate()
{
_jumpIn -= Time.deltaTime;
_velocity.y += Parameters.Gravity * Time.deltaTime;
Move(Velocity * Time.deltaTime);
}
private void Move(Vector2 deltaMovement)
{
var wasGrounded = State.IsCollidingBelow;
State.Reset();
if (HandleCollisions)
{
HandlePlatforms();
CalculateRayOrigins(); /// Since the player is moving every frame, we need to adjust where the Rays are originating with every frame.
if (deltaMovement.y < 0 && wasGrounded)
HandleVerticalSlope(ref deltaMovement);
if (Mathf.Abs (deltaMovement.x) > .001f)
MoveHorizontally (ref deltaMovement);
MoveVertically (ref deltaMovement);
}
_transform.Translate (deltaMovement, Space.World);
// TODO: Additional moving platform code
if (Time.deltaTime > 0)
_velocity = deltaMovement / Time.deltaTime;
_velocity.x = Mathf.Min(_velocity.x, DefaultParameters.MaxVelocity.x);
_velocity.y = Mathf.Min(_velocity.y, DefaultParameters.MaxVelocity.y);
if (State.IsMovingUpSlope)
_velocity.y = 0;
}
private void HandlePlatforms()
{
}
private void CalculateRayOrigins()
{
var size = new Vector2(_boxCollider.size.x * Mathf.Abs (_localScale.x), _boxCollider.size.y * Mathf.Abs (_localScale.y)) / 2;
var center = new Vector2(_boxCollider.center.x * _localScale.x, _boxCollider.center.y * _localScale.y);
_raycastTopLeft = _transform.position + new Vector3(center.x - size.x + SkinWidth, center.y + size.y - SkinWidth);
_raycastBottomRight = _transform.position + new Vector3(center.x + size.x - SkinWidth, center.y - size.y + SkinWidth);
_raycastBottomLeft = _transform.position + new Vector3(center.x - size.x + SkinWidth, center.y - size.y + SkinWidth);
}
private void MoveHorizontally(ref Vector2 deltaMovement)
{
var isGoingRight = deltaMovement.x > 0;
var rayDistance = Mathf.Abs (deltaMovement.x) + SkinWidth;
var rayDirection = isGoingRight ? Vector2.right : -Vector2.right;
// ? is a ternary conditional operator. If the value before the ? returns true, it returns the value
// to the left of the colon (:), if it returns false, it turns the value to the right.
var rayOrigin = isGoingRight ? _raycastBottomRight : _raycastBottomLeft;
for (var i = 0; i < TotalHorizontalRays; i++)
{
var rayVector = new Vector2(rayOrigin.x, rayOrigin.y + (i * _verticalDistanceBetweenRays));
Debug.DrawRay(rayVector, rayDirection * rayDistance, Color.red);
var rayCastHit = Physics2D.Raycast (rayVector, rayDirection, rayDistance, PlatformMask);
if (!rayCastHit)
//checks to see if the ray hit anything, and if it does, it continues in the loop, otherwise it
//jumps to the next value
continue;
if (i == 0 && HandleHorizontalSlope(ref deltaMovement, Vector2.Angle (rayCastHit.normal, Vector2.up), isGoingRight))
break;
deltaMovement.x = rayCastHit.point.x - rayVector.x;
// once we encounter a ray hit, it is irrelevant to shoot the remaining rays the total distance.
// Instead, we set the new rayDistance (or length) equal to the distance to this recently found
// obstacle.
rayDistance = Mathf.Abs (deltaMovement.x);
if (isGoingRight)
{
deltaMovement.x -= SkinWidth;
State.IsCollidingRight = true;
}
else
{
deltaMovement.x += SkinWidth;
State.IsCollidingLeft = true;
}
if (rayDistance < SkinWidth + .0001f)
break;
}
}
private void MoveVertically(ref Vector2 deltaMovement)
{
var isGoingUp = deltaMovement.y > 0;
var rayDistance = Mathf.Abs (deltaMovement.y) + SkinWidth;
var rayDirection = isGoingUp ? Vector2.up : -Vector2.up;
var rayOrigin = isGoingUp ? _raycastTopLeft : _raycastBottomLeft;
rayOrigin.x += deltaMovement.x;
var standingOnDistance = float.MaxValue;
for (var i = 0; i < TotalVerticalRays; i++)
{
var rayVector = new Vector2(rayOrigin.x + (i * _horizontalDistanceBetweenRays), rayOrigin.y);
Debug.DrawRay (rayVector, rayDirection * rayDistance, Color.red);
var raycastHit = Physics2D.Raycast(rayVector, rayDirection, rayDistance, PlatformMask);
if (!raycastHit)
continue;
if (!isGoingUp)
{
var verticalDistanceToHit = _transform.position.y - raycastHit.point.y;
if (verticalDistanceToHit < standingOnDistance)
{
standingOnDistance = verticalDistanceToHit;
StandingOn = raycastHit.collider.gameObject;
}
}
deltaMovement.y = raycastHit.point.y - rayVector.y;
rayDistance = Mathf.Abs (deltaMovement.y);
if (isGoingUp)
{
deltaMovement.y -= SkinWidth;
State.IsCollidingAbove = true;
}
else
{
deltaMovement.y += SkinWidth;
State.IsCollidingBelow = true;
}
if (!isGoingUp && deltaMovement.y > .0001f)
State.IsMovingUpSlope = true;
if (rayDistance < SkinWidth + .0001f)
break;
}
}
private void HandleVerticalSlope(ref Vector2 deltaMovement)
{
var center = (_raycastBottomLeft.x + _raycastBottomRight.x) /2;
var direction = -Vector2.up;
var slopeDistance = SlopeLimitTangant * (_raycastBottomRight.x - center);
var slopeRayVector = new Vector2(center, _raycastBottomLeft.y);
Debug.DrawRay(slopeRayVector, direction * slopeDistance, Color.yellow);
var raycastHit = Physics2D.Raycast (slopeRayVector, direction, slopeDistance, PlatformMask);
if (!raycastHit)
return;
var isMovingDownSlope = Mathf.Sign (raycastHit.normal.x) == Mathf.Sign (deltaMovement.x);
if (!isMovingDownSlope)
return;
var angle = Vector2.Angle (raycastHit.normal, Vector2.up);
if (Mathf.Abs (angle) < .0001f)
return;
State.IsMovingDownSlope = true;
State.SlopeAngle = angle;
deltaMovement.y = raycastHit.point.y - slopeRayVector.y;
}
private bool HandleHorizontalSlope(ref Vector2 deltaMovement, float angle, bool IsGoingRight)
{
if (Mathf.RoundToInt (angle) == 90)
return false;
if (angle > Parameters.SlopeLimit)
{
deltaMovement.x = 0;
return true;
}
if (deltaMovement.y > 0.07f)
return true;
deltaMovement.x += IsGoingRight ? -SkinWidth : SkinWidth;
deltaMovement.y = Mathf.Abs (Mathf.Tan (angle * Mathf.Deg2Rad) * deltaMovement.x);
State.IsMovingUpSlope = true;
State.IsCollidingBelow = true;
return true;
}
public void OnTriggerEnter2D(Collider2D other)
{
}
public void OnTriggerExit2D(Collider2D other)
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment