0
\$\begingroup\$

So I have a game object MapGenerator and 2 attached scripts: MapGenerator.cs and Lexicon.cs.

I need to be able to access a couple variables in Lexicon.cs from MapGenerator.cs, so I imported it with:

void Awake()
{
    GameObject gameObject = new GameObject("Lexicon");
    lexicon = gameObject.AddComponent<Lexicon>();
}

Now, this allows me to pull variables from Lexicon, and I am able to do that successfully for the most part. However, the public fields that are being set from Unity (tiles/sprites) are not being pulled in. They just show up as null.

Example scenario:

public class Lexicon : MonoBehaviour
{ 
public Tile thiccTile;
public Tile thiccTile2;
public Tile thiccTile3;
private string fact = "Yummy porchop";
private string favoritefact;
private Tile favoritetile;

 public Lexicon() { 
  favoritetile = thiccTile2; //this guy is public and set in unity under components.
  favoritefact = fact; 
 }

public Tile Favoritetile { get { return favoritetile; } set { favoritetile= value; } }
public string Favoritefact { get { return favoritefact; } set { favoritefact= value; } }

}

Start() {
UnityEngine.Debug.Log(lexicon.Favoritefact); //"Yummy porchop"
UnityEngine.Debug.Log(lexicon.Favoritetile); // null
}

My first thought was it was because I never explicitly call Lexicon(). However, since favoritefact works, im guessing the gameobject stuff is enough? Anyway, how can I fix this?

EDIT: This is where I set the values (Different variable names but you get the idea)

enter image description here

\$\endgroup\$
5
  • \$\begingroup\$ Where do you populate those public variables after your AddComponent<> call? \$\endgroup\$ – DMGregory Aug 25 '20 at 22:20
  • \$\begingroup\$ Those are from Unity UI, I drag the tile in before starting the game. \$\endgroup\$ – ragnvaldr.js Aug 25 '20 at 22:23
  • \$\begingroup\$ If I am supposed to do something beyond that than maybe thats why it doesnt work... \$\endgroup\$ – ragnvaldr.js Aug 25 '20 at 22:24
  • \$\begingroup\$ Note that when you use AddComponent<>, you get a brand new instance of that component with default values. It can't fetch something you previously set in the Inspector on a different object, because it doesn't know which object it should copy the values from (you could have hundreds of these scripts with different values elsewhere in your project, or zero, for all it knows). \$\endgroup\$ – DMGregory Aug 26 '20 at 0:39
  • \$\begingroup\$ I think that is what the guy under is saying... can you give me an example of how I would do this with code? \$\endgroup\$ – ragnvaldr.js Aug 26 '20 at 5:57
1
\$\begingroup\$

It looks like this is a misunderstanding of how instances of MonoBehaviour scripts work in Unity.

A MonoBehaviour script is a (non-static) class, which means it's a template for a kind of object you can have in your project - not a unique or global repository. When you add a MonoBehaviour component to a Game Object, what you have created is one instance of that script.

Your project could contain zero instances of the script, or one, or hundreds. Each individual instance gets to have its own set of values stored in its member variables. So setting the variables in the Inspector on one instance does not automatically change the values held in a different instance, or in instances you create in the future.

If I understand correctly what you are doing, in the editor you've added a Lexicon component to a game object, and populated that one instance with the variables you want.

Then at runtime, you have another script that creates a new, empty game object called "Lexicon" (no relation to the object you set your variables on previously), and attaches to that game object a new instance of the Lexicon component. Since this component is new, all of its variable have the default values from the code file. Nothing here tells Unity it should fetch the variables you set in the inspector on that particular other Lexicon component at edit time.

This is a bit like writing a letter, and putting it in an envelope. Then the next day, taking a blank sheet of paper, folding it into an envelope, and opening it - being surprised that this new envelope doesn't contain your letter. Well of course it doesn't, it's not the same envelope you put your letter into!

Your attempt to use GameObject.Find("Lexicon") will work if the component you set up in the Inspector was on a game object that currently exists in the same scene your script Awake script is running in, and named exactly "Lexicon". This method searches only the current scene, and won't find objects you configured in other, unloaded scenes, or in prefabs in your Assets folder. It's also extremely picky about the name. So if you have a "Lexicon" prefab that you spawn at runtime to make an instance in your scene called "Lexicon (Clone)", the Find method won't find that one.

Sometimes folks use the singleton pattern to make one globally-accessible instance of a MonoBehaviour script, but singletons have their own complications, and I think we can do better for your use case.

If you just want a data container other scripts can reference, what you might want instead is a ScriptableObject. This is a type like a MonoBehaviour that lets you edit data in the Inspector window, but it doesn't attach to a game object in your scene. Instead, it can exist as a standalone asset in your Assets folder. This makes it useful for authoring shared data that you might want to reference from many scripts or many scenes.

Here's what that might look like:

// This attribute lets us create an instance of this asset via the menus.
[CreateAssetMenu(fileName="Lexicon.Asset", menuName = "Data/Lexicon")]
public class Lexicon : ScriptableObject
{ 
    public Tile thiccTile;
    public Tile thiccTile2;
    public Tile thiccTile3;
    private string fact = "Yummy porchop";

    private string favoritefact;
    private Tile favoritetile;

    // Generally for Unity types we avoid using constructors,
    // since object properties are set during deserialization.
    // OnEnable or Awake are good substitutes for ScriptableObjects.      
    void OnEnable() { 
      favoritetile = thiccTile2;
      favoritefact = fact; 
    }

    // It looks like you could implement these as auto-properties for less typing...
    public Tile Favoritetile { get { return favoritetile; } set { favoritetile= value; } }
    public string Favoritefact { get { return favoritefact; } set { favoritefact= value; } }

The attribute at the top lets us create an instance of this asset type via the Assets-> Create menu or the right-click context menu in the Project window:

Menu creation

Then we get a new asset we can configure in the Inspector:

Lexicon in Inspector

Note how this data is standalone, not attached to a game object or prefab, and not bound to any scene.

Now any script can hold a reference to this Lexicon by declaring a public variable:

public Lexicon lexicon;

And you can assign it in the Inspector to reference this asset:

Assigning a lexicon reference

This reference will work from any scene, any prefab, etc., and keeps your data centralized in a single asset file rather than duplicated among multiple scenes.

\$\endgroup\$
7
  • \$\begingroup\$ Thank you so much for the detailed reply! I am going to this right away and see how it goes... this looks perfect. \$\endgroup\$ – ragnvaldr.js Aug 26 '20 at 14:24
  • \$\begingroup\$ You beautiful, beautiful being... Thank you. This works. \$\endgroup\$ – ragnvaldr.js Aug 26 '20 at 15:04
  • \$\begingroup\$ Ok, slight caveat. This makes everything static? That really isn't ideal for my scenario, but I guess I didn't give you the full scope so you wouldn't know. Back to the drawing board I guess. \$\endgroup\$ – ragnvaldr.js Aug 26 '20 at 15:12
  • \$\begingroup\$ We didn't use the static keyword anywhere, so it's not "static" in that sense. What sense do you mean it in? \$\endgroup\$ – DMGregory Aug 26 '20 at 15:15
  • \$\begingroup\$ Ah geez nvm I think I was just being a noob/idiot. So I was adding thiccTiles to an array, like myArray[12,22] = thiccTile2, but then later on changing the value again by doing myArray[12,22] = thiccTile1. When I did that however, it changed every instance of thiccTile2 to a reference of thiccTile1... I thought this meant it was static but I think I am wrong. That is just normal behavior isn't it? \$\endgroup\$ – ragnvaldr.js Aug 26 '20 at 15:25
0
\$\begingroup\$

Both favoritetile and favoritefact will get assigned when the object is instantiated. Favoritefact will get assigned "yummy porchop", no ifs/ands/or buts about it, since it is a string literal within the script.

IF thiccTile2 has an assigned a value in the inspector (Unity UI as you call it), then that value will get assigned to favoriteTile. This is where the problem is. If favorite tile is null after instantiation, its because thiccTile2 does not have a value. You might think you have assigned it value (but actually have not), for a couple possible reasons:

  1. You have a gameobject in your scene with a Lexicon component attached, and you have assigned the value to the component in that gameobject. In this scenario, that Lexicon component is an instance of the Lexicon class. Any values you assign in the inspector will only be assigned to that instance of the class. When you Instantiate a new Lexicon (in your Awake() method), it has no knowledge of the existing Lexicon that is already attached to the gameobject in the scene, and so thiccTile2 is null. You can fix this by updating your Awake() method to get a reference to the existing Lexicon where you have assigned your values in the inspector (using GameObject.Find().GetComponent()). I believe this is the problem. If you actually want a new instance of Lexicon on your game object, you will have to either copy the values from the existing instance, or create a prefab with the Lexicon component, assign your values to the component of the prefab, and then instantiate that prefab.

  2. A second scenario is that you have assigned your values and references correctly, but done so in play mode. Nearly all changes done in play mode will be discarded / reverted after exiting to edit mode and restarting play mode. Since your debug happens in Awake(), I can imagine a scenario where you enter play mode with bad references > see the null debug > fix your references > realize you need to restart your game to see the new debugs > exit play mode (and unknowingly discard your fixed references) > start play mode and see the same error, and think your changes didnt fix anything. This seems like the unlikely culprit, given the snippets of code you have provided.

The real bottom line is when you instantiate Lexicon using new Lexicon(), thiccTile2 is null, so favoriteTile is null. You need to either pass it in as a constructor variable, or look it up from a reference to a prefab/existing gameobject component or something.

\$\endgroup\$
1
  • \$\begingroup\$ Its definitely not 2)... 1) Sounds pretty promising though. I tried changing: GameObject gameObject = new GameObject("Lexicon"); lexicon = gameObject.AddComponent<Lexicon>(); to GameObject gameObject = GameObject.Find("Lexicon"); lexicon = gameObject.GetComponent<Lexicon>(); But just get a null reference. Can you elaborate? \$\endgroup\$ – ragnvaldr.js Aug 26 '20 at 5:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.