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
- This topic has 8 replies, 2 voices, and was last updated 1 year, 1 month ago by Terence.
-
AuthorPosts
-
November 21, 2023 at 3:55 am #12249::
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--; } } }
November 21, 2023 at 7:29 am #12251November 22, 2023 at 2:09 am #12270::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?
November 22, 2023 at 2:24 pm #12274::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.
November 22, 2023 at 2:51 pm #12275::The only debug log that plays is “CastSpell() called” but that plays infinitely when i start the game none of the otheres work??
November 22, 2023 at 3:01 pm #12276November 22, 2023 at 3:20 pm #12277::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 thantimeBetweenCast
, orMana
is never greater thanmanaSpellCost
. That is why your spell never fires.November 23, 2023 at 1:39 am #12278::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?
November 23, 2023 at 11:25 am #12280 -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: