Forum begins after the advertisement:
[Part 4] My down spell is not dealing damage
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 4] My down spell is not dealing damage
- This topic has 4 replies, 3 voices, and was last updated 2 weeks, 1 day ago by
Ser Apustaja.
-
AuthorPosts
-
November 25, 2025 at 1:16 am #19059::
All of my other spells are working as expected, but my down spell won’t deal damage. Here is my player controller script
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Player_Controller : MonoBehaviour { [Header("Horizontal Movement Settings")] [SerializeField] private float WalkSpeed = 1; [Space(5)] [Header("Vertical Movement Settings")] [SerializeField] private float JumpForce = 45; private float Jump_Buffer_Counter; [SerializeField] private float Jump_Buffer_Frames = 0; private float Coyote_Time_Counter; [SerializeField] private float Coyote_Time; private int Air_Jump_Counter = 0; [SerializeField] private int Max_Air_Jumps; [Space(5)] [Header("Ground Check Settings")] [SerializeField] Transform GroundCheckPoint; [SerializeField] private float GroundCheckY = 0.2f; [SerializeField] private float GroundCheckX = 0.5f; [SerializeField] private LayerMask WhatIsGround; [Space(5)] [Header("Dash Settings")] [SerializeField] private float Dash_Speed; [SerializeField] private float Dash_Time; [SerializeField] private float Dash_Cooldown; private bool Can_Dash = true; private bool Dashed; [SerializeField] GameObject Dash_Effect; [Space(5)] [Header("Attack Settings")] [SerializeField] float Time_Between_Attack; float Attack_Time_Counter; [SerializeField] private Transform Side_Attack_Transform; [SerializeField] private Transform Up_Attack_Transform; [SerializeField] private Transform Down_Attack_Transform; [SerializeField] private Vector2 Side_Attack_Area; [SerializeField] private Vector2 Up_Attack_Area; [SerializeField] private Vector2 Down_Attack_Area; [SerializeField] private float Up_Attack_Parameter; [SerializeField] private float Down_Attack_Parameter; [SerializeField] private LayerMask Attackable_Layer; [SerializeField] private float Player_Damage; [SerializeField] GameObject Slash_Effect_Object; bool Attack_Bool = false; bool Restore_Time; float Restore_Time_Speed; [Space(5)] [Header("Recoil Settings")] [SerializeField] int Recoil_X_Steps = 5; [SerializeField] int Recoil_Y_Steps = 5; [SerializeField] int Recoil_X_Speed = 100; [SerializeField] int Recoil_Y_Speed = 100; int Steps_X_Recoiled; int Steps_Y_Recoiled; [Header("Health Settings")] public int Player_Health; public int Player_Max_Health; [SerializeField] GameObject Blood_Spurt; [SerializeField] float Hit_Flash_Speed; public delegate void On_Health_Changed_Delegate(); [HideInInspector] public On_Health_Changed_Delegate On_Health_Changed_Callback; float Heal_Timer; [SerializeField] float Time_To_Heal; [Space(5)] [Header("Mana Settings")] [SerializeField] UnityEngine.UI.Image Mana_Storage; [SerializeField] float Mana; [SerializeField] float Mana_Drain_Speed; [SerializeField] float Mana_Gain; [Space(5)] [Header("Spell Casting")] [SerializeField] float Mana_Spell_Cost = 0.3f; [SerializeField] float Time_Between_Cast = 0.5f; [SerializeField] float Spell_Damage; [SerializeField] float Down_Spell_Force; [SerializeField] GameObject Side_Spell; [SerializeField] GameObject Up_Spell; [SerializeField] GameObject Down_Spell; float Cast_Time_Counter; float Cast_Or_Heal_Timer; [Space(5)] private Vector3 Mouse_Position; [HideInInspector] public Player_State_List Player_State; private Rigidbody2D Rigidbody; private SpriteRenderer Sprite_Renderer; private float Gravity; private float XAxis; Animator Animator; public static Player_Controller Instance; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } Player_Health = Player_Max_Health; } // Start is called before the first frame update void Start() { Player_State = GetComponent<Player_State_List>(); Rigidbody = GetComponent<Rigidbody2D>(); Sprite_Renderer = GetComponent<SpriteRenderer>(); Animator = GetComponent<Animator>(); Gravity = Rigidbody.gravityScale; Player_Mana = Mana; Mana_Storage.fillAmount = Mana; } private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireCube(Side_Attack_Transform.position, Side_Attack_Area); Gizmos.DrawWireCube(Up_Attack_Transform.position, Up_Attack_Area); Gizmos.DrawWireCube(Down_Attack_Transform.position, Down_Attack_Area); } // Update is called once per frame void Update() { GetInputs(); UpdateJumpVariables(); if (Player_State.Dashing) { return; } Restore_Time_Scale(); Flash_While_Invincible(); Move(); Heal(); Cast_Spell(); if (Player_State.Healing) { return; } Flip(); Jump(); Attack(); Start_Dash(); } private void OnTriggerEnter2D(Collider2D Other) { if(Other.GetComponent<Enemy>() != null && Player_State.Casting) { Other.GetComponent<Enemy>().Enemy_Hit(Spell_Damage, (Other.transform.position - transform.position).normalized, -Recoil_Y_Speed); } } private void FixedUpdate() { if (Player_State.Dashing) { return; } Recoil(); } void GetInputs() { XAxis = Input.GetAxisRaw("Horizontal"); Attack_Bool = Input.GetButtonDown("Attack"); Mouse_Position = Camera.main.ScreenToWorldPoint(Input.mousePosition); Mouse_Position.x -= gameObject.transform.position.x; Mouse_Position.y -= gameObject.transform.position.y; Mouse_Position.z = 0f; if (Input.GetButton("Cast/Heal")) { Cast_Or_Heal_Timer += Time.deltaTime; } else { Cast_Or_Heal_Timer = 0; } } void Flip() { if(Attack_Time_Counter < 0.5f) { return; } if(XAxis < 0) { transform.localScale = new Vector2(Mathf.Abs(transform.localScale.x), transform.localScale.y); Player_State.Looking_Right = false; } else if(XAxis > 0) { transform.localScale = new Vector2(-Mathf.Abs(transform.localScale.x), transform.localScale.y); Player_State.Looking_Right = true; } } private void Move() { if(Player_State.Healing) { Rigidbody.velocity = new Vector2(0,0); } Rigidbody.velocity = new Vector2(WalkSpeed * XAxis, Rigidbody.velocity.y); Animator.SetBool("Walking", Rigidbody.velocity.x != 0 && Grounded()); } void Start_Dash() { if(Input.GetButtonDown("Dash") && Can_Dash && !Dashed) { StartCoroutine(Dash()); Dashed = true; } if(Grounded()) { Dashed = false; } } IEnumerator Dash() { Player_State.Dashing = true; Player_State.Invincible = true; Can_Dash = false; Animator.SetTrigger("Dashing"); Rigidbody.gravityScale = 0; Rigidbody.velocity = new Vector2(-transform.localScale.x * Dash_Speed, 0); if(Grounded()) { Instantiate(Dash_Effect, transform); } yield return new WaitForSeconds(Dash_Time); Rigidbody.gravityScale = Gravity; Player_State.Dashing = false; Player_State.Invincible = false; yield return new WaitForSeconds(Dash_Cooldown); Can_Dash = true; } void Attack() { Attack_Time_Counter += Time.deltaTime; if(Attack_Bool && Attack_Time_Counter >= Time_Between_Attack) { Attack_Time_Counter = 0; Animator.SetTrigger("Attacking"); if(Mouse_Position.y < Up_Attack_Parameter && Mouse_Position.y > Down_Attack_Parameter || Mouse_Position.y <= Down_Attack_Parameter && Grounded()) { Hit(Side_Attack_Transform, Side_Attack_Area, ref Player_State.Recoiling_X, Recoil_X_Speed); Slash_Effect_Angle(Slash_Effect_Object, 0, Side_Attack_Transform); } else if(Mouse_Position.y > Up_Attack_Parameter) { Hit(Up_Attack_Transform, Up_Attack_Area, ref Player_State.Recoiling_Y, Recoil_Y_Speed); Slash_Effect_Angle(Slash_Effect_Object, 80, Up_Attack_Transform); } else if(Mouse_Position.y < Down_Attack_Parameter && !Grounded()) { Hit(Down_Attack_Transform, Down_Attack_Area, ref Player_State.Recoiling_Y, Recoil_Y_Speed); Slash_Effect_Angle(Slash_Effect_Object, -90, Down_Attack_Transform); } } } void Hit(Transform Attack_Transform, Vector2 Attack_Area, ref bool Recoil_Directory, float Recoil_Strength) { Collider2D[] Objects_To_Hit = Physics2D.OverlapBoxAll(Attack_Transform.position, Attack_Area, 0, Attackable_Layer); List<Enemy> Hit_Enemies = new List<Enemy>(); if(Objects_To_Hit.Length > 0) { Recoil_Directory = true; } for(int i = 0; i < Objects_To_Hit.Length; i++) { Enemy e = Objects_To_Hit[i].GetComponent<Enemy>(); if(e && !Hit_Enemies.Contains(e)) { e.Enemy_Hit(Player_Damage, (transform.position - Objects_To_Hit[i].transform.position).normalized, Recoil_Strength); Hit_Enemies.Add(e); if(Objects_To_Hit[i].CompareTag("Enemy")) { Player_Mana += Mana_Gain; } } } } void Slash_Effect_Angle(GameObject Slash_Effect, int Effect_Angle, Transform Attack_Transform) { Slash_Effect = Instantiate(Slash_Effect, Attack_Transform); Slash_Effect.transform.eulerAngles = new Vector3(0, 0, Effect_Angle); Slash_Effect.transform.localScale = new Vector2(transform.localScale.x, transform.localScale.y); } void Recoil() { if(Player_State.Recoiling_X) { if(Player_State.Looking_Right) { Rigidbody.velocity = new Vector2(-Recoil_X_Speed, 0); } else if(!Player_State.Looking_Right) Rigidbody.velocity = new Vector2(Recoil_X_Speed, 0); } if(Player_State.Recoiling_Y) { if(Mouse_Position.y < Down_Attack_Parameter) { Rigidbody.gravityScale = 0; Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, Recoil_Y_Speed); } else if(Mouse_Position.y > Up_Attack_Parameter) { Rigidbody.gravityScale = 0; Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, -Recoil_Y_Speed); } Air_Jump_Counter = 0; } else { Rigidbody.gravityScale = Gravity; } if(Player_State.Recoiling_X && Steps_X_Recoiled < Recoil_X_Steps) { Steps_X_Recoiled++; } else { Stop_Recoil_X(); } if(Player_State.Recoiling_Y && Steps_Y_Recoiled < Recoil_Y_Steps) { Steps_Y_Recoiled++; } else { Stop_Recoil_Y(); } if(Grounded()) { Stop_Recoil_Y(); } } void Stop_Recoil_X() { Steps_X_Recoiled = 0; Player_State.Recoiling_X = false; } void Stop_Recoil_Y() { Steps_Y_Recoiled = 0; Player_State.Recoiling_Y = false; } public void Take_Damage(float Damage_Taken) { Player_Health_Int -= Mathf.RoundToInt(Damage_Taken); StartCoroutine(Stop_Taking_Damage()); } public IEnumerator Stop_Taking_Damage() { Player_State.Invincible = true; GameObject Blood_Spurt_Particles = Instantiate(Blood_Spurt, transform.position, Quaternion.identity); Destroy(Blood_Spurt_Particles, 1.5f); Animator.SetTrigger("Take Damage"); yield return new WaitForSeconds(1f); Player_State.Invincible = false; } void Flash_While_Invincible() { Sprite_Renderer.color = Player_State.Invincible ? Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * Hit_Flash_Speed, 1.0f)) : Color.white; } void Restore_Time_Scale() { if(Restore_Time) { if (Time.timeScale < 1) { Time.timeScale += Time.deltaTime * Restore_Time_Speed; } else { Time.timeScale = 1; Restore_Time = false; } } } public void Hit_Stop_Time(float New_Time_Scale, int Restore_Speed, float Delay) { Restore_Time_Speed = Restore_Speed; Time.timeScale = New_Time_Scale; if (Delay > 0) { StopCoroutine(Start_Time_Again(Delay)); StartCoroutine(Start_Time_Again(Delay)); } } IEnumerator Start_Time_Again(float Delay) { Restore_Time = true; yield return new WaitForSeconds(Delay); } public int Player_Health_Int { get { return Player_Health; } set { if (Player_Health != value) { Player_Health = Mathf.Clamp(value, 0, Player_Max_Health); if (On_Health_Changed_Callback != null) { On_Health_Changed_Callback.Invoke(); } } } } private void Heal() { if (Input.GetButton("Cast/Heal") && Cast_Or_Heal_Timer > 0.05f && Player_Health_Int < Player_Max_Health && Player_Mana > 0 && Grounded() && !Player_State.Dashing) { Player_State.Healing = true; Heal_Timer += Time.deltaTime; Animator.SetBool("Healing", true); Player_Mana -= Time.deltaTime * Mana_Drain_Speed; if (Heal_Timer >= Time_To_Heal) { Player_Health_Int++; Heal_Timer = 0; } } else { Player_State.Healing = false; Heal_Timer = 0; Animator.SetBool("Healing", false); } } private float Player_Mana { get { return Mana; } set { if (Mana != value) { Mana = Mathf.Clamp(value, 0, 1); Mana_Storage.fillAmount = Mana; } } } void Cast_Spell() { if (Input.GetButtonUp("Cast/Heal") && Cast_Or_Heal_Timer <= 0.05f && Cast_Time_Counter >= Time_Between_Cast && Mana >= Mana_Spell_Cost) { Player_State.Casting = true; Cast_Time_Counter = 0; StartCoroutine(Cast_Coroutine()); } else { Cast_Time_Counter += Time.deltaTime; } if (Grounded()) { Down_Spell.SetActive(false); } if(Down_Spell.activeInHierarchy) { Rigidbody.velocity = Down_Spell_Force * Vector2.down; } } IEnumerator Cast_Coroutine() { Animator.SetBool("Casting", true); Rigidbody.gravityScale = 0; yield return new WaitForSeconds(0.12f); Rigidbody.gravityScale = Gravity; if (Mouse_Position.y < Up_Attack_Parameter && Mouse_Position.y > Down_Attack_Parameter || Mouse_Position.y <= Down_Attack_Parameter && Grounded()) { GameObject Fire_Ball = Instantiate(Side_Spell, Side_Attack_Transform.position, Quaternion.identity); if (Player_State.Looking_Right) { Fire_Ball.transform.eulerAngles = Vector3.zero; } else { Fire_Ball.transform.eulerAngles = new Vector3(Fire_Ball.transform.eulerAngles.x, 180); } Player_State.Recoiling_X = true; } else if (Mouse_Position.y > Up_Attack_Parameter) { Instantiate(Up_Spell, transform); Rigidbody.velocity = Vector2.zero; } else if (Mouse_Position.y < Down_Attack_Parameter && !Grounded()) { Down_Spell.SetActive(true); } Player_Mana -= Mana_Spell_Cost; yield return new WaitForSeconds(0.11f); Animator.SetBool("Casting", false); Player_State.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 (Jump_Buffer_Counter > 0 && Coyote_Time_Counter > 0 && !Player_State.Jumping) { Rigidbody.velocity = new Vector3(Rigidbody.velocity.x, JumpForce); Player_State.Jumping = true; } else if (!Grounded() && Air_Jump_Counter < Max_Air_Jumps && Input.GetButtonDown("Jump")) { Player_State.Jumping = true; Air_Jump_Counter++; Rigidbody.velocity = new Vector3(Rigidbody.velocity.x, JumpForce); } if (Input.GetButtonUp("Jump") && Rigidbody.velocity.y > 3) { Rigidbody.velocity = new Vector2(Rigidbody.velocity.x, 0); Player_State.Jumping = false; } Animator.SetBool("Jumping", Rigidbody.velocity.y > 0 && !Grounded()); Animator.SetBool("Falling", Rigidbody.velocity.y < 0 && !Grounded()); } void UpdateJumpVariables() { if(Grounded()) { Player_State.Jumping = false; Coyote_Time_Counter = Coyote_Time; Air_Jump_Counter = 0; } else { Coyote_Time_Counter -= Time.deltaTime; } if(Input.GetButtonDown("Jump")) { Jump_Buffer_Counter = Jump_Buffer_Frames; } else { Jump_Buffer_Counter -= Time.deltaTime * 10; } } }November 25, 2025 at 6:34 pm #19060::Can you show me a screenshot of the Inspector for your Down Spell Fireball? I want to check if your collider is properly set up on it.
November 27, 2025 at 10:27 pm #19065::Hello Nirvik! It looks like you’re having trouble with your down spell not dealing damage. Let’s take a closer look at your
Cast_SpellandAttackmethods to see if we can identify the issue.Potential Issues
-
Down Spell Activation: In your
Cast_Spellmethod, you activate theDown_SpellGameObject when the mouse position is below theDown_Attack_Parameterand the player is not grounded. However, you don’t seem to have any logic to actually deal damage when the down spell is active. -
Damage Logic: The damage dealing logic for the down spell is not present in the
Attackmethod. You need to ensure that when the down spell is cast, it checks for enemies in the area and applies damage.
Suggested Fixes
-
Modify the
Cast_SpellMethod: Ensure that when the down spell is activated, it also checks for enemies and applies damage. You can do this by calling a method similar toHitafter activating the down spell. -
Add Damage Logic for Down Spell: You can create a new method to handle the damage logic for the down spell. Here’s a simple example of how you might implement this:
void Cast_Spell() { if (Input.GetButtonUp("Cast/Heal") && Cast_Or_Heal_Timer = Time_Between_Cast && Mana >= Mana_Spell_Cost) { Player_State.Casting = true; Cast_Time_Counter = 0; StartCoroutine(Cast_Coroutine()); } else { Cast_Time_Counter += Time.deltaTime; } if (Grounded()) { Down_Spell.SetActive(false); } if (Down_Spell.activeInHierarchy) { Rigidbody.velocity = Down_Spell_Force * Vector2.down; // Check for enemies and deal damage Hit(Down_Attack_Transform, Down_Attack_Area, ref Player_State.Recoiling_Y, Recoil_Y_Speed); } }Additional Considerations
-
Ensure Down Spell is Active: Make sure that the
Down_SpellGameObject is properly set up in the Unity Editor and that it has the necessary components to interact with enemies (like colliders). -
Debugging: You can add debug logs to check if the
Hitmethod is being called when the down spell is active. This will help you confirm whether the logic is being executed as expected. -
Check Enemy Layer: Ensure that the enemies are on the correct layer that matches the
Attackable_Layermask you have defined.
By implementing these changes, you should be able to get your down spell to deal damage as intended. If you have any further questions or need more assistance, feel free to ask! Happy coding!
November 28, 2025 at 12:10 pm #19066::Sorry for the late response. Here is an image of both the down spell fireball and spell casting settings
View post on imgur.com
November 28, 2025 at 12:16 pm #19067::No problem! I can’t view images directly, but I can help you troubleshoot or adjust your settings based on what you describe. Could you provide more details about the issue you’re facing with the down spell fireball and the spell casting settings? What specific problems or errors are you encountering?
November 28, 2025 at 1:59 pm #19068::@nirvikrajbhandari this part of the code is supposed to trigger when the spell effect touches an enemy:
private void OnTriggerEnter2D(Collider2D _other) //for up and down cast spell { print($"Collided with {_other.name}."); if(_other.GetComponent<Enemy>() != null && pState.casting) { print($"{_other.name} is an enemy. Dealing damage to it."); _other.GetComponent<Enemy>().EnemyHit(spellDamage, (_other.transform.position - transform.position).normalized, -recoilYSpeed); } }Can you add the highlighted lines and show me a screenshot of your Console when the down spell hits the enemy?
November 29, 2025 at 9:59 am #19069November 29, 2025 at 10:02 am #19070::No problem at all! I’m glad to hear that it’s working now. It’s easy to overlook saving changes sometimes. If you have any further questions or need assistance with anything else, feel free to ask!
-
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: