Forum begins after the advertisement:


[part 4] my up spell cast explosion doesnt work

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [part 4] my up spell cast explosion doesnt work

Viewing 9 posts - 1 through 9 (of 9 total)
  • Author
    Posts
  • #12249
    Niyam Shah
    Level 8
    Participant
    Helpful?
    Up
    0
    ::

    For my game i only need the up spell cast- no errors in my code and i’ve assigned the spell cast damage, time between attack and assigned the animation prefab of my explosion to my character in the inspector? maybe something minor in my code??

    using System.Collections;
    using System.Collections.Generic;
    using Unity.VisualScripting;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class PlayerController : MonoBehaviour
    {
        [Header("Horizontal Movement Settings")]
        [SerializeField] private float walkSpeed = 1;
        [Space(5)]
    
        [Header("Dash Settings")]
        [SerializeField] private float dashSpeed;
        [SerializeField] private float dashTime;
        [SerializeField] private float dashCooldown;
        [SerializeField] GameObject dashEffect;
        private bool canDash = true;
        private bool dashed;
        [Space(5)]
    
        [Header("Vertical Movement Settings")]
        [SerializeField] private float jumpForce = 45;
        private int jumpBufferCounter = 0;
        [SerializeField] private int jumpBufferFrames;
        private float coyoteTimeCounter = 0;
        [SerializeField] private float coyoteTime;
        [SerializeField] private float airJumpsCounter = 0;
        [SerializeField] private int maxAirJumps;
        [Space(5)]
    
        [Header("recoil")]
        [SerializeField] int recoilXSteps = 5;
        [SerializeField] int recoilYSteps = 5;
        [SerializeField] float recoilXSpeed = 100; 
        [SerializeField] float recoilYSpeed = 100;
        private int stepsXRecoiled, stepsYRecoiled;
        [Space(5)]
    
        [Header("Attack Settings")]
        private bool attack = false;
        private float timeBetweenAttack, timeSinceAttack;
        [SerializeField] Transform SideAttackTransform, BehindAttackTransform, UpAttackTransform, DownAttackTransform;
        [SerializeField] Vector2 SideAttackArea, BehindAttackArea, UpAttackArea, DownAttackArea;
        [SerializeField] LayerMask attackableLayer;
        [SerializeField] float damage;
        [SerializeField] GameObject slashEffect;
        bool restoreTime;
        float restoreTimeSpeed;
        [Space(5)]
    
        [Header("Ground Check Settings")]
        [SerializeField] private Transform groundCheckPoint;
        [SerializeField] private float groundCheckY = 0.2f;
        [SerializeField] private float groundCheckX = 0.5f;
        [SerializeField] private LayerMask whatIsGround;
        [Space(5)]
    
        [Header("Health")]
        public int health;
        public int maxHealth;
        [SerializeField] GameObject bloodSpurt;
        [SerializeField] float hitFlashSpeed;
        public delegate void OnHealthChangedDelegate(); // delegate voids can be used on multiple methods
        [HideInInspector] public OnHealthChangedDelegate onHealthChangedCallBack;
        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] GameObject upSpellExplosion;
        float timeSinceCast;
        [SerializeField ]float spellDamage;
    
        [HideInInspector] public playerStatesList pState;
    
        private float xAxis, yAxis;
        private Rigidbody2D rb;
        private float gravity;
        private Animator anim;
        private SpriteRenderer sr;
    
    
    
    
    
        public static PlayerController Instance;
    
        private void Awake()
        {
            if(Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
    
            Health = maxHealth;
        }
    
    
    
        // Start is called before the first frame update
        void Start()
        {
            pState = GetComponent<playerStatesList>();
    
            rb = GetComponent<Rigidbody2D>();
    
            anim = GetComponent<Animator>();
    
            sr = GetComponent<SpriteRenderer>();
    
            gravity = rb.gravityScale;
    
            Mana = mana;
    
            manaStorage.fillAmount = Mana;
    
        }
    
    
    
        //draw out hitboxes for basic attacks
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
            Gizmos.DrawWireCube(BehindAttackTransform.position, BehindAttackArea);
            Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
            Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
        }
    
    
    
        // Update is called once per frame
        void Update()
        {
            GetInputs();
            updateJumpVariable();
    
            if (pState.dashing) return;
            Flip();
            Move();
            Jump();
            StartDash();
            Attack();
            RestoreTimeScale();
            FlashWhileInvincible();
            Heal();
            CastSpell();
        }
    
        private void OnTriggerEnter2D(Collider2D _other) //for up and down cast spell
        {
            if (_other.GetComponent<Enemy>() != null && pState.casting)
            {
                _other.GetComponent<Enemy>().EnemyHit(spellDamage, (_other.transform.position - transform.position).normalized, -recoilYSpeed);
            }
        }
        private void FixedUpdate()
        {
            if(pState.dashing) return;
            Recoil();
        }
    
        //basic inputs for movement
        void GetInputs()
        {
            xAxis = Input.GetAxisRaw("Horizontal");
            yAxis = Input.GetAxisRaw("Vertical");
            attack = Input.GetButtonDown("Attack");
        }
    
    
    
        // flip player on x axis when left or right is picked
        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;
            }
    
    
        }
    
    
    
        //move left and right
        private void Move()
        {
            rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y);
            anim.SetBool("walking", rb.velocity.x != 0 && Grounded());
        }
    
    
    
        //allows for dashing and air dashing
        void StartDash()
        {
            if(Input.GetButtonDown("Dash") && canDash && !dashed)
            {
                StartCoroutine(Dash());
                dashed = true;
            }
    
            if (Grounded())
            {
                dashed = false;
            }
        }
        //stops middair infinite dashing
        IEnumerator Dash()
        {
            canDash = false;
    
            pState.dashing = true;
    
            anim.SetTrigger("Dashing");
    
            rb.gravityScale = 0;
    
            rb.velocity = new Vector2(transform.localScale.x * 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;
        }
    
    
    
        //create hitboxes for attacks
        void Attack()
        {
            timeSinceAttack += Time.deltaTime;
            if(attack && timeSinceAttack >= timeBetweenAttack)
            {
    
                timeSinceAttack = 0;
    
    
                //side attack
                if (yAxis == 0 || yAxis < 0 && Grounded())
                {
                    Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingx, recoilXSpeed);
                    Instantiate(slashEffect, SideAttackTransform);
                    anim.SetTrigger("Attacking");
    
                }
    
                //up air attack
                else if (yAxis > 0 && !Grounded())
                {
                    Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingy, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, 80, UpAttackTransform);
                    anim.SetTrigger("Attacking");
                }
    
                //down air attak
                else if (yAxis < 0 && !Grounded())
                {
                    Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingy, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, -80, DownAttackTransform);
                    anim.SetTrigger("Attacking");
                }
    
                if (yAxis > 0 && Grounded())
                {
                    Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingy, recoilYSpeed);
                    SlashEffectAtAngle(slashEffect, 95, UpAttackTransform);
                    anim.SetTrigger("upAttack");
                }
            }
    
        }
    
    
    
        //hitting the enemy and adding recoil to attacks
        void Hit(Transform _attackTransform, Vector2 _attackArea, ref bool _recoilDir, float _recoilStrength)
        {
            Collider2D[] objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, attackableLayer);
    
            if(objectsToHit.Length > 0)
            {
                _recoilDir = true;  
            }
    
            for(int i = 0; i < objectsToHit.Length; i++)
            {
                if (objectsToHit[i].GetComponent<Enemy>() != null)
                {
                    objectsToHit[i].GetComponent<Enemy>().EnemyHit
                        (damage, (transform.position - objectsToHit[i].transform.position).normalized, _recoilStrength);
    
                    if (objectsToHit[i].CompareTag("Enemy"))
                    {
                        Mana += manaGain;
                    }
                }
            }
        }
    
    
    
        //changes the direction of attack based on aerial positions
        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);
        }
    
    
    
        //directional player recoil based on main slash attacks, x and y axis movement
        void Recoil()
        {
            //recoiling in the x axis
            if (pState.recoilingx)
            {
                if (pState.lookingRight)
                {
                    rb.velocity = new Vector2(-recoilXSpeed, 0);
                }
                else
                {
                    rb.velocity = new Vector2(recoilXSpeed, 0);
                }
            }
    
            //recoil in the y axis
            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);
                }
                airJumpsCounter = 0;
            }
            else
            {
                rb.gravityScale = gravity;
            }
    
            //stop recoiling
    
            if (pState.recoilingx && stepsXRecoiled < recoilXSteps)
            {
                stepsXRecoiled++;
            }
    
            else
            {
                StopRecoilX();
            }
    
            if (pState.recoilingy && stepsYRecoiled < recoilYSteps)
            {
                stepsYRecoiled++;
            }
    
            else
            {
                StopRecoilY();
            }
    
            if (Grounded())
            {
                StopRecoilY();
            }
        }
    
    
        //stop recoiling forever
        void StopRecoilX()
        {
            stepsXRecoiled = 0;
            pState.recoilingx = false;
        }
        void StopRecoilY()
        {
            stepsYRecoiled = 0;
            pState.recoilingy = false;
        }
    
    
    
        //take damage, set health and gives some invinicibility frames
        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;
        }
    
        void FlashWhileInvincible()
        {
            sr.material.color = pState.invincible ?
                Color.Lerp(Color.white, Color.black, Mathf.PingPong(Time.time * hitFlashSpeed, 1.0f)) : Color.white;
        }
    
        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;
            Time.timeScale = _newTimeScale;
    
            if (_delay > 0)
            {
                StopCoroutine(StartTimeAgain(_delay));
                StartCoroutine(StartTimeAgain(_delay));
            }
            else
            {
                restoreTime = true;
            }
        }
    
        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("Healing") && 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.GetButtonDown("CastSpell") && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
            {
                pState.casting = true;
                timeSinceCast = 0;
                StartCoroutine(CastCoroutine());
            }
            else
            {
                timeSinceCast += Time.deltaTime;
            }
        }
        IEnumerator CastCoroutine()
        {
            anim.SetBool("Casting", true);
            yield return new WaitForSeconds(0.35f);
    
            //up cast
            if (yAxis > 0)
            {
                Instantiate(upSpellExplosion, transform);
                rb.velocity = Vector2.zero;
            }
    
            Mana -= manaSpellCost;
            yield return new WaitForSeconds(0.35f);
            anim.SetBool("Casting", false);
            pState.casting = false;
        }
    
    
        //checks if you are grounded
        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;   
            }
        }
    
    
    
        //makes you jump when pressing spacebar
        void Jump()
        {
            if(Input.GetButtonUp("Jump") && rb.velocity.y > 0)
            {
                rb.velocity = new Vector2(rb.velocity.x, 0);
    
                pState.jumping = false;
            }
    
            if(!pState.jumping)
            {
                if (jumpBufferCounter > 0 && coyoteTimeCounter > 0)
                {
                    rb.velocity = new Vector3(rb.velocity.x, jumpForce);
    
                    pState.jumping = true;
                }
                else if(!Grounded() && airJumpsCounter < maxAirJumps && Input.GetButtonDown("Jump"))
                {
                    pState.jumping = true;
    
                    airJumpsCounter++;
    
                    rb.velocity = new Vector3(rb.velocity.x, jumpForce);
    
                }
            }
    
    
    
            //animate jumping double jump and falling animation
    
            anim.SetBool("jumping", !Grounded() && rb.velocity.y > -20 && airJumpsCounter == 0);
    
            anim.SetBool("doublejump", !Grounded() && airJumpsCounter == 1);
    
    
            anim.SetBool("falling", !Grounded() && rb.velocity.y < -10);
    
        }
    
    
    
        //coyote time and jump buffering
        void updateJumpVariable()
        {
            //coyote time
            if (Grounded())
            {
                pState.jumping = false;
                coyoteTimeCounter = coyoteTime;
                airJumpsCounter = 0;
            }
            else
            {
                coyoteTimeCounter -= Time.deltaTime;
            }
    
    
            //jump buffering
            if (Input.GetButtonDown("Jump"))
            {
                jumpBufferCounter = jumpBufferFrames;
            }
            else
            {
                jumpBufferCounter--;
            }
        }
    }
    #12251
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    Hi niyam, does the casting animation on your character play when you cast the spell?

    #12270
    Niyam Shah
    Level 8
    Participant
    Helpful?
    Up
    0
    ::

    no nothing about the spell works but ive assigned it in the inspector properly?? and my animation tree is correct also i checked the project settings and the name of my button input is exactly correct and no other inputs use the same key?

    #12274
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    Try adding these Debug.Log lines into your code:

    
    void CastSpell()
    {
        Debug.Log("CastSpell() called");
        if (Input.GetButtonDown("CastSpell") && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
        {
            Debug.Log("Conditions met to cast spell.");
            pState.casting = true;
            timeSinceCast = 0;
            StartCoroutine(CastCoroutine());
        }
        else
        {
            timeSinceCast += Time.deltaTime;
        }
    }
    IEnumerator CastCoroutine()
    {
        Debug.Log("Coroutine started");
        anim.SetBool("Casting", true);
        yield return new WaitForSeconds(0.35f);
        //up cast
        if (yAxis > 0)
        {
            Instantiate(upSpellExplosion, transform);
            rb.velocity = Vector2.zero;
        }
    
        Mana -= manaSpellCost;
        yield return new WaitForSeconds(0.35f);
        anim.SetBool("Casting", false);
        pState.casting = false;
    }
    

    If the animation is not firing, it means your coroutine is not firing at all. We need to check whether its because CastSpell() is not being called at all, or its called, but the condition is not being met.

    Let me know what shows up on your console when you add the code and run this.

    If you’re able to take a screenshot that will be great as well.

    #12275
    Niyam Shah
    Level 8
    Participant
    Helpful?
    Up
    0
    ::

    The only debug log that plays is “CastSpell() called” but that plays infinitely when i start the game none of the otheres work??

    #12276
    Niyam Shah
    Level 8
    Participant
    Helpful?
    Up
    0
    ::
    #12277
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    That means that the highlighted condition in yellow is not returning true. Update the debug logs as in the green highlight, then see the console log again.

    void CastSpell()
    {
        Debug.Log("CastSpell() called: " + Input.GetButtonDown("CastSpell"));
        Debug.Log("Time since cast / between cast: " + timeSinceCast + " | " + timeBetweenCast;
        Debug.Log("Mana / mana cost: " + Mana + " | " + manaSpellCost);
        Debug.Log("--------------");
        if (Input.GetButtonDown("CastSpell") && timeSinceCast >= timeBetweenCast && Mana >= manaSpellCost)
        {
            Debug.Log("Conditions met to cast spell.");
            pState.casting = true;
            timeSinceCast = 0;
            StartCoroutine(CastCoroutine());
        }
        else
        {
            timeSinceCast += Time.deltaTime;
        }
    }

    What the prints do is it shows you all the values involved in the condition. When you press the cast spell button, see all the printed values. You will find that either timeSinceCast is never greater than timeBetweenCast, or Mana is never greater than manaSpellCost. That is why your spell never fires.

    #12278
    Niyam Shah
    Level 8
    Participant
    Helpful?
    Up
    0
    ::
    View post on imgur.com

    when i click the cast spell button, it doesnt change to true??? but all parameters are proper so im very confused?

    #12280
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    Did you set the key bindings for the "CastSpell" action in the Input Manager?

Viewing 9 posts - 1 through 9 (of 9 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: