Here’s my code
“UIManager.cs”
<code>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIManager : MonoBehaviour
{
public static UIManager Instance;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
DontDestroyOnLoad(gameObject);
}
public SceneFader sceneFader;
private void Start()
{
sceneFader = GetComponentInChildren<SceneFader>();
}
}
</code>
“PlayerController.cs”
<code>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEngine.UI;
using Unity.VisualScripting;
public class PlayerController : MonoBehaviour
{
[Header("Horizontal Movement Settings:")]
[SerializeField] private float walkSpeed = 1; //sets the players movement speed on the ground
[Space(5)]
[Header("Vertical Movement Settings")]
[SerializeField] private float jumpForce = 45f; //sets how hight the player can jump
private int jumpBufferCounter = 0; //stores the jump button input
[SerializeField] private int jumpBufferFrames; //sets the max amount of frames the jump buffer input is stored
private float coyoteTimeCounter = 0; //stores the Grounded() bool
[SerializeField] private float coyoteTime; ////sets the max amount of frames the Grounded() bool is stored
private int airJumpCounter = 0; //keeps track of how many times the player has jumped in the air
[SerializeField] private int maxAirJumps; //the max no. of air jumps
private float gravity; //stores the gravity scale at start
[Space(5)]
[Header("Ground Check Settings:")]
[SerializeField] private Transform groundCheckPoint; //point at which ground check happens
[SerializeField] private float groundCheckY = 0.2f; //how far down from ground chekc point is Grounded() checked
[SerializeField] private float groundCheckX = 0.5f; //how far horizontally from ground chekc point to the edge of the player is
[SerializeField] private LayerMask whatIsGround; //sets the ground layer
[Space(5)]
[Header("Dash Settings")]
[SerializeField] private float dashSpeed; //speed of the dash
[SerializeField] private float dashTime; //amount of time spent dashing
[SerializeField] private float dashCooldown; //amount of time between dashes
[SerializeField] GameObject dashEffect;
private bool canDash = true, dashed;
[Space(5)]
[Header("Attack Settings:")]
[SerializeField] private Transform SideAttackTransform; //the middle of the side attack area
[SerializeField] private Vector2 SideAttackArea; //how large the area of side attack is
[SerializeField] private Transform UpAttackTransform; //the middle of the up attack area
[SerializeField] private Vector2 UpAttackArea; //how large the area of side attack is
[SerializeField] private Transform DownAttackTransform; //the middle of the down attack area
[SerializeField] private Vector2 DownAttackArea; //how large the area of down attack is
[SerializeField] private LayerMask attackableLayer; //the layer the player can attack and recoil off of
private float timeBetweenAttack, timeSinceAttck;
[SerializeField] private float damage; //the damage the player does to an enemy
[SerializeField] private GameObject slashEffect; //the effect of the slashs
bool restoreTime;
float restoreTimeSpeed;
[Space(5)]
[Header("Recoil Settings:")]
[SerializeField] private int recoilXSteps = 5; //how many FixedUpdates() the player recoils horizontally for
[SerializeField] private int recoilYSteps = 5; //how many FixedUpdates() the player recoils vertically for
[SerializeField] private float recoilXSpeed = 100; //the speed of horizontal recoil
[SerializeField] private float recoilYSpeed = 100; //the speed of vertical recoil
private int stepsXRecoiled, stepsYRecoiled; //the no. of steps recoiled horizontally and verticall
[Space(5)]
[Header("Health Settings")]
public int health;
public int maxHealth;
[SerializeField] GameObject bloodSpurt;
[SerializeField] float hitFlashSpeed;
public delegate void OnHealthChangedDelegate();
[HideInInspector] public OnHealthChangedDelegate onHealthChangedCallback;
[Space(5)]
float healTimer;
[SerializeField] float timeToHeal;
[Space(5)]
[Header("Mana Settings")]
[SerializeField] UnityEngine.UI.Image manaStorage;
[SerializeField] float mana;
[SerializeField] float manaDrainSpeed;
[SerializeField] float manaGain;
[Space(5)]
[Header("Spell Settings")]
[SerializeField] float manaSpellCost = 0.3f;
[SerializeField] float timeBetweenCast = 0.5f;
[SerializeField] float spellDamage; //upspellexplosion and downspellexplosion only
[SerializeField] float downSpellForce; //desolate dive only
//spell cast objects
[SerializeField] GameObject sideSpellFireball;
[SerializeField] GameObject upSpellFireball;
[SerializeField] GameObject downSpellFireball;
float timeSinceCast;
float castOrHealthtimer;
[Space(5)]
[Header("Camera Stuff")]
[SerializeField] private float playerFallSpeedThreshold = -10;
[HideInInspector] public PlayerStateList pState;
[HideInInspector] public Rigidbody2D rb;
private Animator anim;
private SpriteRenderer sr;
//Input Variables
private float xAxis, yAxis;
private bool attack = false;
private bool canFlash = true;
public static PlayerController Instance;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
DontDestroyOnLoad(gameObject);
}
// Start is called before the first frame update
void Start()
{
pState = GetComponent<PlayerStateList>();
rb = GetComponent<Rigidbody2D>();
sr = GetComponent<SpriteRenderer>();
anim = GetComponent<Animator>();
gravity = rb.gravityScale;
Mana = mana;
manaStorage.fillAmount = Mana;
health = maxHealth;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
}
// Update is called once per frame
void Update()
{
if (pState.cutscene) return;
GetInputs();
UpdateJumpVariables();
UpdateCameraYDampForPlayerFall();
RestoreTimeScale();
if (pState.dashing) return;
Flip();
Move();
Jump();
StartDash();
Attack();
FlashWhileInvincible();
Heal();
CastSpell();
}
private void OnTriggerEnter2D(Collider2D _other) //for up and down cast spell
{
if(_other.GetComponent<Enemy>() != null && pState.casting)
{
_other.GetComponent<Enemy>().EnemyGetsHit(spellDamage, (_other.transform.position - transform.position).normalized, -recoilYSpeed);
}
}
private void FixedUpdate()
{
if (pState.cutscene) return;
if (pState.dashing) return;
Recoil();
}
void GetInputs()
{
xAxis = Input.GetAxisRaw("Horizontal");
yAxis = Input.GetAxisRaw("Vertical");
attack = Input.GetButtonDown("Attack");
if(Input.GetButtonDown("Cast/Heal"))
{
castOrHealthtimer += Time.deltaTime;
}
else
{
castOrHealthtimer = 0;
}
}
void Flip()
{
if (xAxis < 0)
{
transform.localScale = new Vector2(-1, transform.localScale.y);
pState.lookingRight = false;
}
else if (xAxis > 0)
{
transform.localScale = new Vector2(1, transform.localScale.y);
pState.lookingRight = true;
}
}
private void Move()
{
rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y);
anim.SetBool("Walking", rb.velocity.x != 0 && Grounded());
}
void UpdateCameraYDampForPlayerFall()
{
//if falling past a certain speed threshold
if(rb.velocity.y < playerFallSpeedThreshold && !CameraManager.Instance.isLerpingYDamping && !CameraManager.Instance.hasLerpedYDamping)
{
StartCoroutine(CameraManager.Instance.LerpYDamping(true));
}
//if standing still or moving up
if(rb.velocity.y >= 0 && !CameraManager.Instance.isLerpingYDamping && CameraManager.Instance.hasLerpedYDamping)
{
//reset camera function
CameraManager.Instance.hasLerpedYDamping = false;
StartCoroutine(CameraManager.Instance.LerpYDamping(false));
}
}
void StartDash()
{
if (Input.GetButtonDown("Dash") && canDash && !dashed)
{
StartCoroutine(Dash());
dashed = true;
}
if (Grounded())
{
dashed = false;
}
}
IEnumerator Dash()
{
canDash = false;
pState.dashing = true;
anim.SetTrigger("Dashing");
rb.gravityScale = 0;
int _dir = pState.lookingRight ? 1 : -1;
rb.velocity = new Vector2(_dir * dashSpeed, 0);
if (Grounded()) Instantiate(dashEffect, transform);
yield return new WaitForSeconds(dashTime);
rb.gravityScale = gravity;
pState.dashing = false;
yield return new WaitForSeconds(dashCooldown);
canDash = true;
}
public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay)
{
//If exit direction is upwards
if (_exitDir.y != 0)
{
rb.velocity = jumpForce * _exitDir;
}
//If exit direction requires horizontal movement
if (_exitDir.x != 0)
{
xAxis = _exitDir.x > 0 ? 1 : -1;
Move();
}
Flip();
yield return new WaitForSeconds(_delay);
pState.cutscene = false;
}
void Attack()
{
timeSinceAttck += Time.deltaTime;
if (attack && timeSinceAttck >= timeBetweenAttack)
{
timeSinceAttck = 0;
anim.SetTrigger("Attacking");
if (yAxis == 0 || yAxis < 0 && Grounded())
{
int _recoilLeftOrRight = pState.lookingRight ? 1 : -1;
Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX, Vector2.right * _recoilLeftOrRight, recoilXSpeed);
Instantiate(slashEffect, SideAttackTransform);
}
else if (yAxis > 0)
{
Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, Vector2.up, recoilYSpeed);
SlashEffectAtAngle(slashEffect, 80, UpAttackTransform);
}
else if (yAxis < 0 && !Grounded())
{
Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, Vector2.down, recoilYSpeed);
SlashEffectAtAngle(slashEffect, -90, DownAttackTransform);
}
}
}
void Hit(Transform _attackTransform, Vector2 _attackArea, ref bool _recoilBool, Vector2 _recoilDir, float _recoilStrength)
{
Collider2D[] objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, attackableLayer);
if (objectsToHit.Length > 0)
{
_recoilBool = true;
}
for (int i = 0; i < objectsToHit.Length; i++)
{
if (objectsToHit[i].GetComponent<Enemy>() != null)
{
objectsToHit[i].GetComponent<Enemy>().EnemyGetsHit(damage, _recoilDir, _recoilStrength);
if (objectsToHit[i].CompareTag("Enemy"))
{
Mana += manaGain;
}
}
}
}
void SlashEffectAtAngle(GameObject _slashEffect, int _effectAngle, Transform _attackTransform)
{
_slashEffect = Instantiate(_slashEffect, _attackTransform);
_slashEffect.transform.eulerAngles = new Vector3(0, 0, _effectAngle);
_slashEffect.transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y);
}
void Recoil()
{
if (pState.recoilingX)
{
if (pState.lookingRight)
{
rb.velocity = new Vector2(-recoilXSpeed, 0);
}
else
{
rb.velocity = new Vector2(recoilXSpeed, 0);
}
}
if (pState.recoilingY)
{
rb.gravityScale = 0;
if (yAxis < 0)
{
rb.velocity = new Vector2(rb.velocity.x, recoilYSpeed);
}
else
{
rb.velocity = new Vector2(rb.velocity.x, -recoilYSpeed);
}
airJumpCounter = 0;
}
else
{
rb.gravityScale = gravity;
}
//stop recoil
if (pState.recoilingX && stepsXRecoiled < recoilXSteps)
{
stepsXRecoiled++;
}
else
{
StopRecoilX();
}
if (pState.recoilingY && stepsYRecoiled < recoilYSteps)
{
stepsYRecoiled++;
}
else
{
StopRecoilY();
}
if (Grounded())
{
StopRecoilY();
}
}
void StopRecoilX()
{
stepsXRecoiled = 0;
pState.recoilingX = false;
}
void StopRecoilY()
{
stepsYRecoiled = 0;
pState.recoilingY = false;
}
public void TakeDamage(float _damage)
{
Health -= Mathf.RoundToInt(_damage);
StartCoroutine(StopTakingDamage());
}
IEnumerator StopTakingDamage()
{
pState.invincible = true;
GameObject _bloodSpurtParticles = Instantiate(bloodSpurt, transform.position, Quaternion.identity);
Destroy(_bloodSpurtParticles, 1.5f);
anim.SetTrigger("TakeDamage");
yield return new WaitForSeconds(1f);
pState.invincible = false;
}
IEnumerator Flash()
{
sr.enabled = !sr.enabled;
canFlash = false;
yield return new WaitForSeconds(0.1f);
canFlash = true;
}
void FlashWhileInvincible()
{
if (pState.invincible)
{
if (Time.timeScale > 0.2 && canFlash)
{
StartCoroutine(Flash());
}
}
else
{
sr.enabled = true;
}
}
void RestoreTimeScale()
{
if (restoreTime)
{
if (Time.timeScale < 1)
{
Time.timeScale += Time.deltaTime * restoreTimeSpeed;
}
else
{
Time.timeScale = 1;
restoreTime = false;
}
}
}
public void HitStopTime(float _newTimeScale, int _restoreSpeed, float _delay)
{
restoreTimeSpeed = _restoreSpeed;
if (_delay > 0)
{
StopCoroutine(StartTimeAgain(_delay));
StartCoroutine(StartTimeAgain(_delay));
}
else
{
restoreTime = true;
}
Time.timeScale = _newTimeScale;
}
IEnumerator StartTimeAgain(float _delay)
{
restoreTime = true;
yield return new WaitForSeconds(_delay);
}
public int Health
{
get { return health; }
set
{
if (health != value)
{
health = Mathf.Clamp(value, 0, maxHealth);
if (onHealthChangedCallback != null)
{
onHealthChangedCallback.Invoke();
}
}
}
}
void Heal()
{
if(Input.GetButton("Cast/Heal") && castOrHealthtimer > 0.05f && Health < maxHealth && Mana > 0 && !pState.jumping && !pState.dashing)
{
pState.healing = true;
anim.SetBool("Healing", true);
//healing
healTimer += Time.deltaTime;
if(healTimer >= timeToHeal)
{
Health++;
healTimer = 0;
}
//drain mana
Mana -= Time.deltaTime * manaDrainSpeed;
}
else
{
pState.healing = false;
anim.SetBool("Healing", false);
healTimer = 0;
}
}
float Mana
{
get { return mana; }
set
{
//if mana stats change
if (mana != value)
{
mana = Mathf.Clamp(value, 0, 1);
manaStorage.fillAmount = Mana;
}
}
}
void CastSpell()
{
if(Input.GetButtonUp("Cast/Heal") && castOrHealthtimer <= 0.05f && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
{
pState.casting = true;
timeSinceCast = 0;
StartCoroutine(CastCoroutine());
}
else
{
timeSinceCast += Time.deltaTime;
}
if(Grounded())
{
//disable downspell if on the ground
downSpellFireball.SetActive(false);
}
//if down spell is active, force player down until grounded
if (downSpellFireball.activeInHierarchy)
{
rb.velocity += downSpellForce * Vector2.down;
}
}
IEnumerator CastCoroutine()
{
anim.SetBool("Casting", true);
yield return new WaitForSeconds(0.09f);
//side cast
if (yAxis == 0 || (yAxis == 0 && Grounded()))
{
GameObject _fireBall = Instantiate(sideSpellFireball, SideAttackTransform.position, Quaternion.identity);
//flip fireball
if(pState.lookingRight)
{
_fireBall.transform.eulerAngles = Vector3.zero; //if facing right, fireball continues as per normal
}
else
{
_fireBall.transform.eulerAngles = new Vector2(_fireBall.transform.eulerAngles.x, 180);
//if not facing right, rotate the fireball 180 degree
}
pState.recoilingX = true;
}
//up cast
else if (yAxis > 0)
{
Instantiate(upSpellFireball, transform);
rb.velocity = Vector2.zero;
}
//down cast
else if (yAxis < 0 && !Grounded())
{
downSpellFireball.SetActive(true);
}
Mana -= manaSpellCost;
yield return new WaitForSeconds(0.09f);
anim.SetBool("Casting", false);
pState.casting = false;
}
public bool Grounded()
{
if (Physics2D.Raycast(groundCheckPoint.position, Vector2.down, groundCheckY, whatIsGround)
|| Physics2D.Raycast(groundCheckPoint.position + new Vector3(groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround)
|| Physics2D.Raycast(groundCheckPoint.position + new Vector3(-groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround))
{
return true;
}
else
{
return false;
}
}
void Jump()
{
if (jumpBufferCounter > 0 && coyoteTimeCounter > 0 && !pState.jumping)
{
rb.velocity = new Vector3(rb.velocity.x, jumpForce);
pState.jumping = true;
}
else if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump"))
{
pState.jumping = true;
airJumpCounter++;
rb.velocity = new Vector3(rb.velocity.x, jumpForce);
}
if (Input.GetButtonUp("Jump") && rb.velocity.y > 3)
{
rb.velocity = new Vector2(rb.velocity.x, 0);
pState.jumping = false;
}
anim.SetBool("Jumping", !Grounded());
}
void UpdateJumpVariables()
{
if (Grounded())
{
pState.jumping = false;
coyoteTimeCounter = coyoteTime;
airJumpCounter = 0;
}
else
{
coyoteTimeCounter -= Time.deltaTime;
}
if (Input.GetButtonDown("Jump"))
{
jumpBufferCounter = jumpBufferFrames;
}
else
{
jumpBufferCounter--;
}
}
}
</code>