Am I using classes correctly?

I am attempting to make my first project using classes. I created a weapons class with all the necessary variables and methods to make shooting weapons work. Next, I created a script called cannon and made an instance of the weapons class under the variable "cannon".  

Here is the code for the weapon class:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class Weapon : MonoBehaviour {
    public AudioClip clip;
    public AudioSource audioSource;
    public GameObject bullet;
    public int firePower = 30;
    public int bulletDelay = 90;
    public int fireSpeed = 10;


    public void Start () {
        audioSource = GetComponent<AudioSource>();
        audioSource.clip = clip;
    }


    public void Update () {
        bulletDelay--;
        if (bulletDelay < 0 && OVRInput.Get(OVRInput.Button.SecondaryIndexTrigger))
        {
            audioSource.Play();
            bulletDelay = fireSpeed;
            ShootGun();
        }
    }


    public void ShootGun()
    {
        GameObject thisBullet = Instantiate(bullet, transform.position, transform.rotation);
        thisBullet.GetComponent<Rigidbody>().AddRelativeForce(0, 0, firePower, ForceMode.Impulse);
    }
}


This is the code for the cannon script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class Cannon : MonoBehaviour {




    void Start()
    {
        Weapon cannon = new Weapon();
    }


    void Update()
    {
        cannon.Update();
    }
}


My thought process is that all the default variables and methods in my weapons class will work perfectly for the cannon so I should just be able to access the Update method of the weapons class by calling it every time the Update in the cannon class is called and it should shoot perfectly. But I run into a problem. I am getting the following error in the Cannon script  -  The name 'cannon' does not exist in the current context. This error goes away if I move - Weapon cannon = new Weapon();  - into the Update method instead of the Start method but it doesn't seem like it should be running that line of code every frame. Also, I tried playing my game with that line of code in update and the bullet did not shoot when I clicked the trigger button. 


Sorry this question is so poorly formed. I am really struggling to understand even the basics about classes :(

  • Jonathan Gonzalez(jgonzalez) replied

    The error you're getting is actually not a class issue, but a local method issue. Whenever you declare a variable within a method it is only accessible to that method. If you added the "cannon.Update()" in the start method you'll notice that error won't appear. That said the way you're approaching this is not ideal. 

    Instead of including various things within any of your update methods, create separate methods for the various game mechanics. You kind of already did this with your "ShootGun" method, do the same for the others.  I'm not sure about how your methods work, but I can organize them a bit. 

    So for everything within the update method create a separate method:

    public void CheckDelay () {
             bulletDelay--;
    
            if (bulletDelay < 0 && OVRInput.Get(OVRInput.Button.SecondaryIndexTrigger))
            {
                audioSource.Play();
                bulletDelay = fireSpeed;
                ShootGun();
            }
    }
    


    We make it public so that we can access it from other classes. Since this is being called in another script, you don't need to constantly update it within the update method in another class. It's being updated every time it is called in another class. 


    In your second class you could call it as such:

    void Update ()
    {
    cannon.CheckDelay();
    }
    
  • Jonathan Gonzalez(jgonzalez) replied

    I'll try to write something up or maybe write an article on how to work with classes a bit more.  I don't know what you're building, but based on what you posted it doesn't look like you'd need to have multiple classes. The second class calling the method you built in the first class isn't necessary. 

    Whenever you're accessing methods from other classes it should be because that class has no reason to have that method itself. For example if I shoot a player, I don't need my shooting script to have a reference to health, but I do need to deduct the health of the player I hit. In which case I would access the health class of that player and call a deduct health method. 

  • Hunter & Tiff Eidmann(eithman) replied


    jgonzalez  I cleaned it up in the way that you suggested and the reason I am using classes is that I will be creating multiple weapons that all work pretty much the same way and it seemed like using a main Weapon class could save me a lot of redundant typing. The issue I am running into now is with the audio playing. Below is the script for the Weapon class:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    
    public class Weapon : MonoBehaviour {
        public AudioClip clip;
        public AudioSource audioSource;
        public GameObject bullet, thisBullet;
        public int firePower = 30;
        public int bulletDelay = 90;
        public int fireSpeed = 10;
    
    
        public void Start () {
            audioSource = GetComponent<AudioSource>();
            //audioSource.clip = clip;
        }
    
    
        public void BulletDelay ()
        {
            bulletDelay--;
            if (bulletDelay < 0 && OVRInput.Get(OVRInput.Button.SecondaryIndexTrigger))
            {
                audioSource.Play();
                bulletDelay = fireSpeed;
                ShootGun();
            }
        }
    
    
        public void ShootGun()
        {
            thisBullet = Instantiate(bullet, transform.position, transform.rotation);
            thisBullet.GetComponent<Rigidbody>().AddRelativeForce(0, 0, firePower, ForceMode.Impulse);
        }
    }
    
    
    

    and below is the script for the Cannon that is inheriting the Weapon class:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    
    public class Cannon : Weapon {
    
    
        Weapon cannon = new Weapon();
    
    
        void Update()
        { 
            cannon.BulletDelay();
        }
    }
    

    When I play the game and press the shoot button on my Oculus controller, I get the following error, "

    NullReferenceException: Object reference not set to an instance of an object

    Weapon.BulletDelay () (at Assets/Scripts/Weapon.cs:23)

    Cannon.Update () (at Assets/Scripts/Cannon.cs:11) "


    I rewatched your video on how to play audio clips and I am still not sure what it is that I am doing wrong here. It was working perfectly earlier when I had the Weapon class applied directly to the weapon so I suspect it might have something to do with it being called from another class but I am not sure. 

  • Jonathan Gonzalez(jgonzalez) replied

    eithman Inheritance in this case is not needed, but I will show you how you can use it nonetheless. For the cannon script, it's inheriting from the Weapon class. All the public methods within the base class will be available to the children than inherit from it.

     Everything looks setup properly in the base class, albeit I'm not familiar with the Oculus inputs. So the child class, Cannon, inherits the Weapon class so everything public should be made available. We don't need to create a new class for this. 


    In the weapon class if you want to call the BulletDelay or ShootGun methods you'd call them directly as you would in the Weapon class. I didn't make that clear initially so I'll provide a code snippet:

    //this is in the Cannon script
    void Update() {
    BulletDelay(); 
    }
    


    Since the Cannon class is inheriting from the Weapon class, you also need to assign any components needed like the audio source. The Cannon class would need an AudioSource component that you need to assign. If it's public (which it is) you can manually assign it, otherwise use GetComponent to grab it for you automatically. 

    To add onto that you can also override methods. So if you wanted all weapons to have the same general mechanics, but use them in different ways you'd override specific methods to work in a different way. You'd use a virtual method in the base class, allowing child classes to override those methods. Here is a very simple example:


    public class Weapon : MonoBehaviour {
        public AudioSource aud;
        public bool canFire;
        public virtual void ShootGun () //making it virtual allows you to override it within a child class
        {
            if(Input.GetButtonDown("Fire1")){
                Debug.Log("Pew Pew");
            }
        }
    }
    


    public class Shotgun : Weapon {
       void Update ()
    {
    ShootGun();
    }
    
    //use override instead of virtual to modify the method
    public override void ShootGun()
    {
    if(Input.GetButtonDown("Fire2"))
    Debug.Log("Bang Bang");
    }
    }
    
    
    

    There's a lot more to it as well, but those are basic examples. You could also do chain inheritance. So the Shotgun class may also have it's own child classes. Those child classes would have access to all the public methods and variables available to the shotgun class as well.