Search Unity

Shooting multiple weapons

Discussion in 'Scripting' started by Alexbeav, May 1, 2017.

  1. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    Hello,

    I am trying to implement a system where a controller (MechController) is accessing a child object's (Mech) weapons prefabs and fires them according to their rate of fire. Each weapon prefab has its own damage and rate of fire variables.

    I'm trying to implement it in such a way that the weapon prefabs can be reused on different Mech prefabs so that for example, Mech1 can have 1x Gun and 1x Missile and Mech2 can have 2xGun and 2xMissile. The weapons themselves do not change, but depending on the Mech selected by the player, multiple weapons may be firing at the same time.

    What should I use to store the weapons as in the script? List, array? An extra consideration is that the slots may not always be full i.e. Mech 2 might only have 2xGun equipped or 1xGun and 1xMissile even though it has more slots free.

    Right now I am able to fire all weapons and while their damage is working properly, they all have the same rate of fire.

    Any starting points will be greatly appreciated!

    Thank you.
     
  2. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    List/Array is a minor detail here.. either would work. whichever you feel is more comfortable.
    Maybe you need to setup the rate of fire to be different? :) :)

    And as for empty slots.. you can check an array (or list) for empty entries. With a list, if you don't explicitly add an empty entry, you can iterate the list up to '.Count - 1' and you won't hit anything "empty". :)

    Hope that helps/points you in some decent direction.
     
  3. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    So this is what I have right now... I put the weapons into an array.

    Code (CSharp):
    1.  
    2.  
    3. private float nextFire;
    4. public float fireRate;
    5. public GameObject [] weapons;
    6. public Transform [] hardpoints;
    7.  
    8. if (Input.GetButton("Fire" + m_PlayerNumber) && Time.timeScale > 0 && Time.time > nextFire)
    9. {
    10. nextFire = Time.time + fireRate;
    11.  
    12. for (int i = 0; i < weapons.Length; i++)
    13.       {
    14.          if(weapons[i] != null)
    15.                {
    16.                Instantiate(weapons[i], hardpoints[i].position, hardpoints[i].rotation);
    17.                }
    18.       }
    19. }
    20.  


    This goes through the array of weapons and instatiates them at the next hardpoint location. However as you can see, fireRate is the same across the board.

    Now inside the GameObject weapon I have a float called fireRate that I want to use instead of the above general fire rate. So the flowchart would be something like this:

    - Access the array
    - If there is a weapon, check its fire rate
    - If it can fire (fireRate), instantiate it at the hardpoint location/rotation.
    - If it can't fire, move to and evaluate the next weapon in the array (break; )

    Thanks!
     
    Last edited: May 2, 2017
  4. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    that code doesn't appear to be even correct.. You don't use indexers in the loop; maybe you re-wrote this & didn't copy/paste?
    Please try to use code tags , also when you're posting code :)

    You've gotta store the next fire time for each weapon/ammo type :)
     
  5. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    Sorry about that - first time posting code :( I updated it.
     
  6. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Much nicer. :)

    If you don't have any weapons around other than the instantiated ones, you can still store the next fire time (you can take the fire rate from the script on the prefab and add that to the current time, like you do now with the fire rate.)
    You could , as just 1 example, make an array that is the same size as the weapons and store the firing times there. Also, you'd have to store the rates, to know how to increment them. Shouldn't be too hard. Then instead of looping over a general fire, you can loop over the objects (as you're doing) but check their times, instead.
    Just make sure that you update the fire rates if you add/alter/remove weapons, etc..
     
  7. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    So this is what it looks like right now, and it's working fine (no errors even if some weapon slots are empty), however the fire rate is uniform accross weapons.

    The weapon's fireRate variable is accessed through:
    var rate = weapons.GetComponent<PlayerWeaponDamage>();
    var fireRate = rate.fireRate;

    Code (CSharp):
    1. if (Input.GetButton("Fire" + m_PlayerNumber) && Time.timeScale > 0 && Time.time > nextFire)
    2.                 {
    3.                 for (int i = 0; i < weapons.Length; i++)
    4.                     {
    5.                         if(weapons[i] != null)
    6.                         {
    7.                         var rate = weapons[i].GetComponent<PlayerWeaponDamage>();
    8.                         var fireRate = rate.fireRate;
    9.                         nextFire = Time.time + fireRate;
    10.                         Instantiate(weapons[i], hardpoints[i].position, hardpoints[i].rotation);
    11.                         GetComponent<AudioSource>().Play ();
    12.                         }
    13.                     }
    14.                 }
     
  8. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    The updated version doesn't quite make sense to me for achieving your goal. When ever you add/load/equip a weapon (not sure how you go about that in your game), that's when you want to assign the fire rate, I would think.
    When you fire, you want to not check the fire rate on the whole group at once, but check the current time against the nextFire for each weapon individually and if it's the right time, then fire.. :)
    And you didn't include an array/list for nextFire, either. Once you setup the fireRate outside of where it is now, that'd be an array/list, too. :)
     
  9. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    Thank you for your reply. How you translate that to flowchart or code?
     
  10. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    well, if there's a place where you add weapons, whichever weapons index you're adding (or altering), that's the index of the firerate and nextfire you'd want to update.
    if you have 2 weapons say, and you add a 3rd in there.
    fireRate[2] = whatever_fire_rate;
    nextFire[2] = Time.time // right now, because it just loaded.

    as for the flow of how it would go:
    if the fire button is hit ...
    check each weapon slot..
    if it's not empty
    check weapon [0..2 - assuming 3 weapons possible] 's next fire time
    if that's less than or equal to the current time, fire weapon [index we're at]
    set this weapon's nextfire to the time + rate (all 3 of these variables would have the same index)
    ... repeat until all weapons have been checked.
     
  11. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    Thank you so very much, your help has been invaluable! I'll test it out and report back with the results.
     
  12. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Cool, cool. Hope it goes well :) You're welcome.
     
  13. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    So I finally got around to working on this, and this is what I've come up with currenty:

    Code (CSharp):
    1.        void Update ()
    2.         {
    3.             if (Input.GetButton("Fire" + m_PlayerNumber))
    4.                 {
    5.                     if (weapons[0] != null){fire0();}
    6.                     if (weapons[1] != null){fire1();}
    7.                     if (weapons[2] != null){fire2();}
    8.                     if (weapons[3] != null){fire3();}
    9.                 }
    10.         }
    11.  
    12.         void fire0()
    13.         {
    14.             if (Time.timeScale > 0 && Time.time > nextFire0)
    15.             {
    16.             var rate = weapons[0].GetComponent<PlayerWeaponDamage>();
    17.             var fireRate = rate.fireRate;
    18.             nextFire0 = Time.time + fireRate;
    19.             Instantiate(weapons[0], hardpoints[0].position, hardpoints[0].rotation);
    20.             GetComponent<AudioSource>().Play ();
    21.             }
    22.         }
    23.  
    24.         void fire1()
    25.         {
    26.             if (Time.timeScale > 0 && Time.time > nextFire1)
    27.             {
    28.             var rate = weapons[1].GetComponent<PlayerWeaponDamage>();
    29.             var fireRate = rate.fireRate;
    30.             nextFire1 = Time.time + fireRate;
    31.             Instantiate(weapons[1], hardpoints[1].position, hardpoints[1].rotation);
    32.             GetComponent<AudioSource>().Play ();
    33.             }
    34.         }
    While the code works, obviously this is sub-optimal and I would like to use something like this:

    Code (CSharp):
    1. for (int i = 0; i < weapons.Length; i++)
    2.                     {
    3.                         if(weapons[i] != null)
    4.                         {
    5.             if (Time.timeScale > 0 && Time.time > nextFire1)
    6.             {
    7.                         var rate = weapons[i].GetComponent<PlayerWeaponDamage>();
    8.                         var fireRate = rate.fireRate;
    9.                         nextFire = Time.time + fireRate;
    10.                         Instantiate(weapons[i], hardpoints[i].position, hardpoints[i].rotation);
    11.                         GetComponent<AudioSource>().Play ();
    12.              }
    13.                         }
    14.                     }
    But I can't figure out how to convert nextFire to an array. I've tried to declare it using:

    public float [] nextFire

    And then changing

    Time.timeScale > 0 && Time.time > nextFire1 to Time.timeScale > 0 && Time.time > nextFire
    and
    nextFire = Time.time + fireRate;

    But no dice, even if I initialize the array using for example
    nextFire[0] = 1.0f;
     
  14. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    One way to do it is nextFire could be in the PlayerWeaponDamage on your game object just like you have fireRate. Initialize it to 0. So it's look something like this:

    Code (csharp):
    1.  
    2. for (int i = 0; i < weapons.Length; i++)
    3. {
    4.     if(weapons[i] != null)
    5.     {
    6.         var rate = weapons[i].GetComponent<PlayerWeaponDamage>();
    7.         if (Time.timeScale > 0 && Time.time > rate.nextFire)
    8.         {            
    9.                     rate.nextFire = Time.time + rate.fireRate;
    10.                     Instantiate(weapons[i], hardpoints[i].position, hardpoints[i].rotation);
    11.                     GetComponent<AudioSource>().Play ();
    12.         }
    13.     }
    14. }
    15.  
    I'm not sure it would make sense to store the nextFire values outside of that PlayerWeaponDamage class.. if you were really anal about separating things, maybe it would (in which case you'd have to set up some sort of array I guess to map the nextFire value to a particular weapons reference). That's how I'd do it though.
     
  15. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    Why not move the whole Fire method into the PlayerWeaponDamage component. Let each instance of PlayerWeaponDamage manage its own internal fire rate.
     
  16. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    How would instantiate work in that case?
     
  17. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    My recommendation is to push this kind of logic as far down into the subcomponents as possible. In this case, I guess you would need to move the weapon type and knowledge of its position and rotation down as well.

    If that's too hard for now, try a smaller step. You could have a CanFire method on your PlayerWeaponDamage which simply returns a Boolean if its internal fire rate allows it.
     
  18. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    Thank you both for your replies. I'll try both and see what works.
     
  19. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    Actually, in hindsight, the way eisenpony describes it is how I personally have done it, as well as setting up an interface for my shootable weapons. basically let the weapon's script handle the logic of whether it can fire again or not.
     
  20. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
  21. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    Actually that exact article was inspiration for me when I coded this for my current game in progress. I found it worked really well using the interfaces.. you could get components of type IShootable, for example, and then call their shoot function.
     
  22. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    Excellent, thank you. I'll start reading that. If you have any other resources that you'd like to share, I would appreciate it greatly!

    Thanks once again!
     
  23. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Hey, cool to see you're (back) at this. Good luck, again, as I arrived after you got some good tips/inspiration :)
     
  24. Alexbeav

    Alexbeav

    Joined:
    Jan 15, 2017
    Posts:
    15
    Thanks! Funny thing actually - I'm not a programmer but I am currently doing a B.Sc in Network Security, and that includes a couple of Object-Oriented Programming classes namely Python and Java. Now while I disliked Java compared to the little C# I've done with Unity, it did taught and helped me understand some of the basics of Object-Oriented Programming which allowed me to approach this problem from a different direction, hence my using arrays now :)

    Of course, I'm still a newbie with little experience, and I should still sit down and study C# and all the Unity scripting tutorials & live sessions, so it may take a while to reach an agreeable point. So far this isn't too much of a problem because I'm only using one Mech and it has 4 hardpoints, the problems will arise when I implement Mech selection in the game and players can pick different Mechs with different number of hardpoints... But I still have a ways to go until then :)
     
  25. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Ya, it's a journey. You'll learn as you go :) I'm still learning things regularly, too. :)
     
  26. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    So my implementation is a bit different than yours. I've basically got one weapon armed at a time. The game is still in its infancy, but I did set up the interfaces already, and have one weapon so far that is working beautifully. I think this should work well for when I allow for cycling through the weapons.

    In your case, you mentioned different mechs with different hardpoints. Off the top of my head, what I would be doing (which isn't necessarily the best way) is having a structure within your various Mechs to indicate the hardpoints. In other words, you'd do something like this:

    Mech1 Prefab...
    .... LeftShoulderRocket <- these are child objects that have transform set to the spot on the mech, and have their IShootable, etc. implementation scripts on them.
    .... RightShoulderRocket
    .... LeftHand
    .... RightHand

    On each of these children, you'd have a script that implements the interfaces that are needed (such as "IShootable", "IReloadable"). They may implement the same way (in which case I guess you'd use a class), or they might implement slightly differently (think inheritance), or they might be completely different, in which case you'd have completely different implementation for each (although I'm sure that there will be some similarities between them all that will allow you to use something like inheritance).

    Anyways, within your Mech1 prefab, you drop your MechShooting script of some sort.... on it, you have a public GameObject hardpoints[] array. In the property inspector, you drop your hardpoints (LeftShoulderRocket, etc.) in there.

    Then in your script you'd want to set up component references.. something like... (this is pretty much pseudo code, sorry, not exact)

    IShootable hardpoint1Shootable = hardpoints[1].GetComponent<IShootable>();


    Then you can call the shoot function like so... hardpoint1Shootable.Shoot(); ... when the player has applied appropriate input.

    Again, bear in mind this isn't exact code, but it's probably not far off from what you would do either.

    And one more thing, if you can use the same prefab for all your mechs, then you might actually do it slightly different... like have a property indicating the number of hardpoints, their actual locations... and then create your array based on the # of hardpoints. Or you might always have all the max # of hardpoints on your prefab, and just enable/disable the ability to arm them with something. Hope that makes sense to give you enough to go on.

    Either way, I really loved the flexibility that component based approach gives.
     
    Last edited: May 26, 2017
    eisenpony likes this.