- Home /
how do I access values from a script which is attached to a list’s elements and count them?
Hi,
I’m working on a model to simulate a population. I have a script that lets me spawn citizens and assigns them to a list as elements. These clones are based on a prefab which has several more scripts attached to it. One of it contains information about a citizen’s profession. For example, it determines if a citizen is employable or not based on his/her age. I want to find out how many citizens are currently employable in total. So how do I access values from a script which is attached to a list’s elements and count them?
What I have done so far: I created an empty game object with the following script attached to it in order to spawn the citizens. it is also supposed to tell me demographic facts about the employment status:
public GameObject citizen;
public int totalCitizens;
public Text totalCitizensText;
public int totalEmployedCitizens;
public Text totalEmployedCitizensText;
public int totalEmployableCitizens;
public Text totalEmployableCitizensText;
public int totalUnfitCitizens;
public Text totalUnfitCitizensText;
public float unemploymentRate;
public Text unemploymentRateText;
public List<GameObject> CitizenRegister = new List<GameObject>();
void Update()
{
if (Input.GetKey(KeyCode.Space)) // add a lot quickly
{
CitizenRegister.Add( (GameObject)Instantiate(citizen, new Vector3(1 * Random.Range(-10, 10), 0, 1 * Random.Range(-10, 10)), Quaternion.Euler(0, 0, 0)));
}
if (Input.GetKeyDown(KeyCode.N)) // add a single one
{
CitizenRegister.Add((GameObject)Instantiate(citizen, new Vector3(1 * Random.Range(-10, 10), 0, 1 * Random.Range(-10, 10)), Quaternion.Euler(0, 0, 0)));
}
//Remvoe missing elements
for (var i = CitizenRegister.Count - 1; i > -1; i--)
{
if (CitizenRegister[i] == null)
CitizenRegister.RemoveAt(i);
}
//Count all citizens
totalCitizens = CitizenRegister.Count + 1; // + 1 because of player
totalCitizensText.text = "Population: " + totalCitizens;
//Count all employable citizens
totalEmployableCitizens = ; // Employable if citizen is not unfit for work (professionNumber 1)
totalEmployableCitizensText.text = "Employable: " + totalEmployableCitizens;
/*
//Count all employed citizens
totalEmployedCitizens = ; // don't forget the player
totalEmployedCitizensText.text = "Employed: " + totalEmployedCitizens;
//Count all citizens who are unfit for work
totalUnfitCitizens = 0; // don't forget the player
totalUnfitCitizensText.text = "Unfit for work: " + totalUnfitCitizens;
//Calculate unemplyoment rate
unemploymentRate = 100 * totalEmployedCitizens / totalEmployableCitizens;
unemploymentRateText.text = "Unemplyoment rate: " + unemploymentRate.ToString("0") + "%";*/
}
As can be seen, I'm stuck at this line:
totalEmployableCitizens = ; // Employable if citizen is not unfit for work (professionNumber 1)
The information about the profession of a citizen is in the script below. It is attached to the GameObject "citizen".
public bool emplyoed;
public bool unfitForWork;
public int retirementAge;
public int professionNumber;
public string profession;
public Text professionText;
public float AgeCurrent;
// Use this for initialization
void Start () {
retirementAge = 65;
professionNumber = 0;
}
// Update is called once per frame
void Update () {
//Profession determination
if (citizens.age < 18 | citizens.age >= retirementAge)
{
professionNumber = 1;
}
//Profession List
if (professionNumber == 0)
{
profession = "none";
}
else if (professionNumber == 1)
{
profession = "unfit";
}
else if (professionNumber == 2)
{
profession = "food producer";
}
else if (professionNumber == 3)
{
profession = "water producer";
}
else
{
profession = "none";
}
//UI
//professionText.text = "Profession: " + profession;
}
Please feel free to give me additional notes as I am fairly new to c# and unity, for example if it is better to have an array in this case. Thanks a lot for your help!
Answer by Mercbaker · Jul 13, 2017 at 12:04 AM
Ok, I've changed the example to be more resembling your design.
Make a Citizen class > extend MonoBehavior like normal, add this to the prefab object
When you instantiate the prefab into your scene, we set the values for that unique citizen
At the same time, we add the Citizen to a list that we can access at a later point in time
Note the syntax to get the specific component values
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CheckCitizens : MonoBehaviour { [SerializeField] GameObject randomCitizen; public List<Citizen> allCitizens; private void Start() { allCitizens = new List<Citizen>(); //making 10 random citizens for (int i = 0; i < 10; i++) { //instantiate the prefab object to the scene GameObject obj = Instantiate(randomCitizen); //place it somewhere random obj.transform.position = new Vector3(Random.Range(0, 10), 5, Random.Range(0, 10)); //assigning the age of the citizen obj.GetComponent<Citizen>().age = Random.Range(10, 50); //assigning a random name obj.GetComponent<Citizen>().objectName = "Citizen" + Random.Range(1, 100); //adding this individual citizen to the List, so we can check its values later on allCitizens.Add(obj.GetComponent<Citizen>()); } CheckAgeOfCitizen(); } //This method access the values inside of the List of Citizens (as per your question) public void CheckAgeOfCitizen() { //Use a for loop to check the List. If you want to check ALL of the list //use the ".Count" keyword to get the size of the list //The conditional is up to your design. for (int i = 0; i < allCitizens.Count; i++) { //Here we access the value of "age" directly //We can even check to see if the age is one we are looking for //In this case we will check how many are over age "13" if (allCitizens[i].age > 13) { Debug.Log(allCitizens[i].objectName + "(age: " + allCitizens[i].age + ")" + " - is over the age of 13"); } else { Debug.Log(allCitizens[i].objectName + " is " + allCitizens[i].age + " years old."); } } } }
If your CheckCitizens Class equivalent is a Singleton, then you will be able to keep track of all the citizens as they live in your scene.
Note I changed the Citizen class variable "name" to "objectName". I forgot "name" is actually a keyword to the Object class.
You can attach the above scrip to any object in the scene. Then drag and drop the Citizen prefab(with the Citizen class scrip) into the [SerializedField] randomCitizen.
Everything should work. ;)
Note: My citizen object prefab is a cube with a RidgidBody and a BoxCollider.
Answer by Mercbaker · Jul 12, 2017 at 07:22 PM
In general you can utilize a for, or forEach loop to check through your list.
You then need to define the condition to be met.
At this point you can access the data by utilizing the iterator variable.
Make sure in your Citizen Class that you make the variable you want to access public.
Example is below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CheckCitizens : MonoBehaviour {
private List<Citizen> allCitizens;
private void Start() {
allCitizens = new List<Citizen>();
// For this example I'll just add 10 citizens to the list
for (int i = 0; i < 10; i++) {
//Create instance of Citizen...
Citizen newDood = new Citizen();
//Assign an age...
newDood.age = i + 10;
//Assign a random name...
newDood.name = "Person" + Random.Range(0, 100);
//Add citizen to the list of AllCitizens
allCitizens.Add(newDood);
}
CheckAgeOfCitizen();
}
//This method access the values inside of the List of Citizens (as per your question)
public void CheckAgeOfCitizen() {
//Use a for loop to check the List. If you want to check ALL of the list
//use the ".Count" keyword to get the size of the list
//The conditional is up to your design.
for (int i = 0; i < allCitizens.Count; i++) {
//Here we access the value of "age" directly
//We can even check to see if the age is one we are looking for
//In this case we will check how many are over age "13"
if (allCitizens[i].age > 13) {
Debug.Log(allCitizens[i].name + "(" + allCitizens[i].age + ")" + " - is over the age of 13");
} else {
Debug.Log(allCitizens[i].name + " is " + allCitizens[i].age + " years old.");
}
}
}
}
Just put this code into a script and it'll run the example, read the debug.log
As a note, you should make a List of Citizens, and in the Citizen class define the variables that are appropriate to the Citizen. My Citizen class of this example is:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Citizen {
public string name;
public int age;
}
Hope this helps.
Also, just a pointer about the update function... read below.
You should be careful what you put in the update function, because it runs every frame. Your current code will not cause much problems, because simple conditionals like those should process pretty quick. But you don't need to check every frame for those changes.
Ins$$anonymous$$d, check right after the variable changes.
If you have a function that is changing a variable, put an if statement right after the variable change to check if it meets the appropriate criteria.
This way, you don't need to check every frame for the change.
Again, be careful what you check for in the Update function, it can create a lot of unnecessary processing work.
You are absolutely right about my failure to use anything other but the Update function. Right now I’m able to run around 400 citizens simultaneously until the system lags. I will also add scripts in the future to let citizens trade automatically among each other according to their needs and finances. By using Update I can probably be happy if my computer manages to compute 50 citizens at once. $$anonymous$$y main need script (responsible for hunger, thirst, status, etc.) runs in an IEnumerator function where it is supposed to wait 3 seconds. By doing this I thought it would calculate everything only every 3 seconds but I doubt it is actually working. You are recommending to put an if statement after variable changes. How exactly do I do that? I tried something similar before where I asked for a bool to be true before the calculation was made. For example, I didn’t want to have the total food and water demand (which makes a citizen decide to invest in a farm) calculated every frame but ins$$anonymous$$d only five times a month so I tried this:
// Demands
/*if (demand$$anonymous$$anager.demandAct == true)
{
demand$$anonymous$$anager.foodDem = demand$$anonymous$$anager.foodDem + hunger; //sending the hunger info of a single citizen to be added up in the demand$$anonymous$$anager script
demand$$anonymous$$anager.waterDem = demand$$anonymous$$anager.waterDem + thirst;
demand$$anonymous$$anager.demandAct = false; // closing the statement to skip it for a few days
}
if ((days == 1 && hours == 1) | (days == 7 && hours == 1) | (days == 13 && hours == 1) | (days == 19 && hours == 1) | (days == 25 && hours == 1))
{
if (demand$$anonymous$$anager.foodDem > 0 | demand$$anonymous$$anager.waterDem > 0)
{
demand$$anonymous$$anager.foodDem = 0;
demand$$anonymous$$anager.waterDem = 0;
}
demand$$anonymous$$anager.demandAct = true;
}
However, this does not work as every citizen runs the same script and the first one who finishes the if statement sets the Boolean to false, thereby denying access to it for everyone else. Anyway, I guess this is going to require a new thread as I understand only one question per post is allowed in this forum - sorry for straying from the subject and the long post. Thanks again for your help! I hope I’ll be good enough one day to return the favor.
If your Object is having its variables changed, just include an if statement right under the change to send a signal that the Object is ready to have something happen.
So, if its hunger...
//call this method on the object when you need to
public void IncreaseHunger(int amount) {
hunger += amount;
if (hunger >= 30) {
Citizen$$anonymous$$anager.Instance.HandleCitizenHunger();
}
So with the above, ins$$anonymous$$d of checking literally thousands of times, we can actually just check only when the variable changes.
The problem you may be facing is that maybe you haven't designed your project to be Object oriented in nature.
You may be mixing unrelated work in your Classes functions. But yeah, overall, this is just something to think about.
Try to find ways to eli$$anonymous$$ate the Update calls and you can probably increase the Citizen count far beyond 400.
Thanks so much for your help! I tried to run your code separately from my scripts to see how exactly it works. Unfortunately, I’m having problems with your Citizen Class. Where exactly should I put it? When I copy it in a script and try to attach it to a cube (serving as a NPC prefab) it doesn’t let me as it wants to be a $$anonymous$$onoBehaviour, I guess. It also does not work when I put in on top of the CheckCitizens script.
As to applying your example to my script, I see that line 35 in your code looks exactly like what I was looking for. When it says allCitizens[i].age, it shows an error as I haven’t declared age without the citizen class. In my case, age is declared in another script that is also attached to my citizen prefab. In this script, age is based on calculation referring to a time script which works outside and independent of the whole list and citizen scripts. By now, I have a need, personality and attribute system in five scripts which are all attached to the citizen prefab. I just wish, I could write a code like “: if (allCitizens[i].citizenBasicInfo.age > … “ inside the for loop to refer directly to the script where age is defined. As that is not possible, does it mean, I need to copy all five scripts in your Citizen class and put it in the same script where I declare the list?
Your answer
![](https://koobas.hobune.stream/wayback/20220612130115im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
A node in a childnode? 1 Answer
How to assign saved transform values(tr,rot,sc) from a List, to the same gameobject ? 1 Answer
Changing gameobjects Float from another script? 1 Answer
how can I get values stored in a list? 3 Answers
How can I get all array/list values within a given range? 3 Answers