How to randomly pick value from a list in Unity3D? - c#

Suppose I have a list of Robot class [List< Robot> myList=new List< Robot>()]. Each Robot has a name and id depending on its colour. Now randomly pick values from the list and give an output of how many Robots of each colour are there on your list.
(N.B. Consider you have only 3 colored Robot[Yellow,green, red])
my code:
public class Test : MonoBehaviour
{
private void Start()
{
List<Robot> myList = new List<Robot>();
List<string> robotList = new List<string>();
robotList.Add("yellow");
robotList.Add("green");
robotList.Add("red");
int someNum = Random.Range(0, robotList.Count);
string robotNumber = robotList[someNum];
robotList.RemoveAt(someNum);
Robot robot;
int id = 0;
robot = new Robot(robotNumber, id);
Debug.Log(robot);
id++;
}
}
public class Robot
{
public string name;
public int id;
public Robot(string name, int id)
{
this.name = name;
this.id = id;
}
}
but this not work maybe.. actually I don't understand what actually my output is...

Not sure to really understand what you're asking for: if it's only about the meaning of the Debug.Log(robot); output, check for #Smartis answer as it answers it perfectly :)
Otherwise, I feel like you wanted to populate a List<Robot> with random picked names. In this case you need to use a loop: Start() method is only called once on start (as its name suggest). If you need to populate a list with random picked colors/names and then display how many of each colors/names are in the list you can do it as follow:
public class Test : MonoBehaviour
{
private void Start()
{
List<Robot> robotsList = new List<Robot>();
List<string> namesList = new List<string>();
namesList.Add("yellow");
namesList.Add("green");
namesList.Add("red");
PopulateRobotsList();
DisplayRobotsListContent();
}
private void PopulateRobotsList()
{
for(int id = 0; id < 100; id++)
{
string robotName = namesList[Random.Range(0, namesList.Count)];
robotsList.Add(new Robot(robotName, id));
//Debug.Log(robotsList[robotsList.Count - 1]);
}
}
private void DisplayRobotsListContent()
{
int[] robotsNamesCount = new int[namesList.Count];
for (int i = 0; i < robotsList.Count; i++)
{
robotsNamesCount[namesList.IndexOf(robotsList[i].name)] += 1;
}
for (int i = 0; i < namesList.Count; i++)
{
Debug.Log("Robot(s) named \"" + namesList[i] + "\" : " + robotsNamesCount[i]);
}
}
}
public class Robot
{
public string name;
public int id;
public Robot(string name, int id)
{
this.name = name;
this.id = id;
}
}
Please note I changed some variable names as I found it really hard to understand with the one you provided (ex: robotsList to store the potential colors/names of the robots is a weird choice of name :) ).
Hope this helps,

Your random pick works fine (even when your code is a little bit confusing). I guess your problem is, you don't understand the output of Debug.Log(robot);.
actually I don't understand what actually my output is... - OP
What does Debug.Log() print?
According to the Unity3D Documentation for this function will converted the given object to be to string representation for display. This means simply the return value of ToString() method on your Object will printed.
So let's have look at the Object.ToString() method and it's behavior in the MSDN Documentation.
Default implementations of the Object.ToString method return the fully qualified name of the object's type. - MSDN
So, your output in the Unity Log will be the Type Definition of your object.
Now, how to get useful information?
Just override the default ToString() method of your Robot class to something like this:
public class Robot
{
public string name;
public int id;
public Robot(string name, int id)
{
this.name = name;
this.id = id;
}
// Here start's the magic!
public override string ToString()
{
return string.Format("Robot -> Id:'{0}' Name:'{1}'", id, name);
}
}

Related

Custom Class Array sets value to two elements at once

I have some c# unity code that when I execute isn't working right. The code is below and should create an array of 5 score class objects that contain the score as an int and the name of the player as a string.
[SerializeField] Scores[] archeryScore = new Scores[5];
for (int i = 0; i < archeryScore.Length; i++)
{
archeryScore[i] = new Scores();
}
This is the scores definition
[System.Serializable]
public class Scores
{
[SerializeField] private int score;
[SerializeField] private string name;
public int GetScore()
{
return score;
}
public string GetName()
{
return name;
}
public void SetScore(int newScore)
{
score = newScore;
}
public void SetName(string newName)
{
name = newName;
}
}
I'm using private variables and public methods to set the values but if I tried to set a score of 3 with the name Dave to element 0, it would set element 0 AND element 1 to a score of 3 with name Dave.
archeryScore[x].SetScore(score);
archeryScore[x].SetName(name);
archeryScore is just an array of Score class objects This is the code that sets the information and breakpoint testing shows that it's setting to the two elements at once.
The really confusing part of this is that if i set the array to be an integer array all of code works just fine.
Here's a link to the full game manager file pastebin
TIA
Deep down in SetHighScore
you are doing
archeryScore[x] = archeryScore[x - 1];
after this line these two indices hold a reference to the very same instance of Scores => You change the exact same object!
You should probably rather only copy over the according values.
I would use a dedicated method for that like e.g.
public class Scores
{
...
public void CopyFrom(Scores other)
{
this.name = other.name;
this.value = other.value;
}
}
and then do
archeryScore[x].CopyFrom(archeryScore[x - 1]);
Note, however, that this probably still isn't doing what you want. Imagine running through that loop, you always copy the values of the last entry.
So the next time you iterate you again copy from the last entry -> But this last entry already copied the values from the entry before -> The all end up being one and the same values.
So in order to make your life way easier:
Why not simply use a List<Scores> instead?
[SerializeField] private List<Scores> acheryScore = new List<Scores>();
There you could simply always use Insert to insert a new HighScore at a certain index and then remove the very last entry of the list.
First of all I would use a proper constructor like
public class Scores
{
...
public Scores(string name, int score)
{
this.name = name;
this.score = score;
}
}
And then do
if (updateScore)
{
archeryScore.Insert(scoreToUpdate, new Score(name, score));
while(archeryScore.Count > 5)
{
archeryScore.RemoveAt(archeryScore.Count - 1);
}
}

Best way to write multiple constructor overloads in C#

I am learning C# and made a simple "Player" class. But I struggle having multiple overload.
Here's my best solution but I feel like it could be done simpler/better.
class Player : Entity
{
public Player() {
Name = "Player";
XP = 0;
LVL = 1;
XPToLvlUp = 10;
XpRank = 10;
}
public Player(string name) : this() {
Name = name;
}
public Player(string name, int _Hp, int _Mp) : this(name) {
HP = _Hp;
MP = _Mp;
}
public Player(string name, int _Hp, int _Mp, int _Xp, int _Lvl) : this(name, _Hp, _Mp) {
XP = _Xp;
LVL = _Lvl;
}
public Player(string name, int _Hp, int _Mp, int _Xp, int _Lvl, int XpByRank) : this(name, _Hp, _Mp, _Xp, _Lvl) {
XpRank = XpByRank;
}
//deleted code for better reading
private int XPToLvlUp;
private int XpRank;
public int XP;
public int LVL;
public string Name;
}
Is it good and if not please tell me why.
Thanks for your responses!
I think it's fine as is. One question to ask yourself: Are each of those methods actually likely to be called?
One option is to just let the programmer set those values after they've instantiated the class:
var myPlayer = new Player();
myPlayer.XP = 5;
However, there are situations where you really want all the info up front, so that may not be suitable.
Another option could be an options class that is passed to the ctor:
public class PlayerSettings
{
public Name = "Player";
public XP = 0;
public LVL = 1;
public XPToLvlUp = 10;
public XpRank = 10;
}
Then your ctors looks like this:
public Player() : this(new PlayerSettings())
{
}
public Player(PlayerSettings settings)
{
//Fill in appropriate variables here
}
That option would be called in this way:
var playerSettings = new PlayerSettings() { XP = 5 };
var myPlayer = new Player(playerSettings());
In the end, I'm not sure one is "better" than the other, it largely depends on your needs.
Your class is almost good and acceptable.
Short story: use Properties.
Long story:
First of all make or follow the naming rules, it will make your code more friendly to read. It's up to you, just a suggestion. For complex names consisting of multiple words you may use CamelCasedNames. And avoid shorten names for all types of data where it maybe useful. For example you may expand Lvl to Level but Xp to Experience will look as something odd. It's up to you too.
string name; // local Variable, first character lower cased
private string _name; // private Field, first character is lower cased with leading "_"
public string Name { get; set; } // public Property, first character is upper cased
I'll show you alternatives to overriden constructors and will follow the naming rules.
1) Default values for constructor (with a part of your class to keep it simple)
class Player
{
public Player(string name = "Player", int xp = 0, int level = 1)
{
Name = name;
Xp = xp;
Level = level;
}
// Properties instead of Fields
public int Xp { get; private set; } // restrict modification of the property outside of a class but reading is available
public int Level { get; private set; }
public string Name { get; set; }
}
2) Properties without constructor with default values
First Property purpose is restrict access to data to keep internal object data consistent. Even you make mistakes in the code. Good way to avoid some bugs.
Second property purpose is executing code while you're getting or setting one. For example, making properties dependent on each other to store less and only unique data.
class Player
{
public int Xp { get; private set; } = 0;
public int Level { get; private set; } = 1;
public string Name { get; set; } = "Player";
}
Usage
Player player = new Player() { Name = "KillerPWNZ", Level = 100, Xp = 999999 };
Bonus: Another Property feature
You can execute any code in get or set clause.
Let's assume that each next player's level require doubled amount of xp from previous but 2nd level requre 100 XP. And you decided to invoice to the 1st leveled player 1000 XP. Obviously you'll need to bump the Level few times. Assuming that Xp contains relative to Level value.
The invoice
player.Xp += 1000;
The Property with code
private int _xp = 0;
public int Level { get; private set; } = 1;
public int Xp
{
get => _xp; // same as: get { return _xp; }
set
{
_xp = value; // here value is keyword containing data you want to set
while (_xp >= GetXpPerLevel(Level))
{
_xp -= GetXpPerLevel(Level);
Level++;
}
while (_xp < 0 && Level > 1)
{
_xp += GetXpPerLevel(Level - 1);
Level--;
}
}
}
// helper method
private int GetXpPerLevel(int level)
{
if (level < 1) return 0;
// int result = 100;
// for (int i = 1; i < level; i++) result *= 2;
// return result;
// or the same with some binary shift magic :)
return 100 << (level - 1);
}

Why I can't create object instance?

I need add objects to list, but I can't understand how to do it correctly.
Every new object I get from console.
How to fix it?
My try:
namespace ExampleCars
{
public class Car
{
public string name;
public int speed;
public Car(string name, int speed)
{
this.name = name;
this.speed = speed;
}
}
class Program
{
static void Main(string[] args)
{
string name;
int speed, elements;
List<Object> cars = new List<Object>();
elements = Convert.ToInt32(Console.ReadLine());
if (elements > 0)
{
for (int i = 0; i < n; i++)
{
name = Convert.ToString(Console.ReadLine());
speed = Convert.ToInt32(Console.ReadLine());
Car newCar = new Car(name, speed);
cars.Add(newCar);
}
}
foreach (var oneCar in cars)
Console.WriteLine(oneCar);
}
}
}
In console I get this (elements == 1):
ExampleCars.Car
First of all, it would be better to create a List of Cars, instead of list of Objects. So change this:
List<Object> cars = new List<Object>();
To this:
List<Car> cars = new List<Car>();
Also it would be great, if you use properties instead of fields. And finally as the solution for your question, and based on what you need to show in your last Console.Writeline method, you can override the ToString method. Your class should be something like this:
public class Car
{
public string Name { get; set; }
public int Speed { get; set; }
public Car(string name, int speed)
{
Name = name;
Speed = speed;
}
public override string ToString()
{
return $"Name = {Name}, Speed = {Speed} ";
}
}
And if you are using the older versions of C#:
return String.Format("Name = {0}, Speed = {1}", Name, Speed);
The $ is called String Interpolation and is available from C#6+. I have provided an equivalent of it using String.Format that is available in older versions of C#.
when you write your Car object in the console, Car.ToString method call in background.
Object.ToString is the major formatting method in the .NET Framework.
It converts an object to its string representation so that it is
suitable for display. Default implementations of the Object.ToString
method return the fully qualified name of the object's type.
Object.ToString Method
if you want to change default string presentation of your object, you have to override the method.
public class Car
{
public string name;
public int speed;
public Car(string name, int speed)
{
this.name = name;
this.speed = speed;
}
public override string ToString()
{
return $"{name} {speed}";
}
}
Console.WriteLine, is responsible of printing text.
In case of Console.WriteLine(string) is actually returns the string value;
In any other case, it tries to convert the value into a string by invoking the instance object .ToString() method.
Any type in C# inherits from System.Object, and thus, it has the .ToString() method.
By using Inheritance, many types override the actual inherited method and implement their perception of what their value should look like as a string.
This can be easily done, by using method overriding and apply your own logic.
Example taken from MSDN
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return "Person: " + Name + " " + Age;
}
}
Test code
Person person = new Person { Name = "John", Age = 12 };
Console.WriteLine(person);
// Output:
// Person: John 12
So you may ask, why do i get ExampleCars.Car ? you're getting it, because you didn't implement your own representation of the object .ToString method. Thus, the System.Object implementation is to actually return the instance type as string, which is exactly what you're getting.
System.Object.ToString() Source code.
public virtual String ToString()
{
return GetType().ToString();
}
System.Int32 implement by changing the value of 1 to "1"
While other type can implement their own. e.g: new DateTime(2018, 12, 31) can return "12/31/2018"

Creating list/array of class instances?

I'm fairly new to C# and I have just learned about creating custom classes. The problem is, I can't figure out how to take the 40~65 instances of this class and put them in a list/array (whichever one I need) where I can locate and choose one based on an attribute defined in it.
Here's the class I have created right now:
public class Team
{
protected int teamNum;
protected double averageMatchPoints;
protected string location;
protected int matchesPlayed;
protected int matchesPending;
protected int blowouts;
//Team Number
public void SetNumber(int num)
{
teamNum = num;
}
public int GetNumber()
{
return teamNum;
}
//Average Points per match
public void AverageMatchPoints(double p)
{
averageMatchPoints = p;
}
public double GetAverageMatchPoints()
{
return averageMatchPoints;
}
//location information
public void SetLocation(string l)
{
location = l;
}
public string GetLocation()
{
return location;
}
//Number of Played Matches
public void PlayedMatches(int mat)
{
matchesPlayed = mat;
}
public int GetPlayedMatches()
{
return matchesPlayed;
}
//Number of matches pending (not played)
public void PendingMatches(int pen)
{
matchesPending = pen;
}
public int GetPendingMatches()
{
return matchesPending;
}
//Number of Blowouts (matches where the robot was disbaled for any number of reasons)
public void SetBlowouts(int b)
{
blowouts = b;
}
public int GetBlowouts()
{
return blowouts;
}
}
Now, if I had 40~65 of these teams competing at an event and I made an instance of this class for each one, how would I populate a combobox with each team number (teamNum) and then locate one specific team out of all the instances in the program by their team numbers?
I recommend a dictionary!
// Declared somewhere
private Dictionary<int, Team> _teamDictionary = new Dictionary<int, Team>();
.
.
.
//Initialization code - I assume you have gotten your teams from a database or somewhere?
foreach (var team in myTeamsList)
{
_teamDictionary.Add(team.teamNum, team);
}
.
.
.
// Later when you want to locate a team:
var team = _teamDictionary[selectedTeamNum];
Have you tried creating a List yet?
List<Team> Teams { get; set; }
You can then bind your combobox to the list/collection/IEnumerable of all the teams that you have. To initialize the teams up to 40/60 do the following?
for(int i = 0; i < 60; i++)
{
Team t = new Team();
t.Name = "Team 1";
t.TeamNumber = i + 1;
Teams.Add(t);
}
List<Team> allTheTeams = new List<Team>();
for(var i = 0; i < 65; i++){
allTheTeams.Add(new Team { teamNum = i });
}
And to get the team with number 34:
allTheTeams.FirstOrDefault(x => x.teamNum == 34);
Like this:
Add a constructor to your class that takes the teamnumber:
(this is the best solution if every team needs to have a number. So you can not forget to set the team number as you can not create an object of type team without setting the number in the constructor)
public class Team
{
protected int _teamNum;
public Team(int teamNum)
{
_teamNum = teamNum;
}
public int getTeamNum()
{
return _teamNum;
}
//more logic
}
Populate a dictionary, the comboBox and get a team for its number:
Dictionary<int, Team> dictionary = new Dictionary<int, Team>();
int teamNum = 1;
// Add your Teams to a dictionary (example)
dictionary.Add(teamNum ,new Team(teamNum++));
dictionary.Add(teamNum, new Team(teamNum++));
dictionary.Add(teamNum, new Team(teamNum++));
// Populate a comboBox
foreach(KeyValuePair<int,Team> kvp in dictionary)
{
comboBox1.Items.Add(kvp.Value.getTeamNum().ToString());
}
// get a team for a given teamNumer
int targetTeamNumber = 2;
if (dictionary.ContainsKey(targetTeamNumber))
{
Team team = dictionary[targetTeamNumber];
// do something with the team
}

