using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; public class Player : MonoBehaviour { [Header("Speed")] // All player speed related variables [SerializeField] float const_moveSpeed = 3f; [SerializeField] float moveSpeed = 5f; [SerializeField] float speedInStrongAttack = 1f; [SerializeField] float dashSpeed = 10f; Vector2 rawInput; public bool isDashing = false; bool isRunning; [Header("Teleport")] // All player teleport related variables [SerializeField] float teleportTime = 1f; [SerializeField] float teleportOffsetX = 0.5f; [SerializeField] float teleportOffsetY = 3f; public bool trueTeleport = true; [Header("Player Stamina&Health")] // All player stamina and health related variables [SerializeField] int playerHealth = 100; [SerializeField] int playerStamina = 100; [SerializeField] int attackStamina = 100; [SerializeField] int strongAttackStamina = 100; [SerializeField] int dashStamina = 100; [SerializeField] float staminaRegenSpeed = 0.015f; [SerializeField] float staminaRegenDelay = 0.5f; [SerializeField] int enemyDamageMult = 50; [SerializeField] Color damageColor = Color.red; [SerializeField] float damageTime = 0.2f; [SerializeField] float knockbackStrength = 0.25f; bool isStaminaRegenDelay = false; [Header("Weapon")] // All player weapon related variables [SerializeField] float weaponCooldown = 1f; [SerializeField] float strongWeaponCooldown = 1f; [SerializeField] float sliderTimer = 1f; bool isSwinging = false; bool isStrongAttack = false; bool strongAttackMovement = false; [Header("Audio")] // All player audio related variables AudioSource audioSource; [SerializeField] AudioClip weaponFX; [SerializeField] AudioClip[] footstepFX; [SerializeField] AudioClip[] damageFX; [SerializeField] AudioClip potionFX; [SerializeField] AudioClip deathFX; [Header("Misc")] // Misc player variables [SerializeField] float fadeInTime = 1f; [SerializeField] float collisionTolerance = 0.1f; Vector2 collisionNormal; // Misc private player variables bool isDead = false; bool hitWall = false; bool hitProp = false; float facing = 1; public int potionCount = 2; public int cauldronCount = 0; string currentLocation; bool IFrames = false; bool key1Collected = false; bool key2Collected = false; bool hasWon = false; // Component variables Rigidbody2D myRigidbody; Animator myAnimator; Renderer objectRenderer; Slider healthSlider; Slider staminaSlider; Slider cooldownSlider; TextMeshProUGUI potionText; TextMeshProUGUI cauldronText; TextMeshProUGUI locationText; ParticleSystem bloodParticleSystem; ParticleSystem footstepParticleSystem; ParticleSystem dashParticleSystem; Collider2D myCollider; Image deathBarImage; Image deathTextImage; Image fadeInImage; // GameObject variables [Header("GameObjects")] [SerializeField] GameObject weapon; [SerializeField] GameObject healthSliderObject; [SerializeField] GameObject staminaSliderObject; [SerializeField] GameObject cooldownSliderObject; [SerializeField] GameObject potionTextObject; [SerializeField] GameObject cauldronTextObject; [SerializeField] GameObject locationTextObject; [SerializeField] GameObject bloodParticle; [SerializeField] GameObject footstepParticle; [SerializeField] GameObject dashParticle; [SerializeField] GameObject deathBar; [SerializeField] GameObject deathText; [SerializeField] GameObject fadeIn; [SerializeField] GameObject enemyObject; [SerializeField] GameObject winConditionTilemap; // Start method (called first frame) void Start() { // Locking and hiding the cursor to the screen when game is in focus Cursor.visible = false; Cursor.lockState = CursorLockMode.Locked; // Getting all components myRigidbody = GetComponent(); myAnimator = GetComponent(); objectRenderer = GetComponent(); myCollider = GetComponent(); audioSource = GetComponent(); healthSlider = healthSliderObject.GetComponent(); staminaSlider = staminaSliderObject.GetComponent(); cooldownSlider = cooldownSliderObject.GetComponent(); potionText = potionTextObject.GetComponent(); cauldronText = cauldronTextObject.GetComponent(); locationText = locationTextObject.GetComponent(); bloodParticleSystem = bloodParticle.GetComponent(); footstepParticleSystem = footstepParticle.GetComponent(); dashParticleSystem = dashParticle.GetComponent(); deathBarImage = deathBar.GetComponent(); deathTextImage = deathText.GetComponent(); fadeInImage = fadeIn.GetComponent(); // Setting sliders to match player variables for health,stamina and weapon cooldown healthSlider.maxValue = playerHealth; staminaSlider.maxValue = playerStamina; cooldownSlider.maxValue = weaponCooldown; // Stopping dash particles on start and setting movespeed to the constant dashParticleSystem.Stop(); moveSpeed = const_moveSpeed; // Disabling objects that are unlocked or showed not on start deathBar.SetActive(false); deathText.SetActive(false); weapon.SetActive(false); winConditionTilemap.SetActive(false); // Starting various coroutines that run in the background off start StartCoroutine(FootstepFunc()); StartCoroutine(StaminaRegen()); StartCoroutine(FadeInLevel()); } // Update method (called every frame) void Update() { Run(); FlipSprite(); WinCondition(); Death(); healthSlider.value = playerHealth; staminaSlider.value = playerStamina; cooldownSlider.value = sliderTimer; potionText.text = potionCount.ToString(); cauldronText.text = cauldronCount.ToString(); locationText.text = currentLocation.ToString(); } // Called when WASD is pressed void OnMove(InputValue value) { // If the player is not dead record movement into rawInput if (!isDead) { rawInput = value.Get(); } } // Called when left mouse btn is pressed void OnFire() { // If not in attack, dead and the player has enough stamina play weapon sfx if (!isSwinging && !isDead && (playerStamina > attackStamina)) { audioSource.PlayOneShot(weaponFX); // Get if user is doing a strong attack if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { isStrongAttack = true; } // Start weapon swing StartCoroutine(SwingWeapon()); // Start cooldown for weapon StartCoroutine(SliderTimer()); } } // Called when space is pressed void OnDash() { // If player is not attacking, not dead, running, not already dashing and have enough stamina if (!isSwinging && !isDead && isRunning && !isDashing && (playerStamina > dashStamina)) { // Start Dash Coroutine StartCoroutine(DashTimer()); } } // Called when r is pressed void OnDrink() { // If player has enough potions and is not already full health if (potionCount > 0 && playerHealth < 100) { // Play sound, decrement potion count and start healing coroutine audioSource.PlayOneShot(potionFX); potionCount--; StartCoroutine(PotionDrink()); } } // Coroutine for fading into the level IEnumerator FadeInLevel() { // Starting at 3/4 opacity slowly fading in to where its full brightness float fadeInOpacity = 0.75f; for (int i = 0; i < 100; i++) { fadeInImage.color = new Color(0f, 0f, 0f, fadeInOpacity); fadeInOpacity -= 0.0075f; yield return new WaitForSeconds(fadeInTime/100); } } // Coroutine for dashing IEnumerator DashTimer() { // Set dashing to true start the stamina regen timer, decrease stamina, play the particles and play the dash animation isDashing = true; StartCoroutine(ActionTimer()); playerStamina -= dashStamina; dashParticleSystem.Play(); myAnimator.SetBool("isDash", true); // Get initial x and y input float dashx = rawInput.x; float dashy = rawInput.y; // Use the initial input when pressed to lock the play dashing in a certian direction for (int i=0; i<5; i++) { Vector2 dashVelocity = new Vector2(dashx * dashSpeed, dashy * dashSpeed); myRigidbody.velocity = dashVelocity; yield return new WaitForSeconds(0.05f); } // Set dashing to false, stop the particles and disable the dashing animation isDashing = false; dashParticleSystem.Stop(); myAnimator.SetBool("isDash", false); } // Coroutine for stamina regen IEnumerator StaminaRegen() { // Run when the program is running while (true) { // If action has been performed and the regen delay is over slowly regen stamina if (!isStaminaRegenDelay) { if (playerStamina < 100) { playerStamina++; } yield return new WaitForSeconds(staminaRegenSpeed); } yield return new WaitForSeconds(0.01f); } } // Coroutine for if drinking potion IEnumerator PotionDrink() { // Slowly regenerate health for (int i=0; i < 100; i++) { if (playerHealth < 100) { playerHealth++; } yield return new WaitForSeconds(0.015f); } } // Coroutine to wait for death animation before resetting level IEnumerator WaitForAnimation() { // Setting bar and 'You Died' text opacity to 0.5 and slowly fading in float deathOpacity = 0.5f; for (int i=0; i<100; i++) { deathBarImage.color = new Color(0f, 0f, 0f, deathOpacity); deathTextImage.color = new Color(255f, 255f, 255f, deathOpacity); deathOpacity += 0.005f; yield return new WaitForSeconds(0.035f); } // And then fading out for (int i = 0; i < 100; i++) { deathBarImage.color = new Color(0f, 0f, 0f, deathOpacity); deathTextImage.color = new Color(255f, 255f, 255f, deathOpacity); deathOpacity -= 0.005f; yield return new WaitForSeconds(0.035f); } // The process takes 7 seconds before resetting level SceneManager.LoadScene("GameScene"); } // A small wait each time the player teleports IEnumerator WaitForTeleport() { // Hide character and lock player movement objectRenderer.enabled = false; myRigidbody.bodyType = RigidbodyType2D.Static; yield return new WaitForSeconds(teleportTime); // Show character and allow player movement myRigidbody.bodyType = RigidbodyType2D.Dynamic; objectRenderer.enabled = true; } // Tint player back to normal after being damaged IEnumerator DamageTint() { yield return new WaitForSeconds(damageTime); objectRenderer.material.color = Color.white; } // When player has been hit IEnumerator DamageKnockback() { // Disable movement and make the player temporarily invicible moveSpeed -= moveSpeed; IFrames = true; // Detect where the player got hit from and apply suitable knockback, the knockback will stop if the player hits a wall or a prop if (Vector2.Dot(collisionNormal, Vector2.left) > 1 - collisionTolerance) { for (int i = 0; i < 15; i++) { if (!hitWall && !hitProp) { transform.position += new Vector3(-knockbackStrength, 0, 0); } yield return new WaitForSeconds(0.01f); } } // Detect where the player got hit from and apply suitable knockback, the knockback will stop if the player hits a wall or a prop if (Vector2.Dot(collisionNormal, Vector2.right) > 1 - collisionTolerance) { for (int i = 0; i < 15; i++) { if (!hitWall && !hitProp) { transform.position += new Vector3(knockbackStrength, 0, 0); } yield return new WaitForSeconds(0.01f); } } // Detect where the player got hit from and apply suitable knockback, the knockback will stop if the player hits a wall or a prop if (Vector2.Dot(collisionNormal, Vector2.up) > 1 - collisionTolerance) { for (int i = 0; i < 15; i++) { if (!hitWall && !hitProp) { transform.position += new Vector3(0, knockbackStrength, 0); } yield return new WaitForSeconds(0.01f); } } // Detect where the player got hit from and apply suitable knockback, the knockback will stop if the player hits a wall or a prop if (Vector2.Dot(collisionNormal, Vector2.down) > 1 - collisionTolerance) { for (int i = 0; i < 15; i++) { if (!hitWall && !hitProp) { transform.position += new Vector3(0, -knockbackStrength, 0); } yield return new WaitForSeconds(0.01f); } } // Allow player to move again and disable invincibility moveSpeed = const_moveSpeed; IFrames = false; } // After player has pressed the attack button IEnumerator SwingWeapon() { // Weapon is shown and swinging is true isSwinging = true; weapon.SetActive(true); // If the attack is a strong attack if (isStrongAttack) { // Start stamina regen delay, subtract stamina, allow the player to move in the attack and swing the weapon 2 full rotations StartCoroutine(ActionTimer()); playerStamina -= strongAttackStamina; strongAttackMovement = true; cooldownSlider.maxValue = strongWeaponCooldown; moveSpeed -= speedInStrongAttack; for (int i = 0; i < 144; i++) { weapon.transform.Rotate(0f, 0f, -5f); yield return new WaitForSeconds(0.005f); } moveSpeed += speedInStrongAttack; } // If attack is normal attack if (!isStrongAttack) { // Start stamina regen delay, subtract stamina, lock player movement, and rotate weapon for a half swing StartCoroutine(ActionTimer()); playerStamina -= attackStamina; cooldownSlider.maxValue = weaponCooldown; moveSpeed -= moveSpeed; for (int i = 0; i < 72; i++) { weapon.transform.Rotate(0f, 0f, -2.5f); yield return new WaitForSeconds(0.005f); } moveSpeed = const_moveSpeed; weapon.transform.Rotate(0f, 0f, 180f); } // Hide weapon and reset variables weapon.SetActive(false); strongAttackMovement = false; trueTeleport = true; // Set the according cooldown if (isStrongAttack) { yield return new WaitForSeconds(strongWeaponCooldown); } // Set the according cooldown if (!isStrongAttack) { yield return new WaitForSeconds(weaponCooldown); } // Reset variables after the cooldown has finished isStrongAttack = false; isSwinging = false; } // Stamina regen delay, the stamina will freeze after an attack and then start regenerating (balancing purposes) IEnumerator ActionTimer() { isStaminaRegenDelay = true; yield return new WaitForSeconds(staminaRegenDelay); isStaminaRegenDelay = false; } // Weapon cooldown timer coroutine IEnumerator SliderTimer() { // Reset slider timer and increment it until it reaches the specific cooldown time (ect. if weapon cooldown is 2secs it increments from 0 to 200) sliderTimer = 0; if (isStrongAttack) { for (int i = 0; i < strongWeaponCooldown * 100; i++) { sliderTimer += 0.01f; yield return new WaitForSeconds(0.01f); } } if (!isStrongAttack) { for (int i = 0; i < weaponCooldown * 100; i++) { sliderTimer += 0.01f; yield return new WaitForSeconds(0.01f); } } } // Player collides with anything void OnCollisionEnter2D(Collision2D col) { // If player collides with enemy when their not dashing and not invicible if (col.gameObject.tag == "Enemy" && !IFrames && !isDashing) { // Get side collision was on ContactPoint2D contact = col.contacts[0]; collisionNormal = contact.normal; // Emit blood particles play hit sfx, subtract from player health tint player and if not touching wall or prop apply knock back bloodParticleSystem.Emit(50); audioSource.PlayOneShot(damageFX[Random.Range(0, 1)]); playerHealth -= enemyDamageMult; objectRenderer.material.color = damageColor; StartCoroutine(DamageTint()); if (!hitWall && !hitProp) { StartCoroutine(DamageKnockback()); } } // If colliding with wall set hitWall true if (col.gameObject.tag == "Wall") { hitWall = true; } // If colliding with prop set hitProp true if (col.gameObject.tag == "Prop") { hitProp = true; } } // Player enters a trigger void OnTriggerEnter2D(Collider2D col) { // If player enters potion trigger increase potion amount if (col.gameObject.tag == "Potion") { potionCount++; } // If player enters cauldron trigger increase cauldron amount if (col.gameObject.tag == "Cauldron") { cauldronCount++; } // If player enters key1 trigger mark key1 as collected if (col.gameObject.tag == "Key1") { key1Collected = true; } // If player enters key2 trigger mark key2 as collected if (col.gameObject.tag == "Key2") { key2Collected = true; } // If player enters a location set the location UI to the name of the location if (col.gameObject.tag == "Location") { currentLocation = col.gameObject.name; } // If player enters the void and isnt dashing kill them if (col.gameObject.tag == "Void" && !isDashing) { playerHealth -= 100; } // If the player enters a certain part of hidden room spawn 15 skeletons behind them if (col.gameObject.tag == "TrapHiddenRoom") { for (int i = 0; i < 15; i++) { Instantiate(enemyObject, new Vector3(transform.position.x, transform.position.y + 5f), Quaternion.identity); } // Destroy trigger to prevent duplicates Destroy(col.gameObject); } // If the player enters a certain part of Abandoned Room spawn 15 skeletons behind them if (col.gameObject.tag == "TrapAbandonRoom") { for (int i = 0; i < 15; i++) { Instantiate(enemyObject, new Vector3(transform.position.x - 5f, transform.position.y), Quaternion.identity); } // Destroy trigger to prevent duplicates Destroy(col.gameObject); } // If exiting the dungeon after all 4 cauldrons are collected change back to menu if (col.gameObject.tag == "WinCondition" && hasWon && trueTeleport) { SceneManager.LoadScene("MenuScene"); } // If entering hidden room enter trigger, teleport player into hidden room and start teleport delay if (col.gameObject.tag == "HiddenRoomEnter" && trueTeleport) { transform.position = new Vector3(5.48f + teleportOffsetX, -49.33f - teleportOffsetY); StartCoroutine(WaitForTeleport()); } // If entering storage room enter trigger, teleport player into storage room and start teleport delay if (col.gameObject.tag == "StorageRoomEnter" && key2Collected && trueTeleport) { transform.position = new Vector3(-33.52f + teleportOffsetX, -25.56f + teleportOffsetY); StartCoroutine(WaitForTeleport()); } // If entering storage room leave trigger, teleport player back and start teleport delay if (col.gameObject.tag == "StorageRoomLeave" && key2Collected && trueTeleport) { transform.position = new Vector3(-33.52f + teleportOffsetX, -30.53f - teleportOffsetY); StartCoroutine(WaitForTeleport()); } // If entering sewer room enter trigger, teleport player into sewer room and start teleport delay if (col.gameObject.tag == "SewerRoomEnter" && key1Collected && trueTeleport) { transform.position = new Vector3(57.47f + teleportOffsetX, 1.44f + teleportOffsetY); StartCoroutine(WaitForTeleport()); } // If entering sewer room leave trigger, teleport player back and start teleport delay if (col.gameObject.tag == "SewerRoomLeave" && key1Collected && trueTeleport) { transform.position = new Vector3(57.47f + teleportOffsetX, -3.53f - teleportOffsetY); StartCoroutine(WaitForTeleport()); } // If entering abandoned room enter trigger, teleport player into abandoned room and start teleport delay if (col.gameObject.tag == "AbandonedRoomEnter" && trueTeleport) { transform.position = new Vector3(73.52f + teleportOffsetX + 2.5f, 5.46f); StartCoroutine(WaitForTeleport()); } } // Player stops colliding with anything void OnCollisionExit2D(Collision2D col) { // If stop colliding with wall set hitWall false if (col.gameObject.tag == "Wall") { hitWall = false; } // If stop colliding with prop set hitProp false if (col.gameObject.tag == "Prop") { hitProp = false; } } // Running method void Run() { // If player is not dashing push raw input into a vector2 and apply it to the players ridgidbody if (!isDashing) { Vector2 playerVelocity = new Vector2(rawInput.x * moveSpeed, rawInput.y * moveSpeed); myRigidbody.velocity = playerVelocity; } } // Death method void Death() { // If player is not already dead and has 0 or less health if (!isDead && playerHealth <= 0) { // Set dead true, disable collider, disable player input, enable you died bar and text, play deathfx and play death animation then start death coroutine isDead = true; myCollider.enabled = false; myRigidbody.bodyType = RigidbodyType2D.Static; deathBar.SetActive(true); deathText.SetActive(true); audioSource.PlayOneShot(deathFX); myAnimator.SetTrigger("isDead"); StartCoroutine(WaitForAnimation()); } } // Win Condition method void WinCondition() { // If player has all cauldrons, open the door at the start and set hasWon true if (cauldronCount >= 4) { winConditionTilemap.SetActive(true); hasWon = true; } } // Flip sprite method void FlipSprite() { // Get if player has horizontal or vertical speed bool playerHasHorizontalSpeed = Mathf.Abs(myRigidbody.velocity.x) > Mathf.Epsilon; bool playerHasVerticalSpeed = Mathf.Abs(myRigidbody.velocity.y) > Mathf.Epsilon; // Set running animation to false myAnimator.SetBool("isRunning", false); // If player has horizontal speed and is not in strong attack flip sprite if needed if (playerHasHorizontalSpeed && !strongAttackMovement) { transform.localScale = new Vector2(Mathf.Sign(myRigidbody.velocity.x), 1f); facing = Mathf.Sign(myRigidbody.velocity.x); } // If player is moving play footstep particles, set isRunning true and set the running animation to true if (playerHasHorizontalSpeed || playerHasVerticalSpeed) { if (!footstepParticleSystem.isPlaying) { footstepParticleSystem.Play(); } isRunning = true; if (moveSpeed > 0) { myAnimator.SetBool("isRunning", true); } } // If player is not moving stop footstep particles and set isRunning to false if (!playerHasHorizontalSpeed && !playerHasVerticalSpeed) { if (footstepParticleSystem.isPlaying) { footstepParticleSystem.Stop(); } isRunning = false; } } // Coroutine for footstep sfx IEnumerator FootstepFunc() { // Run as long as game is running while (true) { // If running play footstep sfx if (isRunning) { audioSource.PlayOneShot(footstepFX[Random.Range(0, 2)]); } yield return new WaitForSeconds(0.504f); } } }