Forum begins after the advertisement:
[Part 5] Healing and Cast problems
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 5] Healing and Cast problems
- This topic has 9 replies, 4 voices, and was last updated 1 month, 4 weeks ago by Terence.
-
AuthorPosts
-
September 20, 2024 at 3:32 am #15864::
Hello everyone! I have a problem again, but it’s not so significant anymore, in short, the first is that my enemy doesn’t take damage from spells at all, and the second is that when I press the button to heal, my character freezes with the healing animation and does nothing else. I also want to note that there are no errors either in Unit or in the console with the code itself. Sorry to bother you again, I hope you can help me with something. Thank you in advance.
Video: https://i.imgur.com/jHXB0rM.mp4
Code of my PlayerController:
using System.Collections; using System.Collections.Generic; using System.Net.Sockets; using UnityEngine; using UnityEngine.UIElements; using UnityEngine.UI; public class PlayerController : MonoBehaviour { [Header("Horizontal Movement Settings:")] [SerializeField] private float walkSpeed = 1; //sets the players movement speed on the ground [Space(5)] [Header("Vertical Movement Settings")] [SerializeField] private float jumpForce = 45f; //sets how hight the player can jump private int jumpBufferCounter = 0; //stores the jump button input [SerializeField] private int jumpBufferFrames; //sets the max amount of frames the jump buffer input is stored private float coyoteTimeCounter = 0; //stores the Grounded() bool [SerializeField] private float coyoteTime; ////sets the max amount of frames the Grounded() bool is stored private int airJumpCounter = 0; //keeps track of how many times the player has jumped in the air [SerializeField] private int maxAirJumps; //the max no. of air jumps private float gravity; //stores the gravity scale at start [Space(5)] [Header("Ground Check Settings:")] [SerializeField] private Transform groundCheckPoint; //point at which ground check happens [SerializeField] private float groundCheckY = 0.2f; //how far down from ground chekc point is Grounded() checked [SerializeField] private float groundCheckX = 0.5f; //how far horizontally from ground chekc point to the edge of the player is [SerializeField] private LayerMask whatIsGround; //sets the ground layer [Space(5)] [Header("Dash Settings")] [SerializeField] private float dashSpeed; //speed of the dash [SerializeField] private float dashTime; //amount of time spent dashing [SerializeField] private float dashCooldown; //amount of time between dashes [SerializeField] GameObject dashEffect; private bool canDash = true, dashed; [Space(5)] [Header("Attack Settings:")] [SerializeField] private Transform SideAttackTransform; //the middle of the side attack area [SerializeField] private Vector2 SideAttackArea; //how large the area of side attack is [SerializeField] private Transform UpAttackTransform; //the middle of the up attack area [SerializeField] private Vector2 UpAttackArea; //how large the area of side attack is [SerializeField] private Transform DownAttackTransform; //the middle of the down attack area [SerializeField] private Vector2 DownAttackArea; //how large the area of down attack is [SerializeField] private LayerMask attackableLayer; //the layer the player can attack and recoil off of private float timeBetweenAttack, timeSinceAttck; [SerializeField] private float damage; //the damage the player does to an enemy [SerializeField] private GameObject slashEffect; //the effect of the slashs bool restoreTime; float restoreTimeSpeed; [Space(5)] [Header("Recoil Settings:")] [SerializeField] private int recoilXSteps = 5; //how many FixedUpdates() the player recoils horizontally for [SerializeField] private int recoilYSteps = 5; //how many FixedUpdates() the player recoils vertically for [SerializeField] private float recoilXSpeed = 100; //the speed of horizontal recoil [SerializeField] private float recoilYSpeed = 100; //the speed of vertical recoil private int stepsXRecoiled, stepsYRecoiled; //the no. of steps recoiled horizontally and verticall [Space(5)] [Header("Health Settings")] public int health; public int maxHealth; [SerializeField] GameObject bloodSpurt; [SerializeField] float hitFlashSpeed; public delegate void OnHealthChangedDelegate(); [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")] //spell stats [SerializeField] float manaSpellCost = 0.3f; [SerializeField] float timeBetweenCast = 0.5f; float timeSinceCast; [SerializeField] float spellDamage; //upspellexplosion and downspellfireball [SerializeField] float downSpellForce; // desolate dive only //spell cast objects [SerializeField] GameObject sideSpellFireball; [SerializeField] GameObject upSpellExplosion; [SerializeField] GameObject downSpellFireball; [Space(5)] [HideInInspector] public PlayerStateList pState; private Animator anim; private Rigidbody2D rb; private SpriteRenderer sr; //Input Variables private float xAxis, yAxis; private bool attack = false; public static PlayerController Instance; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } DontDestroyOnLoad(gameObject); } // Start is called before the first frame update void Start() { pState = GetComponent<PlayerStateList>(); rb = GetComponent<Rigidbody2D>(); sr = GetComponent<SpriteRenderer>(); anim = GetComponent<Animator>(); gravity = rb.gravityScale; Mana = mana; manaStorage.fillAmount = Mana; } private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea); Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea); Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea); } // Update is called once per frame void Update() { if (pState.cutscene) return; GetInputs(); UpdateJumpVariables(); if (pState.dashing || pState.healing) 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.cutscene) return; if (pState.dashing) return; Recoil(); } void GetInputs() { xAxis = Input.GetAxisRaw("Horizontal"); yAxis = Input.GetAxisRaw("Vertical"); attack = Input.GetButtonDown("Attack"); } void Flip() { if (xAxis < 0) { transform.localScale = new Vector2(-3, transform.localScale.y); pState.lookingRight = false; } else if (xAxis > 0) { transform.localScale = new Vector2(3, transform.localScale.y); pState.lookingRight = true; } } private void Move() { if (pState.healing) rb.velocity = new Vector2(0, 0); rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y); anim.SetBool("Walking", rb.velocity.x != 0 && Grounded()); } void StartDash() { if (Input.GetButtonDown("Dash") && canDash && !dashed) { StartCoroutine(Dash()); dashed = true; } if (Grounded()) { dashed = false; } } IEnumerator Dash() { canDash = false; pState.dashing = true; anim.SetTrigger("Dashing"); rb.gravityScale = 0; int _dir = pState.lookingRight ? 1 : -1; rb.velocity = new Vector2(_dir * dashSpeed, 0); if (Grounded()) Instantiate(dashEffect, transform); if (!Grounded()) Instantiate(dashEffect, transform); yield return new WaitForSecondsRealtime(dashTime); rb.gravityScale = gravity; pState.dashing = false; yield return new WaitForSecondsRealtime(dashCooldown); canDash = true; } public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay) { if(_exitDir.y > 0) { rb.velocity = jumpForce * _exitDir; } if (_exitDir.x != 0) { xAxis = _exitDir.x > 0 ? 1: -1; Move(); } Flip(); yield return new WaitForSecondsRealtime(_delay); pState.cutscene = false; } void Attack() { timeSinceAttck += Time.deltaTime; if (attack && timeSinceAttck >= timeBetweenAttack) { timeSinceAttck = 0; anim.SetTrigger("Attacking"); if (yAxis == 0 || yAxis < 0 && Grounded()) { Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX, recoilXSpeed); Instantiate(slashEffect, SideAttackTransform); } else if (yAxis > 0) { Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, recoilYSpeed); SlashEffectAtAngle(slashEffect, 80, UpAttackTransform); } else if (yAxis < 0 && !Grounded()) { Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, recoilYSpeed); SlashEffectAtAngle(slashEffect, -90, DownAttackTransform); } } } 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); Mana += manaGain; } } } 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 / 3, transform.localScale.y / 3); } void Recoil() { if (pState.recoilingX) { if (pState.lookingRight) { rb.velocity = new Vector2(-recoilXSpeed, 0); } else { rb.velocity = new Vector2(recoilXSpeed, 0); } } 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); } airJumpCounter = 0; } else { rb.gravityScale = gravity; } //stop recoil if (pState.recoilingX && stepsXRecoiled < recoilXSteps) { stepsXRecoiled++; } else { StopRecoilX(); } if (pState.recoilingY && stepsYRecoiled < recoilYSteps) { stepsYRecoiled++; } else { StopRecoilY(); } if (Grounded()) { StopRecoilY(); } } void StopRecoilX() { stepsXRecoiled = 0; pState.recoilingX = false; } void StopRecoilY() { stepsYRecoiled = 0; pState.recoilingY = false; } 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 WaitForSecondsRealtime(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.unscaledDeltaTime * restoreTimeSpeed; } else { Time.timeScale = 1; restoreTime = false; } } } public void HitStopTime(float _newTimeScale, int _restoreSpeed, float _delay) { restoreTimeSpeed = _restoreSpeed; if (_delay > 0) { StopCoroutine(StartTimeAgain(_delay)); StartCoroutine(StartTimeAgain(_delay)); } else { restoreTime = true; } Time.timeScale = _newTimeScale; } IEnumerator StartTimeAgain(float _delay) { yield return new WaitForSecondsRealtime(_delay); restoreTime = true; } 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 && Grounded() && !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; } if (Grounded()) { //disable downspell if on the ground downSpellFireball.SetActive(false); } //if down spell is active, force player down until grounded if (downSpellFireball.activeInHierarchy) { rb.velocity += downSpellForce * Vector2.down; } } IEnumerator CastCoroutine() { anim.SetBool("Casting", true); yield return new WaitForSecondsRealtime(0.15f); //side cast if (yAxis == 0 || (yAxis < 0 && Grounded())) { GameObject _fireBall = Instantiate(sideSpellFireball, SideAttackTransform.position, Quaternion.identity); //flip fireball if (pState.lookingRight) { _fireBall.transform.eulerAngles = Vector3.zero; // if facing right, fireball continues as per normal } else { _fireBall.transform.eulerAngles = new Vector2(_fireBall.transform.eulerAngles.x, 180); //if not facing right, rotate the fireball 180 deg } pState.recoilingX = true; } //up cast else if (yAxis > 0) { Instantiate(upSpellExplosion, transform); rb.velocity = Vector2.zero; } //down cast else if (yAxis < 0 && !Grounded()) { downSpellFireball.SetActive(true); } Mana -= manaSpellCost; yield return new WaitForSecondsRealtime(0.35f); anim.SetBool("Casting", false); pState.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 (jumpBufferCounter > 0 && coyoteTimeCounter > 0 && !pState.jumping) { rb.velocity = new Vector3(rb.velocity.x, jumpForce); pState.jumping = true; } if (!Grounded() && airJumpCounter < maxAirJumps && Input.GetButtonDown("Jump")) { pState.jumping = true; airJumpCounter++; rb.velocity = new Vector3(rb.velocity.x, jumpForce); } if (Input.GetButtonUp("Jump") && rb.velocity.y > 3) { pState.jumping = false; rb.velocity = new Vector2(rb.velocity.x, 0); } anim.SetBool("Jumping", !Grounded()); } void UpdateJumpVariables() { if (Grounded()) { pState.jumping = false; coyoteTimeCounter = coyoteTime; airJumpCounter = 0; } else { coyoteTimeCounter -= Time.deltaTime; } if (Input.GetButtonDown("Jump")) { jumpBufferCounter = jumpBufferFrames; } else { jumpBufferCounter--; } } }
Code of PlayerStateList:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerStateList : MonoBehaviour { public bool jumping = false; public bool dashing = false; public bool recoilingX, recoilingY; public bool lookingRight; public bool invincible; public bool healing; public bool casting; public bool cutscene = false; }
UIManager:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UIManager : MonoBehaviour { public SceneFader sceneFader; public static UIManager Instance; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } DontDestroyOnLoad(gameObject); sceneFader = GetComponentInChildren<SceneFader>(); } }
HeartController:
using UnityEngine; using UnityEngine.UI; public class HeartController : MonoBehaviour { private GameObject[] heartContainers; private Image[] heartFills; public Transform heartsParent; public GameObject heartContainerPrefab; // Start is called before the first frame update void Start() { heartContainers = new GameObject[PlayerController.Instance.maxHealth]; heartFills = new Image[PlayerController.Instance.maxHealth]; PlayerController.Instance.onHealthChangedCallback += UpdateHeartsHUD; InstantiateHeartContainers(); UpdateHeartsHUD(); } // Update is called once per frame void Update() { } void SetHeartContainers() { for (int i = 0; i < heartContainers.Length; i++) { if (i < PlayerController.Instance.maxHealth) { heartContainers[i].SetActive(true); } else { heartContainers[i].SetActive(false); } } } void SetFilledHearts() { for (int i = 0; i < heartFills.Length; i++) { if (i < PlayerController.Instance.health) { heartFills[i].fillAmount = 1; } else { heartFills[i].fillAmount = 0; } } } void InstantiateHeartContainers() { for (int i = 0; i < PlayerController.Instance.maxHealth; i++) { GameObject temp = Instantiate(heartContainerPrefab); temp.transform.SetParent(heartsParent, false); heartContainers[i] = temp; heartFills[i] = temp.transform.Find("HeartFill").GetComponent<Image>(); } } void UpdateHeartsHUD() { SetHeartContainers(); SetFilledHearts(); } }
FireBall:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FireBall : MonoBehaviour { [SerializeField] float damage; [SerializeField] float hitForce; [SerializeField] int speed; [SerializeField] float lifetime = 1; // Start is called before the first frame update void Start() { Destroy(gameObject, lifetime); } private void FixedUpdate() { transform.position += speed * transform.right; } //detect hit private void OnTriggerEnter2D(Collider2D _other) { if (_other.tag == "Enemy") { _other.GetComponent<Enemy>().EnemyHit(damage, (_other.transform.position - transform.position).normalized, -hitForce); } } }
Enemy:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy : MonoBehaviour { [SerializeField] protected float health; [SerializeField] protected float recoilLength; [SerializeField] protected float recoilFactor; [SerializeField] protected bool isRecoiling = false; [SerializeField] protected float speed; [SerializeField] protected float damage; protected float recoilTimer; protected Rigidbody2D rb; // Start is called before the first frame update protected virtual void Start() { rb = GetComponent<Rigidbody2D>(); } // Update is called once per frame protected virtual void Update() { if (health <= 0) { Destroy(gameObject); } if (isRecoiling) { if (recoilTimer < recoilLength) { recoilTimer += Time.deltaTime; } else { isRecoiling = false; recoilTimer = 0; } } } public virtual void EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce) { health -= _damageDone; if (!isRecoiling) { rb.AddForce(-_hitForce * recoilFactor * _hitDirection); } } protected void OnCollisionStay2D(Collision2D _other) { if (_other.gameObject.CompareTag("Player") && !PlayerController.Instance.pState.invincible) { Attack(); PlayerController.Instance.HitStopTime(0, 5, 0.5f); } } protected virtual void Attack() { PlayerController.Instance.TakeDamage(damage); } }
Skeleton_Warrior:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Skeleton_Warrior : Enemy { // Start is called before the first frame update protected override void Start() { base.Start(); rb.gravityScale = 12f; } // Update is called once per frame protected override void Update() { base.Update(); if (!isRecoiling) { transform.position = Vector2.MoveTowards (transform.position, new Vector2(PlayerController.Instance.transform.position.x, transform.position.y), speed * Time.deltaTime); } } public override void EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce) { base.EnemyHit(_damageDone, _hitDirection, _hitForce); } }
September 21, 2024 at 8:05 pm #15872::For the spell not doing damage, check if your enemies are tagged “Enemy”, because the code only damages GameObjects tagged as such.
//detect hit private void OnTriggerEnter2D(Collider2D _other) { if (_other.tag == "Enemy") { _other.GetComponent
().EnemyHit(damage, (_other.transform.position - transform.position).normalized, -hitForce); } } For healing, you need to find where the point of failure in your
Heal()
function is:void Heal() { if (Input.GetButton("Healing") && Health < maxHealth && Mana > 0 && Grounded() && !pState.dashing) { print("Heal tick"); pState.healing = true; anim.SetBool("Healing", true); //healing healTimer += Time.deltaTime; if (healTimer >= timeToHeal) { print("Heal proc"); Health++; healTimer = 0; } //drain mana Mana -= Time.deltaTime * manaDrainSpeed; } else { pState.healing = false; anim.SetBool("Healing", false); healTimer = 0; } }
See in your console if the “Heal tick” and “Heal proc” messages appear.
September 23, 2024 at 2:10 am #15892::Greetings! I wrote the code and all I can say is that only the Heal tick is displayed, and then for some reason the code does not work. And I also added a few lines of code that could add more clarity to this situation and I understood that the problem is in manna, here is the code I made for Heal –
void Heal() { print($"Healing attempt. Health: {Health}, Mana: {Mana}, Grounded: {Grounded()}, Dashing: {pState.dashing}"); if (Input.GetButton("Healing") && Health < maxHealth && Mana > 0 && Grounded() && !pState.dashing) { print("Heal tick"); pState.healing = true; anim.SetBool("Healing", true); //healing healTimer += Time.deltaTime; print($"healTimer: {healTimer}, timeToHeal: {timeToHeal}"); if (healTimer >= timeToHeal) { print("Heal proc"); Health++; healTimer = 0; } //drain mana Mana -= Time.deltaTime * manaDrainSpeed; print($"Mana after drain: {Mana}"); } else { pState.healing = false; anim.SetBool("Healing", false); healTimer = 0; print("Healing stopped"); } }
And here is what is displayed in the console –
19:21:00] Healing attempt. Health: 10, Mana: 10, Grounded: False, Dashing: False UnityEngine.MonoBehaviour:print (object)
[19:21:04] Healing stopped UnityEngine.MonoBehaviour:print (object)
[19:21:01] Healing attempt. Health: 10, Mana: 10, Grounded: True, Dashing: False UnityEngine.MonoBehaviour:print (object)
[19:21:04] Healing attempt. Health: 9, Mana: 10, Grounded: True, Dashing: False UnityEngine.MonoBehaviour.print (object) [19:21:02] Healing attempt. Health: 9, Mana: 10, Grounded: False, Dashing: False UnityEngine.MonoBehaviour:print (object)
[19:21:04] Heal tick UnityEngine.MonoBehaviour:print (object)
[19:21:04] healTimer: 0.0041707, time ToHeal: 1 UnityEngine.MonoBehaviour:print (object)
[19:21:04] Mana after drain: 1
UnityEngine.MonoBehaviour.print (object
I sincerely thank you for your help!!!September 23, 2024 at 2:25 am #15893::out of context. Can I ask that where is the Destroy after animation scripts is? Where did the part that i miss it?
September 23, 2024 at 2:33 am #15894::sorry for out of context but can I ask that where can i find the Destroy after animation script? I has watch from the begining but still doesn’t see it
September 23, 2024 at 2:39 am #15895::Here is DestroyAfterAnimation:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DestroyAnimation : MonoBehaviour { // Start is called before the first frame update void Start() { Destroy(gameObject, GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).length); } }
has upvoted this post. September 23, 2024 at 2:45 am #15896September 23, 2024 at 11:09 am #15900::Hi, there’s some things that should be checked regarding the issue of the enemy not taking damage from the fireball
1. Enemy is using “Enemy” tag
2. Set up a Trigger boxcollider2d on the fireball
3. fireball has a damage value entered in
I’ll also suggest to sort the enemy in another layer like “Attackable”For the healing, in your Update() function, if (pState.healing) return; is conflicting with Heal(), so can try to sort it in this way instead
if (pState.dashing) return; RestoreTimeScale(); FlashedWhileInvincible(); Move(); Heal(); CastSpell(); if (pState.healing) return; Flip(); Jump(); StartDash(); Attack(); Recoil();
has upvoted this post. September 23, 2024 at 11:51 pm #15906September 24, 2024 at 5:09 pm #15914 -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: