Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Rapid Fire With Input System

Discussion in 'Scripting' started by TheMadHattter, Apr 13, 2022.

  1. TheMadHattter

    TheMadHattter

    Joined:
    Jan 24, 2019
    Posts:
    5
    Hello, I am trying to figure out how to have my turrets continuously fire while button is held down. Everywhere I look everyone is using Time.time or Fire1. Any help would be appreciated! Below is Some of the code:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TestScript : MonoBehaviour
    6. {
    7.     private Transform bulletParent;
    8.     private Transform cameraTransform;
    9.  
    10.     public List<GameObject> bulletPrefab = new List<GameObject>(1);
    11.     public List<Transform> TurretBarrell = new List<Transform>(1);
    12.  
    13.     private void Awake()
    14.     {
    15.         playerInput = GetComponent<PlayerInput>();
    16.         shootAction = playerInput.actions["FireTurrets"];
    17.     }
    18.  
    19.     private void OnEnable()
    20.     {
    21.         shootAction.performed += _ => ShootGun();
    22.     }
    23.     private void OnDisable()
    24.     {
    25.         shootAction.performed -= _ => ShootGun();
    26.     }
    27.  
    28.     private void ShootGun()
    29.     {
    30.         RaycastHit hit;
    31.         for (int j = 0; j < TurretBarrell.Count; j++)
    32.         {
    33.             for (int i = 0; i < bulletPrefab.Count; i++)
    34.             {
    35.                 GameObject bullet = GameObject.Instantiate(bulletPrefab[i], TurretBarrell[j].position, Quaternion.identity);
    36.                 BulletController bulletController = bullet.GetComponent<BulletController>();
    37.  
    38.                 if (Physics.Raycast(cameraTransform.position, cameraTransform.forward, out hit, Mathf.Infinity))
    39.                 {
    40.                     bulletController.target = hit.point;
    41.                     bulletController.hit = true;
    42.                 }
    43.                 else
    44.                 {
    45.                     bulletController.hit = false;
    46.                 }
    47.  
    48.             }
    49.         }
    50.     }
    51. }
    52.  
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,970
    The code above only implicitly maps input to ShootGun(). It does not decide what kind of input.

    If
    PlayerInput
    is only calling
    ShootGun() 
    once, that's where your change has to go, I assume some property in the new input system, in order to make it call ShootGun() every frame that the input is happening.

    Likely you would want to fire at a more-controllable rate, which requires that you maintain a cooldown timer.

    Cooldown timers, gun bullet intervals, shot spacing, rate of fire:

    https://forum.unity.com/threads/fire-rate-issues.1026154/#post-6646297

    GunHeat (gunheat) spawning shooting rate of fire:

    https://forum.unity.com/threads/spawning-after-amount-of-time-without-spamming.1039618/#post-6729841
     
  3. TheMadHattter

    TheMadHattter

    Joined:
    Jan 24, 2019
    Posts:
    5
    Yeah, I've seen several examples like that, and I've been trying to modify that part of the code, problem is the new input system seems to only recognize the input once regardless of how long you hold it. So ive been trying to mess around with a while loop, but to no avail. something like this"
    Code (CSharp):
    1.    private void OnEnable()
    2.     {
    3.         while (Gunheat < 10) {
    4.             Gunheat += 1;
    5.             shootAction.performed += _ => ShootGun();
    6.         }
    7.     }
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,970
    Heh, that's an excellent first try... the trick is that OnEnable() is tied to the MonoBehaviour lifecycle, and only fires once when the MonoBehaviour is ... enabled. All you are doing there is potentially attaching 10 copies of the fire script... not what you want.

    I would love to tell you what to change in the new Input system but I haven't tinkered with it much. Go checkout how to connect something like a "move left" or "move right" button because obviously those continue to generate input for the duration of them going down.

    Or alternately you might need to connect to the PRESS and the RELEASE events of a button, set and clear a boolean, then observe and process that boolean from your Update(). Either of these might be solutions, I'm afraid I just can't tell you off the top of my head.
     
  5. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,064
    A method needs to be called every N amount of seconds whilst performing an action (button down).
    It has to be moderated with a cooldown so you cannot spam the action.

    So you want to handle Button Down / Button Up events. This can be done through the
    started
    and
    canceled
    events.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3. using UnityEngine.InputSystem;
    4.  
    5. public class InputActionComponent : MonoBehaviour
    6. {
    7.     public InputActionProperty Input;
    8.     public UnityEvent ButtonDownEvent;
    9.     public UnityEvent ButtonUpEvent;
    10.  
    11.     private void OnEnable()
    12.     {
    13.         Input.action.Enable();
    14.     }
    15.  
    16.     private void Awake()
    17.     {
    18.         Input.action.started += OnInputActionStart;
    19.         Input.action.canceled += OnInputActionEnd;
    20.     }
    21.  
    22.     private void OnDisable()
    23.     {
    24.         Input.action.Disable();
    25.     }
    26.  
    27.     private void OnDestroy()
    28.     {
    29.         Input.action.started -= OnInputActionStart;
    30.         Input.action.canceled -= OnInputActionEnd;
    31.     }
    32.  
    33.     private void OnInputActionStart(InputAction.CallbackContext obj)
    34.     {
    35.         ButtonDownEvent.Invoke();
    36.     }
    37.  
    38.     private void OnInputActionEnd(InputAction.CallbackContext obj)
    39.     {
    40.         ButtonUpEvent.Invoke();
    41.     }
    42. }

    In the ButtonDownEvent assign the BulletHandler.enable = true
    In the ButtonUpEvent assign the BulletHandler.enabled = false
    This will cause the BulletHandler only to update when needed.
    Set the initial state to disabled. So it doesn't instantly start shooting without button press.

    upload_2022-4-14_1-35-12.png


    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class BulletHandler : MonoBehaviour
    4. {
    5.     public float FireRate = 0.5f;
    6.     public GameObject BulletPrefab;
    7.     public Transform BulletSpawnTransform;
    8.  
    9.     private float lastShot = float.NaN;
    10.     private float currentTime;
    11.  
    12.     private void OnEnable()
    13.     {
    14.         // Check whether you can shoot immediately
    15.         if (float.IsNaN(lastShot) || Time.time - lastShot >= FireRate)
    16.         {
    17.             Shoot();
    18.             lastShot = Time.time;
    19.             currentTime = 0f;
    20.         }
    21.         else
    22.         {
    23.             // We can't shoot yet, set the time that has passed.
    24.             currentTime = Time.time - lastShot;
    25.         }
    26.     }
    27.  
    28.     void Update()
    29.     {
    30.         // Increment time
    31.         currentTime += Time.deltaTime;
    32.  
    33.         // Can we shoot yet?
    34.         if (currentTime >= FireRate)
    35.         {
    36.             Shoot();
    37.             lastShot = Time.time;
    38.             currentTime = 0;
    39.         }
    40.     }
    41.  
    42.     private void Shoot()
    43.     {
    44.         Debug.Log("Shoot the bullet");
    45.     }
    46. }

    So on enable you check whether it should shoot instantly or there is still a cooldown left.
    In the update loop the
    currentTime
    will increment until it reaches the
    FireRate

    It'll call
    Shoot
    and reset the time.

    You can potentially use ObjectPool<T> to pool bullets so you don't have to create / destroy as much saving performance. Or use your own object pooling implementation. Or no pooling at all but I wouldn't recommend that.

    There's probably some improvement that can be done. But it's an idea you can work something out with.
    You could even separate the whole cooldown logic into its own component and reuse it elsewhere.
     
  6. TheMadHattter

    TheMadHattter

    Joined:
    Jan 24, 2019
    Posts:
    5
    Okay, That pretty much makes sense! my only question is how does does this code:

    Code (CSharp):
    1.     private void OnInputActionStart(InputAction.CallbackContext obj)
    2.     {
    3.         ButtonDownEvent.Invoke();
    4.  
    5.     }
    link to this code:

    Code (CSharp):
    1.     private void ShootGun()
    2.     {
    3.         Debug.Log("inShootgun!!!");
    4.        
    5.         RaycastHit hit;
    6.         for (int j = 0; j < TurretBarrell.Count; j++)
    7.         {
    8.             for (int i = 0; i < bulletPrefab.Count; i++)
    9.             {
    10.                 Debug.Log("Raycasting!!!");
    11.                 GameObject bullet = GameObject.Instantiate(bulletPrefab[i], TurretBarrell[j].position, Quaternion.identity);
    12.  
    13.  
    14.                 BulletController bulletController = bullet.GetComponent<BulletController>();
    15.  
    16.                 if (Physics.Raycast(cameraTransform.position, cameraTransform.forward, out hit, Mathf.Infinity))
    17.                 {
    18.                     bulletController.target = hit.point;
    19.                     bulletController.hit = true;
    20.                 }
    21.                 else
    22.                 {
    23.  
    24.                     bulletController.hit = false;
    25.                 }
    26.  
    27.             }
    28.         }


    Also for more clarification, This is my current bulletController:


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class BulletController : MonoBehaviour
    6. {
    7.     [Header("=== Bullet Attributes ===")]
    8.     [SerializeField]
    9.     public GameObject BulletImpactImage;
    10.     [SerializeField]
    11.     public float speed = 50f;
    12.     [SerializeField]
    13.     public float timeToDestroy = 3f;
    14.  
    15.     public Vector3 target { get; set; }
    16.     public bool hit { get; set; }
    17.  
    18.  
    19.     private void OnEnable()
    20.     {
    21.         Destroy(gameObject, timeToDestroy);
    22.     }
    23.  
    24.  
    25.     void Update()
    26.     {
    27.         transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
    28.         if (!hit && Vector3.Distance(transform.position, target) < .01f)
    29.         {
    30.             Destroy(gameObject);
    31.         }
    32.     }
    33.  
    34.     private void OnCollisionEnter(Collision other)
    35.     {
    36.         ContactPoint contact = other.GetContact(0);
    37.         GameObject.Instantiate(BulletImpactImage, contact.point + contact.normal * .0001f, Quaternion.LookRotation(contact.normal));
    38.         Destroy(gameObject);
    39.     }
    40. }
    41.  
     
  7. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,064
    In my own example when I press and hold the space bar, I enable the BulletHandler script. I've done this through serialization of the UnityEvent in the scene.

    Once the
    BulletHandler
    script is enabled the
    OnEnable
    and
    Update
    method will be called.
    Update
    is called every frame.
    Once the
    BulletHandler
    is disabled on button up, the
    Update
    method will no longer be called.

    The update loop in the BulletHandler script keeps track of time that has passed using
    Time.deltaTime

    Once the time has reached the threshold condition, in this case
    FireRate
    . It will call the
    Shoot
    method. It will also keep track when it has shot the last time and resets the current time.

    Code (CSharp):
    1. void Update()
    2.     {
    3.         // Increment time
    4.         currentTime += Time.deltaTime;
    5.         // Can we shoot yet?
    6.         if (currentTime >= FireRate)
    7.         {
    8.             Shoot();
    9.             lastShot = Time.time;
    10.             currentTime = 0;
    11.         }
    12.     }
    There are other ways to do this. It could've been one huge MonoBehaviour class, could've been a singleton input class where you register to. Could've been a Scriptable Object event. Whatever you can imagine to create.
    It depends on your needs and what you're comfortable with.

    Personally I like to separate functionality into components. Hence I mentioned that you could even separate the cooldown logic into its own component. You'd activate / deactivate the cooldown component on button press. The cooldown logic would call another UnityEvent when enough time has passed. Then a Bullet factory class can instantiate the bullet. The bullet itself then has some component containing movement logic and another component for collision logic.
    You don't have to do it. but it's an idea.