Hi
I have repeating objects throughout my app which are signposts to different content. I want to grab the URL that sits on each object then loop them to extract that URL and then process i.e. to save me having to attach the same scripts to 50 different objects and copy the url.
So i figure I need to group all the signposts by tag: signposts
Get them and loop through them. But what I can’t work out is how to access the URL that has been set for each object in an attached script: “setURL”)
This is as far as I have got:
var signs = GameObject.FindGameObjectsWithTag("signpost");
foreach (var sign in signs)
{
//this is the line i have no idea about. How do I get the URL, which will be different for each object
// it is set as a public string within the script, setURL
var URL = sign.GetComponent<setURL(URL)>;
GameObject[] signs = GameObject.FindGameObjectsWithTag("signpost");
foreach (GameObject sign in signs)
{
string url = sign.GetComponent<setURL>().URL;
//do thing with url
}
This assumes your setURL class has a public field called URL of type string. Note that anything you do to this string is working on a copy and won’t change the URL string in the class itself. If you want to change the string in the class, remove the string url = asignment and set .URL equal to whatever string you want.
Another approach might be making a script that you put on your signpost objects that automatically registers and unregisters them with a central list somewhere. Then you wouldn’t have to find anything, just go straight to the list of already registered objects.
thank you for the quick and very helpful response. I will start with this solution and then look to improve further.
This is the actual code I am looking to get to work to make sure it is working properly:
var signs = GameObject.FindGameObjectsWithTag("signpost");
foreach (var sign in signs)
{
// var URL = sign.GetComponent<Script(URL)>;
string url = sign.GetComponent<ToggleShowHideVideo>().VideoUrlLink;
Debug.Log("url is: " + url);
}
This is returning all the URLs, as I had hoped. But I hadn’t thought about the other half of what I need to do!
Next step is to query my DB with information relating to that URL and then use the results to either show or hide a graphic on a child object:
Gameobject of where I have attached the script →
Zonenames (multiple) →
SignPosts (multiple) →
Favourite->
Heart->
node_id34
where node_id34 is a graphic i want to display
I have got the query to the DB and the return of 1 or 0 based on whether criteria are matched, but how do i then access node_id34
I assume the sign that you refer to in the foreach is a GameObject that has a node_id34 GameObject in its child heirarchy somewhere.
You could use GetComponentInChildren<>. Using your code as a base:
var signs = GameObject.FindGameObjectsWithTag("signpost");
foreach (var sign in signs)
{
//var URL = sign.GetComponent<Script(URL)>;
string url = sign.GetComponent<ToggleShowHideVideo>().VideoUrlLink;
Debug.Log("url is: " + url);
//get some component on the node_id34 gameObject
sign.GetComponentInChildren<SomeComponentMaybeTheImageComponentOnNode>();
}
If node_id34 is the only object in the heirarchy that has SomeComponentMaybeTheImageComponent then this will give you a reference to it. Now you can either disable the image component that you have a reference to, or if you wanted to, disable the entire GameObject like this:
var signs = GameObject.FindGameObjectsWithTag("signpost");
foreach (var sign in signs)
{
//var URL = sign.GetComponent<Script(URL)>;
string url = sign.GetComponent<ToggleShowHideVideo>().VideoUrlLink;
Debug.Log("url is: " + url);
//get ref to component on node game object
var component = sign.GetComponentInChildren<SomeComponentMaybeTheImageComponentOnNode>();
//get ref to game object that component is on
GameObject componentGameObject = component.gameObject;
//disbale the gameobject
componentGameObject.SetActive(false);
}
ok there are other objects in the sign child object - i thought i was being organised by creating sub sections! Attached is the hierarchy that I am trying to traverse. The script is on “zones” with the red arrow pointing to it.
The image to turn off, repeats for each sign (white line) and each image will either be on or off for the given sign (green line)
so i tried to follow your code:
sign.GetComponentInChildren<heart>();
but get an error on “heart” - it doesn’t recognise this:
Severity Code Description Project File Line Suppression State
Error CS0246 The type or namespace name ‘heart’ could not be found (are you missing a using directive or an assembly reference?) Assembly-CSharp E:\Development\masterchange\Assets\MyStuff\Scripts\using\getfavbatch.cs 35 Active
The GetComponent family of functions refer to a component on the GameObject, not the GameObject itself. In order to find a ‘heart’ your game object would need a script on it called ‘heart’
If you want to find a specifically named GameObject by name instead, you can by first grabbing all the children, then checking their names. General case
var signs = GameObject.FindGameObjectsWithTag("signpost");
foreach (var sign in signs)
{
//var URL = sign.GetComponent<Script(URL)>;
string url = sign.GetComponent<ToggleShowHideVideo>().VideoUrlLink;
Debug.Log("url is: " + url);
//get all children of sign
Transform[] allSignChildren = sign.transform.GetComponentsInChildren<Transform>();
//get ready to store the found node
Transform foundNode = null;
//check all children of sign for the correct node
foreach (Transform child in allSignChildren)
{
if (transform.name == "node_id34")
{
foundNode = transform;
break;
}
}
//check if we found it
if (foundNode != null)
{
//disable it if we found the right node
foundNode.gameObject.SetActive(false);
}
}
As a side note, all this iteration might get expensive with a foreach inside a foreach depending on how many objects you have.
An alternative approach to consider might be to cache a reference to the node_id34 on the ‘sign’ game object in its Start() function. When you get the ‘sign’ game object in your URL related foreach loop, you could just use that reference and not need to foreach through its children on the fly.
i am worried about overhead so that sounds interesting. I am also making a call to my db to find out whether the URL is a favourite within those loops. Feels like very bad practice but can’t work out how to make this efficient. If this was done by someone who knows what they are doing they would probably dynamicaly generate the signs and place through the scene.
I now understand about the script/object name… thanks for explaining
If performance is an issue, you could elimnate the FindByTag and move most of your iterations to when the program starts rather than while its running. Maybe something similar to this approach:
Make a singleton or static class, something like class SignsRegister that contains a List<Transform> Signs objects.
Give each sign gameobject a Sign class with a RegisterSelf() function that runs in Start().
Make the RegisterSelf() function register the Sign with the SignsRegister. You
Now your signs register themselves with the manager/registry. To iterate them, you just refer to the manager’s list. That’s the FindByTag() eliminated.
Give your Sign class with the RegisterSelf() function on each sign game object a reference to its node_id34 game object. In its Start(), run a function that iterates its children and stores a ref to that node as we saw above. The iteration is there, but at least it only runs once when the script starts.
Now when you want to do your DB stuff, you just go straight to the SignManager, foreach the list of Signs, and do what you want to each of them or their components. No finding by tag, no iterating through children to find the right node.
sorry to trouble you again @mopthrow but I have hit a problem with identifying the child node and turning it off/on. I am only getting the name of the parent
Transform[] allSignChildren = sign.transform.GetComponentsInChildren<Transform>();
//for storing the matching node
Transform foundNode = null;
//check all children of sign for the correct node
foreach (Transform child in allSignChildren)
{
//get ready to store the found node
Debug.Log("child name" + transform.name);
if (transform.name == "heart")
{
foundNode = transform;
Debug.Log("heart");
break;
}
}
//check if we found it
if (foundNode != null)
{
//enable it if we found the right node
foundNode.gameObject.SetActive(true);
}
I am only getting the name of the gameobject that the script is attached to; nothing else
any ideas?
thank
Line 13 prints transform.name. That is the name of the object that this script is on. I think you wanted child.name there instead.
Line 14 checks to see if the transform with this script attached to it has a name “heart”. Another transform.name, I think you meant child.name.
Line 16 sets foundNode equal to the object this script is on too with foundNode = transform. You also want foundNode = child.transform.
All of these are because when you type transform.something you are always working with the transform of the object this particular script is on. Any time you type transform.something think of it as this.transform.something. When I started working with transforms a lot, I used to type the redundant ‘this’ part in my code to remind myself. It works fine if it helps until you get used to it.
Your code with the changes:
private void DisableHeartTransform()
{
Transform[] allSignChildren = sign.transform.GetComponentsInChildren<Transform>();
//for storing the matching node
Transform foundNode = null;
//check all children of sign for the correct node
foreach (Transform child in allSignChildren)
{
//get ready to store the found node
Debug.Log("child name" + child.name);
if (child.name == "heart")
{
foundNode = child;
Debug.Log("heart");
break;
}
}
//check if we found it
if (foundNode != null)
{
//enable it if we found the right node
foundNode.gameObject.SetActive(true);
}
}
thanks, that makes much more sense. Unfortunately there seems to be a problem because I deactivate all the “hearts” on start up and only switch on when there is a match between a favourited URL and the signpost URL.
It looks like the gameobject “heart” is only being picked up when it is active. Having done various debugging this appears to be the reason why i can’t get it to appear.
Debug.Log("heart");
only appears in console when heart gameobject is active before I run the app
Does that make sense to you? If so how do i get the transform array to include deactivated gameobjects, so I can then activate?
Makes sense yep, I believe ignoring inactives is the default behaviour. GetComponentsInChildren has some overloads though and one of them has an includeInactive flag. That should do it.
thanks - getting closer i think but now I am getting every heart being activated. I think it is because the if statement at line 22 above:
if (foundNode != null)
//needs to be inside this?
foreach (Transform child in allSignChildren)
needs to be inside the loop? However when I change this I don’t ever get into that if statement.
This is the entire script:
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
public class matchfav : MonoBehaviour
{
public void Start()
{
MyFaves();
}
private void MyFaves()
{
// read the json containing all the user's favourites
string favs = File.ReadAllText(Application.persistentDataPath + "/favourites.json");
Debug.Log("json from file: " + favs);
sites loadedPlayerData = JsonUtility.FromJson<sites>(favs);
//get all the signposts and place in signs object
var signs = GameObject.FindGameObjectsWithTag("signpost");
//loop through the user favourites
for (int i = 0; i < loadedPlayerData.data.Count; i++)
{
//trim the url of escape characters /\/\
// string myfaves = loadedPlayerData.data[i].URL.Substring(8);
string myfaves = loadedPlayerData.data[i].URL;
Debug.Log("my favourite url: " + myfaves);
//loop through all the signs
foreach (var sign in signs)
{
Debug.Log("in sign in signs loop");
//assign the specific url to signUrls
string signUrls = sign.GetComponent<ToggleShowHideVideo>().VideoUrlLink;
//trim the url so can match against the url stored in JSON which has escape characters /\/\
// signUrls = signUrls.Substring(8);
Debug.Log("signUrls is: " + signUrls);
//do the urls match
if (myfaves == signUrls)
Debug.Log("Got a match: " + signUrls + " with: " + myfaves);
{
//get all the child objects so you can identify the heart to activate
Transform[] allSignChildren = sign.transform.GetComponentsInChildren<Transform>(includeInactive: true);
//for storing the matching node
Transform foundNode = null;
//check all children of sign for the correct node
foreach (Transform child in allSignChildren)
{
//get ready to store the found node
Debug.Log("child name" + child.name);
if (child.name == "heart")
{
foundNode = child;
Debug.Log("heart matched");
break;
}
}
//check if we found it
if (foundNode != null)
{
Debug.Log("in final money shot: " + foundNode.name);
//enable it if we found the right node
foundNode.gameObject.SetActive(true);
}
}
}
}
}
[Serializable]
public class Favourites
{
public string URL;
}
[Serializable]
public class sites
{
public List<Favourites> data;
}
}
In pseudocode:
Store user favourites in JSON
Loop through each favourite url
Find signpost that contains the same url
Switch on the heart for that signpost/matching url
I think these are the major points in the code that relate to the above
read favourite URLs from json - line 26
Get all the signposts which contain a url - line 30
loop through my favourite URLs - line 34
for each sign get the url - line 46
if favourite url and signpost url match start process of activating the heart - line 54
find the heart child object for the relevant parent sign - line 58
once found, activate - line 80