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.

Viewing 16 posts - 21 through 36 (of 36 total)
  • Author
    Posts
  • #17210
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    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:

    1. 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.
    2. 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.

    3. 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.

    4. 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.

    5. 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!

    #17211
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::

    @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.

    #17212
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    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:

    1. 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 the SideAttackArea vector significantly (e.g., 4 times or more) to see if that resolves the inconsistency.

    2. 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.

    3. 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.

    4. 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.

    5. Debugging OverlapBox: To visualize what’s happening, you can use Debug.DrawLine to draw the OverlapBox 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:

    1. Attach it to your player GameObject.
    2. 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!

    #17213
    Da
    Level 3
    Participant
    Helpful?
    Up
    0
    ::

    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).

    #17214
    Da
    Level 3
    Participant
    Helpful?
    Up
    0
    ::

    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)

    #17215
    Lukas Kazda
    Level 24
    Former Patron
    Helpful?
    Up
    1
    ::

    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.
    #17216
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    1
    ::

    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:

    1. 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.

    2. Avoid Index Out of Range: When accessing the detectedCollisions array, ensure you check if there are any collisions detected to avoid IndexOutOfRangeException.

    3. 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 main List<List> so that you can keep track of all frames’ collisions.
    • Initialization and Usage: Make sure to initialize boxCenter, halfExtents, boxOrientation, and layerMask as needed. You can call this coroutine using StartCoroutine(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.
    #17220
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::
    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?

    #17222
    Da
    Level 3
    Participant
    Helpful?
    Up
    0
    ::

    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).

    #17223
    Da
    Level 3
    Participant
    Helpful?
    Up
    0
    ::

    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!

    #17227
    Lukas Kazda
    Level 24
    Former Patron
    Helpful?
    Up
    0
    ::

    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

    #17228
    Ser Apustaja
    Level 6
    AI Assistant
    (BETA)
    Helpful?
    Up
    0
    ::

    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:

    1. BoxCollider2D: The collider is assigned in the Inspector and is disabled by default.
    2. PerformAttack: This method starts the coroutine to handle the attack.
    3. AttackCoroutine: This coroutine enables the collider, waits for the specified duration, and then disables it.
    4. 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 the DealDamage method.
    5. 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.

    #17237
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::
    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:

    Attack frames

    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.

    #17244
    Da
    Level 3
    Participant
    Helpful?
    Up
    1
    ::

    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.
    #17248
    Terence
    Level 31
    Keymaster
    Helpful?
    Up
    0
    ::
    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.

    #17253
    Da
    Level 3
    Participant
    Helpful?
    Up
    1
    ::

    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.
Viewing 16 posts - 21 through 36 (of 36 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: