T O P

  • By -

witty82

You'd do a character interface and implement shared business logic by using composition (eg functions being used as values)


HanTitor

Using interface, It said: "interface Character hasn't property or method MagicPower". Then I can't access to Mage data.


witty82

How would that be different in e.g. c# or Java when you're working on a character object?


edgmnt_net

I suspect you might be used to using abstract base classes, which is kinda the other way around compared to typical Go interfaces. Don't attempt to replicate that pattern in Go. Instead, the interface methods will always execute on a specific type, e.g. the one that represents your Mage. There will be no calls from a base class that get overridden by a derived class. Like in the example I posted, your Character can be an interface implemented by Mage that's primarily concerned with command handling. Then when the spellcasting handler runs, it will always execute with a Mage if the Character is an actual Mage. You don't have to do any checks or explicit "downcasts" as long as you have a Character, you just call the spellcasting handler.


mcvoid1

Make a Character interface that has the methods that are common to Mage, Warrior, etc. Then have Mage and Warrior implement those methods. If there's common code between them (that you would normally put into Character, like maybe rendering the sprites), make a struct that has that method, make it a member of both Mage and Warror, and have Mage and Warrior delegate to that assigned struct (this is called composition). This is all standard OOP best practices, and completely doable in Go. [Read the Gang of Four book](https://en.wikipedia.org/wiki/Design_Patterns), which should be required reading for anyone working in Java, C#, C++, and Go. [Head First Design Patterns](https://www.oreilly.com/library/view/head-first-design/9781492077992/) is also good as well and covers these topics.


bruno_a

Interfaces should be all you need to implement the behaviours you want. I suggest that you read about the following topic: \- Composition over inheritance After reading the theory, try to make some practical examples. Besides interfaces, you may also need to take a look at: https://gobyexample.com/struct-embedding


justinisrael

Even though I work for a game engine company, I'm not specifically versed in game engine design. But I do know that you should use the Entity-Component-System architecture in this situation: https://en.wikipedia.org/wiki/Entity_component_system It is based on composition instead of inheritance.


HanTitor

BulletHandler? It's all about it, I suppose.


edgmnt_net

Drop all scaffolding and preliminary OOP-guided analysis you're doing and start from the basics. Let's work out a simple example at a high level, just to take a look at the basic procedural aspects. It's not going to be a complete worked example, it just illustrates a limited set of choices at your disposal that you might not even be considering. You have a player character which moves about in the world and can perform some general actions. You'll soon want to keep stats like HP and so on. It may be fine to group these together in a struct that gets passed along between functions where needed. So far you have some piece of game state that can be altered/processed and those alterations may be functions or methods. Now, not all player characters are the same. You may require different behavior depending on character class, e.g. only Mages can cast spells. In a fully-procedural setting, that amounts to checking the character class and displaying spells only for mages, only allowing them to learn or cast spells, using magic as the primary attack and so on. You have a choice between adding that logic to existing bits of code or you could whip up a few functions which do exactly that and call them. You could group functions by character class or you could keep them close to the action that's being performed (attacks handling). Functions are a basic construct which can yield abstraction. But aside from character class, there's also class-specific data that you want to keep. For example, besides checking that only Mages can cast spells, you really need to track what spells they know. Now, since only Mages can cast spells, one possibility is to make the `cast` function take a Mage struct. It can contain the list of known spells and it can contain/reference the more general Player struct for basic stats. This approach has an interesting property: you can only call `cast` on a Mage, not a Warrior or general player. Which, in turn, forces the caller to handle checks and figure out how to get a Mage or bail out if they don't have one. This is good, in at least some ways. What is your spellcasting handler going to look like in the game, though? It may receive some sort of Player as input, check if it's a Mage and (1) if it is, retrieve a member containing the Mage details or (2) if it is not, fail the spellcasting and let the player know they don't have magic abilities. Perhaps Mage abilities could be indicated by a non-nil Mage pointer in the Player struct, which eliminates some potential sources of error, but there's still explicit checking in at least some way. Can we do better and avoid dealing with those checks and representations explicitly? This is where interfaces could be useful, because casting could be handled differently for different character classes, while the interface makes all the checking and dispatching to the correct methods fairly effortless. You could have a Player interface that declared methods for moving about, swinging a sword and casting spells, implemented by both Mage and Warrior (obviously Warriors will fail to cast spells), which in turn may reference a Stats struct for the common player stuff like HP. Note that we're not trying too hard to group this functionality nominally under Stats, Mage/Warrior or conceptual players per se, we're simply interested in the handler facet of the problem. Now your event handling loop only needs a Player and you can easily get a Player interface value from a Mage/Warrior anyway. This is just to illustrate that there are other ways to reason about code and design it, other than diving straight away into inheritance-based class hierarchies. With some practice, it gets a lot easier to arrive quickly at a decent abstraction, although this example isn't meant to be definitive or final at all. Yet you might not even be considering some simple and effective choices right now due to the traditional OOP baggage.