Search Unity

Script for shooting with a LineRender (Laser Weapon)

Discussion in 'Scripting' started by MichaelABC, Apr 8, 2020.

  1. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I am modding Unity Tanks!, a game from a 2015 tutorial. In the game two players control two tanks on one keyboard and have to destroy the opponent by shooting shells at them. I want to add new secondary weapons for the players to use. for simplicity's sake I am going to assign them to dedicated secondary fire buttons.

    I first created a WeaponHolder object where to store the weapons. I start working on a Laser Beam (a continuous laser), as a child item of the WeaponHolder. I drew it with a line renderer (I don't think I need to add colliders and rigidbody?). Online I found and attached this script for the laser behaviour.

    Code (CSharp):
    1. public class LineLaser : MonoBehaviour
    2. {
    3.      private LineRenderer lr;
    4.      // Start is called before the first frame update
    5.      void Start()
    6.      {
    7.          lr = GetComponent<LineRenderer>();
    8.      }
    9.      // Update is called once per frame
    10.      void Update()
    11.      {
    12.          lr.SetPosition(0, transform.position);
    13.          RaycastHit hit;
    14.          if (Physics.Raycast(transform.position, transform.forward, out hit))
    15.          {
    16.              if (hit.collider)
    17.              {
    18.                  lr.SetPosition(1, hit.point);
    19.              }
    20.          }
    21.          else lr.SetPosition(1, transform.forward * 5000);
    22.      }
    23. }
    The way the original game is designed there is one tank prefab that gets cloned in two variants (red and blue) (GameManager.cs does it). the tank holds the shooting script while the shell is a separate prefab that holds a script for the impact explosion and damages count.

    I would like to keep the same organisation for my mods, with one minor difference: the laser prefab must exist as a child of the WeaponHolder because the player will pickup a "?" block on the map in the style of Mario Kart, which will randomply pick a powerup weapon in the WeaponHolder.

    That said, while the laser hold the script above, the tank should control the shooting (but fails) with the following:

    Code (CSharp):
    1. public class WeaponLaser : MonoBehaviour
    2. {
    3.      public int m_PlayerNumber = 1;
    4.      public Transform firePoint;
    5.      public GameObject laserPrefab;
    6.      private string SpecialButton;
    7.      private LineRenderer lr;
    8.      private void Start()
    9.      {
    10.          SpecialButton = "Special" + m_PlayerNumber;
    11.          lr = GetComponent<LineRenderer>();
    12.      }
    13.      // Update is called once per frame
    14.      void Update()
    15.      {
    16.          if(Input.GetButtonDown(SpecialButton))
    17.          {
    18.              Shoot();
    19.          }
    20.          void Shoot()
    21.          {
    22.              //shooting logic
    23.              Instantiate(laserPrefab, firePoint.position, Quaternion.identity);
    24.              laserPrefab.GetComponent<LineRenderer>.Invoke();
    25.          }
    26.      }
    27. }
    I know. It's definitely not .Invoke(), but I had to try with something... Help! Firepoint is an empty object on the tank cannon and laserPrefab obvs refers to the laser prefab (but does not follow the behavioural script attached? maybe?).

    Little note: m_PlayerNumber refers to the way the game handles cloning and, here specifically, the 2-in-1 input system. This is also giving me problems, but so far I would be content if even one single tank was able to shoot this laser.

    If I manage to solve this I will probably come back here to also deal with the laser impact and explosion, but so far please help me understand this one here! :)
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,696
    First and foremost let me say a HUGE BRAVO! Modding an existing game to learn about Unity is literally THE BEST WAY to learn a new engine, try new techniques out, etc. GREAT WORK!

    Now, I cannot solve your problem, but let me offer you some awesome generic tips to help you track it down.

    1. when you fire the laser beam, execute a Debug.Break(). This will instantly pause the editor. Now you can look through the scene and see it the line renderer is there, see if it is where you think it should be, see if it is set up right, enabled, etc.

    2. when debugging stuff like this in a live-playing game without interrupting it, use plenty of Debug.Log() statements to do things like print out "I got to LineLaser.Start()" and also the values of things like positions. use Debug.Log() in general for spitting out positions, points, values, true/false stuff, etc.

    Also it may be helpful to strip out everything else in the scene and put just the two tanks on a plane. Sometimes that can be a real pain if there's a lot of connected stuff, but sometimes it can help you understand just the part you are having problems with.

    Best of luck, I predict you will a) solve this problem, and b) learn a lot in the process, and c) soon be moving onto impacts and explosions using your new arsenal of tricks!
     
    BMRG14, jasebeaumont and Emolk like this.
  3. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    Thank you for your suggestions Kurt. I have to use debug more, you are right. I think I am making progress already.
     
  4. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    Thanks to the help of some amazing people over to Unity answers I am making important progresses: I made the WeaponHolder a child of the Tank prefab, made a new hierarchy like so:


    And finally changed the WeaponLaser script to:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class WeaponLaser : MonoBehaviour
    6. {
    7.     public int m_PlayerNumber = 1;
    8.     public Transform firePoint;
    9.  
    10.     //I am not sure about this
    11.     public LineLaser laser;
    12.  
    13.     private string SpecialButton;
    14.  
    15.     private void Start()
    16.     {
    17.         SpecialButton = "Special" + m_PlayerNumber;
    18.     }
    19.  
    20.     // Update is called once per frame
    21.     void Update()
    22.     {
    23.         if (Input.GetButton(SpecialButton))
    24.         {
    25.             Shoot();
    26.         }
    27.         else
    28.         {
    29.             TurnOffLaser();
    30.         }
    31.     }
    32.  
    33.     void Shoot()
    34.     {
    35.         //Shooting logic
    36.         laser.gameObject.SetActive(true);
    37.         //Do damage here
    38.         //In your LineLaser script you could find the object that the raycast hits (hit.gameObject will give you the gameObject)
    39.         //Then you could grab that object here and do damage or whatever you want
    40.     }
    41.  
    42.     void TurnOffLaser()
    43.     {
    44.         laser.gameObject.SetActive(false);
    45.     }
    46. }
    What I still need to figure out is:
    1. How to lift the point from which the laser is shot (see image below)

    2. How to have the laser shoot only last a couple of seconds after pressing the button

    3. Why pressing the dedicated "SpecialButton" triggers the lasers of all tanks instead of just tank clone 1
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,696
    Let me just answer #1 for the moment: it's usually best to make a new GameObject in your tank hierarchy that you can use as the starting point. Think if it like a "laser muzzle." That way no code has to change when you (or your artist when you are on a team) wants to move the startpoint around because they changed the visuals, for example.
     
    MichaelABC likes this.
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,696
    Anwering #2 above: If I read your code, your laser either lasts indefinitely, or what I think I see is that it instantly turns off the next frame.

    Two approaches:

    approach a) If you want to destroy and recreate your laser, when you fire it you can clone it, enable it, and immediately Destroy() it with a second argument supplied to Destroy() that tells how long to wait before destruction.

    approach b) If you are just turning the same laser on and off, then have a timer (usually just a float) that you count down (usually by subtracting Time.deltaTime from it every frame), and when it becomes zero or less, hide the laser. When you fire, you enable the laser and set that float equal to the desired time in seconds.
     
    MichaelABC likes this.
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,696
    Answering #3: you are relying on m_PlayerNumber being different for each tank. Therefore to see if this is the case, in Start() print it out for each tank and see if it is indeed different.
     
    MichaelABC likes this.
  8. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I was initially working from this premise, but I followed someone's instruction and they asked me to removed my "laser muzzle".

    Luckily I just found a way to lift the laser (I don't know what I did but it worked ahah)

    I also believe it's good practice to have such an object, and I will probably add it back in the scene in a second moment when I want to add a more advanced particle animation like the laser charging before the shot.
     
    Last edited: Apr 14, 2020
    Kurt-Dekker likes this.
  9. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I followed approach b), it works great :) :

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class WeaponLaser : MonoBehaviour
    6. {
    7.     public int m_PlayerNumber = 1;
    8.  
    9.     public LineLaser laser;
    10.  
    11.     private string SpecialButton;
    12.  
    13.     // from https://answers.unity.com/questions/1715792/script-for-shooting-with-a-linerender-laser-weapon.html?childToView=1716165#comment-1716165
    14.     public float LaserFireTime = 0.7f;
    15.     public float laserTimer;
    16.  
    17.     private void Start()
    18.     {
    19.         SpecialButton = "Special" + m_PlayerNumber;
    20.     }
    21.  
    22.     // Update is called once per frame
    23.     void Update()
    24.     {
    25.         if (Input.GetButtonDown(SpecialButton))
    26.         {
    27.             laserTimer = LaserFireTime;
    28.             Shoot();
    29.         }
    30.         else
    31.         {
    32.             if (laserTimer > 0)
    33.             {
    34.                 laserTimer -= Time.deltaTime;
    35.                 Shoot(); //Keep calling shoot while laser is still active
    36.             }
    37.             else TurnOffLaser(); //When timer <= 0 turn off the laser
    38.         }
    39.     }
    40.  
    41.     void Shoot()
    42.     {
    43.         //Shooting logic
    44.         laser.gameObject.SetActive(true);
    45.         //Do damage here
    46.         //In your LineLaser script you could find the object that the raycast hits (hit.gameObject will give you the gameObject)
    47.         //Then you could grab that object here and do damage or whatever you want
    48.     }
    49.  
    50.     void TurnOffLaser()
    51.     {
    52.         laser.gameObject.SetActive(false);
    53.     }
    54. }
    55.  
     
    rayman600 and Kurt-Dekker like this.
  10. MichaelABC

    MichaelABC

    Joined:
    Jan 25, 2020
    Posts:
    69
    I am going to start doing some debug printing. Meanwhile I have opened a question here.
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,696
    Debug.Log() is your best friend. It will always work and can be used to solve 100% of all bugs, just about.