Skip to content
This repository has been archived by the owner on May 1, 2021. It is now read-only.

Commit

Permalink
Revamp CharacterController & make it more robust
Browse files Browse the repository at this point in the history
  • Loading branch information
NanoSector committed Jun 1, 2020
1 parent cdf9bb5 commit debdf39
Showing 1 changed file with 119 additions and 87 deletions.
206 changes: 119 additions & 87 deletions src/Character/CharacterController2D.cs
@@ -1,120 +1,118 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;

namespace Character
{
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
[RequireComponent(typeof(CircleCollider2D))]
[AddComponentMenu("Physics 2D/Character Controller 2D")]
public class CharacterController2D : MonoBehaviour
{
[Serializable]
public class BoolEvent : UnityEvent<bool> { }

private static readonly int HorizontalSpeed = Animator.StringToHash("HorizontalSpeed");
private static readonly int VerticalSpeed = Animator.StringToHash("VerticalSpeed");
private static readonly int Crouching = Animator.StringToHash("Crouching");
private static readonly int Alive = Animator.StringToHash("Alive");

[SerializeField] [Tooltip("Enable animations.")]
private bool enableAnimations = true;
public bool enableAnimations = true;

[SerializeField] [Tooltip("Triggers the Hurt animation trigger when touching an object with Enemy tag.")]
private bool triggerHurtAnimation;
[FormerlySerializedAs("triggerHurtAnimation")] [Tooltip("Updates the Alive animation boolean when touching an object with Enemy tag.")]
public bool enableDeath;


[Header("Movement & jump settings")]

[SerializeField] [Tooltip("Enable the jump mechanics.")]
private bool jumpingEnabled = true;
[FormerlySerializedAs("jumpingEnabled")] [Tooltip("Enable the jump mechanics.")]
public bool enableJumping = true;

[SerializeField] [Tooltip("Amount of force added when the player jumps.")]
private float jumpForce = 400f;

[SerializeField] [Range(0, .3f)] [Tooltip("How much to smooth out the movement")]
private float movementSmoothing = .05f;
[Tooltip("Amount of force added when the player jumps.")]
public float jumpForce = 400f;

[Range(0, .3f)] [Tooltip("How much to smooth out the movement")]
public float movementSmoothing = .05f;

[Header("Crouch settings")]

[SerializeField] [Tooltip("Enable the crouch mechanics.")]
private bool crouchingEnabled = true;
[FormerlySerializedAs("crouchingEnabled")] [Tooltip("Enable the crouch mechanics.")]
public bool enableCrouching = true;

[SerializeField] [Range(0, 1)] [Tooltip("This value will be multiplied with the player speed while they are crouching. 1 = 100% (as fast as running), 0 = no movement while crouching.")]
private float crouchSpeed = .36f;
[Range(0, 1)] [Tooltip("This value will be multiplied with the player speed while they are crouching. 1 = 100% (as fast as running), 0 = no movement while crouching.")]
public float crouchSpeed = .36f;

[SerializeField] [Tooltip("The distance used when calculating if the player is stuck under a ceiling.")]
private float ceilingDistance = 0.6f;
[Tooltip("The distance used when calculating if the player is stuck under a ceiling.")]
public float ceilingDistance = 0.6f;

[SerializeField] [Tooltip("A collider that will be disabled when crouching")]
private Collider2D crouchDisableCollider;

private bool _grounded;
[Tooltip("A collider that will be disabled when crouching")]
public Collider2D fallThroughCollider;

public bool Grounded => _collider2D.IsTouching(_groundContactFilter);
private bool FacingRight => transform.localScale.x > 0;

private Animator _animator;
private CircleCollider2D _collider2D;
private Rigidbody2D _rigidBody2D;
private Vector3 _velocity = Vector3.zero;

private bool FacingRight => transform.localScale.x > 0;

private static readonly int HorizontalSpeed = Animator.StringToHash("HorizontalSpeed");
private static readonly int VerticalSpeed = Animator.StringToHash("VerticalSpeed");
private static readonly int Crouching = Animator.StringToHash("Crouching");
private static readonly int Hurt = Animator.StringToHash("Hurt");
private Animator _animator;

private ContactFilter2D _groundContactFilter;

private void Awake()
{
_rigidBody2D = GetComponent<Rigidbody2D>();
_collider2D = GetComponent<CircleCollider2D>();
_animator = GetComponent<Animator>();
}

private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Ground"))
if (_animator == null)
{
_grounded = true;
Debug.LogError("No animator component found on CharacterController2D; disabling animations.");
enableAnimations = false;
}

if (enableAnimations && triggerHurtAnimation && other.gameObject.CompareTag("Enemy"))
{
_animator.SetTrigger(Hurt);
_rigidBody2D.bodyType = RigidbodyType2D.Static;
}
_groundContactFilter = new ContactFilter2D();
_groundContactFilter.SetLayerMask(LayerMask.GetMask("Ground"));

if (fallThroughCollider != null && fallThroughCollider.composite != null)
fallThroughCollider = fallThroughCollider.composite;
}

private void OnCollisionExit2D(Collision2D other)
public void Jump()
{
if (other.gameObject.CompareTag("Ground"))
if (!enableJumping || !Grounded) return;

_rigidBody2D.AddForce(new Vector2(0f, jumpForce));
}

public void Move(float move, out Vector2 targetVelocity)
{
// Move the character by finding the target velocity
Vector2 velocity = _rigidBody2D.velocity;
targetVelocity = new Vector2(move * 10f, velocity.y);

// Triggering movement animations
if (enableAnimations)
{
_grounded = false;
_animator.SetFloat(HorizontalSpeed, Math.Abs(targetVelocity.x));
_animator.SetFloat(VerticalSpeed, targetVelocity.y);
}

// And then smoothing it out and applying it to the character
_rigidBody2D.velocity = Vector3.SmoothDamp(velocity, targetVelocity, ref _velocity, movementSmoothing);
}

public void Move(float move, bool crouch, bool jump)
{
if (_rigidBody2D.bodyType == RigidbodyType2D.Static)
return;

if (crouchingEnabled)
if (enableCrouching)
{
// If crouching, check to see if the character can stand up
if (!crouch)
{
// Raycast a line right above us.
Vector3 currentPosition = transform.position;
var castOrigin = new Vector2(currentPosition.x, currentPosition.y + ceilingDistance);
RaycastHit2D castResult = Physics2D.Raycast(castOrigin, Vector2.up, 0.1f);

// If the character has a ceiling preventing them from standing up, keep them crouching
if (!ReferenceEquals(castResult.collider, null))
{
crouch = true;
}
}

if (Physics2D.OverlapCircleAll(transform.position, 0.2f).Any(o => o == crouchDisableCollider))
if (!crouch && Grounded && IsVerticallyCramped())
{
crouch = true;
}

// Disable one of the colliders when crouching
crouchDisableCollider.enabled = !crouch;

// If crouching
if (crouch)
{
Expand All @@ -123,16 +121,12 @@ public void Move(float move, bool crouch, bool jump)
}
}

// Move the character by finding the target velocity
Vector2 velocity = _rigidBody2D.velocity;
Vector3 targetVelocity = new Vector2(move * 10f, velocity.y);
// And then smoothing it out and applying it to the character
_rigidBody2D.velocity = Vector3.SmoothDamp(velocity, targetVelocity, ref _velocity, movementSmoothing);

// Disable crouch platform collision if we are jumping.
if (crouchingEnabled && velocity.y > 0)
Move(move, out Vector2 targetVelocity);

// Disable crouch collision if appropriate.
if (enableCrouching)
{
crouchDisableCollider.enabled = false;
fallThroughCollider.isTrigger = ShouldDisableFallThroughCollision(crouch, targetVelocity.y);
}

// Flip the player if we are facing the other direction.
Expand All @@ -141,22 +135,49 @@ public void Move(float move, bool crouch, bool jump)
Flip(move < 0);
}

// If the player should jump...
if (jumpingEnabled && _grounded && jump)
// If the player should jump, add a vertical force to the player.
if (jump)
Jump();

if (enableAnimations && enableCrouching)
{
// Add a vertical force to the player.
_grounded = false;
_rigidBody2D.AddForce(new Vector2(0f, jumpForce));
_animator.SetBool(Crouching, crouch);
}
}

if (enableAnimations)
{
_animator.SetFloat(HorizontalSpeed, Math.Abs(targetVelocity.x));
_animator.SetFloat(VerticalSpeed, targetVelocity.y);
private bool IsVerticallyCramped()
{
// Raycast a line right above us.
Vector3 currentPosition = transform.position;
var castOrigin = new Vector2(currentPosition.x, currentPosition.y + ceilingDistance);
RaycastHit2D castResult = Physics2D.Raycast(castOrigin, Vector2.up, 0.1f);

// If the character has a ceiling preventing them from standing up, keep them crouching
return !ReferenceEquals(castResult.collider, null);
}

private bool ShouldDisableFallThroughCollision(bool crouch, float verticalVelocity)
{
// Naturally if we are crouching...
if (crouch)
return true;

if (crouchingEnabled)
_animator.SetBool(Crouching, crouch);
}
// ...if we are jumping...
if (!Grounded && verticalVelocity > 0f)
return true;

// ...or if we are stuck in a fall-through platform.
return GetOverlappingColliders().Any(IsColliderCrouchDisable);
}

private IEnumerable<Collider2D> GetOverlappingColliders()
{
return Physics2D.OverlapCircleAll(transform.position, _collider2D.radius / 2);
}

private bool IsColliderCrouchDisable(Collider2D otherCollider)
{
return otherCollider.gameObject == fallThroughCollider.gameObject;
}

private void Flip(bool left)
Expand All @@ -166,5 +187,16 @@ private void Flip(bool left)

transform.localScale = left ? new Vector3(-oldX, oldScale.y, oldScale.z) : new Vector3(oldX, oldScale.y, oldScale.z);
}

private void OnCollisionEnter2D(Collision2D other)
{
if (!enableAnimations || !enableDeath || !other.gameObject.CompareTag("Enemy"))
return;

_animator.SetFloat(HorizontalSpeed, 0);
_animator.SetFloat(VerticalSpeed, 0);
_animator.SetBool(Alive, false);
_rigidBody2D.bodyType = RigidbodyType2D.Static;
}
}
}

0 comments on commit debdf39

Please sign in to comment.