-1
\$\begingroup\$

When i shoot my bullet the unity explosion prefab keeps playing

I have a gun Script

using UnityEngine;
using TMPro;
public class ProjectileGunTutorial : MonoBehaviour
{
    //bullet 
    public GameObject bullet;

    //bullet force
    public float shootForce, upwardForce;

    //Gun stats
    public float timeBetweenShooting, spread, reloadTime, timeBetweenShots;
    public int magazineSize, bulletsPerTap;
    public bool allowButtonHold;

    int bulletsLeft, bulletsShot;

    //Recoil
    public Rigidbody playerRb;
    public float recoilForce;

    //bools
    bool shooting, readyToShoot, reloading;

    //Reference
    public Camera fpsCam;
    public Transform attackPoint;

    //Graphics
    public GameObject muzzleFlash;
    public TextMeshProUGUI ammunitionDisplay;

    //bug fixing :D
    public bool allowInvoke = true;

    private void Awake()
    {
        //make sure magazine is full
        bulletsLeft = magazineSize;
        readyToShoot = true;
    }

    private void Update()
    {
        MyInput();

        //Set ammo display, if it exists :D
        if (ammunitionDisplay != null)
            ammunitionDisplay.SetText(bulletsLeft / bulletsPerTap + " / " + magazineSize / bulletsPerTap);
    }
    private void MyInput()
    {
        //Check if allowed to hold down button and take corresponding input
        if (allowButtonHold) shooting = Input.GetKey(KeyCode.Mouse0);
        else shooting = Input.GetKeyDown(KeyCode.Mouse0);

        //Reloading 
        if (Input.GetKeyDown(KeyCode.R) && bulletsLeft < magazineSize && !reloading) Reload();
        //Reload automatically when trying to shoot without ammo
        if (readyToShoot && shooting && !reloading && bulletsLeft <= 0) Reload();

        //Shooting
        if (readyToShoot && shooting && !reloading && bulletsLeft > 0)
        {
            //Set bullets shot to 0
            bulletsShot = 0;

            Shoot();
        }
    }

    private void Shoot()
    {
        readyToShoot = false;

        //Find the exact hit position using a raycast
        Ray ray = fpsCam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); //Just a ray through the middle of your current view
        RaycastHit hit;

        //check if ray hits something
        Vector3 targetPoint;
        if (Physics.Raycast(ray, out hit))
            targetPoint = hit.point;
        else
            targetPoint = ray.GetPoint(75); //Just a point far away from the player

        //Calculate direction from attackPoint to targetPoint
        Vector3 directionWithoutSpread = targetPoint - attackPoint.position;

        //Calculate spread
        float x = Random.Range(-spread, spread);
        float y = Random.Range(-spread, spread);

        //Calculate new direction with spread
        Vector3 directionWithSpread = directionWithoutSpread + new Vector3(x, y, 0); //Just add spread to last direction

        //Instantiate bullet/projectile
        GameObject currentBullet = Instantiate(bullet, attackPoint.position, Quaternion.identity); //store instantiated bullet in currentBullet
        //Rotate bullet to shoot direction
        currentBullet.transform.forward = directionWithSpread.normalized;

        //Add forces to bullet
        currentBullet.GetComponent<Rigidbody>().AddForce(directionWithSpread.normalized * shootForce, ForceMode.Impulse);
        currentBullet.GetComponent<Rigidbody>().AddForce(fpsCam.transform.up * upwardForce, ForceMode.Impulse);

        //Instantiate muzzle flash, if you have one
        if (muzzleFlash != null)
            Instantiate(muzzleFlash, attackPoint.position, Quaternion.identity);

        bulletsLeft--;
        bulletsShot++;

        //Invoke resetShot function (if not already invoked), with your timeBetweenShooting
        if (allowInvoke)
        {
            Invoke("ResetShot", timeBetweenShooting);
            allowInvoke = false;

            //Add recoil to player (should only be called once)
            playerRb.AddForce(-directionWithSpread.normalized * recoilForce, ForceMode.Impulse);
        }

        //if more than one bulletsPerTap make sure to repeat shoot function
        if (bulletsShot < bulletsPerTap && bulletsLeft > 0)
            Invoke("Shoot", timeBetweenShots);
    }
    private void ResetShot()
    {
        //Allow shooting and invoking again
        readyToShoot = true;
        allowInvoke = true;
    }

    private void Reload()
    {
        reloading = true;
        Invoke("ReloadFinished", reloadTime); //Invoke ReloadFinished function with your reloadTime as delay
    }
    private void ReloadFinished()
    {
        //Fill magazine
        bulletsLeft = magazineSize;
        reloading = false;
    }
}

And a Bullet Script

using UnityEngine;


public class CustomBullet : MonoBehaviour
{
    //Assignables
    public Rigidbody rb;
    public GameObject explosion;
    public LayerMask whatIsEnemies;

    //Stats
    [Range(0f,1f)]
    public float bounciness;
    public bool useGravity;

    //Damage
    public int explosionDamage;
    public float explosionRange;
    public float explosionForce;

