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
- This topic has 40 replies, 3 voices, and was last updated 4 months, 2 weeks ago by
Ethan.
-
AuthorPosts
-
April 22, 2024 at 2:50 am #14131
Elvin Sim
ParticipantApril 22, 2024 at 2:51 am #14132Elvin Sim
ParticipantApril 22, 2024 at 2:52 am #14133Elvin 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 leftyield 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
#endregionpublic 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);
}
}April 22, 2024 at 2:52 am #14134April 22, 2024 at 2:52 am #14135Elvin 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;
}
}April 22, 2024 at 2:53 am #14136Elvin 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();
}
}April 22, 2024 at 2:54 am #14137Elvin 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)
{}
}
April 22, 2024 at 2:56 am #14138Elvin Sim
ParticipantApril 22, 2024 at 3:37 pm #14140Joseph 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.
April 22, 2024 at 4:53 pm #14141Elvin 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
April 22, 2024 at 4:54 pm #14142Elvin Sim
ParticipantApril 22, 2024 at 5:00 pm #14143Elvin 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
April 22, 2024 at 5:34 pm #14145April 22, 2024 at 5:34 pm #14146April 22, 2024 at 5:36 pm #14147 -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: