using MyCollections.DesignPatterns.Visitor; using MyCollections.Managers; using MyCollections.SceneManagement; using MyCollections.Stats; using System; using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class PlayerController : MonoBehaviour, IHealthSystem, IVisitable { //Variables private Rigidbody2D rb; private Animator anim; private static PlayerController instance; public bool flashActive; [SerializeField] public float flashLength = 0f; [SerializeField] public float flashCounter = 0f; public Stats Stats { get; private set; } [Header("Attribute Settings: ")] [SerializeField] private int health = 100; [SerializeField] private int maxHealth = 100; [SerializeField] private float speed = 0f; [SerializeField] private float runSpeed = 0f; private InventoryManager inventoryManager; [Header("Combat Settings: ")] [SerializeField] private LayerMask enemyLayerMask; [SerializeField] private bool isAttacking = false; [SerializeField] private int currentPlayerAttack = 20; [SerializeField] private int currentPlayerDefense = 5; [SerializeField] private float attackRange = 2f; [SerializeField] private float attackTimer = 0f; [SerializeField] private float maxAttackTimer = 1.5f; [SerializeField] private float attackForce = 5f; [Header("Mana Settings: ")] [SerializeField] private float mana = 100; [SerializeField] private float maxMana = 100; [SerializeField] private float manaIncreaseRate = 0.5f; [SerializeField] private float manaDecreaseRate = 0.5f; [SerializeField] private float manaTimer = 2f; [SerializeField] private float manaTimerMax = 2f; [Header("Other Settings: ")] [SerializeField] private bool canMove = true; [SerializeField] private bool isRuning = false; [SerializeField] private Vector2 lastInput; private float horizontalInput, verticalInput; public bool isTalking = false; public bool shield; private GameObject interactibleGameObject; //Events public event Action<int> OnHealthChanged = delegate { }; public event Action<float> OnManaChanged = delegate { }; public event Action<int> OnXPChange = delegate { }; //Properties public static PlayerController Instance => instance; public bool CanMove { get => canMove; set => canMove = value; } public int Health { get => health; set { health = Mathf.Clamp(value, 0, maxHealth); // Clamp health within valid range OnHealthChanged?.Invoke(health); // Trigger the event when health changes } } public float Mana { get => mana; set { mana = Mathf.Clamp(value, 0, maxMana); OnManaChanged?.Invoke(mana); } } public float MaxMana => maxMana; public int MaxHealth => maxHealth; public int CurrentPlayerDefense { get => currentPlayerDefense; set => currentPlayerDefense += value; } public int CurrentPlayerAttack { get => currentPlayerAttack; set => currentPlayerAttack += value; } public InventoryManager InventoryManager => inventoryManager; private void Awake() { rb = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); manaTimer = manaTimerMax; inventoryManager = GetComponentInChildren<InventoryManager>(); if (instance == null) { instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } Stats = new Stats(new StatsMediator(), this); currentPlayerAttack = Stats.Attack; } void Start() { SceneManager.sceneLoaded += OnSceneLoaded; } private void Update() { if (!Stats.Mediator.IsEmpty) Stats.Mediator.Update(Time.deltaTime); if (Input.GetKeyDown(KeyCode.M)) { TakeDamage(5); } if (!IsDead() && canMove) { if (!isRuning) { //Get Keyboard Input values horizontalInput = Input.GetAxisRaw("Horizontal"); verticalInput = Input.GetAxisRaw("Vertical"); } else { //Get Keyboard Input values horizontalInput = Input.GetAxisRaw("Horizontal") * 2; verticalInput = Input.GetAxisRaw("Vertical") * 2; } RecoverMana(); PlayerDefend(); PlayerAttack(); PlayerAnimController(); Run(); if (Input.GetKeyDown(KeyCode.R)) { health = 0; } if (Input.GetKeyDown(InputManager.GetKey(InputAction.Interact))) { if (interactibleGameObject.TryGetComponent(out IInteractable interaction)) interaction.OnInteract(); else if (interactibleGameObject.TryGetComponent(out IInteractableAsync asyncInteraction)) asyncInteraction.OnInteractAsync(); } } else if (IsDead()) { InvokeDeath(); } if (Input.GetKeyDown(KeyCode.V)) { RevivePlayer(); } if (Input.GetKeyDown(KeyCode.C)) { //IsTalking(); } } private void FixedUpdate() { if (!IsDead() && canMove) { PlayerMovement(); } } private void DestroyObj() { Destroy(this); } private void Run() { if (Input.GetKey(InputManager.GetKey(InputAction.Run)) && Mathf.Round(mana) > 0) { isRuning = true; Mana = Mathf.Max(0, mana - manaDecreaseRate * Time.deltaTime); manaTimer = manaTimerMax; } else isRuning = false; } //Player Movement private void PlayerMovement() { if (isRuning) { //Set velocity based on the input rb.velocity = new Vector2(horizontalInput, verticalInput).normalized * runSpeed; if (rb.velocity != Vector2.zero) lastInput = new Vector2(horizontalInput, verticalInput).normalized; } else { //Set velocity based on the input rb.velocity = new Vector2(horizontalInput, verticalInput).normalized * speed; if (rb.velocity != Vector2.zero) lastInput = new Vector2(horizontalInput, verticalInput).normalized; } } //If player runs out of mana or if it stops to perform an action that costs mana //The mana will have a timer to recover private void RecoverMana() { if (mana != maxMana) { if (manaTimer > 0f) manaTimer -= Time.deltaTime; else { Mana = Mathf.Max(Mathf.Clamp((mana + manaIncreaseRate * Time.deltaTime), 0, maxMana)); } } } //Player Attack private void PlayerAttack() { if (Input.GetKey(InputManager.GetKey(InputAction.Attack)) && mana > 0.1f) { isAttacking = true; manaTimer = maxAttackTimer; if (attackTimer <= maxAttackTimer) attackTimer += Time.deltaTime; if (attackTimer < maxAttackTimer) { Mana = Mathf.Max(0, mana - manaDecreaseRate * Time.deltaTime); } } if (Input.GetKeyUp(InputManager.GetKey(InputAction.Attack)) && isAttacking) { Vector2 origin = transform.position; Vector2 direction = lastInput; anim.SetBool("IsAttacking", true); //Perform attack Animation anim.SetTrigger("BasicAttack"); RaycastHit2D hit = Physics2D.Raycast(origin, direction, attackRange, enemyLayerMask); Debug.DrawRay(origin, direction * attackRange, Color.red, 1.5f); if (hit.collider != null) { Debug.Log($"Collider: {hit.collider}"); if (hit.collider.TryGetComponent(out IHealthSystem healthSystem)) { int damage = currentPlayerAttack + (int)(currentPlayerAttack * attackTimer); healthSystem.TakeDamage(damage); if (hit.collider.gameObject.TryGetComponent(out Rigidbody2D rb)) { rb.AddForce(lastInput * attackForce, ForceMode2D.Impulse); } } } attackTimer = 0f; // Reset attack timer isAttacking = false; // Ensure the animation can play out by delaying the reset of IsAttacking StartCoroutine(ResetAttackAnimation()); } } private void OnTriggerStay2D(Collider2D other) { interactibleGameObject = other.gameObject; } // Coroutine to delay resetting the attack animation private IEnumerator ResetAttackAnimation() { yield return new WaitForSeconds(0.1f); // Adjust the delay as necessary anim.SetBool("IsAttacking", false); anim.ResetTrigger("BasicAttack"); } private void PlayerDefend() => shield = Input.GetKey(KeyCode.Space); //Set Animation Values private void PlayerAnimController() { anim.SetFloat("MoveX", horizontalInput); anim.SetFloat("MoveY", verticalInput); anim.SetFloat("LastMoveX", lastInput.x); anim.SetFloat("LastMoveY", lastInput.y); anim.SetFloat("Velocity", new Vector2(horizontalInput, verticalInput).normalized.magnitude); } public void TakeDamage(int amount) { Health = Mathf.Max(0, health - amount); anim.SetTrigger("Hurt"); } public void Heal(int amount) { // Trigger health-changed event to update observers (e.g., UI) OnHealthChanged?.Invoke(Health); } public int CurrentHealth() => health; public bool IsDead() => health <= 0f ? true : false; private void InvokeDeath() { anim.SetTrigger("IsDead"); rb.velocity = Vector2.zero; } private void RevivePlayer() { health = 100; anim.ResetTrigger("IsDead"); anim.Play("Idle", 0); } private void IsTalking() { isTalking = !isTalking; anim.SetBool("Chat", isTalking); } public void Accept(IVisitor visitor) => visitor.Visit(this); void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (scene.name == "MainMenu") { Destroy(gameObject); } } void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; } }