How to associate an int with a GameObject

I am instantiating many GameObjects in my scene and then I operate on them using a raycast. I want to associate an int with each instantiated GameObject. Currently I use the GameObject’s name:
for setting:

AllLvlArray[allLvlArrayNum].name = allLvlArrayNum.ToString();

for getting:

hitNodeNum = int.Parse(hitPointer.collider.gameObject.name);

This works and does not slow things down, but I am sure there is a cleaner way.
Thanks in advance for your help.

I don’t think that ther’s a much better way unless you tell us for what purpose you want to do this. The name is an attribute of the gameObject itself and most other attributes you’ll have to add by adding a component. You can try and hack the reference to be interpreted as an int, and that will be pretty much unique (which is not guaranteed for the Name). And you can always access the InstanceID of each object, which is also unique (note that in both cases uniqueness persist only through the existence of the Scene, not across Scenes or re-plays)

You can also create a singleton (which I’m sure most of us do) for an objectDB of all active objects in the Scene, and then implement that as a Dictionary<int, GameObject>.

So you are spoilt for choices . now why would you want to do that?

My use case is a bit complicated, but I’ll try to explain. For my scene I have to spawn many spheres (“nodes”) that have a SphereCollider and a MeshRenderer. They remain mostly static for the full duration of the scene. However, I instantiate as GameObjects a prefab that has only a SphereCollider. I render the meshes of the nodes with batched instanced rendering at the place of each spawn. When I instantiate the GameObjects with the SphereColliders, I put all those GameObjects in an array and also assign their names to match their index in the array and at the same time I keep all the information (position, rotation, scale, transformation matrix, etc.) for the batched instanced nodes indexed in the same way. So when I have to, for example, change the color of the node that I am selecting, I use a raycast to the SphereColliders, I parse the SphereCollider’s GameObject name and then modify the instanced color of the node with SetVectorArray() at the same index number.
I understand that there is no “cleaner” way (I remember reading something about HashIDs, but I am not sure what it is and how it is applied), so I might just go with what I have now.

I am not so familiar with Dictionaries. I understand that I can get the GameObject when I provide the “key” (int), but I also need to get the “key” (int) for a GameObject when the raycast hits that GameOject’s SphereCollider.

Are you sure you need to batch update when updating the spheres? Do you need to do it that way?
Wouldn’t that only be needed for the initial burst of instancing? Afterwards you can just update the actual object right?

Actually,you can turn that around. Since the game objects will be unique, you can use them as a key in a Dict. Define the Dict as Dictionary<GameObject, int>, and as Long as you make sure your ints are also unique easily have a reverse lookup.

3 Likes

Ah, cool, I did not know that GameObjects can be a key in a Dictionary. Thanks a lot!

I am trying to use the GameObject as the key, but it seems that whenever I add it to a Dictionary it gets cloned and has a (Clone) tag in its name. Meaning I cannot use the original to look it up. Can you post an example of how you got around that?

Try to add a string key

GameObject.name

and be sure that the key is unique :slight_smile:

I’m not sure you have the order of events correct. Please post your code to show the issue. The reason being that if you add a GameObject to a Dictionary via Add(key, value), the object will definitely not get cloned unless you write cloning code yourself. If, for example, you invoke Instantiate() on a game object, the resulting new object in your scene will be a clone of the object you are instantiating, and Unity will by default add ‘Clone’ to it’s name. Dictionary.Add will not do that, so I presume you have some additional cloning code in there somewhere.

Note also that there is a big difference between the object itself (which you can use a as key in a dictionary) and the object’s ‘name’ attribute (which you can also use as a key in the a dictionary, but which you will have to insure that it is unique). If you use an object as key in a dictionary, your dictionary will be a Dictionary<GameObject, int>. All GameObjects in your game are always unique. If you use a GameObject’s name property as a key, that definitionwould be Dictionary<string, int>, something very different. you must ensure that all strings you enter as keys are unique.

-ch

Thank you for your fast response, hope you had a good new years.
Here is the one of the places in my code where I instantiate the game objects, and add them to the dictionary. I think I see what you are trying to say, so how can I copy the same prefab multiple times and associate it with different coordinates then? I am trying to make a hex tile map.

public void drawMap(EditorModel em){
        foreach(HexModel hmodel in em.hexes){
            GameObject hexGO = GameObject.Instantiate(HexPrefab, hmodel.Position(), Quaternion.identity, go.transform);
            map.Add(hexGO, hmodel);
            MeshRenderer mr = hexGO.GetComponentInChildren<MeshRenderer>();
            Debug.Log("drawing: "+hmodel.type);
            if (hmodel.type.Replace(" ", string.Empty) == "Water"){
                mr.material = HexMaterials[1];
            }else if (hmodel.type.Replace(" ", string.Empty) == "Grass"){
                mr.material = HexMaterials[0];
            }
        }
    }

From whatI can see, the code looks more or less fine. Let’s be careful whith prasing in this particular case: You don’t really ‘copy the same prefab multiple times’, you are (and your code does this correctly via Instantiate), you create a clone (an instance) of the prefab, and then work with that clone. I do not see the type you chose for map, but I’m assuming you defined it as Dictionary<GameObject, HexModel>. Which means that for any existing hexGO you can find the HexModel that is was created for in map.
For each instantiation, you create a clones of HexPrefab (they will all be named ‘ clone ’ and be set at the root of the scene), set the newly instanced hexGo’s position to be the same as the existing hexModel, and set the rotation to identity (no rotation at all).

So, to me all this looks correct: you now have a number of cloned GameObjects of the same type and composition as HexPrefab, with their transform’s position set to the value the current hmodel’s ‘position’ attribute (note that this is not the transform’s ‘position’, but a vector4 attribute in HexModel that you named ‘position’ - is this intentional?).

What are you getting, what is not working?

void Update () {
        if (Input.GetMouseButtonDown(0)){
            RaycastHit hit;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            int layerMask =LayerIDForHexTiles.value;
            if(Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask)){
                GameObject go = hit.rigidbody.gameObject;
                Debug.Log("Dictionary Size: " + hm.map.Count + " Object name: " + go.name);
                HexModel hmodel = hm.map[go];
            }
        }
    }

Here is me trying to get the HexModel i want to associate with the GameObject from the dictionary. The Debug.Log outputs “Dictionary Size: 35 Object name: HexModel” for any hex that I click on, so I know it is not empty. It’s size is also 35 since it is a 5x7 map. It also seems that all the hexes are called “HexModel”. When the last line of Update runs, when I try to get the dictionary value, it outputs this error:
KeyNotFoundException: The given key was not present in the dictionary.
System.Collections.Generic.Dictionary`2[TKey,TValue].get_Item (TKey key) (at <599589bf4ce248909b8a14cbe4a2034e>:0)
which is weird, cus I thought these were the same objects that I just instantiated :confused:

More information:
hm is a HexMap object that contains the dictionary.
You were also right, it is Dictionary<GameObject, HexModel>

If I get this to work, my design pattern would work perfectly, but sadly I have not figured it out yet.

Since you were curious, Position() converts the HexModel’s game map coordinates, to Unity’s grid coordinates so that the hexes are placed in their proper spots when they are generated. I followed Quill18’s tutorial to figure that one out :smile:

How are yu making sure that the object returned by raycast is an object that is actually a key in the Dictionary? If, for example, the collider your raycast hits belongs to a child object of one of the objects in the Dict, your access will fail because it’s parent is the key for the lookup, not the collider container itself.

So, before you access ‘map’ make sure that one of its keys is actually the object who’s collider you hit:

if (map.ContainsKey(go)) {
    ... access ...
} else {
   Debug.Log(go + " is not a key in map");
}

That way you can find out what object was hit by raycast, and which objects serve as keys in your dict. You’ll probably have aneasier time if you named the instantiated prefabs bychanging their name to contain an index to the hex

I always just add a specific component (MonoBehaviour) to the GameObject in this case and just retrieve that after the raycast. Then you can place whatever you want in that MonoBehaviour, like an int. Usually using an abstract superclass that is searched for after the raycast and various implementations of that class that are actually placed in the scene.

So, in a way that is missing all null checks:

hitNodeNum = hitPointer.collider.gameObject.GetComponent<DataClass>.number;

That seems to make this specific problem worse, not better.

  • If the returned object isn’t the one he’s looking for, it also won’t have the special component
  • This is run during Update() - invoking GetComponent is very costly with regards to performance, and any advantage of quickly looking the object up via Dict is completely negated.

Well, of course, and then you know it’s not what you are looking for. Or in other words, you didn’t click on a Hexagon. How is that different from not having the entry in the Dictionary?

Is it? I’m not aware of GetComponent being specifically slow, though I do generally cache them. However, since this is one RayCast, there is also at most one call to GetComponent. It makes no difference in running order (O(1)), so any performance loss is constant.

The advantages of course being:

  • You can view the actual int values in the scene, because they are attached to the actual collision objects.
  • You can make use of inheritance. Instead of a Field, you can use a Property that can execute any required code based on the specific instance type before returning the value.
  • There is no need to build/maintain a separate Dictionary for lookup, the scene is the lookup.

OMG I didnt even notice that I was saving HexPrefab, but raycasting HexModel. I got the parent of the raycast object (HexPrefab) using go.transform.parent.gameObject though. Thank you so much! You are my hero!

IMHO, performance loss isn’t better when it is constant, even if O(n) and following an already costly Raycast. Still, my performance comment was off-topic, so please disregard. I guess I simply stated that because recently was heavily involved in a somewhat painful performance exercise (after exhausting the profiler). Some findings caught me by surprise: checking an object against null is very costly, and the (to be further substantiated) claim that in C# ‘foreach’ is less performant than ‘for’ (which is the direct opposite to ObjectiveC, where fast enumeration is facilitated with foreach, and this probably comes down to compiler implementation).

Well, running order really is more important than anything, but any loss is indeed a loss. Note that both a Dictionary lookup and GetComponent should be O(1), not O(n), so that really is the best kind of performance loss. In all, it’s just a suggestion/option. It’s the way I generally set these things up, but other ways might be fitting the situation better.

Offtopic, but does that go for any object or specifically for objects derived from Component?

I can’t imagine the first is really the case, but the second might be, which is Unity specific.