Alternative way to fix multiple projectile firing in Laser Defender?

So in Unity2D lecture 93, there’s a bug with the “FireContinuously coroutine” where pressing multiple fire buttons result in the player ship permanently firing their weapon. The lecture addresses this by simply removing any additional keybinds, but I don’t want to remove the extra keybinds for convenience purposes. Any alternatives to fixing this problem?

Here’s my current source code as of 7/27/2020, 5:50pm

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;

using Random = UnityEngine.Random;
public class PlayerScript : MonoBehaviour
{
    //Configuration parameters
    [SerializeField] float moveSpeed = 10f;
    [SerializeField] GameObject shipWeapon;
    [SerializeField] float fireRate = 0.1f;
    [SerializeField] float projectileSpeed = 25f;
    [SerializeField] float randomFactor = 1f;

    [SerializeField] float padding = 0.1f;

    //Debug parameters
    [SerializeField] bool ToggleDebugMode = false;
    [SerializeField] bool DebugPosition = false;
    [SerializeField] bool DebugMoveTilt = false;
    [SerializeField] bool DebugTestFire = false;

    //Coroutines
    Coroutine firingCoroutine;

    //Variables
    float xMin, xMax;
    float yMin, yMax;

    // Start is called before the first frame update
    void Start()
    {
        RestrainMovement();
        //StartCoroutine(PrintAndWait());
    }

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

    /*****
     * FIRE CONTINUOUSLY
     * 
     * This enables the weapon to fire continuously while the FireWeapon() button is held.
     * 
     * This entire code is inside a while loop, to ensure it continuously operates while the FireWeapon() button is held.
     * 
     * The ship fires the weapon when "Fire1" is pressed, via Edit->Project Settings->Input Manager.
     * Currently it should be binded to
     * -[z] & [j] on keyboard,
     * -PS???
     * -XBOX???
     * -[ZR](Joystick Button 7) & [A](Joystick BUtton 2) on Nintendo Switch Pro.
     * 
     * GameObject "laser" variable is declared, and creates a clone of whatever is attached in "shipWeapon" GameObject.
     * transform.position means the laser will be created at the ship's location.
     * Quarternion.identity simply tells the program to not rotate the laser.
     * as GameObject ensures the objects spawns as a GameObject.
     * 
     * laser.GetComponent<RigidBody2D>().velocity retrieves the value of velocity from RigidBody2D component within the laser GameObject.
     * laser.GetComponent<RigidBody2D>().velocity is assigning a velocity or speed of the projectile upon being spawned.
     * 
     * WARNING: There's a bug that lets you fire more than one weapon if multiple FireWeapon() buttons are pressed.
     * Please fix if you can!
     *****/
    IEnumerator FireContinuously()
    {
        while (true)
        {
            GameObject laser = Instantiate(shipWeapon, transform.position, Quaternion.identity) as GameObject;
            laser.GetComponent<Rigidbody2D>().velocity = new Vector2(projectileSpeed, Random.Range(-randomFactor, randomFactor));

            yield return new WaitForSeconds(fireRate);
        }
    }

    /*****
     * PRINT AND WAIT
     * 
     * Made as an introduction to StarCoroutine & IEnumerator. Can be ignored.
     *****/
    /*
    IEnumerator PrintAndWait()
    {
        Debug.Log("Next message will be delayed!");
        yield return new WaitForSeconds(3);
        Debug.Log("This is delayed by 3 sec!");
    }
    */

    /*****
     * RESTRAIN MOVEMENT
     * 
     * Limit's the player's ship position to be in the game screen.
     * 
     * Declares a Camera type variable, necessary to access ViewportToWorldPoint.
     * The coordinate and position of the camera is as follows:
     * -(0, 0): Bottom-left
     * -(1, 0): Bottom-right
     * -(0, 1): Top-left
     * -(1, 1): Top-right
     * 
     * "xMin, xMax, yMin, yMax," variables is used to establish boundary coordinates on the camera,
     * then the camera's coordinates are converted via "gameCamera.ViewportToWorldPoint(new Vector3(x, y, z))"
     * to standard object coordinates.
     * Padding is in the equation to ensure the ship is semi-fully onscreen instead of half-screen.
     * xMax has a max border of 0.67f to ensure players aren't killed by enemy ships/attacks offscreen to the right,
     * and can see the enemy ship/attack coming.
     *****/
    private void RestrainMovement()
    {
        Camera gameCamera = Camera.main;

        xMin = gameCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).x + padding;
        xMax = gameCamera.ViewportToWorldPoint(new Vector3(0.67f, 0, 0)).x - padding;

        yMin = gameCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).y + padding;
        yMax = gameCamera.ViewportToWorldPoint(new Vector3(0, 1f, 0)).y - padding;
    }

    /*****
     * MOVESHIP
     * 
     * Instructs the game to move the playerShip, via Edit->Project Settings->Input Manager.
     * By default, "Horizontal" should be binded to, "Left Arrow, Right Arrow, A, and D,"
     * while "Vertical" should be binded to, "Up arrow, Down arrow, W, and D."
     * 
     * Using [Input.GetAxis("String name");] is better than [Input.GetKey("String name" or KeyCode.(Keycode))]
     * Because Input.GetAxis is compatible with controllers.
     * 
     * Putting in "Time.deltaTime" ensures the player's ship moves at the same speed, regardless of the PC's framerate.
     * Without it, everything in the game would move slower on weaker PCs, and faster on stronger PCs.
     * 
     * Mathf.Clamp() is there to ensure the player's ship does not fly offscreen.
     *****/
    private void MoveShip()
    {
        //Declares and makes these variables equal to the "tilt rate" of the controller/keyboard.
        var deltaX = Input.GetAxis("Horizontal") * Time.deltaTime * moveSpeed;
        var deltaY = Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;

        //newXPos & newYPos are declared as the current gameObject's position,
        //plus the direction the player has pressed, or tilted on controller.
        var newXPos = Mathf.Clamp(transform.position.x + deltaX, xMin, xMax);
        var newYPos = Mathf.Clamp(transform.position.y + deltaY, yMin, yMax);
        
        //Assigns this gameObject's position to be equivalent to newXPos & newYPos.
        transform.position = new Vector2(newXPos, newYPos);

        //DEBUG
        if (ToggleDebugMode)
        {
            if (DebugPosition)
            {
                //Returns the player ship's position.
                Debug.Log(transform.position.x + ", " + transform.position.y);
            }
            if (DebugMoveTilt)
            {
                //Returns the tilt rate of the player's direcitonal input. 
                Debug.Log(deltaX + ", " + deltaY);
            }
        }
    }

    /*****
     * FIRE WEAPON
     * 
     * Shoots a laser beam.
     * 
     * Refer to IEnumerator FireContinuously().
     * Earlier a "Coroutine firingCoroutine" was declared. Firing Coroutine is assigned to the IEnumerator FireContinuously()
     * So it can be easily turned off when the FireWeapon() button is lifted.
     * 
     * 
     * 
     * BELOW IS PLANNED FEATURE FOR THIS METHOD.
     * 
     * Shoots the currently equipped weapon.
     *****/
    private void FireWeapon()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            firingCoroutine = StartCoroutine(FireContinuously());

            if (DebugTestFire)
            {
                Debug.Log("Pew!");
            }
        }
        if (Input.GetButtonUp("Fire1"))
        {
            StopCoroutine(firingCoroutine);
        }
    }
}

Hi,

Here’s my solution. Maybe you could test it.

bool isFiring = false;

void Start ()
{
    SetUpMoveBoundaries();
    StartCoroutine(FireContinuously());
}

void Update()
{
    isFiring = Input.GetButton("Fire1"); // you can add more keys and conditions here
}
 
IEnumerator FireContinuously()
{
    WaitForSeconds delay = new WaitForSeconds(projectileFiringPeriod);
 
    while (true)
    {
        if (isFiring) {
            GameObject laser = Instantiate(
                    laserPrefab,
                    transform.position,
                    Quaternion.identity) as GameObject;

            laser.GetComponent<Rigidbody2D>().velocity = new Vector2(0f, projectileSpeed);
 
            yield return delay;
        }
        
        yield return null; // come back in the next frame
    }
}

Did this fix it?


See also:

Kind of. It no longer fires continuously permanently after pressing multiple Fire1 buttons, but now doing so makes the gun permanently shoot extra projectiles.

I think something has to be changed in the FireWeapon() method.

I just got an idea. Is there a way to count the number of a specific coroutines active?

Actually, my solution was supposed to prevent multiple shots. When you keep the key pressed, the lasers appear only after x seconds defined via delay.

Of specific coroutines? What you could do is to store every Coroutine object in a List. When you stop a coroutine or when a coroutine stops itself, you could remove that specific Coroutine object from the list. The active coroutines are the number of Coroutine objects in the List.

Perhaps I could try something like this after I finish my lecture’s current session.

Edit: I found a SIGNIFICANTLY easier solution! Create a bool variable “canFire” that’s set to true in Public class PlayerScript. Within the FireContinuously() coroutine, make it instantiate the laser object if canFire is true, but canFire will be briefly toggled to false after firing.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms