Forum begins after the advertisement:
[part 10] boss problems
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [part 10] boss problems
- This topic has 9 replies, 2 voices, and was last updated 7 months, 3 weeks ago by Joseph Tang.
-
AuthorPosts
-
April 27, 2024 at 9:45 pm #14361::
my pillers are rotated 90degrees for some reason and my boss does double damage or sometimes instantly kills me. also my i-frames doesnt seem to work when im fighting the boss
View post on imgur.com
April 28, 2024 at 12:25 am #14362::i changed my diving pillars script to this:
<code>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(piller, _pillarSpawnPointRight, Quaternion.Euler(0,0,0)); Instantiate(piller, _pillarSpawnPointLeft, Quaternion.Euler(0,0,0)); _spawnDistance += 5; } ResetAllAttacks(); }</code>
and now my pillar is straight but now all 10 pillars are in one spot and touching that insta kills me. how do i get my pillars to be far apart?
View post on imgur.com
April 28, 2024 at 6:36 pm #14375::For the code, you would want it to be this:
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(); }
the code should work, and the only problem with yours is that the
Quaternion.Euler()
is not [-90]. This should allow the_spawnDistance
to work.April 28, 2024 at 9:33 pm #14381::i figured it out. the problem was that when i was animating the pillars i have accidently recorded the position along with the scale. that makes every pillar move to a set position.
I made it until the outbreak attack in the video. and a new bug appeared. the boss slowly floats down after doing the outbreak attack.
View post on imgur.com
also my i-frames and time slow effect doesnt seem to work when im fighting the boss
April 29, 2024 at 2:11 am #14382::Fixed the slow falling bug but now the boss keeps running mid air instead of being in the last frame of the cast animation. also if the player hits boss mid outbreak the boss starts to rotate.
View post on imgur.com
i added the
RigidbodyConstraints2D.FreezeRotation;
line to void outbreak() but then the boss doesnt float during outbreak. he just follows me around while shooting fireballsView post on imgur.com
<code>void OutBreakAttack(){ if(THK_Boss.Instance.outBreakAttack){ Vector2 _newPos = Vector2.MoveTowards(rb.position, THK_Boss.Instance.moveToPosition, THK_Boss.Instance.speed * 1.5f * Time.fixedDeltaTime); rb.MovePosition(_newPos); float _distance = Vector2.Distance(rb.position, _newPos); if(_distance < 0.1f){ THK_Boss.Instance.rb.constraints = RigidbodyConstraints2D.FreezePosition; THK_Boss.Instance.rb.constraints = RigidbodyConstraints2D.FreezeRotation; } } }</code>
April 29, 2024 at 9:37 pm #14391::For all the problems with your Outbreak attack, follow the code below and it should fix your rotation, the falling off the Boss and staying in place.
What’s likely happening is that
- Your boss is not falling fast because it’s gravity scale is 1 and you did not include the
rb.velocity
code in yourOutbreak()
method to push the boss to the ground & there is not falling set in the Boss_Idle or Boss_Run. - Your boss is rotating after being hit because in your
Outbreak()
method, likely because you did not freeze rotation in the code. - Check that your Cast animation parameter is a Bool. Then check if you have a code:
anim.SetBool("Cast", false);
, being called early instead of only after theOubreak()
Please check that your code is as follows:
For your TheHollowKnight.cs
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(); }
For the Boss_BendDown.cs
void OutbreakAttack() { if (TheHollowKnight.Instance.outbreakAttack) { Vector2 _newPos = Vector2.MoveTowards(rb.position, TheHollowKnight.Instance.moveToPosition, TheHollowKnight.Instance.speed * 1.5f * Time.fixedDeltaTime); rb.MovePosition(_newPos); float _distance = Vector2.Distance(rb.position, _newPos); if (_distance < 0.1f) { TheHollowKnight.Instance.rb.constraints = RigidbodyConstraints2D.FreezePosition; } } }
In your Boss_Idle and Boss_Run, ensure there’s one of this code:
if (!TheHollowKnight.Instance.Grounded()) { rb.velocity = new Vector2(rb.velocity.x, -25); //if knight is not grounded, fall to ground }
April 29, 2024 at 10:47 pm #14394::my code is identical to yours. and it seems when the boss locks x and y position the rotation(z) lock tickbox becomes unchecked
View post on imgur.com
April 30, 2024 at 11:20 am #14405::If you could, can you copy and paste the entire TheHollowKnight.cs and Boss_Dive.cs code?
You likely have a
RigidbodyConstraints2D.None
being called somewhwere early.April 30, 2024 at 1:41 pm #14416::THK_boss code:
<code>using System.Collections; using System.Collections.Generic; using UnityEngine; public class THK_Boss : Enemy { public static THK_Boss Instance; [SerializeField] GameObject slashEffect; [SerializeField] public Transform SideAttackTransform; //the middle of the side attack area [SerializeField] public Vector2 SideAttackArea; //how large the area of side attack is [SerializeField] public Transform UpAttackTransform; //the middle of the up attack area [SerializeField] public Vector2 UpAttackArea; //how large the area of side attack is [SerializeField] public Transform DownAttackTransform; //the middle of the down attack area [SerializeField] public Vector2 DownAttackArea; //how large the area of down attack is public float attackRange; public float attackTimer; [HideInInspector] public bool facingRight; [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 int hitCounter; bool stunned, canStun; bool alive; [HideInInspector] public float runSpeed; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); } else { Instance = this; } } 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); } protected override void Update() { base.Update(); if(!attacking){ attackCountdown-=Time.deltaTime; } if(stunned){ rb.velocity = Vector2.zero; } /*if(!Grounded()){ anim.SetBool("Run",false); }*/ } 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 UpdateEnemyStates(){ if(PlayerController.Instance != null){ switch(GetCurrentEnemyState){ case EnemyStates.THK_Stage1: canStun = true; break; case EnemyStates.THK_Stage2: canStun = true; break; case EnemyStates.THK_Stage3: canStun = false; break; case EnemyStates.THK_Stage4: canStun = false; 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; [HideInInspector] public bool barrageAttack; public GameObject barrageFireBall; public GameObject divingCollider; public GameObject pillar; [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()); DiveAttackJump(); //BarrageBendDown(); //OutBreakBendDown(); //BounceAttack(); } } } public void ResetAllAttacks(){ attacking = false; StopCoroutine(TripleSlash()); StopCoroutine(Lunge()); StopCoroutine(Parry()); StopCoroutine(Slash()); diveAttack = false; barrageAttack = false; outBreakAttack = false; bounceAttack = false; } #endregion #region Stage1 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,DownAttackTransform); } } 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(0.5f); 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 Stage2 public 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-20); } else{ _projectile.transform.eulerAngles = new Vector3(_projectile.transform.eulerAngles.x,180,_currentAngle-20); } _currentAngle+=5f; yield return new WaitForSeconds(0.3f); } yield return new WaitForSeconds(0.1f); anim.SetBool("Cast",false); ResetAllAttacks(); } #endregion #region Stage3 void OutBreakBendDown(){ attacking = true; rb.velocity = Vector2.zero; moveToPosition = new Vector2(transform.position.x, rb.position.y +5); outBreakAttack = true; anim.SetBool("BendDown",true); } 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(230,310)));//down Instantiate(barrageFireBall, transform.position, Quaternion.Euler(0,0,Random.Range(0,50)));//right Instantiate(barrageFireBall, transform.position, Quaternion.Euler(0,0,Random.Range(130,180)));//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,6); BounceBendDown(); } int _bounces = 0; public void CheckBounce(){ if(_bounces < bounceCount-1){ _bounces++; BounceBendDown(); } else{ _bounces = 0; anim.Play("THK_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 EnemyHit(float _damageDone,Vector2 _hitDirection,float _hitForce){ if(!stunned){ if(!parrying){ if(canStun){ hitCounter++; if(hitCounter>=3){//testing how many hits to stun ResetAllAttacks(); StartCoroutine(Stunned()); } } base.EnemyHit(_damageDone,_hitDirection,_hitForce); if(currentEnemyState != EnemyStates.THK_Stage4){ ResetAllAttacks(); 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); } #endregion } public IEnumerator Stunned(){ stunned = true; hitCounter = 0; anim.SetBool("Stunned",true); yield return new WaitForSeconds(6f); anim.SetBool("Stunned",false); stunned = false; } } </code>
Boss_Dive:
<code>using System.Collections; using System.Collections.Generic; using UnityEngine; public class THK_Dive : StateMachineBehaviour { Rigidbody2D rb; bool callOnce; // 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) { THK_Boss.Instance.divingCollider.SetActive(true); if(THK_Boss.Instance.Grounded()){ THK_Boss.Instance.divingCollider.SetActive(false); if(!callOnce){ THK_Boss.Instance.DivingPillars(); animator.SetBool("Dive", false); THK_Boss.Instance.ResetAllAttacks(); callOnce = true; } } } // 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) { callOnce = false; } } </code>
April 30, 2024 at 3:33 pm #14436::Sorry, I gave the wrong instruction, I wanted to check the Boss_BendDown script instead.
However, I want you to try this first:
- Delete the
RigidbodyConstraints2D.None;
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(); }2) If you’ve tested it out and see that your Boss now has both it’s Freeze Position and Freeze Rotation active,
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.FreezePosition;
rb.constraints = RigidbodyConstraints2D.FreezeRotation;rb.velocity = new Vector2(rb.velocity.x, -10); yield return new WaitForSeconds(0.1f); anim.SetBool("Cast", false); ResetAllAttacks(); }The code above selectively removes the freezeposition constraints only, effectively doing the initial intentions of the code by removing the freeze position and keeping freeze rotation.
Of course, If Freeze Rotation is still being removed, that means that there is definitely another piece of code somewhere calling to remove the freezerotation.
- Your boss is not falling fast because it’s gravity scale is 1 and you did not include the
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: