0

I am currently working on a small project where the player will shoot enemies and score points. I have been implementing the shooting mechanic and ran into issues with Unity's collision detection. I am instantiating the bullet the exact same way for both the player and enemy, the only difference in their scripts is how the direction of the bullet is obtained. Anyways, I have a bullet behavior script that is meant to handle collisions with the bullet and the world environment. Currently, the bullet works almost as intended, with the bullets colliding with the player, enemy, and world environment. My problem occurs when the Player shoots several bullets in succession. If during this quick succession the Enemy fires a bullet towards the player, it will phase right through the player.

This is my current script for handling the bullets behavior.

public class bulletScript : MonoBehaviour
{
    private ParticleSystem bulletParticle;

    public bool spawnedByPlayer = true; // Flag to indicate if spawned by player
    public Transform impactPrefab;

    // Start is called before the first frame update
    void Start()
    {
        // Logic to ignore layers depending on object that spawned this instance.
        int playerLayer = LayerMask.NameToLayer("Player");
        int enemyLayer = LayerMask.NameToLayer("Enemy");
        int bulletLayer = LayerMask.NameToLayer("Bullet");

        int ignoreLayer = spawnedByPlayer ? playerLayer : enemyLayer;
        int otherLayer = spawnedByPlayer ? enemyLayer : playerLayer;

        Physics2D.IgnoreLayerCollision(gameObject.layer, bulletLayer);
        Physics2D.IgnoreLayerCollision(bulletLayer, ignoreLayer, true);
        Physics2D.IgnoreLayerCollision(bulletLayer, otherLayer, false);

        bulletParticle = GetComponent<ParticleSystem>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void OnCollisionEnter2D(Collision2D collision)
    {

        ContactPoint2D contact = collision.contacts[0];
        Vector2 impactDirection = -contact.normal; // Reverse the normal vector to face away from the collision

        Quaternion rotation = Quaternion.FromToRotation(Vector2.right, impactDirection);
        Vector2 position = contact.point;

        Transform impactObj = Instantiate(impactPrefab, position, rotation);

        ParticleSystem bulletParticleSystem = GetComponentInChildren<ParticleSystem>();
        ParticleSystem[] impactParticleSystems = impactObj.GetComponentsInChildren<ParticleSystem>();

        ParticleSystem.MainModule bulletMain = bulletParticleSystem.main;

        foreach (ParticleSystem impactParticleSystem in impactParticleSystems)
        {
            ParticleSystem.MainModule impactMain = impactParticleSystem.main;
            impactMain.startColor = bulletMain.startColor;
        }

        Destroy(gameObject);
    }
}

My suspicion is that this has something to do with Unity's physics engine and the collision detection. Mainly because once the Player stops shooting in quick succession, everything works as intended. My only idea to solve this collision issue was to decrease the TimeStep value in the Time Manager, hoping that it would increase the number of collision detection calls. This had no effect. My bullet collision components are fairly small, which was another possibility I explored, however, I came to the same conclusion that because the collisions are detected in all other situations it probably isn't the source of the problem.

Edit I was able to alleviate this issue by going into the object inspection tab of my projectile prefab and including the "Player" layer in the Layer Overrides of the Rigidbody2D component. While I would consider this a temporary fix, I didn't want to submit it as an answer because it doesn't help me understand the cause of my problem.

Thanks to anyone that answers or has suggestions!

  • this often happens when at speed - have you set it to continuous? – BugFinder Jul 14 '23 at 06:22
  • Yes, continuous dynamic collision detection for the bullet and player objects. I know the bug you're referring to. This isn't the same though the bullet collision detection works great, with the exclusion of this one issue. *Edit* After further testing, I'm fairly certain it is related to my code in the Start method. I think the issue is caused by the enemy and the player firing within the same frame. – Thirstymidget28 Jul 14 '23 at 07:10
  • id agree, theres a chance that code overwrites the layers.. why not just set a bullet to be a layer, and set in the matrix what it should/shouldnt hit? – BugFinder Jul 14 '23 at 07:26
  • How do you mean to set what it should and shouldn't hit in the matrix? Is this similar to what I said in the edit to my question? I don't have a problem with hard baking the fix in with Unity, but would prefer to fix the code if I can. – Thirstymidget28 Jul 14 '23 at 07:44
  • Consider using raycasts instead of relying solely on Unity's collision detection system for bullet-player collisions. You can cast a ray from the bullet's starting position to its destination, checking if it intersects with the player's collider. This approach can provide more accurate and reliable collision detection. – arlan schouwstra Jul 14 '23 at 07:48
  • I considered the use of Raycasting, however, the core gameplay loop involves dodging enemy projectiles. To my knowledge, this was not possible with Raycasting which is why I went for physics-based projectiles. – Thirstymidget28 Jul 14 '23 at 08:06

1 Answers1

0

The system you are using via Physics2D.IgnoreLayerCollision is of course extremely flawed!

If both fire a bullet "at the same time" (before the first one has arrived at the target) then the last fired bullet will overwrite the entire global layer collision matrix so if your enemy fires and then the player the enemy's bullet will no hit the player as currently the global settings are configured for a player bullet which ignores the player layer.


The simplest solution to this would be to simply also have two layers for the bullets Bullet_Player and Bullet_Enemy which you can configure once to ignore according layer(s) via the collision matrix

              | Player | Enemy | Bullet_Player | Bullet_Enemy | Default (? walls etc)
Player        |   ?    |   ?   |      ☐        |       ✔      |    ✔
-----------------------------------------------------------------------------------------
Enemy                  |   ?   |      ✔        |      ☐       |    ✔
-----------------------------------------------------------------------------------------
Bullet_Player                  |      ☐        |      ☐       |    ✔
-----------------------------------------------------------------------------------------
Bullet_Enemy                                    |      ☐       |    ✔
-----------------------------------------------------------------------------------------
Default                                                         |     ?

and then simply set the bullet's layer accordingly

gameObjct.layer = spawnedByPlayer ? LayerMask.NameToLayer("Bullet_Player") : LayerMask.NameToLayer("Bullet_Enemy");

Alternatively you can also go through excludeLayers and includeLayers and include/exclude according layers per bullet.

Those basically work like a flag mask so refer to Most common C# bitwise operations on enums for how to add/remove layers on runtime


Another alternative is a bit uncanny but you could keep track of all the players/enemies and all their existing bullets, loop over all of them and go through Physics2D.IgnoreCollision for each collider combo individually. This way you wouldn't be bound to the limited amount of layers at all but of course it is a bit hideous and might be more performance heavy

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Thank you so much for the informative response and explanation. I was glad to see that I wasn't completely off base with my suspicions. I was unaware of the collision matrix in the project settings. Out of curiosity, in the edit to my post, I mentioned that I was able to fix this issue with the Layer Override property of the Rigidbody component of my bullet. Is it possible to access this setting through the script? So instead of creating new a separate layer, I could just set the Layer Override based on the object that spawned the bullet? – Thirstymidget28 Jul 14 '23 at 08:03
  • @Thirstymidget28 ha! now myself learned something new :D Yes! you totally can set [`excludeLayers`](https://docs.unity3d.com/ScriptReference/Rigidbody2D-excludeLayers.html) and [`includeLayers`](https://docs.unity3d.com/ScriptReference/Rigidbody2D-includeLayers.html) – derHugo Jul 14 '23 at 08:10
  • Glad, you liked the idea! Unfortunately, the only way I've found to do this is directly through the Unity Editor as the Layer Override property is not exposed by the Rigidbody API at runtime, so it isn't possible to incorporate it into scripts. – Thirstymidget28 Jul 14 '23 at 13:58
  • @Thirstymidget28 hm it should be exposed via scripting also at runtime ... `GetComponent().excludeLayers |= 1 << LayerMask.NameToLayer("Enemy")` seems to do exactly what you want – derHugo Jul 14 '23 at 14:37