    //Lifetime
    public int maxCollisions;
    public float maxLifetime;
    public bool explodeOnTouch = true;

    int collisions;
    PhysicMaterial physics_mat;

    private void Start()
    {
        Setup();
    }

    private void Update()
    {
        //When to explode:
        if (collisions > maxCollisions) Explode();

        //Count down lifetime
        maxLifetime -= Time.deltaTime;
        if (maxLifetime <= 0) Explode();
    }

    private void Explode()
    {
        //Instantiate explosion
        if (explosion != null) Instantiate(explosion, transform.position, Quaternion.identity);

        //Check for enemies 
        Collider[] enemies = Physics.OverlapSphere(transform.position, explosionRange, whatIsEnemies);
        for (int i = 0; i < enemies.Length; i++)
        {
            //Get component of enemy and call Take Damage

            //Just an example!
            ///enemies[i].GetComponent<ShootingAi>().TakeDamage(explosionDamage);

            //Add explosion force (if enemy has a rigidbody)
            if (enemies[i].GetComponent<Rigidbody>())
                enemies[i].GetComponent<Rigidbody>().AddExplosionForce(explosionForce, transform.position, explosionRange);
        }

        //Add a little delay, just to make sure everything works fine
        Invoke("Delay", 0.05f);
    }
    private void Delay()
    {
        Destroy(gameObject);
    }

    private void OnCollisionEnter(Collision collision)
    {
        //Don't count collisions with other bullets
        if (collision.collider.CompareTag("Bullet")) return;

        //Count up collisions
        collisions++;

        //Explode if bullet hits an enemy directly and explodeOnTouch is activated
        if (collision.collider.CompareTag("Enemy") && explodeOnTouch) Explode();
    }

    private void Setup()
    {
        //Create a new Physic material
        physics_mat = new PhysicMaterial();
        physics_mat.bounciness = bounciness;
        physics_mat.frictionCombine = PhysicMaterialCombine.Minimum;
        physics_mat.bounceCombine = PhysicMaterialCombine.Maximum;
        //Assign material to collider
        GetComponent<SphereCollider>().material = physics_mat;

        //Set gravity
        rb.useGravity = useGravity;
    }

    /// Just to visualize the explosion range
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(transform.position, explosionRange);
    }
}

The Explosion prefab

It is not set to looping so why is it looping?

\$\endgroup\$
3
  • \$\begingroup\$ Hint: What stops your bullet from spawning a second copy of the explosion one frame after the first? And then again the frame after that? etc... \$\endgroup\$
    – DMGregory
    Nov 27 '21 at 12:20
  • \$\begingroup\$ but my bullet only spawns once, \$\endgroup\$
    – Sam
    Nov 27 '21 at 15:36
  • \$\begingroup\$ Telling us what you've tried to fix this issue will make this post look less like a code dump. \$\endgroup\$
    – Vaillancourt
    Nov 27 '21 at 16:56
0
\$\begingroup\$

This function runs every frame as long as the bullet object still exists / is active and the script is enabled:

private void Update()
{
    //When to explode:
    if (collisions > maxCollisions) Explode();

    //Count down lifetime
    maxLifetime -= Time.deltaTime;
    if (maxLifetime <= 0) Explode();
}

It looks like it's been written in a way that expects Explode() will cause the bullet to be destroyed, so it doesn't have to worry about any future frames after that. But it doesn't - not right away. You've added a 50 millisecond delay between when Explode is called and when the bullet actually gets destroyed:

private void Explode()
{
    //Instantiate explosion
    if (explosion != null) Instantiate(explosion, transform.position, Quaternion.identity);

    // ...(skipping over irrelevant code)
 
    //Add a little delay, just to make sure everything works fine
    Invoke("Delay", 0.05f);
}
private void Delay()
{
    Destroy(gameObject);
}

So let's say that on frame X, the collisions > maxCollisions test is true and we call Explode(). We spawn an instance of the explosion effect, and schedule the bullet's destruction a half-second in the future.

On frame X+1, the bullet still exists (50 ms hasn't passed yet), and it's still true that collisions > maxCollisions, so we call Explode() again. We spawn a second instance of the explosion effect.

The same repeats for frame X+2 and so on. If we're running at 60 FPS, we might spawn 3 copies of the explosion effect in the delay between the initial explosion and the destruction of the bullet, making it look like the explosion is looping when really it's just multiple copies playing in rapid succession. If you're running at a higher framerate, you might get even more copies.

The same thing can happen if the bullet reaches the end of its lifetime, or if it explodes on touch. The OnCollisionEnter() method can keep firing if the bullet keeps nudging against an enemy during its death throes, firing off more explosions.

One way to fix this is to add a bool _hasExploded = false; flag as a member variable. When you call Explode(), check if _hasExploded is true - if so, abort before you spawn a new explosion. If not, set it to true so you never spawn a second explosion after this one.

You could also destroy the bullet sooner, or use enabled = false to turn off the Update() method during the bullet's death throes. This won't stop the OnCollisionEnter() handler from being invoked, so you'd want to call Explode() there only if enabled is true.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .