Classes

Hi there!

I have been programming for quite some tie, and feel that I’ quite proficient with C# and Unity in general (over a year experience).

Something that for some reason always flies above my head are classes.

I have read a dozen OOP books, tutorials, guides and youtube videos, all explaining the same thing.

For some obscure reason, my brain cannot grasp the usefulness of classes. I understand what they are, and that they define OOP, but I simply do not see why they are that useful.

All the code I read can be easily replaced with more GameManager scripts or new methods, so I cannot see why I should rewrite everything to accommodate classes.

In other words: I understand Polymorphism, Constructors, Overloads, and similar terms, the issue is implementing them into fresh code.

Anyone has a “definitive” (idiot proof) guide to classes?

Thanks!

Edit: I see that non-interactive answers do not help. Anyone kind enough to “teach” me via Skype? I speak native English, Spanish and Italian. Send a PM and I’ll tell you my username. Thanks again!

Just about every script you write is a class (typically extending MonoBehaviour) so you do find them useful, you just don’t know it.

I’m afraid this does not answer the main question: why should I bother using them instead of normal scripts?

Cause they drastcally cut down on the amount of code needed in some cases, and is generally just a better workflow for games since you can model what data objects contain and what they do.

Not to mention holding state since with a plain old static function you lose the contained data once it drops our of scope.

Also if you use GetComponent() in any of your code your already using oop concepts.

Don’t think of a class just as a container to hold your methods, but think of it as the template to create individual objects that either model data and a data type or model objects that do things or hold game state.

The question doesn’t really make sense. Every time you make a “script” you’re making a new class definition.

Ok, let’s do another approach: confirm I actually got it. This is from the official tutorial on classes:

public class Inventory : MonoBehaviour
{
    public class Stuff
    {
        public int bullets;
        public int grenades;
        public int rockets;
        public float fuel;
       
        public Stuff(int bul, int gre, int roc)
        {
            bullets = bul;
            grenades = gre;
            rockets = roc;
        }
       
        public Stuff(int bul, float fu)
        {
            bullets = bul;
            fuel = fu;
        }
       
        // Constructor
        public Stuff ()
        {
            bullets = 1;
            grenades = 1;
            rockets = 1;
        }
    }
   

    // Creating an Instance (an Object) of the Stuff class
    public Stuff myStuff = new Stuff(50, 5, 5);
   
    public Stuff myOtherStuff = new Stuff(50, 1.5f);
   
    void Start()
    {
        Debug.Log(myStuff.bullets);
    }
}

Lines 1-8 is initialising parent class and defining variables
Lines 10-15 is the default child class, if given the 3 wanted variables
Lines 17-21 Polymorphes the child class if given 2 different variables instead of the 3 wanted ones
Lines 24-27 Is the default constructor, aka the child class with no given variables
Line 34 instantiates the default child class
Line 36 instantiates the poly’d child class
Lines 38-41 prints out an example. In this case, prints the poly’d child class.

All this is done via another script file, so if I understood everything properly, why not do it in the original script?

public int bullets;
public int grenades
public int rockets;
public float fuel;

void Start()
    {
        Debug.Log(bullets);
    }

Have 2 child GameObjects with this code. One is called myStuff, and the other is myOtherStuff. Variables are set in-code or via the inspector, as they are public.

How is the original solution easier?

Take the following inventory class as an example:

using UnityEngine;
using System.Collections;

public class Inventory {
    private static Inventory instance;

    private int iHelmetCount = 0;
    private int iGrenadeArrowCount = 0;
   
    private const string HELMETS_PREF = "invHelmets";
    private const string GRENADE_ARROW_PREF = "invGrenadeArrows";
       
    private Inventory()
    {
        iHelmetCount = SaveData.GetInt(HELMETS_PREF,3);
        iGrenadeArrowCount = SaveData.GetInt(GRENADE_ARROW_PREF,5);
    }

    // Singleton pattern
    public static Inventory Instance
    {
        get
        {
            if(instance == null)
            {
                // no inventory exists, create one
                instance = new Inventory();
            }
            return instance;
        }
    }

    public void AddGrenadeArrow(int iAddVal)
    {
        iGrenadeArrowCount += iAddVal;
        SaveData.SetInt(GRENADE_ARROW_PREF, iGrenadeArrowCount);
    }
    public int GetGrenadeArrowCount()
    {
        return iGrenadeArrowCount;
    }
    public bool ConsumeGrenadeArrow()
    {
        if(iGrenadeArrowCount > 0)
        {
            AddGrenadeArrow(-1);
            return true;
        }
        return false;
    }

    public void AddHelmets(int iAddVal)
    {
        iHelmetCount += iAddVal;
        SaveData.SetInt(HELMETS_PREF, iHelmetCount);
    }
    public int GetHelmetCount()
    {
        return iHelmetCount;
    }
    public bool ConsumeHelmet()
    {
        if(iHelmetCount > 0)
        {
            AddHelmets(-1);
            return true;
        }
        return false;
    }
}

This class uses a singleton pattern, meaning that if I call Inventory.Instance(), I will get a valid inventory even if it was not yet instantiated. Since the Instance() method is static, I can call this without any particular instance of the class. With a monobehavior, you would need to have the reference to the instance to use the inventory (though you can do the same thing in the monobehavior, make a static method that returns “this”.)

Really for me, it boils down to using the classes that only contain what I need. With the above inventory class, there is no need for an Update function, or for a Start function, or for the object to always be instanced or on an object in the scene. I can at any point, from any class in the project, call Inventory.Instance().GetHelmetCount() and there will always be a value returned. Since I don’t need the overhead of the monobehavior, why have it? It will just consume additional resources.

Monobehaviors do not use constructors, which is your first mistake. You are right - that entire first class is not well written or simple. But the second script you wrote is STILL contained in a class. All monobeheviors are classes. Anything that you add as a component to a gameobject in the unity editor is a class.

Its not a matter of IF you use classes or not - its a matter of HOW you use classes.

Thanks GTZ, seeing a real-world example helps.

I do see why you did the inventory class, but not why you do several things:

Why create a whole method to just return a value?
Example: lines 38-41 could be something like this in the original script:
grenadeArrowCount = InventoryManager.GetComponent().iGrenadeArrowCount;

Or 33-37 could simply be
grenadeArrowCount += addVal;
SaveData.SetInt(GRENADE_ARROW_PREF, grenadeArrowCount );

That works great until you decide you want to play a sound effect when you pick up an arrow and you’ve copy-pasted those two lines in three dozen places across 15 files in your code base.

By the same logic - wouldn’t it be simpler to take the contents of SaveData.SetInt() out of the method and copy-paste it wherever you need it?

Its a matter of scope and proper accesses. Simply making all of your variables public means that anything, anywhere that can reach that class can do whatever it wants with the variables. You should only expose the desired actions to other classes.

Anyone who interacts in anyway with grenadeArrows should do it in a uniform manner. If what happens when you add an arrow changes, you only have to change it in one location. Additionally, no one can come in with a whacky change in the script (increasing the grenade count but failing to Save the data is a good example, here). Bugs like that become a pain to track down.

Fair point.

So, in short, classes can be considered as a jack-of-all-trades scripts, where you throw a bunch of code you know you will have to call or access many times, across any scripts?

True, this has been a major issue on my first 3 or 4 projects. Also, the inspector explodes with so many variables.

I created methods for those things for the purpose of encapsulation (basically hide the complexity and support change) So in this case, the grenade arrow count is stored in player prefs and in a local variable, so you could just expose the local variable iGrenadeArrowCount and read that for now. But what if I later find out that people are modifying the player prefs and not paying for their grenade arrows? I may decide that I want to hit a web service whenever a grenade arrow count is requested so I can check how many arrows on their account. I can just simply change my GetGrenadeArrowCount() function and all of the existing code calling it will continue to work. I only have to update 1 file which leads to fewer bugs and easier adaptation to changing requirements.

Stop thinking of scripts and classes as two different things :slight_smile:

1 Like

Goooootcha

Apparently I have been using my “Manager” scripts exactly like classes, without taking advantage of them actually being one.

I wish to thank you all for your patience in forcing this knowledge into my thick skull.

I can now code happier!

To go along with that, you see where I use SaveData calls above? This is another class I wrote that encapsulates PlayerPrefs calls, but whenever a value is saved or read, a hash of the value is also saved or read (and compared) to prevent people from easily editing the values in player prefs. I have hidden that level of complexity though, and I don’t have to think about it anytime I want to save or read a value.

Also, if I decide to change how all of my game data is saved (and put it into a SQLite database, or store it in an online database via a web service), I can just change my SaveData class, and everything calling it will just continue to work. Just in case you were wondering what the SaveData class was and why you don’t have it.

Yeah, guessed that it would be that.

Maybe a question for another day is how to do something like that: protect the files from people to access them easily

I started with this script and modified it: