More questions on classes and the dreaded null reference

Hello everyone. Please be patient with your unworthy servant.

Still trying to wrap my head around how classes work. Making progress but have hit a stumbling block.

I have a script, monobehaviour, that I want to use to control random item(armor weapons etc)
A couple scripts, non monobehavior, to actually do the random generating.
A UI Control script, MonoBehavior, to display stats, allow selections etc.
It all works fine (as much as I have prototyped so far) when I use key input from the loot generation script.
Problem occurs when I made a prototype chest with the script, monobehavior, to set the arguments to pass to the loot generator script, I can not figure out how to make it work.

So, in theory;
Player opens chest, chest randomly picks armor, weapon or item and passes those arguments to LootGenControl.ArmorGeneration which randomizes equipment slot, type bonuses etc.

Here are the main scripts. Any help pointing me in the correct direction would be most greatly appreciated.

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

public class LootGenController : MonoBehaviour
{
    private int lootIdentifier; // use to generate unique number for loot items

    private Inventory inventory;
    private ArmorGeneration armorGen; // Armor Generation Script
    private bool armorGenCheck;
    private WeaponGeneration weaponGen; // Weapon Generation Script
    private bool weaponGenCheck;
    //private ItemGeneration itemGen;   // Item Generation Script
    // private bool itemGenCheck;
  
    public UI_Controller uiControl; // UI Control Script

    void Start()
    {
        lootIdentifier = 0;
        inventory = new Inventory();
        armorGen = new ArmorGeneration();
        armorGenCheck = false;
        weaponGen = new WeaponGeneration();
        weaponGenCheck = false;
        //itemGen = new ItemGeneration();
        //itemGenCheck = false;
    }

    void Update()
    {
        //use for testing loot generation - temporary
        if (Input.GetKeyDown("z"))
            {
                ArmorGeneration(1);
            }
        if (Input.GetKeyDown("x"))
        {
            WeaponGeneration(1);
        }

    }

    public void ArmorGeneration(int armorChestLevel)
    {
        Debug.Log("Inside Armor Gen");
        lootIdentifier = inventory.AdvanceIdentifier(lootIdentifier); // increase loot identifier
        armorGenCheck = armorGen.Generate(armorChestLevel, lootIdentifier); // generate random armor
        if (armorGenCheck)
        {
            uiControl.DisplayArmorGenStats(armorGen); // display armor stats
        }
        else Debug.Log("ArmorGenCheck Failed!");
    }

    public void WeaponGeneration(int weaponChestLevel)
    {
        lootIdentifier = inventory.AdvanceIdentifier(lootIdentifier); // increase loot identifier
        weaponGenCheck = weaponGen.Generate(weaponChestLevel, lootIdentifier);
        if (weaponGenCheck)
        {
            uiControl.DisplayWeaponGenStats(weaponGen);
        }
        else Debug.Log("WeaponGenCheck Failed!");
    }

    public void Discard() // discard random item
    {
        uiControl.DeactivateInvButtons();
        uiControl.StatsTextReset();
    }

    public void PlaceInInventory() // place item in inventory, call inventory management
    {
        uiControl.DeactivateInvButtons();
        inventory.InventoryIn();
    }

    public void EquipItem() // equip item, call inventory management
    {
        uiControl.DeactivateInvButtons();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChestLevelOne : MonoBehaviour
{
    private bool isOpen = false;
    private int chestLevel = 1;
    private int chestType;

    // public GameObject lootGenControl; // No worky
    private LootGenController lootGenControl;

    void Start ()
    {
        chestType = (int)Random.Range(1.1f, 3.9f);
        //lootGenControl = new LootGenController();    No worky
        //lootGenControl = GetComponent<LootGenController>(); // Cannot Implicitly Convert Type "LootGenController
                                                            // to type "Unity Game Object"
        lootGenControl = GetComponent<LootGenController>();           
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            Debug.Log("Player opens chest!");
            lootGenControl.ArmorGeneration(chestLevel);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ArmorGeneration
{
    // variables to be passed to armor class after generation
    public int armorIdentifier;
    public int level;
    public int protection;
    public string itemName;
    public string itemSlot;
    public int meleeDef;
    public int magicDef;
    public int iceRes;
    public int fireRes;
    public int poisonRes;
    public int electricRes;

    private bool assignment;

    public bool Generate(int lootLevel, int identify) // armor loot generator control
    {
        level = lootLevel;
        armorIdentifier = identify; // use as unique identifier for instances
        Reset();
        GenerateArmor();
        return assignment;
    }

    void Reset() // resets stats prior to generation
    {
        protection = 0;
        itemName = "";
        itemSlot = "";
        meleeDef = 0;
        magicDef = 0;
        iceRes = 0;
        fireRes = 0;
        poisonRes = 0;
        electricRes = 0;
        assignment = false;
    }

    void GenerateArmor() // set useable slot for generated armor
    {
        int slot = (int)(Random.Range(1.1f, 6.9f));
        switch (slot)
        {
            case 1:
                itemSlot = "head";
                HeadArmor();
                break;
            case 2:
                itemSlot = "body";
                BodyArmor();
                break;
            case 3:
                itemSlot = "legs";
                LegArmor();
                break;
            case 4:
                itemSlot = "hands";
                HandArmor();
                break;
            case 5:
                itemSlot = "feet";
                FeetArmor();
                break;
            case 6:
                itemSlot = "shield";
                ShieldArmor();
                break;
            default:
                Debug.Log("armorSlot undefined!");
                break;
        }
    }

    void HeadArmor()
    {
        if (level == 1)
        {
            itemName = "Small Leather Cap";
            meleeDef = 1;
        }
        if (level == 2)
        {
            itemName = "Studded Leather Cap";
            meleeDef = 2;
        }
        if (level == 3)
        {
            itemName = "Tin Helmet";
            meleeDef = 3;
        }
        if (level == 4)
        {
            itemName = "Bronze Helmet";
            meleeDef = 4;
        }
        if (level == 5)
        {
            itemName = "Steel Helmet";
            meleeDef = 6;
        }
        if (itemName == "")
        {
            Debug.Log("Helmet Level Unidentified!"); //debug if helmet generation fails.
        }
        else assignment = true;
    }

    void BodyArmor()
    {
        if (level == 1)
        {
            itemName = "Worn Leather Armor";
            meleeDef = 2;
        }
        if (level == 2)
        {
            itemName = "Sturdy Leather Armor";
            meleeDef = 4;
        }
        if (level == 3)
        {
            itemName = "Studded Leather Armor";
            meleeDef = 6;
        }
        if (level == 4)
        {
            itemName = "Bronze Armor";
            meleeDef = 8;
        }
        if (level == 5)
        {
            itemName = "Steel Armor";
            meleeDef = 12;
        }
        if (itemName == "")
        {
            Debug.Log("Armor Level Unidentified!"); // debug if armor generation fails.
        }
        else assignment = true;
    }

    void LegArmor()
    {
        if (level == 1)
        {
            itemName = "Worn Leather Leggings";
            meleeDef = 2;
        }
        if (level == 2)
        {
            itemName = "Sturdy Leather Leggings";
            meleeDef = 3;
        }
        if (level == 3)
        {
            itemName = "Studded Leather Leggings";
            meleeDef = 4;
        }
        if (level == 4)
        {
            itemName = "Bronze Leggings";
            meleeDef = 5;
        }
        if (level == 5)
        {
            itemName = "Steel Leggings";
            meleeDef = 7;
        }
        if (itemName == "")
        {
            Debug.Log("Legging Level Unidentified!"); //debug if legging generation fails.
        }
        else assignment = true;
    }

    void HandArmor()
    {
        if (level == 1)
        {
            itemName = "Torn Leather Gloves";
            meleeDef = 1;
        }
        if (level == 2)
        {
            itemName = "Good Leather Gloves";
            meleeDef = 2;
        }
        if (level == 3)
        {
            itemName = "Padded Leather Gloves";
            meleeDef = 3;
        }
        if (level == 4)
        {
            itemName = "Chain Gloves";
            meleeDef = 4;
        }
        if (level == 5)
        {
            itemName = "Steel Gauntlets";
            meleeDef = 6;
        }
        if (itemName == "")
        {
            Debug.Log("Glove Level Unidentified!"); //debug if glove generation fails.
        }
        else assignment = true;
    }

    void FeetArmor()
    {
        if (level == 1)
        {
            itemName = "Worn Leather Sandals";
            meleeDef = 1;
        }
        if (level == 2)
        {
            itemName = "Quality Leather Sandals";
            meleeDef = 2;
        }
        if (level == 3)
        {
            itemName = "Leather Boots";
            meleeDef = 3;
        }
        if (level == 4)
        {
            itemName = "Bronze Boots";
            meleeDef = 4;
        }
        if (level == 5)
        {
            itemName = "Steel Boots";
            meleeDef = 6;
        }
        if (itemName == "")
        {
            Debug.Log("Boot Level Unidentified!"); //debug if boot generation fails.
        }
        else assignment = true;
    }
   
    void ShieldArmor()
    {
        if (level == 1)
        {
            itemName = "Damaged Wooden Shield";
            meleeDef = 2;
        }
        if (level == 2)
        {
            itemName = "Good Wooden Shield";
            meleeDef = 3;
        }
        if (level == 3)
        {
            itemName = "Tin Shield";
            meleeDef = 4;
        }
        if (level == 4)
        {
            itemName = "Bronze Shield";
            meleeDef = 5;
        }
        if (level == 5)
        {
            itemName = "Steel Shield";
            meleeDef = 7;
        }
        if (itemName == "")
        {
            Debug.Log("Shield Level Unidentified!"); //debug if shield generation fails.
        }
        else assignment = true;
    }   
}

GetComponent<T>() looks for a component of that type on a specific game object. And since it is a member function of MonoBehaviour, C# allows you to call it from within any other member function of a MonoBehaviour class, using the current instance of the MonoBehaviour which is currently executing code. In other words, it implicitly becomes _**this**_.GetComponent<T>(), which then delegates to the game object that the current MonoBehaviour is attached to. If this particular game object does not have any component of the specified type, you get null.

So, does the game object which has your ChestLevelOne component attached also have a LootGenController attached? Or is the LootGenController attached to a different game object? If the latter, then you somehow have to get access to that other game object. This can be done in a variety of ways, such as Object.FindObjectOfType(), using a singleton pattern with a static accessor, having a public LootGenController field on the ChestLevelOne class and setting that field in the inspector, or having the LootGenController find all of the chests at start and telling each one that it is the loot generator.

Andy,

Thanks for the reply. The ChestLevelOne is attached to a cube. The LootGenController is attached to an empty. So different objects. When I put a public LootGenController on the ChestLevelOne script (line 11 chest script), and then drag the LootGenController object(empty) into the space on the inspector and try the GetComponent like I would for, say the Rigidbody, is when I get the error about no implicit conversion (line 18).

When I get home tonight I will see if I can dissect your explanation and figure it out. Thanks again.

hi, i think you are talking about two different problems that occured but you are not being clear here because you said
that when you added line 11 code:

// public GameObject lootGenControl; // No worky

you recieved

which is to be expected, because your LootGenController is not a GameObject.

then,i think you tried correcting it with using the correct type:

 private LootGenController lootGenControl;

but got a NullReferenceException ,because the LootGenController was empty?

the only other posibility i can see is that you could at one time forgotten the :monobehaviour to have a conversion error ,but that is obviously not the case here, so i assume one of the above ?

let me further extend on this for you , the code:

private LootGenController lootGenControl;

has a very specific issue , this field variable of type LootGenController will not show up in the inspector , beacause it is private , only if you made it public would it show up in the inspector for you ,so you cannot add it via the editor , you have to add it with :

LootGenControl=gameObject.AddComponent<LootGenController>();

this will add it as a new component to your script , instead you used :LootGenControl=GetComponent<LootGenController>();
which will not work since you had it private and could not have added the component via editor and there is no code which added the component visible to me.

your other option is to make it public and then add an reference via the editor.

and then also you have a multitude of options other than what i mentioned ,like this member said

it’s all about reference and instances here ,if an object points to emptyness it is null ,you can call a variable GameObject gameobject; , but untill you assign a gameobject to that variable ,it points to nothing.

sorry for all the edits :slight_smile:
regards.

1 Like

Thanks for all the explanation wjbender. I will be home in a couple hours and play around with it and let you know what I find.

To a new person this is a bit confusing because everything is so close to each other, but yet very different. Hard to keep it straight. But I will get it. I got nice book on C# yesterday that I will go through with a fine tooth comb, and working through the tutorials.

I appreciate the help. This is a great site.

Thanks for the help. I played with it for awhile and then just deleted everything related to calling the LootGenController and started fresh. I just was over thinking it and thoroughly confused myself in the process, but then went step by step. and it worked. I don’t fully understand it yet but that will come with time.

Solution:

public class ChestLevelOne : MonoBehaviour
{
    private bool isOpen = false;
    private int chestLevel = 1;
    private int chestType;

    public LootGenController lootGenControl;

  

    void Start ()
    {
        //chestType = (int)Random.Range(1.1f, 2.9f);
    }

    private void OnTriggerEnter(Collider other)
    {
        chestType = (int)Random.Range(1.1f, 2.9f);
        if (other.CompareTag("Player"))
        {
            switch (chestType)
            {
                case 1:
                    lootGenControl.ArmorGeneration(chestLevel);
                    break;
                case 2:
                    lootGenControl.WeaponGeneration(chestLevel);
                    break;
                default:
                    Debug.Log("Chest Type Failure");
                    break;
            }
        }
    }
}

you are doing great in my opinion ,just little mistakes ,they happen do not worry .

you just need to realize that any function you use from your component which uses monobehaviour ,works directly on that component instance , so for instance if you get the transform.position like:

Vector3 mypos=transform.position;

you are getting the transform from “this” gameobject your coding on, if you need to access the transform from another gameobject you need to access functions that works outside of “this” gameobject ,like using findobjectswithtag or perhaps a stored reference somewhere globaly accesable ,because it falls outside of the scope of “this” gameobject ,it is a seperate thing altogether , i can see how getcomponent can easily be confused for a global search ,because it sounds like it is saying "get me this component from anywhere you find it " ,whilest its actually dealing with “this”->getcomponent “inside this gameobject scope”. ,anyway i am not the best to clearly explain this ,Andy said it best .

I think the root of my misunderstanding is more from the hierarchy reference.

I have an Empty object with the script attached. I made the public reference

public LootGenController lootGenControl;

And I can get the transform with

Vector3 testV3 = lootGenControl.transform.position;
Debug.Log(testV3);

So I knew I had my reference. When I tried to access my function, my chain of thought was
ObjectReferrence> ScriptName>MethodName, or

lootGenControl.LootGenController.ArmorGeneration();

Which gave me an error "Game Object does not contain a definition for LootGenerController.

So then thought process was "I need to use GetComponent for the Rigidbody component, I must also need to use the GetComponent for the script too. And that’s where it all fell apart :slight_smile:

Just plain ol’ over thinking it.

Thanks again for everyones help.