Forum begins after the advertisement:
[Part 3.5] After jumping and falling, damage is not counted.
Home › Forums › Video Game Tutorial Series › Creating a Metroidvania in Unity › [Part 3.5] After jumping and falling, damage is not counted.
- This topic has 28 replies, 4 voices, and was last updated 6 months, 2 weeks ago by
Da.
-
AuthorPosts
-
February 7, 2025 at 9:11 pm #17210::
Hi Da,
It’s great to hear that you’re diving into game development with Unity! Your issue with the discrepancy between the animation and the impact is quite common, especially when dealing with attacks and hitboxes.
Here are a few approaches you can consider to ensure that the attack lasts for the entire animation:
-
Animation Events: Unity allows you to add events to your animations. You can place an event at the point in your attack animation where you want the hit detection to occur. This way, you can ensure that the impact is registered precisely when the animation reaches that point. To do this:
- Open your animation in the Animation window.
- Find the frame where you want to trigger the hit.
- Right-click and select “Add Animation Event.”
- In the inspector, you can assign a function that will handle the hit registration.
-
Adjust Hitbox Size and Timing: As you’ve discovered, increasing the size of your hitbox can help. You might also consider adjusting the timing of when the hitbox is active. For example, you could activate the hitbox a little before the animation reaches the point of impact and deactivate it after.
-
Using Triggers: Instead of directly registering a hit on the animation’s frame, you can use triggers to manage hits. For example, when the attack animation starts, you set a boolean variable that indicates the attack is happening, and then check for hits in the Update method based on that variable.
-
Physics Overlap: If you’re using colliders, you might want to utilize
Physics.OverlapSphere
or similar methods to check for hits in a certain area around the character during the attack. This can give you a more forgiving hit detection system. -
Animation Blending: If your character’s attack animations blend into other animations (like standing or running), make sure that the transitions aren’t causing any issues with your hit detection. Sometimes, tweaking the blend tree can help with the timing of when the hit is registered.
Here’s a simple example of using an animation event:
public class CharacterAttack : MonoBehaviour { public void RegisterHit() { // Your hit registration logic here Debug.Log("Hit registered!"); } }
In your animation, add an event that calls
RegisterHit()
at the appropriate moment.Keep experimenting with these techniques, and you’ll find the right balance that suits your gameplay. Don’t hesitate to reach out if you have more questions or need further clarification on any of these methods!
Happy game developing!
February 7, 2025 at 11:03 pm #17211::@lukaskazda thank you for popping in to help. It is much appreciated!
@Da, regarding the issue of expanding the duration that the hitbox exists, we’ll need to modify the attack mechanics so that it is possible to keep the attack active for multiple frames.
void Attack() { timeSinceAttack += Time.deltaTime; attackActiveTime -= Time.deltaTime; if(attack && (timeSinceAttack >= timeBetweenAttack || attackActiveTime > 0)) { timeSinceAttack = 0; attackActiveTime = 0.1f; // Keep the attack active for 0.1 seconds. You can adjust this accordingly. anim.SetTrigger("Attacking"); if(yAxis == 0 || yAxis < 0 && Grounded()) { Hit(SideAttackTransform, SideAttackArea); } else if(yAxis > 0) { Hit(UpAttackTransform, UpAttackArea); } else if (yAxis < 0 && !Grounded()) { Hit(DownAttackTransform, DownAttackArea); } } }
I don’t recommend keeping the attack active for the entire duration of the animation though. That will look a little weird visually, and make the attack very powerful. Anywhere between 0.1 or 0.2 seconds is a healthy value.
February 7, 2025 at 11:11 pm #17212::Hi Lukas! It sounds like you’re on the right track with your testing and experimentation. To help you isolate the issue with hit detection and overlapping colliders in Unity, here are a few suggestions you might find helpful:
-
Increase OverlapBox Size: As you mentioned, increasing the size of the
OverlapBox
can help ensure that it overlaps with the walls during your attacks more consistently. If you haven’t already, try increasing the height of theSideAttackArea
vector significantly (e.g., 4 times or more) to see if that resolves the inconsistency. -
Adjust Gravity Settings: Lowering the gravity settings can indeed give your player a longer time to fall, which can help you test the hit detection more thoroughly. You can do this by adjusting the gravity scale in your Rigidbody2D component.
-
Animation Events: If you want to trigger hit detection on every frame of your attack animation, consider using Animation Events in Unity. You can create an event in your attack animation that calls a method to check for collisions or overlaps at specific frames of the animation. This way, you can manually check for hits as the animation progresses.
-
Continuous Hit Checking: Instead of relying solely on the
OverlapBox
method, you could also use raycasting or a collider trigger to continuously check for overlaps during the attack animation. This way, you can detect collisions more reliably throughout the attack animation’s duration. -
Debugging OverlapBox: To visualize what’s happening, you can use
Debug.DrawLine
to draw theOverlapBox
in the Scene view. This can help you see if the box is registering overlaps correctly during the player’s attack.
Here’s a simple example of how to implement an Animation Event in Unity:
public class PlayerAttack : MonoBehaviour { public BoxCollider2D attackCollider; // This method can be called as an Animation Event public void CheckForHits() { Collider2D[] hits = Physics2D.OverlapBoxAll(attackCollider.bounds.center, attackCollider.bounds.size, 0f); foreach (var hit in hits) { if (hit.CompareTag("AttackableWall")) { // Handle hit logic here Debug.Log("Hit: " + hit.name); } } } }
To use this code:
- Attach it to your player GameObject.
- Create an Animation Event in your attack animation timeline, and link it to the
CheckForHits
method.
By implementing these suggestions, you should be able to better understand and resolve the hit detection issue you’re encountering. Good luck with your game development, and feel free to ask if you have more questions!
February 7, 2025 at 11:54 pm #17213::Hello. Thanks for the provided code. But I wanted something a little different (I probably didn’t make myself clear earlier). I want information about hitting the enemy to be collected for a short time after the attack. That is, hits are registered not only at the moment of pressing the button, but also after (for some time).
February 7, 2025 at 11:59 pm #17214::There are two problems. The first is how to “extend” the attack after pressing the button. The second is how to add colliders to an array without replacing it. Unfortunately, these problems are causing me great difficulties so far. But I’m not giving up)
February 8, 2025 at 2:05 am #17215::I dont know if i understood the goal correctly. if you want every frame of your attack to draw a new boxcollider2D which then detects a collision with the enemy, you could use a coroutine, i think(?). It could look like this:
List<Collider2D> collisionsOfAllFrames = new List<Collider2D>(); IEnumerator WaitForFrames(int attackFrameCount) { for (int i = 0; i < attackFrameCount; i++) { Collider2D[] detectedCollisions = Physics2D.OverlapBox(boxCenter, halfExtents, boxOrientation, layerMask); collisionsOfAllFrames.Add(detectedCollisions[0]); //this always adds the first collision registered by the //overlapbox, if info of multiple collisions shall be //gathered, you need a List<Collider2D[]> (a List of arrays, //aka 2 dimensional array) yield return new WaitForEndOfFrame(); } Debug.Log("Waited for " + attackFrameCount + " frames."); }
i hope i am not talking nonsense here XD
has upvoted this post. February 8, 2025 at 3:11 am #17216::Your approach to using a coroutine to detect collisions over multiple frames is a good idea, and you’re on the right track! However, there are a few adjustments and improvements that could help make your code more effective and clear. Here’s a refined version of your coroutine, along with some explanations:
-
Use a List to Store All Collisions: Instead of just storing the first collision found in each frame, you should collect all collisions in each frame.
-
Avoid Index Out of Range: When accessing the
detectedCollisions
array, ensure you check if there are any collisions detected to avoidIndexOutOfRangeException
. -
Consider Frame Timing: Depending on the specific use case, you might want to control how long you wait between frames.
Here’s an updated version of your coroutine:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Attack : MonoBehaviour { public Vector2 boxCenter; // Center of the box collider public Vector2 halfExtents; // Half size of the box collider public float boxOrientation; // Rotation of the box collider public LayerMask layerMask; // Layer mask to filter collisions List<List> collisionsOfAllFrames = new List<List>(); IEnumerator WaitForFrames(int attackFrameCount) { for (int i = 0; i < attackFrameCount; i++) { Collider2D[] detectedCollisions = Physics2D.OverlapBoxAll(boxCenter, halfExtents, boxOrientation, layerMask); List currentFrameCollisions = new List(detectedCollisions); collisionsOfAllFrames.Add(currentFrameCollisions); yield return new WaitForEndOfFrame(); } Debug.Log("Waited for " + attackFrameCount + " frames."); // Process the collisionsOfAllFrames list as needed here } }
Key Changes:
- Use
OverlapBoxAll
: This method returns all colliders that overlap the specified box, allowing you to collect multiple collisions at once. - Store Collision Lists: Each frame’s collisions are stored in a
List
and then added to the mainList<List>
so that you can keep track of all frames’ collisions. - Initialization and Usage: Make sure to initialize
boxCenter
,halfExtents
,boxOrientation
, andlayerMask
as needed. You can call this coroutine usingStartCoroutine(WaitForFrames(attackFrameCount));
from another method, such as an attack trigger.
This setup should give you a solid base to work with collision detection across multiple frames in Unity. If you have any further questions or specific scenarios you want to discuss, feel free to ask!
has upvoted this post. February 8, 2025 at 3:35 pm #17220::There are two problems. The first is how to “extend” the attack after pressing the button. The second is how to add colliders to an array without replacing it. Unfortunately, these problems are causing me great difficulties so far. But I’m not giving up)
Do you mean you want the attack hitbox to be present even after the attack animation finishes?Regarding the colliders in an array, what are you aiming to achieve with that?
February 8, 2025 at 6:01 pm #17222::Not quite. Let’s call the attack method itself “Attack” and the attack animation “Attack_1”. In Attack, when the button is pressed and the attack silence time has passed (you can attack), a hit is registered. But the problem is that registration only happens at this moment. I want the registration to continue for some time after pressing the button (without having to press the button again), regardless of Attack_1 (I’m happy with the animation playback, it doesn’t matter here in principle, as I think it doesn’t matter. You can set it up later). For example, take circular attacks or sustained attacks (for example, in dark souls). They use the same logic, that is, after pressing the key, hits are registered for a while. About the array of colliders. This is a separate issue (or maybe a common one with the previous one). Let’s say we’ve made the “Hit” method work for a while after pressing a key. Now we need to record all the colliders that fall under our area of attack. This does not pose any problems if the method itself is instantaneous (only at the moment when the attack is pressed). When it should last for a certain amount of time, the question arises how to remember all the colliders that were hit at these points in time. This point is still unclear to me, although the previous post suggested to me that we should introduce a common array of colliders and write there from a buffer (another array of colliders), everything that got into it at some point in time. After that, there is also a problem of how to eliminate repetitions so that there is no double damage (that is, to exclude repeated hits of identical colliders).
February 8, 2025 at 10:40 pm #17223::Yes, that’s exactly what you need. Now, for some time after the attack, the collisions that were touched are collected. Now the problem is the extra damage done to the enemy. Do you know a way to remove duplicate collisions from this array (not a buffer, but a shared one)? So that there is no double damage. I think it’s possible to add non-repeating data to a new array each time to avoid double damage. But it seems this operation is a bit expensive. I wanted to clarify if there are alternatives to this approach? And thanks again, not only for the help, but also for the attached codes to make it easier to get acquainted with the new solutions!
February 9, 2025 at 9:31 am #17227::i guess you could also attach and configure a boxcollider2d in the coroutine (for the duration of however many frames you want), instead of creating a overlapbox every frame. and then at the end of the coroutine you destroy(or remove from player gameobject(?)) the boxcollider2d. this would eliminate the problem of having multiple collisions on the same enemy with a single attack, however if an enemy is exactly on the edge of the spawned boxcollider2d and moves in and out it might trigger twice. to fix that, you could do a list that collects collisions, and on adding a new collision you check if the list already has an object with the same enemy gameobject as its triggersource, if it doesnt, add to list and call the deal damage function. i am a bit to lazy to figure out a code for this right now, its 3 am for me xD sorry
February 9, 2025 at 11:11 am #17228::No problem, Lukas! It sounds like you’re trying to manage enemy collisions in a Unity game, and you’re considering using a
BoxCollider2D
to simplify your collision detection for a temporary attack. Here’s how you can implement the idea you have in mind.Step 1: Create a Coroutine to Manage the BoxCollider2D
You can create a coroutine that will enable a
BoxCollider2D
, check for collisions, and then disable or destroy it after a certain duration.Step 2: Use a List to Track Collisions
You can maintain a list of enemies that have already been hit to prevent applying damage multiple times.
Sample Code
Here’s an example of how you might implement this:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerAttack : MonoBehaviour { public BoxCollider2D attackCollider; // Assign this in the Inspector public float attackDuration = 0.5f; // Duration the collider is active private List hitEnemies = new List(); private void Start() { // Make sure the collider is disabled at the start attackCollider.enabled = false; } public void PerformAttack() { StartCoroutine(AttackCoroutine()); } private IEnumerator AttackCoroutine() { // Enable the collider attackCollider.enabled = true; // Wait for the specified duration yield return new WaitForSeconds(attackDuration); // Disable the collider attackCollider.enabled = false; // Clear the list for future attacks hitEnemies.Clear(); } private void OnTriggerEnter2D(Collider2D collision) { // Check if the collided object is an enemy if (collision.CompareTag("Enemy")) { // Check if we have already hit this enemy if (!hitEnemies.Contains(collision.gameObject)) { // Add the enemy to the list and deal damage hitEnemies.Add(collision.gameObject); DealDamage(collision.gameObject); } } } private void DealDamage(GameObject enemy) { // Implement your damage logic here Debug.Log($"Dealt damage to {enemy.name}"); } }
Explanation:
- BoxCollider2D: The collider is assigned in the Inspector and is disabled by default.
- PerformAttack: This method starts the coroutine to handle the attack.
- AttackCoroutine: This coroutine enables the collider, waits for the specified duration, and then disables it.
- OnTriggerEnter2D: This method checks for collisions with enemies. If an enemy collides and is not already in the
hitEnemies
list, it adds the enemy and calls theDealDamage
method. - DealDamage: This method contains the logic to apply damage to the enemy.
Notes:
- Make sure that your enemy GameObjects have the tag “Enemy” set in the Inspector.
- Adjust the
attackDuration
as needed to fit your game. - You may want to implement additional logic to handle what happens when an enemy takes damage or is defeated.
Feel free to modify this code to suit your specific gameplay mechanics! If you have any further questions or need clarification, just let me know.
February 9, 2025 at 1:13 pm #17237::Not quite. Let’s call the attack method itself “Attack” and the attack animation “Attack_1”. In Attack, when the button is pressed and the attack silence time has passed (you can attack), a hit is registered. But the problem is that registration only happens at this moment. I want the registration to continue for some time after pressing the button (without having to press the button again), regardless of Attack_1 (I’m happy with the animation playback, it doesn’t matter here in principle, as I think it doesn’t matter. You can set it up later). For example, take circular attacks or sustained attacks (for example, in dark souls). They use the same logic, that is, after pressing the key, hits are registered for a while. About the array of colliders. This is a separate issue (or maybe a common one with the previous one). Let’s say we’ve made the “Hit” method work for a while after pressing a key. Now we need to record all the colliders that fall under our area of attack. This does not pose any problems if the method itself is instantaneous (only at the moment when the attack is pressed). When it should last for a certain amount of time, the question arises how to remember all the colliders that were hit at these points in time. This point is still unclear to me, although the previous post suggested to me that we should introduce a common array of colliders and write there from a buffer (another array of colliders), everything that got into it at some point in time. After that, there is also a problem of how to eliminate repetitions so that there is no double damage (that is, to exclude repeated hits of identical colliders).
If I understand you correctly, you want your attacks to be active for multiple frames right? Something like this:
Where you want the attack to be active for multiple frames?
The code I shared above works like that, but it is missing an array that records all hit enemies (so that enemies who are hit don’t get hit again) for each attack.
I will be doing a stream tomorrow. I can address this on the stream.
February 9, 2025 at 10:55 pm #17244::Yes, that’s it. I saw this message late, but I’ve already figured it out (not without the help of your posts). I used a list, entered all the colliders that were hit within 0.2 seconds (I used enumeration, a cycle of five times 0.04 seconds) and, so that there would be no repeated damage, I gave invulnerability to the hit enemy (for a few seconds).
has upvoted this post. February 10, 2025 at 1:48 pm #17248::Yes, that’s it. I saw this message late, but I’ve already figured it out (not without the help of your posts). I used a list, entered all the colliders that were hit within 0.2 seconds (I used enumeration, a cycle of five times 0.04 seconds) and, so that there would be no repeated damage, I gave invulnerability to the hit enemy (for a few seconds).
That’s great to hear.
If you don’t mind, I’ll really appreciate it if you can share your code here.
February 14, 2025 at 4:19 pm #17253::Code using attack frames and invulnerability. Character:
private IEnumerator Attack() { TimeSinceAttack += Time.deltaTime; if (Input.GetButtonDown("Attack") && TimeSinceAttack >= TimeBeetwenAttack) { //attack = true; TimeSinceAttack = 0f; anim.SetTrigger("Attacking"); for (int i = 0; i < 5; i++) { Hit(SideAttackTransform, SideAttackArea); yield return new WaitForSeconds(0.08f); } /*yield return new WaitForSeconds(0.15f); Hit(SideAttackTransform, SideAttackArea); //attack = false; */ } } private void Hit(Transform _attackTransform, Vector2 _attackArea) { Collider2D[] objectsToHit; objectsToHit = Physics2D.OverlapBoxAll(_attackTransform.position, _attackArea, 0, attackableLayer); //Debug.Log($"Attack Position: {_attackTransform.position}, Area: {_attackArea}"); for (int i = 0; i < objectsToHit.Length; i++) { if (objectsToHit[i].GetComponent<Enemy_1>() != null && !Enemy_1.xz.invincible) { objectsToHit[i].GetComponent<Enemy>().EnemyHit(damage, (transform.position - objectsToHit[i].transform.position).normalized, 100); objectsToHit[i].GetComponent<Enemy_1>().Invincible_1(); } } if (objectsToHit.Length > 0) { Debug.Log("Hit!"); } else if (objectsToHit.Length <= 0) { Debug.Log("No Hit!"); } }
Enemy:
[SerializeField] public bool invincible = false;public IEnumerator Invincible() { invincible = true; yield return new WaitForSeconds(0.4f); invincible = false; } public void Invincible_1() { StartCoroutine(Invincible()); }
has upvoted this post. -
-
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: