5 ms·
>The vast majority of essential code is not operating on just one object – it is actually implementing cross-cutting concerns. Example: when class Player hits()
by adnzzzzZ 8y ago
>The vast majority of essential code is not operating on just one object – it is actually implementing cross-cutting concerns. Example: when class Player hits() a class Monster, where exactly do we modify data? Monster's hp has to decrease by Player's attackPower, Player's xps increase by Monster's level if Monster got killed. Does it happen in Player.hits(Monster m) or Monster.isHitBy(Player p). What if there's a class Weapon involved? Do we pass it as an argument to isHitBy or does Player has a currentWeapon() getter?
As an indie dev this is something that I struggled with early on and my solution so far has been to choose the most obvious place where all those things should happen and just do it there (in this case it would be on the Player, in other less obvious cases it gets more fuzzy). What does the non-OOP solution for this problem look like?
- jdub 8y agoEntity-Component-System, amusingly explained in this great talk from RustConf. https://www.youtube.com/watch?v=aKLntZcp27M https://www.youtube.com/watch?v=aKLntZcp27M
- adnzzzzZ 8y agoThat solution is more burdensome than OOP
- Epskampie 8y agoDiagree. Unity works like this, and it’s great for attaching multiple behaviours to an object (i.e. a Player can be Hit, but an Enemy as well). It saves you having to make up a inheritence hierarchy that never seems to work out (Player extends Hittable or something? But it also needa these 10 other behaviours...)
- coldtea 8y agoNot at all. It also has excellent visibility, extensibility, and debugabillity.
- jcelerier 8y agobut... all ECS systems use classes or equivalent at some point
- coldtea 8y agoIf the classes are shallow that's fine. The problem is using classes to model the whole behavior, not using them to hold some values together.
- krapht 8y agoIt looks like a bunch of flat, 2d tables like you might find in a spreadsheet or database.
- geocar 8y ago> What does the non-OOP solution for this problem look like? function hit(player, weapon, monster) { This is a major problem with single dispatch, a popular OOP implementation choice, but not the only one. Common Lisp and C++ have multiple dispatch, which is a generally accepted solution to this problem. It seems like a lot of the challenges you're facing has to do with object-oriented design not being a good fit for your problem. Some problems really do organise well into independent actors, maybe the one you're doing isn't one of them?
- vnorilo 8y agoI don't think C++ has a dynamic multiple dispatch mechanism. You can craft one with the visitor pattern or some precompiling or macro sheanigans. I'd love to hear about the best way of doing it.
- geocar 8y agoNo. Not dynamic. The best way is to use Common Lisp. I don't know if there's consensus for other, lesser solutions. Stroustrup seems to think C++ just isn't good enough[1], but good ol' function overloading is good enough for the cases we're discussing. [1]: http://www.stroustrup.com/multimethods.pdf http://www.stroustrup.com/multimethods.pdf
- deleted 8y ago[deleted]
- geowwy 8y ago> when class Player hits() a class Monster, where exactly do we modify data? Monster's hp has to decrease by Player's attackPower, Player's xps increase by Monster's level if Monster got killed. Does it happen in Player.hits(Monster m) or Monster.isHitBy(Player p). What if there's a class Weapon involved? Do we pass it as an argument to isHitBy or does Player has a currentWeapon() getter? I don't see how this is a problem unless you think programming objects correspond with physical objects. My first thought is make a separate Swing class representing the player swinging their weapon. There's probably an even better way to do it but this gets around the issues mentioned above. class Player { fun takeSwing { swing = new Swing( this.currentWeapon, this.location.offset(this.direction) ) if swing.killedMonster { this.xp += swing.monster.level } } } class Swing { fun new(weapon, location) { this.weapon = weapon this.monster = findMonster(location) if this.monster != null { this.monster.takeHit(this) } this.killedMonster = this.monster != null && this.monster.dead } fun damage { return this.weapon.baseDamage + this.weapon.bonusDamage } } class Monster { fun takeHit(swing) { this.hp -= swing.damage - this.defence if this.hp <= 1 { this.die } } }
- coldtea 8y ago>I don't see how this is a problem unless you think programming objects correspond with physical objects. This is a problem because we don't just want "any old design that sorta kinda works" but a guiding principle to our design, and to find the optimal place for each action/data.
- segmondy 8y agoGood example, I think that's why people struggle with OOP, they do think that "programming objects correspond with physical objects". This is the same reason they struggle with storing states in databases because they try to map OOP to tables.