Created
March 26, 2020 21:04
-
-
Save h1ddengames/c93a9c08b4f78d6ec3385a0a9d2e5658 to your computer and use it in GitHub Desktop.
Unity 2D Character Controller
This file contains 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
// Created by h1ddengames | |
using System; | |
using System.Linq; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace h1ddengames { | |
[Serializable] | |
public class AnimationModule { | |
#region Exposed Fields | |
#endregion | |
#region Private Fields | |
private Animator characterAnimator; | |
private List<string> listOfAnimationClips = new List<string>(); | |
#endregion | |
#region Getters/Setters/Constructors | |
public AnimationModule(GameObject character) { | |
this.characterAnimator = character.GetComponent<Animator>(); | |
this.characterAnimator.runtimeAnimatorController.animationClips | |
.ToList() | |
.ForEach(c => listOfAnimationClips.Add(c.name)); | |
} | |
#endregion | |
#region Animation Methods | |
public void AnimateCharacterFlip(Transform character) { | |
Vector2 scale = character.localScale; | |
scale.x *= -1; | |
character.localScale = scale; | |
} | |
// Uses BlendTree for animating between movement and idle animations. | |
// Does not require EndAnimateMove for the above reason. | |
public void AnimateMove(float velocity) { | |
characterAnimator.SetFloat("velocity", velocity); | |
} | |
public void AnimateJump() { | |
} | |
public void EndJump() { | |
} | |
public void AnimateCrouch() { | |
} | |
public void EndCrouch() { | |
} | |
public void AnimateKnockback() { | |
} | |
public void AnimateSwordAttack() { | |
if(listOfAnimationClips.Contains("Player Sword Attack Animation")) | |
characterAnimator.Play("Player Sword Attack Animation"); | |
} | |
public void AnimateShieldBash() { | |
if(listOfAnimationClips.Contains("Player Shield Bash Animation")) | |
characterAnimator.Play("Player Shield Bash Animation"); | |
} | |
#endregion | |
#region My Methods | |
public void PrintAllAnimationClipNames() { | |
listOfAnimationClips.ForEach(item => Debug.Log(item)); | |
} | |
#endregion | |
#region Helper Methods | |
#endregion | |
} | |
} |
This file contains 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
// Created by h1ddengames | |
using System; | |
using UnityEngine; | |
namespace h1ddengames { | |
[Serializable] | |
public class AutomatedMoveModule { | |
#region Exposed Fields | |
#endregion | |
#region Private Fields | |
private CharacterController2D characterController2D; | |
private bool waitingForDelayTimer = false; | |
private int index = 0; | |
private float tempTimer = 0; | |
private float defaultMoveSpeed; | |
#endregion | |
#region Getters/Setters/Constructors | |
public AutomatedMoveModule(CharacterController2D characterController2D) { | |
this.characterController2D = characterController2D; | |
defaultMoveSpeed = this.characterController2D.CharacterMoveSpeed; | |
} | |
#endregion | |
#region Automation Methods | |
public void MoveToLocation(Transform desiredTransform, float moveSpeed) { | |
MoveToLocation(desiredTransform.position, moveSpeed); | |
} | |
public void MoveToLocation(GameObject objectToMoveTo, float moveSpeed) { | |
MoveToLocation(objectToMoveTo.transform.position, moveSpeed); | |
} | |
public void MoveToLocation(Vector2 desiredPosition, float moveSpeed) { | |
if(!waitingForDelayTimer) { | |
characterController2D.transform.position = Vector2.MoveTowards(characterController2D.transform.position, desiredPosition, moveSpeed * Time.deltaTime); | |
} | |
} | |
public void Automate() { | |
if(characterController2D.ListOfWayPoints.Count == 0) { | |
characterController2D.IsBeingControlledByCode = false; | |
return; | |
} | |
// Avoid Out of Bounds Exception. | |
if(index < characterController2D.ListOfWayPoints.Count) { | |
// Check if the player is already at the waypoint. | |
if(characterController2D.ListOfWayPoints[index].HasArrived) { | |
index++; | |
return; | |
} | |
// Wait until delay before moving to waypoint is 0. | |
if(characterController2D.ListOfWayPoints[index].DelayBeforeMovingToWaypoint > 0) { | |
characterController2D.ListOfWayPoints[index].DelayBeforeMovingToWaypoint -= Time.deltaTime; | |
return; | |
} | |
// Until the player hasn't arrived at the waypoint, keep moving the player. | |
if(!characterController2D.ListOfWayPoints[index].HasArrived) { | |
// Check to see if the player is close enough to the waypoint. | |
if(Vector2.Distance(characterController2D.transform.position, characterController2D.ListOfWayPoints[index].Location) < 0.2f) { | |
// Stop the player and wait until delay after reaching waypoint is 0. | |
characterController2D.CharacterRigidBody2D.velocity = Vector2.zero; | |
characterController2D.ListOfWayPoints[index].DelayAfterReachingWaypoint -= Time.deltaTime; | |
if(characterController2D.ListOfWayPoints[index].DelayAfterReachingWaypoint <= 0) { | |
characterController2D.ListOfWayPoints[index].HasArrived = true; | |
} | |
} else { | |
MoveToLocation(characterController2D.ListOfWayPoints[index].Location, characterController2D.ListOfWayPoints[index].MoveSpeedToWaypoint); | |
} | |
} | |
} else { | |
// If index is equal to or greater than the count of all waypoints, reset has arrived based on | |
// characterController2D preference. | |
// TODO: Looping through all waypoints will not work currently. The above loop directly changes the | |
// delay times in each waypoint object. Need to create a temp timer that is only set once at the | |
// beginning to the delay time then subtracted by Time.deltaTime first set. | |
if(characterController2D.LoopThroughAllWaypoints) { | |
for(int i = 0; i < characterController2D.ListOfWayPoints.Count; i++) { | |
characterController2D.ListOfWayPoints[i].HasArrived = false; | |
index = 0; | |
} | |
} else { | |
characterController2D.IsBeingControlledByCode = false; | |
} | |
} | |
} | |
#endregion | |
#region My Methods | |
#endregion | |
#region Helper Methods | |
#endregion | |
} | |
[Serializable] | |
public class WayPoint { | |
#region Exposed Fields | |
[SerializeField] private Vector2 location; | |
[SerializeField] private float moveSpeedToWaypoint; | |
[SerializeField] private float delayBeforeMovingToWaypoint; | |
[SerializeField] private float delayAfterReachingWaypoint; | |
[SerializeField] private bool hasArrived; | |
#endregion | |
#region Getters/Setters/Constructors | |
public WayPoint(Vector2 location, float moveSpeedToWaypoint, float delayBeforeMovingToWaypoint, float delayToNextWaypoint, bool hasArrived) { | |
Location = location; | |
MoveSpeedToWaypoint = moveSpeedToWaypoint; | |
DelayBeforeMovingToWaypoint = delayBeforeMovingToWaypoint; | |
DelayAfterReachingWaypoint = delayToNextWaypoint; | |
HasArrived = hasArrived; | |
} | |
public Vector2 Location { get => location; set => location = value; } | |
public float MoveSpeedToWaypoint { get => moveSpeedToWaypoint; set => moveSpeedToWaypoint = value; } | |
public float DelayBeforeMovingToWaypoint { get => delayBeforeMovingToWaypoint; set => delayBeforeMovingToWaypoint = value; } | |
public float DelayAfterReachingWaypoint { get => delayAfterReachingWaypoint; set => delayAfterReachingWaypoint = value; } | |
public bool HasArrived { get => hasArrived; set => hasArrived = value; } | |
#endregion | |
} | |
} |
This file contains 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
// Created by h1ddengames | |
// Attributes being used within this class require: | |
// https://github.com/dbrizov/NaughtyAttributes | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.Events; | |
using NaughtyAttributes; | |
namespace h1ddengames { | |
public class CharacterController2D : MonoBehaviour { | |
#region Exposed Fields | |
#region Configuration | |
[BoxGroup("Configuration"), | |
SerializeField] | |
private bool showConfiguration = false; | |
[BoxGroup("Configuration"), | |
Tooltip("How fast should the player be able to move left and right?"), | |
ShowIf("showConfiguration"), | |
SerializeField] | |
private float characterMoveSpeed = 5.0f; | |
[BoxGroup("Configuration"), | |
Tooltip("How fast should the player be able to move left and right?"), | |
ShowIf("showConfiguration"), | |
SerializeField] | |
private float characterJumpHeight = 1.0f; | |
[BoxGroup("Configuration"), | |
Tooltip("The amount of time the player needs to wait until they can jump again."), | |
ShowIf("showConfiguration"), | |
SerializeField] | |
private float characterJumpDelay = 0.25f; | |
[BoxGroup("Configuration"), | |
Tooltip("How many times should the player be able to jump before landing on the ground?"), | |
ShowIf("showConfiguration"), | |
SerializeField] | |
private int currentConsecutiveJumps = 2; | |
[BoxGroup("Configuration"), | |
Tooltip("How many times should the player be able to jump before landing on the ground?"), | |
ShowIf("showConfiguration"), | |
SerializeField] | |
private int maxConsecutiveJumps = 2; | |
[BoxGroup("Configuration"), | |
Tooltip("What layers should the player collide with to reset the jump count?"), | |
ShowIf("showConfiguration"), | |
SerializeField] | |
private LayerMask groundLayer; | |
[BoxGroup("Configuration"), | |
Tooltip("Should the script accept player input?"), | |
ShowIf("showConfiguration"), | |
SerializeField] | |
private bool characterUsesSpriteRenderer = false; | |
#endregion | |
#region Quick Information | |
[BoxGroup("Quick Information"), | |
SerializeField] | |
private bool showQuickInformation = false; | |
[BoxGroup("Quick Information"), | |
ShowIf("showQuickInformation"), | |
SerializeField] | |
private Vector2 movementInput; | |
[BoxGroup("Quick Information"), | |
Tooltip("The last time in milliseconds that the player has jumped."), | |
ShowIf("showQuickInformation"), | |
SerializeField] | |
private float lastJumped; | |
[BoxGroup("Quick Information"), | |
Tooltip("Is the character on the ground?"), | |
ShowIf("showQuickInformation"), | |
SerializeField] | |
private bool isCharacterGrounded = true; | |
[BoxGroup("Quick Information"), | |
Tooltip("Is the character facing right?"), | |
ShowIf("showQuickInformation"), | |
SerializeField] | |
private bool isFacingRight = false; | |
[BoxGroup("Quick Information"), | |
Tooltip("Should the script accept player input?"), | |
ShowIf("showQuickInformation"), | |
SerializeField] | |
private bool isAllowedToMove = true; | |
#endregion | |
#region Automation | |
[BoxGroup("Automation"), | |
SerializeField] | |
private bool isBeingControlledByCode = false; | |
[BoxGroup("Automation"), | |
SerializeField] | |
private bool loopThroughAllWaypoints = false; | |
[BoxGroup("Automation"), | |
SerializeField] private bool showWaypoints = false; | |
[BoxGroup("Automation"), | |
NaughtyAttributes.ReorderableList, | |
ShowIf("showWaypoints"), | |
SerializeField] | |
private List<WayPoint> listOfWayPoints = new List<WayPoint>(); | |
#endregion | |
#region Events | |
#region Has Jumped Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showHasJumpedEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will only run on the frame that isGrounded becomes false.", EInfoBoxType.Normal), | |
ShowIf("showHasJumpedEvent"), | |
SerializeField] | |
private UnityEvent hasJumpedEvent; | |
#endregion | |
#region Has Landed Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showHasLandedEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will only run on the frame that isGrounded becomes true.", EInfoBoxType.Normal), | |
ShowIf("showHasLandedEvent"), | |
SerializeField] | |
private UnityEvent hasLandedEvent; | |
#endregion | |
#region Is Grounded Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showIsGroundedEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will run on every frame that the character is grounded.", EInfoBoxType.Normal), | |
ShowIf("showIsGroundedEvent"), | |
SerializeField] | |
private UnityEvent isGroundedEvent; | |
#endregion | |
#region Is Not Grounded Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showIsNotGroundedEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will run on every frame that the character is not grounded.", EInfoBoxType.Normal), | |
ShowIf("showIsNotGroundedEvent"), | |
SerializeField] | |
private UnityEvent isNotGroundedEvent; | |
#endregion | |
#region Has Changed Direction Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showHasChangedDirectionEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will only run on the frame that the character changes direction.", EInfoBoxType.Normal), | |
ShowIf("showHasChangedDirectionEvent"), | |
SerializeField] | |
private UnityEvent hasChangedDirectionEvent; | |
#endregion | |
#region Is Moving Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showIsMovingEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will run on every frame that the character's velocity is not 0.", EInfoBoxType.Normal), | |
ShowIf("showIsMovingEvent"), | |
SerializeField] | |
private UnityEvent isMovingEvent; | |
#endregion | |
#region Is Not Moving Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showIsNotMovingEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will run on every frame that the character's velocity is 0.", EInfoBoxType.Normal), | |
ShowIf("showIsNotMovingEvent"), | |
SerializeField] | |
private UnityEvent isNotMovingEvent; | |
#endregion | |
#region Has Started Moving Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showHasStartedMovingEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will only run on the frame that the character starts moving.", EInfoBoxType.Normal), | |
ShowIf("showHasStartedMovingEvent"), | |
SerializeField] | |
private UnityEvent hasStartedMovingEvent; | |
#endregion | |
#region Is Not Grounded Event | |
[BoxGroup("Events"), | |
SerializeField] | |
private bool showHasStoppedMovingEvent = false; | |
[BoxGroup("Events"), | |
InfoBox("This event will only run on the frame that the character stops moving.", EInfoBoxType.Normal), | |
ShowIf("showHasStoppedMovingEvent"), | |
SerializeField] | |
private UnityEvent hasStoppedMovingEvent; | |
#endregion | |
#endregion | |
#region References | |
[BoxGroup("References"), | |
SerializeField] | |
private bool showReferences = false; | |
[BoxGroup("References"), | |
ShowIf("showReferences"), | |
SerializeField] | |
private Animator characterAnimator; | |
[BoxGroup("References"), | |
ShowIf("showReferences"), | |
SerializeField] | |
private SpriteRenderer characterSpriteRenderer; | |
[BoxGroup("References"), | |
ShowIf("showReferences"), | |
SerializeField] | |
private Rigidbody2D characterRigidBody2D; | |
[BoxGroup("References"), | |
ShowIf("showReferences"), | |
SerializeField] | |
private BoxCollider2D characterBoxCollider2D; | |
[BoxGroup("References"), | |
ShowIf("showReferences"), | |
SerializeField] | |
private CircleCollider2D characterCircleCollider2D; | |
[BoxGroup("References"), | |
ShowIf("showReferences"), | |
SerializeField] | |
private PhysicsMaterial2D stickyMaterial; | |
[BoxGroup("References"), | |
ShowIf("showReferences"), | |
SerializeField] | |
private PhysicsMaterial2D slipperyMaterial; | |
#endregion | |
#endregion | |
#region Private Fields | |
private AutomatedMoveModule automatedMoveModule; | |
private AnimationModule animationModule; | |
private PlayerInputModule playerInputModule; | |
private Collider2D[] debugHits; | |
private int debugGroundColliderCounter; | |
#endregion | |
#region Getters/Setters/Constructors | |
public float CharacterMoveSpeed { get => characterMoveSpeed; set => characterMoveSpeed = value; } | |
public float CharacterJumpHeight { get => characterJumpHeight; set => characterJumpHeight = value; } | |
public float CharacterJumpDelay { get => characterJumpDelay; set => characterJumpDelay = value; } | |
public int CurrentConsecutiveJumps { get => currentConsecutiveJumps; set => currentConsecutiveJumps = value; } | |
public int MaxConsecutiveJumps { get => maxConsecutiveJumps; set => maxConsecutiveJumps = value; } | |
public LayerMask GroundLayer { get => groundLayer; set => groundLayer = value; } | |
public float LastJumped { get => lastJumped; set => lastJumped = value; } | |
public bool IsCharacterGrounded { get => isCharacterGrounded; set => isCharacterGrounded = value; } | |
public bool IsFacingRight { get => isFacingRight; set => isFacingRight = value; } | |
public bool IsAllowedToMove { get => isAllowedToMove; set => isAllowedToMove = value; } | |
public bool IsBeingControlledByCode { get => isBeingControlledByCode; set => isBeingControlledByCode = value; } | |
public bool LoopThroughAllWaypoints { get => loopThroughAllWaypoints; set => loopThroughAllWaypoints = value; } | |
public List<WayPoint> ListOfWayPoints { get => listOfWayPoints; set => listOfWayPoints = value; } | |
public UnityEvent HasJumpedEvent { get => hasJumpedEvent; set => hasJumpedEvent = value; } | |
public UnityEvent HasLandedEvent { get => hasLandedEvent; set => hasLandedEvent = value; } | |
public UnityEvent IsGroundedEvent { get => isGroundedEvent; set => isGroundedEvent = value; } | |
public UnityEvent IsNotGroundedEvent { get => isNotGroundedEvent; set => isNotGroundedEvent = value; } | |
public UnityEvent HasChangedDirectionEvent { get => hasChangedDirectionEvent; set => hasChangedDirectionEvent = value; } | |
public UnityEvent IsMovingEvent { get => isMovingEvent; set => isMovingEvent = value; } | |
public UnityEvent IsNotMovingEvent { get => isNotMovingEvent; set => isNotMovingEvent = value; } | |
public UnityEvent HasStartedMovingEvent { get => hasStartedMovingEvent; set => hasStartedMovingEvent = value; } | |
public UnityEvent HasStoppedMovingEvent { get => hasStoppedMovingEvent; set => hasStoppedMovingEvent = value; } | |
public bool CharacterUsesSpriteRenderer { get => characterUsesSpriteRenderer; set => characterUsesSpriteRenderer = value; } | |
public Animator CharacterAnimator { get => characterAnimator; set => characterAnimator = value; } | |
public SpriteRenderer CharacterSpriteRenderer { get => characterSpriteRenderer; set => characterSpriteRenderer = value; } | |
public Rigidbody2D CharacterRigidBody2D { get => characterRigidBody2D; set => characterRigidBody2D = value; } | |
public BoxCollider2D CharacterBoxCollider2D { get => characterBoxCollider2D; set => characterBoxCollider2D = value; } | |
public CircleCollider2D CharacterCircleCollider2D { get => characterCircleCollider2D; set => characterCircleCollider2D = value; } | |
#endregion | |
#region My Methods | |
public void CheckForGround() { | |
} | |
public void Crouch() { | |
CharacterBoxCollider2D.enabled = !CharacterBoxCollider2D.enabled; | |
} | |
public void Jump() { | |
CharacterRigidBody2D.AddForce(new Vector2(0f, CharacterJumpHeight), ForceMode2D.Impulse); | |
} | |
public void Move() { | |
float xPosition = CharacterRigidBody2D.position.x + (movementInput.x * CharacterMoveSpeed * Time.fixedDeltaTime); | |
float yPosition = CharacterRigidBody2D.position.y + (0.5f * Physics2D.gravity.y * Time.fixedDeltaTime); | |
CharacterRigidBody2D.MovePosition(new Vector2(xPosition, yPosition)); | |
} | |
// TODO: Apply Knockback | |
public void ApplyKnockback() { | |
} | |
// TODO: Allow one-way Platforms that allow players to passthrough from below and stand on top. | |
// TODO: Use the new Unity Input System | |
// TODO: Add Dodge Roll Ability | |
// TODO: Add Dash Ability | |
// TODO: Apply Double/Triple/Quad Jump Ability | |
// TODO: Apply Teleport Ability | |
#endregion | |
#region Unity Methods | |
private void OnEnable() { | |
} | |
void Awake() { | |
automatedMoveModule = new AutomatedMoveModule(this); | |
animationModule = new AnimationModule(gameObject); | |
playerInputModule = PlayerInputModule.Instance; | |
} | |
// For Physics related calculations of objects. Movement methods should be called here. | |
private void FixedUpdate() { | |
} | |
// For moving the visuals of objects. Animation methods should be called here. | |
private void LateUpdate() { | |
} | |
void OnDisable() { | |
} | |
#endregion | |
#region Helper Methods | |
[NaughtyAttributes.Button("Find References")] | |
public void FindReferences() { | |
CharacterRigidBody2D = GetComponent<Rigidbody2D>(); | |
CharacterBoxCollider2D = GetComponent<BoxCollider2D>(); | |
CharacterCircleCollider2D = GetComponent<CircleCollider2D>(); | |
CharacterAnimator = GetComponent<Animator>(); | |
if(CharacterUsesSpriteRenderer) { | |
CharacterSpriteRenderer = transform.GetChild(0).GetComponent<SpriteRenderer>(); | |
} | |
} | |
#endregion | |
} | |
} |
This file contains 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
// PlayerInputModule.cs is found here: https://gist.github.com/h1ddengames/2fa94bd7d1612979f604a630a8d79f37 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment