Forum begins after the advertisement:


[Part 10] The Boss have some bugs and cannot respawn from other scene if die

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 10] The Boss have some bugs and cannot respawn from other scene if die

Viewing 15 posts - 1 through 15 (of 41 total)
  • Author
    Posts
  • #14131
    #14132
    #14133
    Elvin Sim
    Participant

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class TheHollowKnight : Enemy
    {
    public static TheHollowKnight Instance;

    [SerializeField] GameObject slashEffect;
    public Transform SideAttackTransform;
    public Vector2 SideAttackArea;

    public Transform UpAttackTransform;
    public Vector2 UpAttackArea;

    public Transform DownAttackTransform;
    public Vector2 DownAttackArea;

    public float attackRange;
    public float attackTimer;

    [HideInInspector] public bool facingRight;

    [Header(“Ground Check Settings:”)]
    [SerializeField] public Transform groundCheckPoint;
    [SerializeField] private float groundCheckY = 0.2f;
    [SerializeField] private float groundCheckX = 0.5f;
    [SerializeField] private LayerMask whatIsGround;

    int hitCounter;
    bool stunned, canStun;
    bool alive;

    [HideInInspector] public float runSpeed;

    public GameObject impactParticle;

    private void Awake()
    {
    if(Instance != null && Instance != this)
    {
    Destroy(gameObject);
    }
    else
    {
    Instance = this;
    }
    }

    // Start is called before the first frame update
    protected override void Start()
    {
    base.Start();
    sr = GetComponentInChildren<SpriteRenderer>();
    anim = GetComponentInChildren<Animator>();
    ChangeState(EnemyStates.THK_Stage1);
    alive = true;
    }

    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;
    }
    }

    private void OnDrawGizmos()
    {
    Gizmos.color = Color.red;
    Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
    Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
    Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
    }

    float bloodCountdown;
    float bloodTimer;

    // Update is called once per frame
    protected override void Update()
    {
    base.Update();

    if(health <= 0 && alive)
    {
    Death(0);
    }

    if (!attacking)
    {
    attackCountdown -= Time.deltaTime;
    }

    if (stunned)
    {
    rb.velocity = Vector2.zero;
    }

    bloodCountdown -= Time.deltaTime;
    if(bloodCountdown <= 0 && (currentEnemyState != EnemyStates.THK_Stage1 && currentEnemyState != EnemyStates.THK_Stage2))
    {
    GameObject _orangeBlood = Instantiate(orangeBlood, groundCheckPoint.position, Quaternion.identity);
    Destroy(_orangeBlood, 4f);
    bloodCountdown = bloodTimer;
    }
    }

    public void Flip()
    {
    if(PlayerController.Instance.transform.position.x < transform.position.x && transform.localScale.x > 0)
    {
    transform.eulerAngles = new Vector2(transform.eulerAngles.x, 180);
    facingRight = false;
    }
    else
    {
    transform.eulerAngles = new Vector2(transform.eulerAngles.x, 0);
    facingRight = true;
    }
    }

    protected override void UpdateEnemyState()
    {
    if(PlayerController.Instance != null)
    {
    switch (GetCurrentEnemyState)
    {
    case EnemyStates.THK_Stage1:
    canStun = true;
    attackTimer = 6;
    runSpeed = speed;
    break;

    case EnemyStates.THK_Stage2:
    canStun = true;
    attackTimer = 5;
    break;

    case EnemyStates.THK_Stage3:
    canStun = false;
    attackTimer = 8;
    bloodTimer = 5f;
    break;

    case EnemyStates.THK_Stage4:
    canStun = false;
    attackTimer = 10;
    runSpeed = speed / 2;
    bloodTimer = 1.5f;
    break;
    }
    }
    }

    protected override void OnCollisionStay2D(Collision2D _other)
    {

    }

    #region attacking
    #region variables
    [HideInInspector] public bool attacking;
    [HideInInspector] public float attackCountdown;
    [HideInInspector] public bool damagedPlayer = false;
    [HideInInspector] public bool parrying;

    [HideInInspector] public Vector2 moveToPosition;
    [HideInInspector] public bool diveAttack;
    public GameObject divingCollider;
    public GameObject pillar;

    [HideInInspector] public bool barrageAttack;
    public GameObject barrageFireball;
    [HideInInspector] public bool outbreakAttack;

    [HideInInspector] public bool bounceAttack;
    [HideInInspector] public float rotationDirectionToTarget;
    [HideInInspector] public int bounceCount;

    #endregion

    #region Control

    public void AttackHandler()
    {
    if(currentEnemyState == EnemyStates.THK_Stage1)
    {
    if(Vector2.Distance(PlayerController.Instance.transform.position, rb.position) <= attackRange)
    {
    StartCoroutine(TripleSlash());
    }
    else
    {
    StartCoroutine(Lunge());
    }
    }

    if (currentEnemyState == EnemyStates.THK_Stage2)
    {
    if (Vector2.Distance(PlayerController.Instance.transform.position, rb.position) <= attackRange)
    {
    StartCoroutine(TripleSlash());
    }
    else
    {
    int _attackChosen = Random.Range(1, 3);
    if(_attackChosen == 1)
    {
    StartCoroutine(Lunge());
    }
    if(_attackChosen == 2)
    {
    DiveAttackJump();
    }
    if(_attackChosen == 3)
    {
    BarrageBendDown();
    }
    }
    }

    if (currentEnemyState == EnemyStates.THK_Stage3)
    {
    int _attackChosen = Random.Range(1, 4);
    if (_attackChosen == 1)
    {
    OutbreakBendDown();
    }
    if (_attackChosen == 2)
    {
    DiveAttackJump();
    }
    if (_attackChosen == 3)
    {
    BarrageBendDown();
    }
    if(_attackChosen == 4)
    {
    BounceAttack();
    }
    }

    if (currentEnemyState == EnemyStates.THK_Stage4)
    {
    if (Vector2.Distance(PlayerController.Instance.transform.position, rb.position) <= attackRange)
    {
    StartCoroutine(Slash());
    }
    else
    {
    BounceAttack();
    }
    }
    }

    public void ResetAllAttacks()
    {
    attacking = false;

    StopCoroutine(TripleSlash());
    StopCoroutine(Lunge());
    StopCoroutine(Parry());
    StopCoroutine(Slash());

    diveAttack = false;
    barrageAttack = false;
    outbreakAttack = false;
    bounceAttack = false;
    }
    #endregion

    #region Stage 1
    IEnumerator TripleSlash()
    {
    attacking = true;
    rb.velocity = Vector2.zero;

    anim.SetTrigger(“Slash”);
    SlashAngle();
    yield return new WaitForSeconds(0.3f);
    anim.ResetTrigger(“Slash”);

    anim.SetTrigger(“Slash”);
    SlashAngle();
    yield return new WaitForSeconds(0.5f);
    anim.ResetTrigger(“Slash”);

    anim.SetTrigger(“Slash”);
    SlashAngle();
    yield return new WaitForSeconds(0.2f);
    anim.ResetTrigger(“Slash”);

    ResetAllAttacks();
    }

    void SlashAngle()
    {
    if(PlayerController.Instance.transform.position.x > transform.position.x || PlayerController.Instance.transform.position.x < transform.position.x)
    {
    Instantiate(slashEffect, SideAttackTransform);
    }
    else if (PlayerController.Instance.transform.position.y > transform.position.y)
    {
    SlashEffectAtAngle(slashEffect, 80, UpAttackTransform);
    }
    else if (PlayerController.Instance.transform.position.y < transform.position.y)
    {
    SlashEffectAtAngle(slashEffect, -90, UpAttackTransform);
    }
    }

    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);
    }

    IEnumerator Lunge()
    {
    Flip();
    attacking = true;

    anim.SetBool(“Lunge”, true);
    yield return new WaitForSeconds(1f);
    anim.SetBool(“Lunge”, false);
    damagedPlayer = false;
    ResetAllAttacks();
    }

    IEnumerator Parry()
    {
    attacking = true;
    rb.velocity = Vector2.zero;
    anim.SetBool(“Parry”, true);
    yield return new WaitForSeconds(0.8f);
    anim.SetBool(“Parry”, false);

    parrying = false;
    ResetAllAttacks();
    }

    IEnumerator Slash()
    {
    attacking = true;
    rb.velocity = Vector2.zero;

    anim.SetTrigger(“Slash”);
    SlashAngle();
    yield return new WaitForSeconds(0.2f);
    anim.ResetTrigger(“Slash”);

    ResetAllAttacks();
    }

    #endregion
    #region Stage 2
    void DiveAttackJump()
    {
    attacking = true;
    moveToPosition = new Vector2(PlayerController.Instance.transform.position.x, rb.position.y + 10);
    diveAttack = true;
    anim.SetBool(“Jump”, true);
    }

    public void Dive()
    {
    anim.SetBool(“Dive”, true);
    anim.SetBool(“Jump”, false);
    }

    private void OnTriggerEnter2D(Collider2D _other)
    {
    if(_other.GetComponent<PlayerController>() != null && (diveAttack || bounceAttack))
    {
    _other.GetComponent<PlayerController>().TakeDamage(damage * 2);
    PlayerController.Instance.pState.recoilingX = true;
    }
    }

    public void DivingPillars()
    {
    Vector2 _impactPoint = groundCheckPoint.position;
    float _spawnDistance = 5;

    for(int i = 0; i < 10; i++)
    {
    Vector2 _pillarSpawnPointRight = _impactPoint + new Vector2(_spawnDistance, 0);
    Vector2 _pillarSpawnPointLeft = _impactPoint – new Vector2(_spawnDistance, 0);
    Instantiate(pillar, _pillarSpawnPointRight, Quaternion.Euler(0, 0, -90));
    Instantiate(pillar, _pillarSpawnPointLeft, Quaternion.Euler(0, 0, -90));

    _spawnDistance += 5;
    }
    ResetAllAttacks();
    }

    void BarrageBendDown()
    {
    attacking = true;
    rb.velocity = Vector2.zero;
    barrageAttack = true;
    anim.SetTrigger(“BendDown”);
    }

    public IEnumerator Barrage()
    {
    rb.velocity = Vector2.zero;

    float _currentAngle = 30f;
    for(int i = 0; i < 10; i++)
    {
    GameObject _projectile = Instantiate(barrageFireball, transform.position, Quaternion.Euler(0, 0, _currentAngle));

    if (facingRight)
    {
    _projectile.transform.eulerAngles = new Vector3(_projectile.transform.eulerAngles.x, 0, _currentAngle);
    }
    else
    {
    _projectile.transform.eulerAngles = new Vector3(_projectile.transform.eulerAngles.x, 180, _currentAngle);
    }

    _currentAngle += 5f;

    yield return new WaitForSeconds(0.3f);
    }
    yield return new WaitForSeconds(0.1f);
    anim.SetBool(“Cast”, false);
    ResetAllAttacks();
    }

    #endregion
    #region Stage 3
    void OutbreakBendDown()
    {
    attacking = true;
    rb.velocity = Vector2.zero;
    moveToPosition = new Vector2(transform.position.x, rb.position.y + 5);
    outbreakAttack = true;
    anim.SetTrigger(“BendDown”);
    }

    public IEnumerator Outbreak()
    {
    yield return new WaitForSeconds(1f);
    anim.SetBool(“Cast”, true);

    rb.velocity = Vector2.zero;
    for(int i = 0; i < 30; i++)
    {
    Instantiate(barrageFireball, transform.position, Quaternion.Euler(0, 0, Random.Range(110, 130))); //downwards
    Instantiate(barrageFireball, transform.position, Quaternion.Euler(0, 0, Random.Range(50, 70))); //diagonally right
    Instantiate(barrageFireball, transform.position, Quaternion.Euler(0, 0, Random.Range(260, 280))); //diagonally left

    yield return new WaitForSeconds(0.2f);
    }
    yield return new WaitForSeconds(0.1f);
    rb.constraints = RigidbodyConstraints2D.None;
    rb.constraints = RigidbodyConstraints2D.FreezeRotation;
    rb.velocity = new Vector2(rb.velocity.x, -10);
    yield return new WaitForSeconds(0.1f);
    anim.SetBool(“Cast”, false);
    ResetAllAttacks();
    }

    void BounceAttack()
    {
    attacking = true;
    bounceCount = Random.Range(2, 5);
    BounceBendDown();
    }
    int _bounces = 0;
    public void CheckBounce()
    {
    if(_bounces < bounceCount – 1)
    {
    _bounces++;
    BounceBendDown();
    }
    else
    {
    _bounces = 0;
    anim.Play(“Boss_Run”);
    }
    }

    public void BounceBendDown()
    {
    rb.velocity = Vector2.zero;
    moveToPosition = new Vector2(PlayerController.Instance.transform.position.x, rb.position.y + 10);
    bounceAttack = true;
    anim.SetTrigger(“BendDown”);
    }

    public void CalculateTargetAngle()
    {
    Vector3 _directionToTarget = (PlayerController.Instance.transform.position – transform.position).normalized;

    float _angleOfTarget = Mathf.Atan2(_directionToTarget.y, _directionToTarget.x) * Mathf.Rad2Deg;
    rotationDirectionToTarget = _angleOfTarget;
    }

    #endregion
    #endregion

    public override void EnemyGetsHit(float _damageDone, Vector2 _hitDirection, float _hitForce)
    {
    if (!stunned)
    {
    if (!parrying)
    {
    if (canStun)
    {
    hitCounter++;
    if(hitCounter >= 3)
    {
    ResetAllAttacks();
    StartCoroutine(Stunned());
    }
    }
    base.EnemyGetsHit(_damageDone, _hitDirection, _hitForce);

    if (currentEnemyState != EnemyStates.THK_Stage4)
    {
    ResetAllAttacks(); //cancel any current attack to avoid bugs
    StartCoroutine(Parry());
    }

    }
    else
    {
    StopCoroutine(Parry());
    ResetAllAttacks();
    StartCoroutine(Slash()); //riposte
    }
    }
    else
    {
    StopCoroutine(Stunned());
    anim.SetBool(“Stunned”, false);
    stunned = false;
    }
    #region health to state
    if(health > 20)
    {
    ChangeState(EnemyStates.THK_Stage1);
    }
    if (health <= 15 && health < 10)
    {
    ChangeState(EnemyStates.THK_Stage2);
    }
    if (health <= 10 && health < 5)
    {
    ChangeState(EnemyStates.THK_Stage3);
    }
    if (health < 5)
    {
    ChangeState(EnemyStates.THK_Stage4);
    }
    if(health <= 0)
    {
    Death(0);
    }
    #endregion
    }

    public IEnumerator Stunned()
    {
    stunned = true;
    hitCounter = 0;
    anim.SetBool(“Stunned”, true);

    yield return new WaitForSeconds(6f);
    anim.SetBool(“Stunned”, false);
    stunned = false;
    }

    protected override void Death(float _destroyTime)
    {
    ResetAllAttacks();
    alive = false;
    rb.velocity = new Vector2(rb.velocity.x, -25);
    anim.SetTrigger(“Die”);
    bloodTimer = 0.8f;
    }

    public void DestroyAfterDeath()
    {
    Destroy(gameObject);
    }
    }

    #14134
    Elvin Sim
    Participant

    Spawn door also have bugs

    #14135
    Elvin Sim
    Participant

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class SpawnBoss : MonoBehaviour
    {
    public static SpawnBoss Instance;
    [SerializeField] Transform spawnPoint;
    [SerializeField] GameObject boss;
    [SerializeField] Vector2 exitDirection;
    bool callOnce;
    BoxCollider2D col;

    private void Awake()
    {
    if(Instance != null && Instance != this)
    {
    Destroy(gameObject);
    }
    else
    {
    Instance = this;
    }
    }

    // Start is called before the first frame update
    void Start()
    {
    col = GetComponent<BoxCollider2D>();
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void OnTriggerEnter2D(Collider2D _other)
    {
    if (_other.CompareTag(“Player”))
    {
    if (!callOnce)
    {
    StartCoroutine(WalkIntoRoom());
    callOnce = true;
    }
    }
    }

    IEnumerator WalkIntoRoom()
    {
    StartCoroutine(PlayerController.Instance.WalkIntoNewScene(exitDirection, 1));
    yield return new WaitForSeconds(1f);
    col.isTrigger = false;
    Instantiate(boss, spawnPoint.position, Quaternion.identity);
    }

    public void IsNotTrigger()
    {
    col.isTrigger = true;
    }
    }

    #14136
    Elvin Sim
    Participant

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class THKEvents : MonoBehaviour
    {
    void SlashDamagePlayer()
    {
    if (PlayerController.Instance.transform.position.x > transform.position.x || PlayerController.Instance.transform.position.x < transform.position.x)
    {
    Hit(TheHollowKnight.Instance.SideAttackTransform, TheHollowKnight.Instance.SideAttackArea);
    }
    else if (PlayerController.Instance.transform.position.y > transform.position.y)
    {
    Hit(TheHollowKnight.Instance.UpAttackTransform, TheHollowKnight.Instance.UpAttackArea);
    }
    else if (PlayerController.Instance.transform.position.y < transform.position.y)
    {
    Hit(TheHollowKnight.Instance.DownAttackTransform, TheHollowKnight.Instance.DownAttackArea);
    }
    }

    void Hit(Transform _attackTransform, Vector2 _attackArea)
    {
    Collider2D[] _objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0);
    for(int i = 0; i < _objectsToHit.Length; i++)
    {
    if (_objectsToHit[i].GetComponent<PlayerController>() != null)
    {
    _objectsToHit[i].GetComponent<PlayerController>().TakeDamage(TheHollowKnight.Instance.damage);
    }
    }
    }

    void Parrying()
    {
    TheHollowKnight.Instance.parrying = true;
    }

    void BendDownCheck()
    {
    if (TheHollowKnight.Instance.barrageAttack)
    {
    StartCoroutine(BarrageAttackTransition());
    }

    if (TheHollowKnight.Instance.outbreakAttack)
    {
    StartCoroutine(OutbreakAttackTransition());
    }

    if (TheHollowKnight.Instance.bounceAttack)
    {
    TheHollowKnight.Instance.anim.SetTrigger(“Bounce1”);
    }
    }

    void BarrageOrOutbreak()
    {
    if (TheHollowKnight.Instance.barrageAttack)
    {
    TheHollowKnight.Instance.StartCoroutine(TheHollowKnight.Instance.Barrage());
    }

    if (TheHollowKnight.Instance.outbreakAttack)
    {
    TheHollowKnight.Instance.StartCoroutine(TheHollowKnight.Instance.Outbreak());
    }
    }

    IEnumerator BarrageAttackTransition()
    {
    yield return new WaitForSeconds(1f);
    TheHollowKnight.Instance.anim.SetBool(“Cast”, true);
    }

    IEnumerator OutbreakAttackTransition()
    {
    yield return new WaitForSeconds(1f);
    TheHollowKnight.Instance.anim.SetBool(“Cast”, true);
    }

    void DestroyAfterDeath()
    {
    SpawnBoss.Instance.IsNotTrigger();
    TheHollowKnight.Instance.DestroyAfterDeath();
    }
    }

    #14137
    Elvin Sim
    Participant

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class Boss_Idle : StateMachineBehaviour
    {
    Rigidbody2D rb;
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
    rb = animator.GetComponentInParent<Rigidbody2D>();
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
    rb.velocity = Vector2.zero;
    RunToPlayer(animator);

    if(TheHollowKnight.Instance.attackCountdown <= 0)
    {
    TheHollowKnight.Instance.AttackHandler();
    TheHollowKnight.Instance.attackCountdown = Random.Range(TheHollowKnight.Instance.attackTimer – 1, TheHollowKnight.Instance.attackTimer + 1);
    }
    }

    void RunToPlayer(Animator animator)
    {
    if(Vector2.Distance(PlayerController.Instance.transform.position, rb.position) >= TheHollowKnight.Instance.attackRange)
    {
    animator.SetBool(“Run”, true);
    }
    else
    {
    return;
    }
    }

    // OnStateExit is called when a transition ends and the state machine finishes evaluating this state
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {

    }

    }

    #14138
    #14140
    Joseph Tang
    Moderator

    Hey there, if you want to send code, you can use “pre” at the start of the code and “/pre” at the end of the code, use < and > instead of the quotations

    For your door transition, I recommend setting the code in SpawnBoss.cs WalkIntoRoom(), yield return new WaitForSeconds() to a smaller value. Simply put, make the time between the room transition and the collision trigger turn off faster to prevent your player being able to walk out in time.

    For the respawn, I’d assume the code is working just fine by this point so set up some print code to see which part of of your GameManager.cs respawn code is not firing. If everything works, check that you have a save file for your bench and others.

    for the Boss not attacking, you should set the attack timer on the THK lower than 5.

    #14141
    Elvin Sim
    Participant

    Sorry for the code and Thanks, and also one things I want to say is for one of the video I sent, there is a bug that when I attack the boss, while the boss is parrying, and the slash effect apppear, after that, the boss become invincible, and cannot let the boss death

    #14142
    #14143
    Elvin Sim
    Participant

    and one more thing is that the triple slash sometimes works but sometimes it doesn’t works, for example, when The boss hit me, it will slash 2 or three times, cause 2 to 3 damage but not always triple slash

    #14145
    Elvin Sim
    Participant

    #14146
    Elvin Sim
    Participant

    #14147
    Elvin Sim
    Participant

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

Go to Login Page →


Advertisement below: