Inventory / item logic
My Item class holds a constructor which takes a string Name and int Quantity as an argument. Also in the class file, are static methods that create certain items like createSeed. All these methods do is set the name and quantity and then return Seed.
The question is, am I going about this the right way? Eventually I'm going to end up with hundreds of different methods for creating different items which is probably not the best practice, but I am curious to know what other approaches I can take to this.
I won't elaborate too much since I don't know the best solution myself but it does sound like you need more of a data driven approach to this rather than a code driven approach.
Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.
Pretty much each item is going to have multiple attributes associated with it like sell and buy price, if it's a tool, If it's edible etc. So if I created a base item class and inherit each item from it, that'd be a lot of code.
in Caveman, constant info about objects such as name, weight , base price, damage done, model for drawing, etc is stored in an array of structs - basically object_type_structs.
an instance of an object is stored in a struct with type, quantity, quality, and location (used by dropped objects) - basically object_instance_structs.
a stufflist is an array of object_instance_structs. IE a list of objects.
stufflists are used for character inventories, containers, and dropped objects.
stufflists have methods like add_item, remove_item, num_carried, remove_best, remove_worst, etc.
constant data about an item type is stored in the object_type_structs, IE how to draw it, base price, weight, etc.
variable data about instances of items in the game world are stored in object instance structs (type, quantity, quality, location).
in OO terms, you'd probably have two basic types of objects, a list of object types (one instance), and stufflists (many instances). the list of object types would basically be a read only database. so maybe just some getter and setter methods, or load from disk for data driven. most of the work would be done by stufflist methods such as add_item, remove_item, etc..
the combo of the stufflists and the object types list forms a small relational database (related on type).
its also possible to store the constant object type info with each instance of the object. redundant data, but sometimes done for optimization. don't do it until you have the proof you need to. if you find you do, its a simple matter to add the extra variables to an object_instance _struct definition, and copy the values from the object_type_struct to the object_instance_struct when you add an object to the game.
remember - not every little thing has to be an object. in the case of inventories, it appears the pile of stuff is the basic object, not the individual items. makes sense, an individual item is just the special case of a pile of stuff of a single type of quantity one.
Norm Barrows
Rockland Software Productions
"Building PC games since 1989"
PLAY CAVEMAN NOW!
http://rocklandsoftware.net/beta.php
I don't think that's a very effecient way to do things.
Personally, I use a generic Item class with generic variables.
For example:
Class Item{
string name ; //something lazy and quick to type for you, used internally. Also allows "duplicates" since two can have the same 'display name', but not 'name'
string displayname ; //what's shown to the player
string itemType ;
Texture itemIcon ;
int maxStack = 1 ; //maxiumum stack-size of this item So armor = 1, apples = 10, or whatever
itemString1;
itemString2;
...and so forth
itemInt1;
itemInt2;
..etc
itemfloat1 ;
...
}
You then can use the generic variables for different things depending on the 'itemType'
So Armor would look like this:
ItemName: ironarmor
DisplayName: Iron Armor
itemType: chestArmor
itemInt1: 5
itemInt2: 10
itemFloat1: 150
so, if itemType == "chestArmor", and it's in the player's equiptment slot, it adds 5 to the player's armor value. ItemInt2 corresponds to weight, Float1 corresponds to it's cost in shops. I use generic variable names because itemInt1 doesn't necessecarily correspond to "armor value".For itemType "food" it corresponds to how much health is healed when it's consumed. The obvious problem is it's not very human-readable, so you need a cheatsheet or //notes to make sense of it.
Alternatively, if you want a more human-readable form of item, you can just put every named variable on every item. So "Apple" would still have an "ArmorValue" variable, but it's just set to 0, and isn't equipable anyway and IronArmor can't be consumed, even though it has the "restoreHealth" Variable. Personally, I feel it's kind of a pain, because if you have alot of different item types, your list of variables can get quite long.
So you can populate your new items however you like, stick them in an "all items" array, and create a "AddItemToInventory( string itemName)" method. The method looks through the AllItems array, and finds the one with that name, and puts one in your inventory, or increments the count of existing ones. Personally, I used 4 arrays for my inventory:
Item Equipment[], Int EquiptmentCount[], Item Inventory[] , Int InventoryCount[] For the Equipment Array, the index corresponds to the body-slot. (head, torso, legs, etc)
I don't know if this is really the best way to go about it, but it works for me. I've tried tons of different inventory setups, and this one seems to be the easiest to handle and it's totally self-contained, so I've been able to reuse it alot.
class Item
itemName
quantity
public Item Seed(int quantity)
ItemName = "seed"
Quantity = quantity
But if I have 100 items I'll have 100 different methods like the above method.
so, if itemType == "chestArmor", and it's in the player's equiptment slot, it adds 5 to the player's armor value. ItemInt2 corresponds to weight, Float1 corresponds to it's cost in shops. I use generic variable names because itemInt1 doesn't necessecarily correspond to "armor value".For itemType "food" it corresponds to how much health is healed when it's consumed. The obvious problem is it's not very human-readable, so you need a cheatsheet or //notes to make sense of it.
I would advocate strongly against this approach. Not only is it not human-readable (which you might get away with, if you are working on a very small project, alone), but it makes it impossible to use this data in any kind of generic code. Meaning every piece of code that deals with items has to always consider the exact type of the item to determine the data layout it has.
Alternatively, if you want a more human-readable form of item, you can just put every named variable on every item. So "Apple" would still have an "ArmorValue" variable, but it's just set to 0, and isn't equipable anyway and IronArmor can't be consumed, even though it has the "restoreHealth" Variable. Personally, I feel it's kind of a pain, because if you have alot of different item types, your list of variables can get quite long.
If your items really have that many properties, and there isn't much overlap, I would consider making the item a key-value store of sorts. This allows items to have a unique set of properties, that are still accessible in a generic way. For example:
// A very simplistic item class, that is only a key-value store of floats.
// You will probably need something more sophisticated, but this illustrates the concept well.
class Item
{
public:
bool hasAttribute(EItemAttribute) const;
float getAttributeValue(EItemAttribute) const;
private:
std::map<EItemAttribute, float> attributes;
}
...
// This is just an example usage, to illustrate the concept of generic access to items
bool Player::equipWeapon(Item* item)
{
if (item.hasAttribute(E_ATTRIBUTE_ATTACK_VALUE))
{
m_activeWeapon = item;
return true;
}
return false;
}
// This is just an example usage, to illustrate the concept of generic access to items
bool Player::attack(Foo* target)
{
float attackValue = 0.0f;
if (m_activeWeapon)
{
attackValue = m_activeWeapon->getAttributeValue(E_ATTRIBUTE_ATTACK_VALUE));
return target->onAttacked(this, attackValue);
}
return false;
}
As for filling out the data in the first place, you don't have to write that in code, just read it in from a file in whatever format you like (XML, JSON, your own format, whichever suits you the most).
As per the difference between "item instances", and what you could call "item prototypes", refer to the post by Norman Barrows. Note that depending on your requirements, you might or might not need to differentiate between the two.
Normans post says to have an array that stores the info for the item and the item itself. Right now I only have the latter, but if I created a hashmap<Item, Integer> I won't need to constantly be creating instances of class Item? If I understand correctly
Basically, yes. You have your item "prototypes" in a map by item type id, then an item "instance" is an item type id (which you can use to look up the general parameters of the item, that are constant throughout all instances), plus properties that can be different for different instances, if you need them (an example would be item durability). Then you need to create the "prototype" only once, typically by reading a file that lists all item prototypes and their properties.
Inventory = new Item(itemHashmap.getKey("seed");
But I'd still need to write the 100 different constructors to populate the itemHashmap or use XML.