5 ms·
Then how would you model composing behaviors in Go. Say for example I have a representation of Food and a representation of Animal. I then want an ability that
by blacktriangle 5y ago
Then how would you model composing behaviors in Go. Say for example I have a representation of Food and a representation of Animal. I then want an ability that will "animate" things, so I can animate Food to give it the behaviors of Animal.
I'm not being critical either, I'm seriously curious how somebody would implement this behavior in Go. Like you say, just struct with methods is a very appealing mental model since you have fewer moving parts, but how does it deal with a scenario like this which is very common in game dev?
- dilap 5y agoGo has interfaces, but not inheritence. I'm slightly confused about what you're trying to describe, but maybe it's something like this? https://play.golang.org/p/smPyWvBvWWp https://play.golang.org/p/smPyWvBvWWp We can make both Food and Animal statisfy the "Animateable" interface. Go also has a syntactic shortcut which is similar to inheritence, but more explicit. If you include a field in a struct without any name, it is considered "embedded", and you can call methods on the field directly, without referencing its name, which ends up looking the same, at the call site, as inheritence. https://play.golang.org/p/cIMqQqE0eVc https://play.golang.org/p/cIMqQqE0eVc But everything is still explicitly laid out in the struct -- it's just a simple struct, no inheritence shenanigans. I think interheritence is a mistake, but interfaces are the bees-knees. Even in C# (which is my main language these days, 'cuz that's what you use if you're making a game in unity), I just use interfaces, not implementation inheritence. (And even interfaces I don't use much -- but when you need it, you need it!)
- blacktriangle 5y agoThanks very much for the reply. Here's more of a clarifying example: The idea I was going with Animateable was not animation (poor wording on my part), but effictively giving the properties of a creature to a thing, ie magically animating it. So I have an ability in-game that lets me turn anything into a creature, thus merging the Creature interface with whatever other interface this has. From what I'm getting from you, that means every single thing in the game must implement the Animateable interface. Now lets do another one, I want a Metalic property. Say these are things that when hit by lightning will give off lightning damage. Then there's a curse called Midas that will let me turn anything metallic. So I have a Wooden Hammer object which I midas curse mixing in metalic, then a wizard animates it so that it has the properties of a creature, and I have a creature that when I hit with lightning gives off sparks that I can also use to hammer nails. Point being, in this world you're basically requiring every single thing to implement every single interface which sounds like a nightmare, where with more of an ECS style system, you just merge in the new behavior and call it a day.
- dilap 5y agoSo caveat, I've never actually used an ECS system, but I've read about the pattern, and I would say it definitely seems better suited to the kind of situation you're describing than interfaces (or inheritence). (I would say interfaces/inheritence are useful for simple high level abstraction of basic concepts with multiple implementations (here's an interface for a compression algorithm, or a RNG, or whatever), not so useful for trying to model complex game-world relationships. And to me, the useful part is the interface, whereas inheritence mixes in two unrelated concepts: implemementation-sharing and interface.) But you don't need either of those for ECS, right? If I understand it correctly, ECS at its core is basically an optimization of the struct-of-arrays datastructure. E.g., something like this is a super-basic ECS: struct World { Comp1[] Comp1 bool[] HasComp1 Comp2[] Comp2 bool[] HasComp2 ... int NumEntities // all arrays have this length } I'm a big fan in general of struct-of-arrays vs collection-disparate-structs-with-pointers. Besides being way faster, it tends to make code clearer, too. ECS seems like a more efficient and convenient evolution of that pattern. At least from what I've read. No real experience. Take with 13 grains of salt. :-) P.S. If you feel like it, I'd be happy to explore some concrete code (you first!). It's always very interesting to me to try to explore all possible ways to express some concept in code.
- throwaway316943 5y agoI am not a Go programmer but I would assume that each behaviour of Animal would be a struct with one or more methods. Those behaviours are added to the agent to make it an animal. Composition. If you want your Food item to walk like a duck and quack like a duck you just add Walk and Quack to it. Presumably some other system would take care of identifying those behaviours and calling them as needed.
- dgb23 5y agohttps://en.m.wikipedia.org/wiki/Structural_type_system https://en.m.wikipedia.org/wiki/Structural_type_system By structural typing. It’s a form of satisfying an interface by composition.
- mgsouth 5y agoGo uses duck typing. "Interfaces," such as Food or Animal, declare sets of functions that must be implemented in order to qualify as an object of that type. So a "Food" thing might need to implement "beInjested()" and "spoil()", and Animal "run()" and "die()". If, for type "Chicken", there are "beInjested(Chicken)", "spoil(Chicken)", "run(Chicken)", and "die(Chicken)", then a Chicken can be used anywhere a Food or Animal is wanted. edit: You cannot, however, explicitly declare that a "Chicken" is supposed to be a Food and/or an Animal. And you can't directly inherit implementations; to make Chicken directly use a generic function you need to do something like implement "baseSpoil(Food)", and then have the body of "spoil(Chicken)" explicitly call "baseSpoil(self)". As mentioned in another reply, there is a shortcut. You can declare that the Chicken structure includes (anonymous) Food and Animal fields. Then, if you call "spoil(myChicken)", Go will automatically replace it with "spoil(myChicken.Food)".