Can't add to a list within a class object

First, I'm going to apologize if this is a stupid question. I've been using C# for 16 hours after having not programmed anything since VB6. I'm just trying to hack together a small program for personal use that reads from an old access database and spits out a formatted report in Excel. I apologize for the messy/inefficient code.
Overview: I have two class types, "Zone" and "Device". Each "Zone" has a List of Devices in it. The main program has a List of Zones. Each database has a varying number of "zones" in it, and each "zone" has a varying number of devices assigned to it. I need to parse, sequentially, the zone list and the devices on each zone. I started with structs and arrays and popular opinion seems to be that those are both bad ways to do it, and I wasn't having much luck anyway, so I moved to lists and classes, and it was going well.
I can pull all the "zones" from the database, add them to the list, assign them their labels and IDs. The problem is when I go to read the "devices" from the database, I can't add them to the list within the Zone.
This is the error I get: "Object reference not set to an instance of an object." Which I gather means the object is null?
Here's the relevant code:
Device Class:
public class Device
{
public string Label;
public string Address;
public string Type;
public Device(string Label, string Address, string Type)
{
this.Address = Address;
this.Label = Label;
this.Type = Type;
}
}
Zone Class:
public class Zone
{
public string Label;
public short ID;
public List<Device> Devices;
public Zone(string Label, short ID) {
this.Label = Label;
this.ID = ID;
// ADDED AS PER SUGGESTIONS BELOW
this.Devices = new List<Device>();
}
// Added this to see if it would work, it would not.
public void AddDevice(string Label, string Address, string Type) {
Devices.Add(new Device(Label, Address, Type));
}
}
Initializing and populating Zone List (on button click) (completes successfully)
List<Classes.Zone> Zones = new List<Classes.Zone>();
dbZoneReader = myZoneSelect.ExecuteReader();
while (dbZoneReader.Read())
{
Classes.dbItem dbRow = new Classes.dbItem();
dbRow.Address = Convert.ToInt16(dbZoneReader["DeviceAddress"].ToString());
dbRow.DeviceType = Convert.ToInt16(dbZoneReader["DeviceType"].ToString());
dbRow.Label = dbZoneReader["DeviceLabel"].ToString();
if (dbRow.Label != "" && dbRow.Address > 0)
{
Zones.Add(new Classes.Zone(dbRow.Label,dbRow.Address));
}
}
Adding Devices to their respective Zones:
while (dbReader.Read()) {
Classes.dbItem dbRow = new Classes.dbItem();
string tempZones;
// Acquire/convert device information
dbRow.Node = Convert.ToInt16(dbReader["NodeAddress"].ToString());
dbRow.Loop = Convert.ToInt16(dbReader["LoopSelection"].ToString());
dbRow.Address = Convert.ToInt16(dbReader["DeviceAddress"].ToString());
dbRow.TypeID = Convert.ToInt16(dbReader["TypeID"].ToString());
dbRow.FlashScanID = Convert.ToInt16(dbReader["FlashScanID"].ToString());
dbRow.DeviceType = Convert.ToInt16(dbReader["DeviceType"].ToString());
dbRow.Label = dbReader["DeviceLabel"].ToString();
// Find "proper" zone ID (some zones have multiple IDs, only one is relevant)
tempZones = dbReader["DevicePointMappingList"].ToString();
tempZones = tempZones.Replace("Z", "");
var elements = tempZones.Split(new[] { ',' }, System.StringSplitOptions.RemoveEmptyEntries);
if (elements.Length >= 2) {
ZoneCheck z = new ZoneCheck();
foreach (string items in elements) { if (z.Check(items)) { dbRow.Zone = Convert.ToInt16(items); } }
} else {
if (elements.Length == 1) { dbRow.Zone = Convert.ToInt16(elements[0]); }
else { dbRow.Zone = 0; }
}
// Only add devices that aren't assigned to zone 0, which is non-existent
if (dbRow.Zone > 0) {
// Add new device to zone's device list [THIS IS WHERE IT FAILS]
Zones.Find(z => z.ID == dbRow.Zone).Devices.Add(new Classes.Device("Test", "test", "Test"));
}
}
I've gone through and found out exactly where it fails, and it's the last line where it tries to add the device. Searching here and on google has lead me to believe that I need to initialize the object list... which I believe I've done? I've tried initializing it within the Zone class constructor, and when the Zone is added (which is what it's set too now).
I've confirmed that the Zone object exists, and that the Detectors list within that Zone object isn't null. Kinda stumped, figure I'm doing something that I shouldn't be doing and just don't know better, or I'm missing something really obvious.
The problem is in your Zone class. You need to initialize the List<Device> as follows.
public class Zone
{
public string Label;
public short ID;
public List<Device> Devices;
public Zone(string Label, short ID) {
this.Label = Label;
this.ID = ID;
this.Devices = new List<Device>();
}
// Added this to see if it would work, it would not.
public void AddDevice(string Label, string Address, string Type) {
Devices.Add(new Device(Label, Address, Type));
}
}
The reason is that when you write public List<Device> Devices;, you're not actually creating an object. You're creating a variable that can hold an instance of the specified object. It's only when you pair the variable declaration up with object initialization ( = new List<Device>();) that you get a usable instance of the object.
Thinking of the same issue in terms of a simpler object may help:
public class Foo
{
public string bar; // bar isn't an actual instance of an object, it's just a spot that can hold a string
public void ManipulateBarWithRuntimeError()
{
bar.Substring(0, 1); // "bar" isn't actually set to anything, so how can we take a substring of it? This is going to fail at runtime.
}
public void ManipulateBarWithoutRuntimeError()
{
bar = "Hello, world!";
bar.Substring(0, 1); // bar is actually set to a string object containing some text, so now the Substring method will succeed
}
}
I think the problem is in your Zone class.
Here is my version of your Zone class:
public class Zone
{
public string Label;
public short ID;
public List<Device> Devices;
public Zone(string Label, short ID) {
this.Label = Label;
this.ID = ID;
this.Devices = new List<Device>();
}
// Added this to see if it would work, it would not.
public void AddDevice(string Label, string Address, string Type) {
Devices.Add(new Device(Label, Address, Type));
}
}
This is an only change that I made to your class;
this.Devices = new List<Device>();
Now it might work...
You can also initialize the list inside a getter
public class Zone
{
public string Label;
public short ID;
private List<Device> _devices;
public List<Device> Devices
{
get
{
return this._devices ?? (this._devices = new List<Device>());
}
}
public Zone(string Label, short ID)
{
this.Label = Label;
this.ID = ID;
}
// Added this to see if it would work, it would not.
public void AddDevice(string Label, string Address, string Type)
{
Devices.Add(new Device(Label, Address, Type));
}
}

Categories