Advertisement

What do you guys use as a key in RPG data structures?

Started by August 25, 2015 01:01 AM
27 comments, last by ankhd 9 years, 4 months ago

I've been using a lot of std::map<std::string, int> or something. An example would be:

std::map<std::string, int> attributes_;
attributes_["str"] = 5
attributes_["def"] = 5

I'm not sure I'm good enough at C++ yet to know, but using "std::string" as a key too much feels like it could go incredibly wrong in the future. What do you guys suggest for readable/usable code?

I considered a global enum, but that strikes me as worse than the string idea.

A global enum describing stats isn't that bad, since you should only have a well-defined set to begin with:

enum STAT {
    STAT_STR,
    STAT_DEF,
    STAT_MAG,
    STAT_COUNT
};

typedef std::array<int,STAT_COUNT> Stats;

int main() {
    Stats base;

    base[STAT_STR] = 5;
    base[STAT_DEF] = 5;
}
Benefits include:
Compile time errors when you happen to rename a stat for everywhere it is used.
Simple storage and fast access.
Easy math:

Stats combine(const Stats& a, const Stats& b) {
    Stats r;
    for ( int sdx = 0; sdx < STAT_COUNT; ++sdx )
        r[sdx] = a[sdx] + b[sdx];

    return r;
}

void equip(Player& p, State& item_stats) {
    p.stats = combine(p.stats, item_stats);
} 

Other alternatives include using unordered_map, which uses a hash of its keys.
Advertisement

I should use an unordered map! The issue with the global enum is that I also use strings as keys to a map which keeps track of what item is equipped where. Also, using an unordered map would lessen the changes I'd have to make to any codes using iterators. ;)

Thanks, by the way. I totally forgot unordered_map was a thing.

What's wrong with just having strongly typed fields? Will you be inventing new stat types at run time? I think that in the quest to build the I-can-build-any-game-with-my-custom-engine engine people go overboard with the dynamic data structures. That code isn't as reusable as you think it is. And you probably won't want reuse it anyway because you'll find 100 reasons during your first implementation that you'll feel it necessary to build your RPG engine 2.0 from scratch. Also when choosing to go dynamic, you're not taking advantage of the facilities of the c++ language such as static typing that will help you avoid some bugs and enables certain types of developer productivity tools like refactoring helpers built in to Visual Studio.

Mm... for something really basic. What I do is

Struct Attributes{
HP
MP
STR
DEX
WIS
CON
PER
}

attribute *ptr_Characters

map<std::string, Attributes> _Character

ptr_Characters = Character[Name]; //Cache the address of what we just accessed.

ptr_Characters->HP = 5; //set HP of selected critter to 5

Reason for this set up is that just about all RPG characters will share these attributes. But listing things individually would be counter productive. Especially if entities are going to be placed in the same map.

What's wrong with just having strongly typed fields? Will you be inventing new stat types at run time? I think that in the quest to build the I-can-build-any-game-with-my-custom-engine engine people go overboard with the dynamic data structures. That code isn't as reusable as you think it is. And you probably won't want reuse it anyway because you'll find 100 reasons during your first implementation that you'll feel it necessary to build your RPG engine 2.0 from scratch. Also when choosing to go dynamic, you're not taking advantage of the facilities of the c++ language such as static typing that will help you avoid some bugs and enables certain types of developer productivity tools like refactoring helpers built in to Visual Studio.

I second this. Also as if you happen to find new stat a that that would better fit as double instead of int, you still have it in the same structure without runtime type checks or introducing another map.

Advertisement

I'm not trying to be dynamic, no, haha.

std::string as a key just feels wrong. I'm not sure why—but apparently it's fine, and I'm still using it.

You are being dynamic when you use a map for the stats. I would recommend using composition of components to indicate that a thing has a certain category of stats (not saying go down the component based entity rabbit hole). I think for most games this would align with the use case anyway. So you might have a class called DefensiveStats that carries along physical defense, magic resistance, etc. then you might have a class called HealthStats that might carry the HP. Then StatusStats or some such that carries any status ailments or buffs. You see where I'm going here. Your game object will have a field for each of these stat categories. When the object has a concept of defense, you'll stick a defense stat in defense stats field, etc. This approach will be dynamic enough while also maintaining static typing and avoiding the overhead of using a map to store stats. You'll also save yourself a ton of casting.

Performance wise, its unlikely you'll be accessing these stats often enough for the key type to make any real difference; the bigger issue is probably that the 'fuzziness' of using strings (or, say, integers, for that matter) can allow unintended mistakes to happen silently. Probably you will catch most of these errors quickly but its often better to have the compiler help you, as it could with an enumeration. But like all things this is a trade-off and your needs/perspective counts for the biggest part, maybe you need the flexibility that a set of string-int key-values provides, in which case this is a fine solution -- the task of an engineer, though, is generally to meet requirements with the simplest and safest machinery that does the job.

So, if you're not being dynamic at all (that is, neither the names of the stats, nor the number of overall stats is decided at runtime), then what you really want is a structure or class with a member for each stat. That does away with the need for an enum/key entirely (though you'll still might need to map between the members and an ID--that might be string-based--in, say, loading code.) Not only is this more concrete, but you can also choose, for instance, to have stats of different fundamental types. For instance, perhaps its more natural to express an all-up combat bonus as a float, or to express immunity to magic-based attacks as a hard yes/no using a bool. The fact that most RPG systems are integer-based is because they stem from pen-and-paper games that used dice and simple math (not even D&D nerds want to math harder than they have to), but there's no reason you have to with a computer simulation.

throw table_exception("(? ???)? ? ???");

Ah!

I was planning to implement status-objects (buffs, debuffs, etc.) but I didn't think of following this same route with base-stats (str., def., etc.)

The way it works now is:
- There are "attack" objects, which all inherit from a base attack class (and may be composed of some other things in the future)—they do all the calculations given a character's stats and some damage multiplier. This will make it easier to give specific characters specific abilities. When performing an attack, one of these objects is created, passed to an attack-event, and an attack-handler applies all the appropriate damage.

- Items have the same attribute-map as characters. When items are equipped, an iterator just adds the appropriate item stats to the character stats by key (equipping, picking up items, etc. is actually handled by a handler, too, since it requires things like removing an item from a room and putting it in the character's inventory.)

This might be an inefficient system, but it's my first game~

Would creating a separate object for each "set" of stats really be less overhead than just using a map? Considering that attacks use these stats, items affect these stats, etc. I didn't really have dynamism in mind when I started using maps, I just thought it was the easiest way to store a base-attribute integer.

This topic is closed to new replies.

Advertisement