Forum begins after the advertisement:
[Part 6] Charger Not Flipping and Detecting Player
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 6] Charger Not Flipping and Detecting Player
- This topic has 6 replies, 3 voices, and was last updated 7 months, 2 weeks ago by Joseph Tang.
-
AuthorPosts
-
May 6, 2024 at 5:53 am #14524::
I’m having issues where the charger isn’t charging at the player and also not flipping (it will continue running into the wall).
Demo
View post on imgur.com
Here’s my code Charger.cs
<code>using System.Collections; using System.Collections.Generic; using UnityEngine; public class Charger : Enemy { [SerializeField] private float ledgeCheckX; [SerializeField] private float ledgeCheckY; [SerializeField] private float chargeSpeedMultiplier; [SerializeField] private float chargeDuration; [SerializeField] private float jumpForce; [SerializeField] private LayerMask whatIsGround; float timer; protected override void Start() { base.Start(); ChangeState(EnemyStates.Charger_Idle); rb.gravityScale = 12f; } protected override void UpdateEnemyStates() { if(health <= 0) { Death(0.05f); } Vector3 _ledgeCheckStart = transform.localScale.x > 0 ? new Vector3(ledgeCheckX, 0) : new Vector3(-ledgeCheckX, 0); Vector2 _wallCheckDir = transform.localScale.x > 0 ? transform.right : -transform.right; switch (GetCurrentEnemyState) { case EnemyStates.Charger_Idle: if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround) || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround)) { transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y); } RaycastHit2D _hit = Physics2D.Raycast(transform.position + _ledgeCheckStart, _wallCheckDir, ledgeCheckX * 10); if (_hit.collider != null && _hit.collider.gameObject.CompareTag("Player")) { ChangeState(EnemyStates.Charger_Surprised); } if (transform.localScale.x > 0) { rb.velocity = new Vector2(speed, rb.velocity.y); } else { rb.velocity = new Vector2(-speed, rb.velocity.y); } break; case EnemyStates.Charger_Surprised: rb.velocity = new Vector2(0, jumpForce); ChangeState(EnemyStates.Charger_Charge); break; case EnemyStates.Charger_Charge: timer += Time.deltaTime; if(timer < chargeDuration) { if(Physics2D.Raycast(transform.position, Vector2.down, ledgeCheckY, whatIsGround)) { if (transform.localScale.x > 0) { rb.velocity = new Vector2(speed * chargeSpeedMultiplier, rb.velocity.y); } else { rb.velocity = new Vector2(-speed * chargeSpeedMultiplier, rb.velocity.y); } } else { rb.velocity = new Vector2(0, rb.velocity.y); } } else { timer = 0; ChangeState(EnemyStates.Charger_Idle); } break; } } protected override void ChangeCurrentAnimation() { if(GetCurrentEnemyState == EnemyStates.Charger_Idle) { anim.speed = 1; } if(GetCurrentEnemyState == EnemyStates.Charger_Charge) { anim.speed = chargeSpeedMultiplier; } } } </code>
Enemy.cs
<code>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 playerController player; [SerializeField] protected float speed; [SerializeField] protected float damage; [SerializeField] protected GameObject enemyBlood; protected float recoilTimer; protected Rigidbody2D rb; protected SpriteRenderer sr; protected Animator anim; protected enum EnemyStates { // Crawler Crawler_Idle, Crawler_Flip, // Bat Bat_Idle, Bat_Chase, Bat_Stunned, Bat_Death, // Charger Charger_Idle, Charger_Surprised, Charger_Charge } protected EnemyStates currentEnemyState; protected virtual EnemyStates GetCurrentEnemyState { get { return currentEnemyState; } set { if(currentEnemyState != value) { currentEnemyState = value; ChangeCurrentAnimation(); } } } // Start is called before the first frame update protected virtual void Start() { rb = GetComponent<Rigidbody2D>(); sr = GetComponent<SpriteRenderer>(); anim = GetComponent<Animator>(); } protected virtual void Awake() { rb = GetComponent<Rigidbody2D>(); player = playerController.Instance; } // Update is called once per frame protected virtual void Update() { if(isRecoiling) { if(recoilTimer < recoilLength) { recoilTimer += Time.deltaTime; } else { isRecoiling = false; recoilTimer = 0; } } else { UpdateEnemyStates(); } } public virtual void EnemyHit(float _damageDone, Vector2 _hitDirection, float _hitForce) { health -= _damageDone; if(!isRecoiling) { GameObject _enemyBlood = Instantiate(enemyBlood, transform.position, Quaternion.identity); Destroy(_enemyBlood, 5.5f); rb.velocity = -_hitForce * recoilFactor * _hitDirection; } } protected void OnCollisionStay2D(Collision2D _other) { if(_other.gameObject.CompareTag("Player") && !playerController.Instance.pState.invincible && health > 0) { Attack(); playerController.Instance.HitStopTime(0, 5, 0.5f); } } protected virtual void Death(float _destroyTime) { Destroy(gameObject, _destroyTime); } protected virtual void UpdateEnemyStates() { } protected virtual void ChangeCurrentAnimation() { } protected void ChangeState(EnemyStates _newState) { GetCurrentEnemyState = _newState; } protected virtual void Attack() { playerController.Instance.TakeDamage(damage); } }</code>
May 6, 2024 at 1:44 pm #14527::From the looks of your code, nothing is different from the code given in the Forums. Which means it could be the scene’s setup which is the issue.
I tested your code and it works fine, leading me to believe that what’s not working is your raycast detection. More particularly, the raycast targets don’t fit the parameters for the Charger to function.
In your video, the Charger was still in Idle state and was walking into the wall. I’ll need you to check 2 things:
- Your Player is Tagged “Player”
- Your Walls are Layer “Ground”
My guess is that your Player is not tagged appropriately and the Charger cannot sense the Player to switch to Surprised state. As for the Charger walking into the wall, I think it is either the wall not having the required
whatIsGround
layer or theledgeCheckX
is too small of a value [You can play around with higher values to test out the range of detection for your Charger.]I’d recommend you add
Debug.log()
orprint()
to both if statements in Charger.cs, underUpdateEnemyStates()
, here, to see if either are being triggered:if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround) || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround)) { print("Turn Around"); transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y); } RaycastHit2D _hit = Physics2D.Raycast(transform.position + _ledgeCheckStart, _wallCheckDir, ledgeCheckX * 10); if (_hit.collider != null && _hit.collider.gameObject.CompareTag("Player")) { print("Found Player"); ChangeState(EnemyStates.Charger_Surprised); }
May 7, 2024 at 4:26 am #14530::Oops, yeah, my walls were not set to the ground layer, so that fixed the running into the wall issue. As for the charger not detecting the player. I had the Player set to the Player tag already, but the charger still doesn’t detect the player. I tried the print statement out and nothing is printed. I also noticed that the charger goes into the surprised state when I jump on top of the charger; this happens randomly, too. It could either take one jump or two jumps.
May 7, 2024 at 8:44 am #14534::Okay, It sounds like the code may be working but there’s an issue with the Raycast2D.
Before we try anything, I would like you to implement this code into your Charger.cs:
private Vector3 ledgeCheckStart; private Vector2 wallCheckDir; private void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawRay(transform.position + ledgeCheckStart, wallCheckDir * (ledgeCheckX * 10)); } protected override void UpdateEnemyStates() { if (health <= 0) { Death(0.05f); } Vector3 _ledgeCheckStart = transform.localScale.x > 0 ? new Vector3(ledgeCheckX, 0) : new Vector3(-ledgeCheckX, 0); Vector2 _wallCheckDir = transform.localScale.x > 0 ? transform.right : -transform.right; ledgeCheckStart = _ledgeCheckStart; wallCheckDir = _wallCheckDir; switch (GetCurrentEnemyState) { case EnemyStates.Charger_Idle: if (!Physics2D.Raycast(transform.position + _ledgeCheckStart, Vector2.down, ledgeCheckY, whatIsGround) || Physics2D.Raycast(transform.position, _wallCheckDir, ledgeCheckX, whatIsGround)) { transform.localScale = new Vector2(transform.localScale.x * -1, transform.localScale.y); }
This code will help draw out a line [only while in game since the
wallCheckDir
andledgeCheckstart
has not been set yet] to represent your raycast. By right, your ray cast should be in a perfectly normal straight line in the game. If it appears shorter, you just need to tweak theledgeCheckX
value, or simply change outledgeCheckX
for a number instead of calling a variable. Otherwise, you may need to change your code values here:RaycastHit2D _hit = Physics2D.Raycast(transform.position + _ledgeCheckStart, _wallCheckDir, ledgeCheckX * 10);
Change the values to some solid floats or integers for the moment and test it out like that.
May 7, 2024 at 9:45 am #14542::I did what you told me to do, and I see a perfectly straight line on the charger enemy and at a perfect length, not too short, not too long. Along with adding the code you provided, this error popped up
NullReferenceException: Object reference not set to an instance of an object UnityEditor.Graphs.Edge.WakeUp () (at <5fb17ff8fbc54f4294a2425fd91a4819>:0) UnityEditor.Graphs.Graph.DoWakeUpEdges (System.Collections.Generic.List<code>1[T] inEdges, System.Collections.Generic.List</code>1[T] ok, System.Collections.Generic.List` 1[T] error, System.Boolean inEdgesUsedToBeValid) (at <5fb17ff8fbc54f4294a2425fd91a4819>:0) UnityEditor.Graphs.Graph.WakeUpEdges (System.Boolean clearSlotEdges) (at <5fb17ff8fbc54f4294a2425fd91a4819>:0) UnityEditor.Graphs.Graph.WakeUp (System.Boolean force) (at <5fb17ff8fbc54f4294a2425fd91a4819>:0) UnityEditor.Graphs.Graph.WakeUp () (at <5fb17ff8fbc54f4294a2425fd91a4819>:0) UnityEditor.Graphs.Graph.OnEnable () (at <5fb17ff8fbc54f4294a2425fd91a4819>:0)
May 7, 2024 at 9:53 am #14543::I did what you told me to do, and I see a perfectly straight line on the charger enemy and at a perfect length, not too short, not too long. Along with adding the code you provided, this error popped up
Alex, just closing and reopening Unity should fix this issue.May 7, 2024 at 11:21 am #14546::Let’s try something else,
RaycastHit2D _hit = Physics2D.Raycast(transform.position + _ledgeCheckStart, _wallCheckDir, ledgeCheckX * 10); if (_hit.collider != null &&
_hit.collider.gameObject.CompareTag("Player")_hit.collider.gameObject.GetComponent<PlayerController>()) { ChangeState(EnemyStates.Charger_Surprised); }We’ll replace the compare tag parameter and swap it out with a simpler identifier of the player to see if that helps with the detection. [It doesn’t come out properly here as the angle brackets are being confused here, but use
_hit.collider.gameObject.GetComponent<strong>"PlayerController"</strong>()
]If nothing changes, the raycast is still faulty and we’ll need to find out why. Could you send a video of you doing this
I also noticed that the charger goes into the surprised state when I jump on top of the charger; this happens randomly, too. It could either take one jump or two jumps.
Then can you also try playing around with adjusting the scale of the Charger while in play?
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: