How can I use a string to call an object in C#? - c#

I am currently making a simple console based RPG dungeon game, I am still fairly new to C#, I took a visual basic course in school, and I have played around with unity for about a month.
My problem is, that I am programming the first battle that the player will encounter. I have constructed a few weapons, and I want to be able to call the persons current weapon with a separate string, for example, My current weapon is the dagger, or the object wp1, I want my "weapon" to be attached to wp1 in some way, so that I can do something like,
Console.WriteLine("Damage: " + weapon.dmg); rather than hardcoding wp1.dmg, so that later in the game making process, when the player has the opportunity to purchase a better weapon, I can do it with variables, for example, the player now has the shortsword (wp2)
private string weapon = "wp2";
...
Console.WriteLine("Damage: " + weapon.dmg);
I have tried to simply put,
String weapon = wp1;
then call weapon.dmg, but this doesn't work because it thinks i'm trying to call
weapon.dmg, and not wp1.dmg
//players base damage
int damage = 0;
//players strength attribute
int strength = 0;
//weapon constructor
class wp
{
public int dmg;
public int cost;
public string name;
public wp(int d, int c, string n)
{
dmg = d;
cost = c;
name = n;
}
}
//three weapons that are constructed
wp wp1 = new wp(1, 25, "dg");
wp wp2 = new wp(3, 100, "ss");
wp wp3 = new wp(5, 250, "ls");
//the current weapon string
public string weapon = "wp1";
void attack()
{
//calculates damage based off of the players damage, strength, and weapon
int newDamage = damage * strength + (weapon.dmg);
}
Expected result:
the program should use the player's current weapon's damage value
Actual result, the program tries to find the dmg value of the weapon, but that is not possible because "weapon" is just a string, so it throws an error

As I said in a comment above, get into good habits now, as a beginner and you won't have to break those habits later. Let's see how we might design your system. Start off with a well-designed class hierarchy. Let's say we have three kinds of weapons: swords, daggers, and clubs. The stuff they have in common goes into an abstract base class:
abstract class Weapon
{
What do they have in common? Damage, cost, and a name. So make abstract, read-only properties for those:
public abstract int Damage { get; }
public abstract decimal Cost { get; }
public abstract string Name { get; }
}
Now make some derived classes. Are you planning on extending them further? If not, seal them:
sealed class Sword : Weapon
{
public override int Damage => 10;
public override decimal Cost => 12.5m;
public override string Name => "normal sword";
}
And so on.
Now do the same for player. Let's say that we can change a player's weapon, but not their name. So Name should be a read-only property, and Weapon should be a read-write property:
sealed class Player
{
public string Name { get; private set; }
public Weapon Weapon { get; set; }
public int Strength { get; private set; }
public int BaseDamage { get; private set; }
public Player(string name, Weapon weapon, int strength, int baseDamage)
{
this.Name = name;
this.Weapon = weapon;
this.Strength = strength;
this.BaseDamage = baseDamage;
}
}
Now we can make some weapons:
Weapon weapon1 = new Sword();
Weapon weapon2 = new Dagger();
Weapon weapon3 = new Club();
Player fighter = new Player("Bob", weapon3, 5, 10);
Or, better:
var weapons = new List<Weapon> { new Sword(), new Dagger(), new Club() };
// weapons is indexed 0, 1, 2.
Player fighter = new Player("Bob", weapons[2], 5, 10);
And now if you have a Player in hand:
static void Attack(Player p)
{
int damage = p.BaseDamage * p.Strength + p.Weapon.Damage;
string text = $"Player {p.Name} attacks with {p.Weapon.Name}";
No strings for referencing objects! Do not use strings for anything except text. References to objects should be references to objects, not strings.
Now, for advanced players only, there are times when you do need to look something up by a string. The way you do that in C# is:
var d = new Dictionary<string, Weapon> {
{ "weapon1", new Sword() },
{ "weapon2", new Dagger() },
{ "weapon3", new Club() }
};
Weapon w = d["weapon1"]; // w is a Sword.
But do not do this by default. That's not the normal way to refer to something in C#.

one way to achieve what you want:
if(weapon=="wp1")
//use wp1 object here.
But a Better way would be to put your 3 wp's into a list or array (since you literally hardcoded a list of 3 objects). Then wp1 would be at
wpArray[0];

Related

Newtonsoft JSON Serialize/Deserialize derived types

Context; Using the Unity Engine. JSON files store game data of assets in the game.
I have the below JSON file which stores data about possible Units in the game.
This is a Dictionary<string, UnitEntity> where UnitEntity inherits Entity (which is an abstract class)
The KEY is the name of the Unit
The VALUE is the Unit:Entity itself
{
"Infantry": {
"moveSpeed": 2.0,
"turnRateRadians": 2.0,
"unitArmourType": 0,
"unitDamage": 10.0,
"unitAttackCooldownSeconds": 2.0,
"unitAttackAoERange": 0.0,
// List of structs of an enum and float
"damageToArmourTypes": [
{
"armour": 0,
"damageRatio": 1.0
},
{
"armour": 1,
"damageRatio": 0.25
},
{
"armour": 2,
"damageRatio": 0.0
}
],
// Below fields are from the abstract Base class
"id": 0,
"name": "Infantry",
"faction": "USA",
"description": "Basic Infantry of the USA",
"menuLocation": 3,
"menuOrderIndex": -1,
"maxHealth": 50,
"entityPrefabReference": "Assets/Resources/Prefabs/Units/InfantryPrefab.prefab",
// A struct of an enum and int
"constructionCost": [
{
"_resourceType": 0,
"_resourceCount": 250
}
]
}
}
Above is an example of the JSON that is created on Serialize which is what I'd expect (perhaps not the base data being at the bottom half, but if its consistent....sure)
Serialize seems to execute fine. No errors. But when I attempt;
string factionEntityJson = File.ReadAllText(filePath);
Dictionary<string, UnitEntity> entity = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, UnitEntity>>(factionEntityJson); ;
I get the error;
Value cannot be null.
Parameter name: collection
I attempted to instead cast to a Dictionary<string, object> and whilst it executed, inspecting the contents it said something along the lines of "Method is not implemented" for the JToken.Value (or something along those lines).
What is the issue here (does NewtonSoft.Json not like inheritance / derived types?) and how do I fix it?
EDIT:
The UnitEntity Class as requested:
[Serializable]
public struct ArmourDamageRatio
{
public Unit_Armour_Type armour;
public float damageRatio;
}
[Serializable]
public class UnitEntity : Entity
{
public float moveSpeed = 1f;
public float turnRateRadians = 1f;
public Unit_Armour_Type unitArmourType = Unit_Armour_Type.NONE;
public float unitDamage = 5f;
public float unitAttackCooldownSeconds = 1f;
public float unitAttackAoERange = 0f; // 0 range means no AoE (IE bullets)
public List<ArmourDamageRatio> damageToArmourTypes = new List<ArmourDamageRatio>();
public UnitEntity(int id)
: base(id)
{
}
[JsonConstructor]
public UnitEntity(int id,
string faction,
string name,
string desc,
int menuLoc,
int menuOrder,
string buildingPath,
int maxHp,
List<GameResourcePair> cost,
float moveSpd,
float turnRate,
Unit_Armour_Type armourType,
float dmgAmt,
float attackCooldown,
float aoeRange,
List<ArmourDamageRatio> damageTypes)
: base(id, faction, name, desc, menuLoc, menuOrder, buildingPath, maxHp, cost)
{
this.moveSpeed = moveSpd;
this.turnRateRadians = turnRate;
this.unitArmourType = armourType;
this.unitDamage = dmgAmt;
this.unitAttackCooldownSeconds+= attackCooldown;
this.unitAttackAoERange = aoeRange;
this.damageToArmourTypes.AddRange(damageTypes);
}
}
The Entity Base class:
[Serializable]
public abstract class Entity
{
public int id; // Unique ID of entity (Not set by user / dev)
public string name; // Name of the Entity
public string faction; // Faction entity belongs to
public string description; // Description of the Entity
public UI_Menu menuLocation; // Which UI menu will it appear in (Infrastructure, Defence, Barracks etc)
public int menuOrderIndex; // Order entity appears in the menu (-1 indicates not set)
public int maxHealth; // Max health of entity
public string entityPrefabReference; // Entity prefab Object (File path to load at runtime)
public List<GameResourcePair> constructionCost; // List of construction costs of Building Entity
public Entity(int id)
{
this.id = id;
this.name = "";
this.faction = "";
this.description = "";
this.menuLocation = UI_Menu.NONE;
this.menuOrderIndex = -1;
this.maxHealth = 0;
this.entityPrefabReference = null;
this.constructionCost = new List<GameResourcePair>();
}
[JsonConstructor]
public Entity(int id, string faction, string name, string desc, int menuLoc, int menuOrder, string buildingPath, int maxHp, List<GameResourcePair> cost)
{
this.id = id;
this.name = name;
this.faction = faction;
this.description = desc;
this.menuLocation = (UI_Menu)menuLoc;
this.menuOrderIndex = menuOrder;
this.maxHealth = maxHp;
this.constructionCost = cost;
this.entityPrefabReference = buildingPath;
}
// Other functions (==. != overrides etc)
}
you have a bug in your code, change the input parameter name "damageTypes" of UnitEntity constructor to "damageToArmourTypes" and IMHO it is better to add a null validation for the future
[JsonConstructor]
public UnitEntity(int id,
//...another parameters
float aoeRange,
List<ArmourDamageRatio> damageToArmourTypes)
{
// ...another code
if (damageToArmourTypes != null)
this.damageToArmourTypes.AddRange(damageToArmourTypes);
//...
}
Looks like there a lot of mismatches between the serialized json and the expected keys you're looking for in the UnitEntity and Entity classes, for example moveSpd in the constructor and moveSpeed in the serialized json.
This is happening since deserialization goes through the constructor and these properties get renamed, but when serializing them you're not doing that renaming back.
The exception you're getting is yet another mismatch, as you're calling addRange(damageTypes) even though the actual serialized value is damageToArmourTypes. renaming the constructor parameter fixed the issue.
This might only be my personal opinion here, but you should really try to avoid having your domain entities be the actual classes that get serialized. I think that you're over-relying on serializer magic when you should be making these mappings explicit. I'd refactor to create a class representation of the entities that is a representation of the serialized data and convert most serializer voodoo into explicit code. Yes, it is more verbose and possibly ugly, but you can be certain on what is going on in your code.

encapsulation of subclasses from a constructor in C#

I am sorry if the question isn't too clear, but I did'nt know
how else to phrase it.
I am attempting to create a card game which contains the following classes
The suits are Red, Blue, Green, and Yellow and depending on the suit
the value is the product of the cards number and the suit multiplier
red = 1
blue = 2
green = 3
yellow = 4
abstract class Card;
public class Deck
{
private List<Card> deckList;
}
public class RedCard : Card, suit
{
private int number;
public int Getvalue()
{
return number;
}
}
interface suit
{
int GetValue();
}
is there a way to encapsulate the Card subclasses so
that the Deck constructor does not need to know what
kind of cards can be added to the deck.
The aim is to make sure that the Deck class does not need
to be altered if I add another suit/card subclass in the future
If you think about this in real world terms, you have a Deck that has instances of Card in it. The types of attributes or physical properties of the cards in the deck are all the same, they all have a Suit and a Number and in your business case they all have a Value as well. From a structural point of view they are identical, all that changes are the values for each of the properties.
If every Card has the same attributes, and the same behaviours, then there is no reason to create further sub-classes or even interfaces of these Cards.
In software design, we use inheritance and composition (Interfaces) to add attributes and behaviours to the base implementation; or to change existing behaviours. It is an anti-pattern to inherit from a Card just to change the values of the attributes and doing so can elad to confusion down the track. You really need to separate the concept of structure vs content. If the structure and beahviour
On top of this you have defined a list of suits and have declared that they have specific integer values, in C# we can encapsulate such fixed lists using an enum.
public enum Suit : int
{
Red = 1,
Blue = 2,
Green = 3,
Yellow = 4
}
public class Deck
{
private List<Card> deckList;
}
public class Card
{
public Suit Suit { get; private set; }
public int Number { get; private set; }
public Card (Suit suit, int number)
{
this.Suit = suit;
this.Number = number;
}
public int Value { get { return (int)Suit * Number; } }
}
We can now create a method to generate a deck of cards for us, based on some fixed criteria, I'll call this from the constructor for this demo:
public class Deck
{
private const int LENGTH_OF_SUIT = 10;
private List<Card> deckList = new List<Card>();
public Deck()
{
BuildDeck();
}
private void BuildDeck()
{
foreach (Suit suit in Enum.GetValues(typeof(Suit)))
{
for(int number = 1 ; number <= LENGTH_OF_SUIT; number ++)
{
deckList.Add(new Card(suit, number));
}
}
}
}
This simple structure is one way to encapsulates the requirements listed in the original post, you can play with this here: https://dotnetfiddle.net/BnhGGG
If the number of suits can change at runtime, then an enum is NOT a good fit, then you would need a class to represent the Suit:
public class Suit
{
public string Name { get;set; }
public int Value { get;set; }
}
But note that the Card class doesn't need to change much:
public class Card
{
public Suit Suit { get; private set; }
public int Number { get; private set; }
public Card (Suit suit, int number)
{
this.Suit = suit;
this.Number = number;
}
public int Value { get { return Suit.Value * Number; } }
}
To build the deck we would need additional information, like what suits to build:
public class Deck
{
private const int LENGTH_OF_SUIT = 10;
private List<Card> deckList = new List<Card>();
public Deck(Suit[] suits)
{
BuildDeck(suits);
}
private void BuildDeck(Suit[] suits)
{
foreach (Suit suit in suits)
{
for(int number = 1 ; number <= LENGTH_OF_SUIT; number ++)
{
deckList.Add(new Card(suit, number));
}
}
}
}
Finally, if we need to get all the Cards of a specific Suit we could add a method that does this for us to the Deck
public List<Card> GetCardsOfSuit(Suit suit)
{
return deckList.Where(x => x.Suit == suit).ToList();
}
There are many other ways to implement the same or similar logic, this is but one example.
You may have started off in your learning using Vehicle and then created sub classes for Car and MotorCycle. Often the example is that Vehicle has a property for Wheels and the Car has 4 Wheels and the MotorCycle has 2.
That model alone is flawed as a learning tool, it can lead you to assume that the reason behind subclassing was to change the value of a fixed property. This example has been overly simplified, fundamentally there are other attributes and behaviours that give us real world, anc conceptual reasons to classify these objects into their own class definition.
It was an example devised because in the real world classification of Cars and MotorCycles separately is very logical and it is easy to comprehend that they are both types of Vehicles.
If we start to talk about Color of a Vehicle, then we are closer to the concept of a Suit of a Card. you can have a Red Car and a Red MotorCycle, but the color is only one of the attributes of that physical object, we do not now create a new class definition to represent RedCar and RedMotorCycle... Red is simply the Value of the Color property.
A better inheritance example
The example of an Animal that has sub classes of Bird and Fish makes it easier to show the similarities (what is inherited) and the differences in the types of attributes and beahviours encapsulated by the class definitions:
We can have a count of eyes for all animals, and some type of animals by definition will only have a fixed number of eyes. So we can use eyes to show overriding fixed values. But it doesn't make sense to have a property on Animal to store the number of wings, because that is part of the definition that makes an animal a bird and no other type of animal will have wings at all. We wouldn't store the fact that a bird has any number of wings because All birds have 2 wings by definition. We would not normally even bother to record that fact in our model, because it is a constant value and not likely to be of any use to us. But Flight is a behaviour common to birds, but not all birds can fly! Now we can start to talk about behaviours.
In this model we will capture 2 types of real world behaviours as properties, we wont specifically add any c# behaviours in these definitions, but it is a better tool to enable new developers to relate these abstract c# concepts to the real world.
public class Animal
{
public string Name { get; set; }
/// <summary>Number of Eyes</summary>
/// <remarks>Not all animals have eyes, use 0 to represent no eyes</remarks>
public virtual int Eyes { get; set; };
public string override ToString()
{
return $"{Name} - Eyes:{Eyes}";
}
}
/// <summary>Vertebrates have a spine, but are also `Chordates`, they have "Camera Eyes" that are specifically 2 eyes using lenses to focus an image.</summary>
/// <remarks>http://www.madsci.org/posts/archives/1999-02/920061344.Ev.r.html#:~:text=This%20is%20the%20same%20process%20at%20work%20in,they%20retained%20the%20trait%20from%20a%20common%20ancestor.</remarks>
public class Vertebrate : Animal
{
public override sealed int Eyes { get { return 2; } set{/*Force only 2 eyes, ignore setter*/} }
}
public class Bird : Vertebrate
{
/// <summary>Not all birds can fly: penguins, emus and ostriches are some examples </summary>
public bool CanFly { get;set; }
public override string ToString()
{
return base.ToString() + $", CanFly :{CanFly}";
}
}
public class Fish : Vertebrate
{
/// <summary>Fun fact, not all fish can swim backwards! Sharks is one example</summary>
public bool CanSwimBackwards { get;set; }
public override string ToString()
{
return base.ToString() + $", CanSwimBackwards :{CanSwimBackwards}";
}
}
What we've shown here is a simple inheritance model that shows different properties being added to the base class we can use these to add some Animals to a list:
List<Animal> myFavouriteAnimals = new List<Animal>();
myFavouriteAnimals.Add(new Animal { Name = "Worm", Eyes = 0 });
myFavouriteAnimals.Add(new Bird { Name = "Hawk", CanFly = true; });
myFavouriteAnimals.Add(new Bird { Name = "Penguin", CanFly = false; });
myFavouriteAnimals.Add(new Fish { Name = "Eel", CanSwimBackwards = true; });
myFavouriteAnimals.Add(new Fish { Name = "Shark", CanSwimBackwards = false; });
foreach(var animal in myFavouriteAnimals)
{
Console.WriteLine(animal.ToString());
}
This would produce the following result:
Try it out here: https://dotnetfiddle.net/n6jgHO
Worm - Eyes:0
Hawk - Eyes:2, CanFly: True
Penguin - Eyes:2, CanFly: False
Eel - Eyes:2, CanSwimBackwards: True
Shark - Eyes:2, CanSwimBackwards: False
There's a little bit of syntactic sugar in that example but hopefully it helps to explain better scenarios for using inheritance other than just changing the value of an attribute defined in the base class.

How can you create your own object in C# that can only have an already defined finite amount of values, for example like Bools as in Haskell?

In Haskell you can create a new datatype for example as follows:
Player = Player1 | Player2
And further in your code you can check whether it's Player1 or Player2 by just typing Player1 and not some strange with like "Player1" that it has to be a String or check with an integer.
Is something similar possible in C#?
I can only think of:
class Player
{
public int CurrentPlayer { get; private set; }
public Player(int plyr)
{
CurrentPlayer = plyr;
}
}
But now I can't check with just thisplayer = Player1 (if thisplayer is a Player).
Eventually this sort of means: How can you create your own object that can only have an already defined finite amount of values, for example like Bools?
Use Enums:
using System;
public class Program
{
private enum Player { PlayerOne, PlayerTwo }
public static void Main()
{
Player player = Player.PlayerTwo; // Change me between Player.PlayerOne and Player.PlayerTwo to see the output change.
switch(player)
{
case Player.PlayerOne:
//this will get executed
Console.WriteLine("Player is Player One!");
//do stuff
break;
case Player.PlayerTwo:
//this will not get executed
Console.WriteLine("Player is Player Two!");
//do stuff
break;
default:
//this will not get executed
break;
}
}
}
Output:
Player is Player Two!
See for yourself on .NET Fiddle
Enums are a way to create a somewhat-strongly-typed set of aliases for integers. However, you can always cast an integer (of whatever type you specified for your enum, by default int) to the enum type, so you have to trust the code to not do that (which might be acceptable for an internal enum type). Additionally, you can't add any additional information to the enum value itself; any time you interpret the value, you have to use a switch or similar construct.
If you want to actually have a class with properties and such, but want to restrict the number of instances of that class, you can extend the singleton pattern like this:
sealed class Player
{
// Properties that a Player object has, an improvement over using an enum
// which don't allow you to specify properties.
public int Number { get; }
public bool IsHost { get; }
// This constructor is private, so only the code inside the Player class may create a Player object
// (notwithstanding reflection, etc., which are outside the rules of static typing).
private Player(int number, bool isHost)
{
Number = number;
IsHost = isHost;
}
// Static properties provide singleton instances of Player for each player number.
public static Player One { get; } = new Player(1, true);
public static Player Two { get; } = new Player(2, false);
public static Player Three { get; } = new Player(3, false);
public static Player Four { get; } = new Player(4, false);
}
And then use it like this:
void Main()
{
SomethingThatTakesPlayer(Player.Three);
}
void SomethingThatTakesPlayer(Player p)
{
Console.WriteLine($"Player #{p.Number} is the host? {p.IsHost}");
}

Creating instances of a class with different preset templates

I'm working on a app on C# Visual Studio '13, and I just hit a brickwall - a newbie brickwall I bet. I have a class - lets say it's a car class. It isn't, but it's a decent example for this question.
Now, I'd like to create - I'm not sure this is the right terminology - templates, from which I'd like to create instances. These created instances will be stored in a List<Car> object.
Let's just say the car class has name, model year and mileage attributes. I'd like to be able to create a template, where I can call up a constructor to create a car with a preset name and model year, but a randomized mileage - and have a couple of different ones. Maybe use it in a constructor in such a way, that I have a few sets of presets - or templates - e.g. '99 BMW, '03 Merc and a '79 Lada. When I call the constructor, I'd like it to pick one of these templates, and add a random mileage.
The adding random mileage is not a problem. Matter of fact, there is no problem with building this other than the fact that for the life of me, I can't figure out how to do templates in a constructor.
One of the easiest solutions for some hardcoded, predefined instance values is the factory pattern. For the following Car class:
public class Car
{
public string Model { get; private set; }
public int Year { get; private set; }
public Color Color { get; private set; }
public int Mileage { get; private set; }
// ...
public Car(string model, int year, Color color, int mileage)
{
Model = model;
Year = year;
Color = color;
Mileage = mileage;
}
}
you can create CarFactory as suggested by Henk Holterman in his comment. For example:
public class CarFactory
{
public CarFactory()
{
random = new Random();
}
public Car CreateBMW99WithRandomMileage(Color color)
{
return new Car("BMW", 1999, color, random.Next());
}
public Car Create03BlackMerc(int mileage)
{
return new Car("Mercedes", 2003, Colors.Black, mileage);
}
public Car Create79Lada(Color color, int mileage)
{
return new Car("Lada", 1979, color, mileage);
}
// ...
private Random random;
}
As you can see you have many possibilities when choosing which attributes to fix, which should be random, and which can be left out as configurable.
Another, similar, pattern you can employ here is the prototype pattern.
Here is how you can combine these two patterns to create a random car:
public class CarFactory
{
public CarFactory()
{
random = new Random();
black99BMW = new Car("BMW", 1999, Colors.Black, 0);
black03Merc = new Car("Mercedes", 2003, Colors.Black, 0);
black79Lada = new Car("Lada", 1979, Colors.Black, 0);
// ...
allCars = new Car[] { black99BMW, black03Merc, black79Lada };
}
public Car CreateBMW99WithRandomMileage(Color color)
{
return black99BMW.Clone(color, random.Next());
}
public Car Create03BlackMerc(int mileage)
{
return black03Merc.Clone(Colors.Black, random.Next());
}
public Car Create79Lada(Color color, int mileage)
{
return black79Lada.Clone(color, mileage);
}
public Car CreateRandomCar()
{
var index = random.Next(allCars.Length);
Color color = // pick random color
int mileage = random.Next();
return allCars[index].Clone(color, mileage);
}
private Car black99BMW;
private Car black03Merc;
private Car black79Lada;
private Car[] allCars;
}
Possibilities are countless - this is just a simple example, and does not necessary fit your scenario best. However, usually and especially for beginners, it is better to implement something to see how does it work, to see its advantages and flaws with your own eyes, and then try to improve it.

Understanding the issue with my custom class for Item and inventory system for game

Sorry, but i can not ask a question without some introducing. (If you are not sure whether to read it all, I still try to ask a question, problem is if i try to change my item properties, it applies to all other the same items, how to fix this?)
I Have Class Item with some properties and variables, like itemCost, durability etc.
Class Weapon inherits Item.
I Have ItemGenerator class, that initialize all items in one array. It contains functuons like:
private static Weapon CreateUniqueWeaponForItemList(int ID, int itemLevel,
..... string description) {
Weapon item = new Weapon();
item.ID = ID;
item.ItemLevel = itemLevel;
...
...
item.Description = description
return item;
}
something like that.
This item list initialize at start of the game.
I Have Item array that contain all items.
public static Item[] ItemList = new Item[200];
Below are list of all unique items in game, created by function above, :
ItemList[1] = CreateUniqueWeaponForItemList(1, 5, ....., "this is item!");
ItemList[2] = ....
and like that.
This works great for now. When i generate item i just use Item ID, for specify what item i want to create. Also it easy to save and load, just store item's ID in PlayerPrefs.
But when i start to add additional functional (like item upgrades, damage upgrade and something) i realized that this architecture is bad.
If player have two or more same items, here the problems begin. If i try to change Item properties, they applied to ItemList[ID], not to item i want to.
I think im need to paste code here to be clear. I have inventory system, with
private List<Item> _inventory = new List<Item>(); in Player class. I have treasure chest which get item from ItemList to create some
loot.Add(ItemGenerator.CreateUniqueItem(2));
loot is Item variable in Chest class. Below are explanations of CreateUniqueItem
public static Item CreateUniqueItem(int ID) {
if (ID > ItemList.Length) {
return null;
}
if (ItemList[ID] != null) {
return ItemList[ID];
}
else {
return ItemList[0];
}
}
When item created, player can grab it to inventory. I Just _inventory.add(item);, for example item is ItemList[2]
Player.Insntance.Inventory.Add(chest.loot[2]);
chest.loot.RemoveAt(2);
chest.loot is
public List<Item> loot = new List<Item>();
that contain all items to grab.
I think problem is here.
-----------------------------------------
So here is question itself.
If i want to do upgrade item i use
_inventory[0].MaxDamage++
but MaxDamage increases on all other same items in Player's inventory, i can't understand, why? I do not use ItemList[ID].MaxDamage++
I think i should save all created unique items in some file or something like this, and do reference to them from inventory, rather than give a link to inventory.
Or store in file only item ID, and add to items int variable like upgradeLevel and store this. So depends of upgradeLevel items can get buffs to damage.
But is it right? And what best way to do this?
-----------------------------------------
Here is short paste of Item class:
public class Item {
private int _ID
private int _itemLevel;
...
...
public Item(){
_ID = 0;
_itemLevel = 0;
...
...
}
public ID{
get { return _ID; }
set { _ID = value; }
public int ItemLevel {
get { return _itemLevel; }
set { _itemLevel = value; }
}
...
...
...
}
Weapon class is same, but have additional variables, like damage.
public class Weapon : Item
{
private int _maxDamage;
public Weapon() {
_maxDamage = 0;
}
public int MaxDamage {
get { return _maxDamage; }
set { _maxDamage = value; }
}
}
I can give full listing of code on GitHub if necessary. But i think, code i was pasted above more than enought. I hope I do not forget anything.
I would not be surprised if the problem is trivial, but for some reason I can not understand it and it causes headache.
I'm sorry if there is too much text, but I could not shorter.
Also sorry for my bad english spelling.
Thanks in advance.
You're storing references in your array, and so there is only ever a single item in existence. Solving this can be done a number of ways. Essentially, your "item list" is almost like a list of templates. A bit like Plato's theory of perfect form. Whenever you bring an item in to existence in the game (be it in the chest, or the player's inventory) you want to clone the item.
Think of your sword item array as the "concept of a sword", and whenever there is one in a chest, we "clone" that template. The chest now contains a duplicate of the template. Obviously when the player takes the sword, we merely transfer from one container to another (we don't leave the sword in the chest, it's transferred to the player's inventory).
So, how can we do this? Well, we can use cloning. Your item base class can therefore look like this:
// Not tying ourselves just to weapons here... what about food, clothes, etc..?
public abstract class Item
{
public int ID { get; set; }
public string Name { get; set; }
// Let's have a copy constructor
public Item(Item other)
{
ID = other.ID;
Name = other.Name;
}
// This part is important!
public abstract Item Clone();
}
Good. We have an item. And it has some basic properties. Let's make a weapon.
public class Weapon : Item
{
public int Damage { get; set; }
// also has a copy constructor
public Weapon(Weapon other) : base(other) // Item copies it's stuff!
{
Damage = other.Damage;
}
// Need to implement this:
public override Item Clone() { return new Weapon(this); }
}
You can now derive a whole bunch of other stuff (food, clothes, dirty magazines, works on Plato, etc).
Now, you can have an array of these Item instances, and whenever you want to place one in a chest in the game, you merely go:
Item newItem = itemList[index].Clone();
This effectively creates a new instance of whatever the item was. Food will get cloned properly. So, if the player has a cloned sword, it's now ok to clone it, and increase the damage of it - because it is a different sword (but based on Plato's original sword!).
This isn't the only way to solve this, and inheritance trees can get pretty messy when items have multiple different kinds of properties and there are potentially hundreds of slight variations. I favour component-based design in those instances, but that's a bit beyond the scope of this answer.
You may find this problem gets simpler if you derive items from ScriptableObject.
ScriptableObject (like GameObject) has to be instantiated explicitly so you can be clear when you are dealing with an instance and when you're dealing with a ScriptableObject asset. The other nice thing is that you can then create your template assets ('the +1 sword', 'the cloak of invisiblity', etc) and edit them by hand (setting individual item properties, etc) and inspect them. Plus all the boring stuff like managing instance ids and manual serialization is done for you.
Consider using just one item class. Switch item type by using enums, in my opinion it´s way more flexible.
I´m using something like the following code, because using inheritance is a messy thing... sooo^^
ehm... this example is for unity users ;)
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
public class Player : MonoBehaviour
{
public List<Item> Items = new List<Item>();
void Start()
{
Test();
AddItem( new ItemGenerator().GetRandomItem());
AddItem( new ItemGenerator(ItemType.Weapon, "New Cool Weapon Item", UnityEngine.Random.Range(1,100), UnityEngine.Random.Range(1,100)));
Item item = new Item();
item.ItemType = ItemType.Quest;
AddItem(item);
AddItem(new Item());
AddItem( new ItemGenerator().GetRandomItem("Random Test Item 1 2 3"));
AddItem( item.Clone());
}
public void AddItem(Item item)
{
Items.Add(item);
}
void Test()
{
Debug.Log("Test starts");
// example flexibility
Item item = new Item();
item.ItemType = Itemtype.Weapon;
if(item.ItemType == Itemtype.Weapon)
Debug.Log(item.WeaponProperties.GetDps(item));
item.ItemType = Itemtype.Armor;
if(item.ItemType == Itemtype.Armor)
Debug.Log(item.ArmorProperties.Defense);
switch(item.ItemType)
{
case ItemType.Weapon: Debug.Log("Do Weapon Stuff - instantiate at weapon spawnpoint"); break;
case ItemType.Armor: Debug.Log("Do Armor Stuff - instantiate at spawnpoint"); break;
default: Debug.Log("what ever..."); break;
}
// example item generator
Item item2 = new ItemGenerator(ItemType.Weapon);
Debug.Log(item2.Name);
Item item3 = new ItemGenerator(ItemType.Armor, "New Armor Item");
Debug.Log(item3.Name);
item3.ItemType = ItemType.Weapon;
item3.Name = "Ultra Sword";
Debug.Log(item3.Name);
Item item4 = new ItemGenerator(ItemType.Weapon, "New Weapon Item", UnityEngine.Random.Range(1,100), UnityEngine.Random.Range(1,100));
Debug.Log(item3.Name);
Item item5 = new ItemGenerator().GetRandomItem();
Debug.Log(item2.Name);
Debug.Log(item2.ItemType);
// clone item
Item item6 = item5.Clone();
Debug.Log(item2.Name);
Debug.Log("Test ends");
}
}
public enum ItemType
{
Weapon, Armor, Consumable, Quest, Key //...
}
public class Item // :ScriptableObject
{
public string Name = "FooItem";
public ItemType Itemtype;
public Attributes Attributes = new Attributes();
public WeaponProperties WeaponProperties = new WeaponProperties();
public ArmorProperties ArmorProperties = new ArmorProperties();
public Item Clone()
{
return (Item)MemberwiseClone();
}
}
[System.Serializable]
public class WeaponProperties
{
public int MinimalDamage = 10;
public int MaximalDamage= 20;
public float Speed = 1.5f;
public static float GetDps(Item weapon)
{
return Mathf.RoundToInt(((weapon.WeaponProperties.MinimalDamage + weapon.WeaponProperties.MaximalDamage) * 0.5f) / weapon.WeaponProperties.Speed);
}
}
[System.Serializable]
public class ArmorProperties
{
public int Defense = 25;
}
[System.Serializable]
public class Attributes
{
public int Strength = 25;
public int Stamina = 20;
}
public class ItemGenerator : Item
{
public ItemGenerator(ItemType itemType) : this(itemType, "NewNamelessItem")
{
}
public ItemGenerator(ItemType itemType, string name)this(itemType, name, 0, 0)
{
}
public ItemGenerator(ItemType itemType, string name, int strength, int stamina) : this(itemType, name, strength, stamina, 0)
{
}
public ItemGenerator(ItemType itemType, string name, int strength, int stamina, int defense)
{
Name = name;
Attributes.Strength = strength;
Attributes.Stamina = stamina;
switch(item.ItemType)
{
case ItemType.Weapon:
break;
case ItemType.Armor:
ArmorProperties.Defense = defense;
break;
default: Debug.Log("what ever..."); break;
}
}
public Item GetRandomItem()
{
return GetRandomItem( "New Random Item");
}
public Item GetRandomItem(string name)
{
Name = name;
Attributes.Strength = Random.Range(0,100);
Attributes.Stamina = Random.Range(0,100);
var values = Enum.GetValues(typeof(ItemType));
ItemType = (ItemType)values.GetValue(Random.Range(0, values.Length));
switch(item.ItemType)
{
case ItemType.Weapon:
break;
case ItemType.Armor:
ArmorProperties.Defense = Random.Range(0,100);
break;
default: Debug.Log("what ever..."); break;
}
return this;
}
}
Finally this means every item can be change to a totally different item and you have full control about all the variables.

Categories