Custom Class Array sets value to two elements at once - c#

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);
}
}

Related

Unity problems with loading save

Loosing my mind over it, but still cant find what's wrong. I have 2 List variables (items, playerEquipment.equipment), trying to save both of them and load after, but it gives me strange results. For items its always good, saves and loads all the time, but for playerEquipment.equipment behaviour is different - if i save and then immidiatly load without exiting play mode, i get right result from load function, but if i save, stop play mode, start play mode, and then load - i get list of "null" is result.
Here is my save code
public void SavePlayer()
{
SaveData data = new SaveData();
data.level = level;
data.nick = nick;
data.experience = experience;
data.experienceToNextLevel = experienceToNextLevel;
data.maxHP = maxHP;
data.minAttack = minAttack;
data.maxAttack = maxAttack;
data.coins = coins;
data.items = items;
data.equipment = playerEquipment.equipment;
for (int i = 0; i < data.equipment.Count; i++)
Debug.Log("Saved type " + data.equipment[i].type + " with id " + data.equipment[i].id+" and type "+ data.equipment[i].type);
//Save data from PlayerInfo to a file named players
DataSaver.saveData(data, "players");
}
Here is my load code
public void LoadPlayer()
{
SaveData data = DataSaver.loadData<SaveData>("players");
if (data == null)
{
Debug.Log("ERROR: data not loaded");
return;
}
level = data.level;
nick = data.nick;
experience = data.experience;
experienceToNextLevel = data.experienceToNextLevel;
maxHP = data.maxHP;
currentHP = data.maxHP;
minAttack = data.minAttack;
maxAttack = data.maxAttack;
coins = data.coins;
items = data.items;
playerEquipment.equipment = data.equipment;
}
SaveData is
[Serializable]
class SaveData
{
//all data types and names that go to save
public int level;
public string nick;
public int experience;
public int experienceToNextLevel;
public int maxHP;
public float minAttack;
public float maxAttack;
public int coins;
//public List<String> itemsString;
//public List<String> equipmentString;
public List<Item> items;
public List<Item> equipment;
}
Save code i grabbed from this answer and didnt change it
UPD:
Item class is
public enum ItemType { HELMET, SHOULDERS, WEAPON_MAIN, WEAPON_SECOND, BODY, ARMOR, HANDS, PANTS, BOOTS }
[CreateAssetMenu(menuName = "item")]
[Serializable]
public class Item : ScriptableObject
{
public int id;
public Sprite sprite;
public string itemName;
public ItemType type;
}
You can not save and load a list of ScriptableObjects like that. JsonUtility.ToJson saves these references as InstanceIDs which are nor persistent. That is why it works when you do not stop play mode.
Since you already implemented an ID you could save a list of IDs instead and add a method which finds them by that references and adds adds them back to your playerEquipment.equipment
Edit: This means your statement of it working for the item list is likely false, you should check and edit your answer accordingly.

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);
}

List keeps getting reset to empty. How can I fix this?

I'm currently learning C# and have assigned myself to make a program to help me understand OOP which essentially takes in values and assigns them to variables. The program takes in information about football players; the name, last name, position, and shirt number.
I'm trying to use getters and setters to ensure that a shirt number can only be used once. So i've set a list up that stores all the shirt numbers that are being used. The problem i'm having is: the list keeps getting reset and I have no idea why. After one value has been added, by the time the next one gets added, the list is empty again. This makes my if statement in the setter not work as the list does not contain any values.
Im sure this is a rookie error and should be shouting at me, but Im new to this language and am not really sure on all the ins and outs of it.
I haven't really tried much, and I cant find anything online as this seems to be a specific error that im having. I don't know enough about the language to properly troubleshoot this, and what I do know about the language tells me this should work.
namespace RandomObject
{
class Program
{
static void Main(string[] args)
{
Player player1 = new Player("Lucas", "Torreira", "Defensive Midfielder", 11);
Player player2 = new Player("Alexandre", "Lacazette", "Striker", 9);
Player player3 = new Player("Pierre-Emerick", "Aubameyang", "Striker", 14);
Player player4 = new Player();
Console.Write("Please enter new players first name: ");
player4.Name = Console.ReadLine();
Console.Write("Please enter new players last name: ");
player4.LastName = Console.ReadLine();
Console.Write("Please enter new players position: ");
player4.Position = Console.ReadLine();
Console.Write("Please enter new players shirt number: ");
player4.ShirtNo = Convert.ToInt32(Console.ReadLine());
player1.PrintPlayerInfo();
player2.PrintPlayerInfo();
player3.PrintPlayerInfo();
player4.PrintPlayerInfo();
Console.ReadKey();
}
}
}
class Player
{
private List<int> shirtNumbers = new List<int>();
private int _shirtNo;
public void PrintPlayerInfo() //<access modifier> <return type> <method name>(parameters)
{
Console.WriteLine("Player: {0} {1}", Name, LastName);
Console.WriteLine("Position: {0}", Position);
Console.WriteLine("Shirt No.: {0}\n", _shirtNo);
}
public Player()
{
Name = string.Empty;
LastName = string.Empty;
Position = string.Empty;
_shirtNo = 0;
}
public Player(string name, string lastName, string position, int shirtNo)
{
Name = name;
LastName = lastName;
Position = position;
_shirtNo = shirtNo;
AddToList(_shirtNo);
}
private void AddToList(int newNumber)
{
shirtNumbers.Add(newNumber);
Console.WriteLine(shirtNumbers[0]);
}
public string Name { get; set; }
public string LastName { get; set; }
public string Position { get; set; }
public int ShirtNo
{
get { return _shirtNo; }
set
{
if (shirtNumbers.Contains(value) == false)
{
_shirtNo = value;
}
else
{
_shirtNo = 0;
}
}
}
}
}
In my main method I declare 3 instances of the class, with shirt numbers 11, 9, and 14. So when it comes to inputting one into the console using readlines and such, if I were to enter 14, the shirt number should be set to 0. However if I enter 10, it should be set to 10.
The Player class now does two things: it holds information about one player, and it contains a list of shirt numbers for all players. One of those two doesn't belong there.
The private List<int> shirtNumbers = new List<int>(); is an instance variable, meaning each player has its own list of shirt numbers. So if you assign a shirt to player X, the list in player Y's instance has no notion of this, enabling you to assign shirt N to both player X and Y.
Sure, you could fix this by declaring the list to be static, but that's just bad design; the Player class needs to know about one player, not all of them.
So instead keep this shirt number check outside your player class. Declare the shirt list before the player list, and modify your code accordingly.
You should have a static list of numbers. Otherwise every player has its own list of valid numbers.
class Player
{
private static List<int> shirtNumbers = new List<int>();
private int _shirtNo;
}
This way you have a single list that all your player share.
You are using the AddToList method on your constructor to add the shirtlist number which is correctly but when you are defining ShirtNo setter you are not adding to the list
Fix :
public int ShirtNo
{
get { return _shirtNo; }
set
{
if (shirtNumbers.Contains(value) == false)
{
_shirtNo = value;
AddToList(value)
}
else
{
_shirtNo = 0;
}
}
}
i copied your code to debug in my local machine. Few changes that needs to be done to your
1.The shirtNumbers list has to be declared static , if not for every instance of player class will have List (private static List shirtNumbers = new List();)
You are assigning values to the variable directly and not to the property in both the constructors.(getter setter is called property in C#).Hence the if condition inside setter wont be called.
class Player
{
private static List<int> shirtNumbers = new List<int>();
private int _shirtNo;
public void PrintPlayerInfo()
{
Console.WriteLine("Player: {0} {1}", Name, LastName);
Console.WriteLine("Position: {0}", Position);
Console.WriteLine("Shirt No.: {0}\n", _shirtNo);
}
public Player()
{
Name = string.Empty;
LastName = string.Empty;
Position = string.Empty;
ShirtNo = 0;
}
public Player(string name, string lastName, string position, int shirtNo)
{
Name = name;
LastName = lastName;
Position = position;
ShirtNo = shirtNo;
AddToList(_shirtNo);
}
private void AddToList(int newNumber)
{
shirtNumbers.Add(newNumber);
Console.WriteLine(shirtNumbers[0]);
}
public string Name { get; set; }
public string LastName { get; set; }
public string Position { get; set; }
public int ShirtNo
{
get { return _shirtNo; }
set
{
if (shirtNumbers.Contains(value) == false)
{
_shirtNo = value;
}
else
{
_shirtNo = 0;
}
}
}
}

How to randomly pick value from a list in Unity3D?

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);
}
}

c# array looping with itself

Why I'm doing this:
So I'm trying to make an application for a game called clash royale, after winning the games there you get a "random" chest, which is actually not random... When you create your account you get a digit assigned to you from 0 to 239, and after that it follows a pattern for the chest drops. The applciation I'm making would take a user's entries and compare it to the pattern, thus being able to predict how soon the next chests of a higher quality would drop.
The help I need with the code:
Is it possible to make an array kind of... loop within itself.. So for example when going through the array in a loop, if "i" is 239, then adding +1 would take it back to the beginning, or #0 (239 not necessarily being the limit).
The class (and it's container that I want to loop):
class Chest
{
public int ID { get; set; }
public string Type { get; set; }
public Chest()
{
}
public Chest(int id, string type)
{
ID = id;
Type = type;
}
}
class ChestContainer
{
private Chest[] ChestList = new Chest[240];
public int Count { get; set; }
public ChestContainer(int size)
{
ChestList = new Chest[size];
}
public void Add(Chest chest)
{
ChestList[Count++] = chest;
}
public Chest Get(int index)
{
return ChestList[index];
}
}
Also wouldn't mind any tips to improve my class / container class, at the moment this is what I've been doing for pretty much my entire "career" as this is what we were thought in uni (minus the string override for the class).
You could use Modulo % in order to get a loop kind of thing.
If you replace the Container.Add method with the one below, the index will be "reset" (for lack of better words).
public void Add(Chest chest)
{
ChestList[Count++%(ChestList.Length)] = chest;
}
After updating the method, if you want an example, you can try the code below:
var container = new ChestContainer(240);
for (int i = 0; i < 1000; i++)
container.Add(new Chest(i, $"{i}"));
Edit In order to have the Get method working as well, modifying it as mentioned below will ensure your container works as expected:
public Chest Get(int index)
{
return ChestList[index%(ChestList.Length)];
}
To test it out, you can use the code below:
var container = new ChestContainer(240);
for (int i = 0; i < 1000; i++)
{
container.Add(new Chest(i, $"{i}"));
var value = container.Get(i);
}
You can overload the [] operator to define it's behaviour.
Something like this:
public static Chest operator [] (int index) {
return ChestList[index%240];
}
public Chest Get(int index)
{
return ChestList[index%240]; //put your limit here
}
How it works: % is the modulo operator.
It returns the remainder of a devision.
Example:
5/2 = 2, remaining 1
=> 5%2 = 1
In your case, when numbers higher than 239 are entered, with modulo it just wraps around.

Categories