MunchGamer
JGO n00b  Posts: 22
|
 |
«
on:
2012-02-05 23:36:42 » |
|
Hey all,
Developing a big RPG style inventory system. Currently it's organized with class hierarchy:
Inventory (contains items, hotbar for quickselect, etc)
Item ->Weapon ->MeleeWeapon ->RangedWeapon ->Pistol ->Shotgun
etc...
So here's what's on my mind-grapes:
If a weapon can be upgraded (ex. purchase silencer for Beretta 9mm) through purchase of upgrades, and also 'upgraded' through player skill trees (ex. '+10% accuracy with pistols'), I was thinking that it might be best, on creation of the item, to poll the player for their skills and adjust the attributes accordingly and then to have an instance reference of a 'WeaponUpgrade' class which holds the internal (and importantly, specific to that instance of that weapon) upgrades, also adjusting the attributes.
Thoughts on this design implementation?
|
|
|
|
|
GabrielBailey74
Full Member   Posts: 157 Medals: 2
Owner of Elite Demons R.I.P
|
 |
«
Reply #1 on:
2012-02-05 23:49:34 » |
|
You could create one class in example the RangedWeapons package/folder that has a sub class that handles each weapon/upgrades/bonuses.
Or you could in that RangedWeapons package create multiple classes 1 for each weapon. Explain a bit more on what you're trying to do please?
|
|
|
|
MunchGamer
JGO n00b  Posts: 22
|
 |
«
Reply #2 on:
2012-02-06 00:00:45 » |
|
The individual weapons are implemented already ie Beretta9mm extends Pistol, which extends RangedWeapon, so on and so forth back up to Item. The reason for this architecture is to allow a player to have any sort of item, be it a Pistol or a Potion, in their hot bar, and use it by clicking. The question, or rather more of a implementation proposal since everyone does things their own way, is that I'm suggesting within the class Weapon: 1 2 3 4 5 6 7 8 9 10
| public abstract class Weapon extends Item { protected double damage; protected double knockback; protected double speed; protected double atkTime; protected double accuracy; protected double accuracyModifier; protected WeaponUpgrade upgrade; |
And when the player stops in at the upgrade shop / other gameplay contrivance, the WeaponUpgrade is retrieved from the weapon, an attribute of it is modified, ex: 1 2 3
| public void setAccuracyBonus(double d){ this.accuracyBonus += d; } |
and off the player goes, now with a more accurate, reliable weapon. When the accuracy (or damage, or knockback, or other attribute) of the weapon then needs to be retrieved, it can be grabbed as some calculation performed on: 1. the weapon's base attribute 2. modifiers provided by specific upgrades to that instance of the weapon 3. the player's global benefits for weapons of that class (ie pistols) If anything, I guess I'm asking if this seems like a reasonable way to go about doing things, or if there's some glaring oversight I'm making.
|
|
|
|
|
Games published by our own members! Go get 'em!
|
|
GabrielBailey74
Full Member   Posts: 157 Medals: 2
Owner of Elite Demons R.I.P
|
 |
«
Reply #3 on:
2012-02-06 00:07:22 » |
|
That's what i'd do, declare all the various upgrade/statistics variables of the UpgradeClass, than when a upgrade is clicked in the shop, set all the variables accordingly to the current level, and set the bonuses++ accordingly to the current upgrade bieng purchased.
|
|
|
|
loom_weaver
Full Member   Posts: 224 Medals: 14
|
 |
«
Reply #4 on:
2012-02-06 00:28:23 » |
|
Look for postings by Ray aka Bear in this thread: http://groups.google.com/group/rec.games.roguelike.development/browse_thread/thread/68850017f706a5dbHis post itemizes many things that you might want to consider for your item framework. The general consensus is that class hierarchies are too restrictive when designing RPG entities. For example if you have a gun with a bayonet on it how will it fit into your hierarchy? I would recommend some kind of very flat framework or component-based system aka composition to give you the needed flexibility.
|
|
|
|
|
theagentd
JGO Wizard     Posts: 1392 Medals: 88
|
 |
«
Reply #5 on:
2012-02-06 07:01:57 » |
|
I think it would be "cleaner" to keep weapons and player skill upgrades clearly separated. I'd not have any player skill related variables in my Weapon class, and instead pass the PlayerSkills/UpgradeClass (whatever you call it xd) to the Weapon each fire()/update().
|
There is no god.
|
|
|
MunchGamer
JGO n00b  Posts: 22
|
 |
«
Reply #6 on:
2012-02-06 11:44:11 » |
|
I think it would be "cleaner" to keep weapons and player skill upgrades clearly separated. I'd not have any player skill related variables in my Weapon class, and instead pass the PlayerSkills/UpgradeClass (whatever you call it xd) to the Weapon each fire()/update().
Right. The idea is that the Player Skills and Upgrades are 2 separate ideas. IE: 1 2 3 4 5 6 7 8
| public void dealDamage(Sprite theOneGettingOwned){ int dmg = 0; dmg += this.damage; dmg *= this.upgrade.getDamageBonus(); dmg *= this.getOwner().getDamageBonus(this); theOneGettingOwned.receiveDamage(dmg); } |
|
|
|
|
|
sproingie
JGO Strike Force    Posts: 899 Medals: 55
|
 |
«
Reply #7 on:
2012-02-06 14:27:06 » |
|
What everyone else has been saying: the less mutation of state you do the better. Wielding a weapon shouldn't modify a "damage" variable on the player, it should simply be included in a "calculateDamage()" method. RPGs can involve dozens of variables for calculating stats, but it's something even the weakest computer nowadays will do in microseconds.
If you want to be fancy, then even an old standby like "hit points" is something you might want to calculate by keeping a list of individual wounds with their respective HP penalty, rather than just decrementing a variable. Then when a player applies the "first aid" skill, you can limit it to removing only wounds that his skill is sufficient to treat. Of course, if your game models a player's health by way of a life bar, then by no means indulge in design overkill like that. Your code design should reflect the game you want
|
|
|
|
|
Roquen
JGO Strike Force    Posts: 827 Medals: 25
|
 |
«
Reply #8 on:
2012-02-06 15:09:02 » |
|
I just said something like this in a previous thread. What's the difference between two ranged weapon? Some data...so you could skip on subclassing RangedWeapon. What's the difference between a MeleeWeapon and a RangedWepon? Some data...so you could skip on subclass Weapon. The point to stop with this kind of thinking is (roughly) when a parent and child require different state data.
|
|
|
|
|
MunchGamer
JGO n00b  Posts: 22
|
 |
«
Reply #9 on:
2012-02-06 16:11:06 » |
|
I just said something like this in a previous thread. What's the difference between two ranged weapon? Some data...so you could skip on subclassing RangedWeapon. What's the difference between a MeleeWeapon and a RangedWepon? Some data...so you could skip on subclass Weapon. The point to stop with this kind of thinking is (roughly) when a parent and child require different state data.
In my implementation MeleeWeapons and RangedWeapons hold different sorts of data and override methods in different ways, so the subclassing is relevant.
|
|
|
|
|
Games published by our own members! Go get 'em!
|
|
sproingie
JGO Strike Force    Posts: 899 Medals: 55
|
 |
«
Reply #10 on:
2012-02-06 18:10:35 » |
|
I think he meant that subclasses of RangedWeapon, like LongBow, CrossBow, Sling, etc aren't useful subclasses. The behaviors of ranged and melee are different enough that they might deserve to be subclasses of Weapon, but individual cases of them aren't really sufficient.
That brings us back to the "gun with a bayonet" problem though, and the answer is pretty simple: it's two weapons. The identity of a single object shouldn't be confused with the possibly multiple instances that may constitute its behavior. Similarly a robe with pouches could be armor and a bag. Rather than trying to fake multiple inheritance, you just accept that one item may actually be a composite of several objects.
This has implications for where you put unrelated properties like "sale value" and "durability" for example. The answer there may be you either create a CompositeItem for such chimeras, or you treat Weapon, Armor, and Bag as behaviors attached to Items, not subclasses of Item. I prefer the behavior angle, but there's probably no single right answer there, it's mostly a matter of what you're most comfortable with and what tradeoffs you're willing to make.
|
|
|
|
|
Roquen
JGO Strike Force    Posts: 827 Medals: 25
|
 |
«
Reply #11 on:
2012-02-07 08:26:00 » |
|
I think he meant that subclasses of RangedWeapon, like LongBow, CrossBow, Sling, etc aren't useful subclasses.
Yes. Exactly this. These things only vary by data. And remember that "code" can be considered data. Take a game like the original doom. All weapons could be used in exactly one way (bound to some input). The original Unreal? Each could be exactly used in two ways (by default bound to L&R mouse button). In the "classic" RPG "Dungeon Master" any item could be placed in the "weapon" hand and by clicking the GUI button the player could choose up to three different attacks depending on the item in question. In none of these examples does the "type" of weapon or what the kind of attack does effect how it's triggered. Each just has more or less ways a given item can be used to perform an attack action. Here's a rough sketch of a possible design direction: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public abstract class OnAttack extends PerhapsSomeBaseActionClass { public abstract ... exec(Enitity attacker, Item item, ....); }
public class WeaponArchtypeForSomeGameWhichHasUpToTwoTypesOfAttackPerItem extends Whatever { public OnAttack OnAttack1; public OnAttack OnAttack2; }
public class OnProjectileAttack extends OnAttack { public ... exec(Enitity attacker, Item item, ....) { } } |
|
|
|
|
|
MunchGamer
JGO n00b  Posts: 22
|
 |
«
Reply #12 on:
2012-02-07 13:17:14 » |
|
I think he meant that subclasses of RangedWeapon, like LongBow, CrossBow, Sling, etc aren't useful subclasses. The behaviors of ranged and melee are different enough that they might deserve to be subclasses of Weapon, but individual cases of them aren't really sufficient.
That brings us back to the "gun with a bayonet" problem though, and the answer is pretty simple: it's two weapons. The identity of a single object shouldn't be confused with the possibly multiple instances that may constitute its behavior. Similarly a robe with pouches could be armor and a bag. Rather than trying to fake multiple inheritance, you just accept that one item may actually be a composite of several objects.
This has implications for where you put unrelated properties like "sale value" and "durability" for example. The answer there may be you either create a CompositeItem for such chimeras, or you treat Weapon, Armor, and Bag as behaviors attached to Items, not subclasses of Item. I prefer the behavior angle, but there's probably no single right answer there, it's mostly a matter of what you're most comfortable with and what tradeoffs you're willing to make.
So I think what you might be getting at with the data part of things (though I am rather ignoring your confusing Robe example), is that instead of : 1 2 3 4 5 6 7 8
| public class M16 extends AutomaticRangedWeapon{ public M16(Sprite owner){ super(owner); this.damage = 3; this.accuracy = 10; } } |
Something like this might be more appropriate: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class AutomaticRangedWeapon{ public static enum AutoWpnType{ M16, P90 }
public AutomaticRangedWeapon(Sprite owner, AutoWpnType type){ super(owner); switch(AutoWpnType) case M16: this.damage = 3; this.accuracy = 10; break; case P90: this.damage = 2; this.accuracy = 15; break; } } |
[EDIT] to clarify, the reason that the Automatic part is specified is that upon use, it may have different attack behavior than a pistol (automatic fire, etc...) or a shotgun (shotgun may generate 8 attacks for spray).
|
|
|
|
|
loom_weaver
Full Member   Posts: 224 Medals: 14
|
 |
«
Reply #13 on:
2012-02-07 22:02:45 » |
|
If you approach this from the opposite direction you start with raw data stored in a common type e.g.: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| HashMap<String, Object> m16 = { "type" -> gun "automatic" -> true "damage" -> 16 "accuracy" -> 10 }
HashMap<String, Object> p90 = { "type" -> gun "automatic" -> true "damage" -> 2 "accuracy" -> 15 }
HashMap<String, Object> knife = { "type" -> blade "range" -> 1 "damage" -> 3 "accuracy" -> 15 } |
Then toss these into a List. Sometimes that's all you'll need and this form has the most amount of flexibility. If you have odd items you could change "type" -> Set(...). From there introduce a class hierarchy where it makes sense. You could design your classes so that they just contain static methods that operate on the raw HashMaps thus decoupling your logic from the data. Anyways, you avoid introducing classes too early which will only end up restricting you.
|
|
|
|
|
loom_weaver
Full Member   Posts: 224 Medals: 14
|
 |
«
Reply #14 on:
2012-02-07 22:24:39 » |
|
And here is an upgrade class (pseudocode): 1 2 3 4 5 6 7 8 9 10
| class DamageUpgrade { static HashMap<String, Object> pimpMyWeapon(HashMap<String, Object> weapon) { HashMap<String, Object> modified = new HashMap<String, Object>(weapon); modified("damage") += 10; return modified; } }
... beefy_m16 = DamageUpgrade.pimpMyWeapon(m16); |
Notice if you construct your class hierarchy properly then you could have a big list of "active upgrades" and then you just daisy chain the calculations to determine the final actual result to use in combat. You still have access to your original unmodified weapon too (m16).
|
|
|
|
|
theagentd
JGO Wizard     Posts: 1392 Medals: 88
|
 |
«
Reply #15 on:
2012-02-07 22:32:13 » |
|
How much you need to complicate things mostly depend on what kind of upgrades that you have. If you want to be able to add and remove "buffs" and modifiers then you'll need to keep track of the base value of the weapon and either compute the value every update or when a weapon's stat modifiers change. This is to avoid rounding errors, obviously assuming you're using floating point variables for stats. I'd strongly recommend doing so though, since a 10% bonus to a 5 damage weapon is still 5 otherwise...
|
There is no god.
|
|
|
loom_weaver
Full Member   Posts: 224 Medals: 14
|
 |
«
Reply #16 on:
2012-02-07 22:48:01 » |
|
How much you need to complicate things mostly depend on what kind of upgrades that you have. If you want to be able to add and remove "buffs" and modifiers then you'll need to keep track of the base value of the weapon and either compute the value every update or when a weapon's stat modifiers change.
Exactly! With all entities represented as a simple data structure i.e. a HashMap, this becomes really easy. A damage buff/debuff is stored as a tuple of <String, Integer> e.g. "damage", 10. Negative numbers are debuffs. Store a list of them and calculate the final value only when you need it. Adding a buff to a weapon/item/mobile/anything is easy: just instantiate another tuple and add it to the list. Expiring buffs are a snap as well: just remove them from the list.
|
|
|
|
|
sproingie
JGO Strike Force    Posts: 899 Medals: 55
|
 |
«
Reply #17 on:
2012-02-07 22:57:57 » |
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class AutomaticRangedWeapon{ public static enum AutoWpnType{ M16, P90 }
public AutomaticRangedWeapon(Sprite owner, AutoWpnType type){ super(owner); switch(AutoWpnType) case M16: this.damage = 3; this.accuracy = 10; ... |
First of all, enums can have fields and constructors, so if you used them to store the stats, you can store them on the enum itself. However, you probably want to avoid this approach, because all instances of enums are staticly defined (which is largely the point of enums), so you would have to recompile your app to add a new weapon. What you're going to want eventually is to read in some kind of config, like xml or json or .ini format, that builds a map of weapon definitions.
|
|
|
|
|
MunchGamer
JGO n00b  Posts: 22
|
 |
«
Reply #18 on:
2012-02-07 23:01:15 » |
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class AutomaticRangedWeapon{ public static enum AutoWpnType{ M16, P90 }
public AutomaticRangedWeapon(Sprite owner, AutoWpnType type){ super(owner); switch(AutoWpnType) case M16: this.damage = 3; this.accuracy = 10; ... |
First of all, enums can have fields and constructors, so if you used them to store the stats, you can store them on the enum itself. However, you probably want to avoid this approach, because all instances of enums are staticly defined (which is largely the point of enums), so you would have to recompile your app to add a new weapon. What you're going to want eventually is to read in some kind of config, like xml or json or .ini format, that builds a map of weapon definitions. Personally I'd prefer keeping it out of config files to prevent users from adding their own weapon definitions, especially for a 'campaign mode' style single player.
|
|
|
|
|
sproingie
JGO Strike Force    Posts: 899 Medals: 55
|
 |
«
Reply #19 on:
2012-02-07 23:05:41 » |
|
You can always make java source code the config format, which will effectively freeze the config for whatever mode you're using, but I still wouldn't go as far as hardwiring them into an enum. Even a shooter will want to support mods.
|
|
|
|
|
MunchGamer
JGO n00b  Posts: 22
|
 |
«
Reply #20 on:
2012-02-07 23:21:26 » |
|
I don't think changing a config file to add a new weapon really counts as "modding" IMO  But I do see your point. Things like NPC (written) dialogue, texture filepaths and other sorts might also belong outside of the code.
|
|
|
|
|
theagentd
JGO Wizard     Posts: 1392 Medals: 88
|
 |
«
Reply #21 on:
2012-02-08 04:03:02 » |
|
How much you need to complicate things mostly depend on what kind of upgrades that you have. If you want to be able to add and remove "buffs" and modifiers then you'll need to keep track of the base value of the weapon and either compute the value every update or when a weapon's stat modifiers change.
Exactly! With all entities represented as a simple data structure i.e. a HashMap, this becomes really easy. A damage buff/debuff is stored as a tuple of <String, Integer> e.g. "damage", 10. Negative numbers are debuffs. Store a list of them and calculate the final value only when you need it. Adding a buff to a weapon/item/mobile/anything is easy: just instantiate another tuple and add it to the list. Expiring buffs are a snap as well: just remove them from the list. Okay, so now you're storing weapons, their modifiers and items in HashMap<String, Object>s. Are you going to store player properties in a HashMap too? Game objects? I'd say you're completely missing the point of object orientation. HashMaps also do not preserve the ordering of objects. This kills determinism for online games, and it might also be good to be able to control in which order for example modifiers are applied, because you'd want a +20% damage bonus to be applied before a +5 flat damage bonus so the % bonus is applied to the flat bonus too. What you're doing is emulating objects with Strings in HashMaps. HashMaps themselves are in my opinion just a glorified emulation of references anyway, so you're emulating objects and keeping them in emulated references. I'd stay far away from that kind of code if I were you... ._.
|
There is no god.
|
|
|
loom_weaver
Full Member   Posts: 224 Medals: 14
|
 |
«
Reply #22 on:
2012-02-08 13:51:16 » |
|
Okay, so now you're storing weapons, their modifiers and items in HashMap<String, Object>s. Are you going to store player properties in a HashMap too? Game objects? I'd say you're completely missing the point of object orientation. HashMaps also do not preserve the ordering of objects. This kills determinism for online games, and it might also be good to be able to control in which order for example modifiers are applied, because you'd want a +20% damage bonus to be applied before a +5 flat damage bonus so the % bonus is applied to the flat bonus too. What you're doing is emulating objects with Strings in HashMaps. HashMaps themselves are in my opinion just a glorified emulation of references anyway, so you're emulating objects and keeping them in emulated references. I'd stay far away from that kind of code if I were you... ._.
Nah, the statistics are in the HashMap but the modifiers are stored separately e.g. in a List.
|
|
|
|
|
|