As a Unity refugee, I’m used to a very aggressively component-oriented hierarchy: every time I want to add functionality to an object, I put it in a separate class, in a separate C# script, and drag that onto whoever gets the functionality. I’m wondering in UE4 C++, though, how should I start thinking about organizing each object’s capacities? For instance, every single humanoid character in the game will share three fundamentally different skills: Movement, Combat, and Data (HP, MP, skills, etc). The Unity-inspired approach I want to do naturally is to write a CharacterMovement.cpp, CharacterCombat.cpp, CharacterData.cpp, drag them all onto my actor, and either directly couple them or teach them how to get references to each other for when I need to share data.
Is there a better way to organize this? It occurred to me that, inheritance, for instance, might be a cleaner way to keep everything integrated: extend the engine’s character movement component, add my combat and data methods as components of the subclass, and end up with a single universal HumanoidCharacter component that, when dragged onto any Actor, would give it every capability characters in my game need to function.
So you’re saying keeping things broken up into separate components based on functionality is still the preferable way to organize things? That’s a relief if true, it means I may not have to completely reorient my design tendencies as I learn C++
And by refugee, I just mean that I’m one of the many developers who recently ditched Unity when UE4 ditched their subscription fee.
Components are still pretty much the way to go. Unreal isn’t quite fully on board with components yet (better in blueprint but the underlying C++ code isn’t quite there).
CharacterMovement component exists. So you’d want the two other components.
The main benefit of components is exactly as you stated. You can keep functionality small and in narrowly defined scopes and then simply let them work together via composition. Its (IMHO of course) the correct way to create games, because it more closely resembles the way we iterate on games (i.e minor changes to objects in narrow scope to get new objects), buuuut, of course there are people who grew up with inheritance-based architecture and it takes a while for them to understand the difference and fully buy into it.
This is more of a general programming question than a UE4-specific one, but if components are the way to go, how do you avoid excessive dependency? Thinking about the combat, movement, data triad we’ve been using as the example, Combat needs data from movement to determine who it’s facing, and it needs to send data back to movement in the form of a request to rotate towards the enemy Combat has decided to target, and to toggle some bool bIsAttacking on and off so Movement can check if it’s in an attack animation and not respond to movement inputs if it is. Both components also need access to Data, Combat so it can figure out how much damage to do, and Movement so it can see how fast it should be running and how high it should be jumping (based on skills, statistics, and so forth). In cases like this, isn’t an inheritance hierarchy preferable to three components that need to effectively be directly coupled to do their job?
No, because what you actually have are three narrowly defined sets of functionality (i.e. move something, figure out how to attack something and remember data about something). What you might have, is a “move something” that can happily handle walking around. But now what if you want something that occasionally can use a jetpack? Now in the component case, we simply derive from the movement component and specialize the parts that need to deal with jetpacking, in the meantime the other components are blissfully unaware anything has changed.
In the inheritance case, you’d derive from some base class and extend it, but then you’d have a problem if you then had a third use case, lets say swimming. So now what do you do? Do you have a swimming thing that inherits from the mover, but now that means it cant jetpack, or do you have a swimmer that inherits from a jetpacker? It gets very messy and almost impossible to understand where the functionality is going to be handled. Is it currently in the jetpacker, the mover, or the swimmer?
The main difference, is that by doing it by composition, you don’t tie yourself into these choices at compile time (i.e. the compiler doesn’t lock you into it). Rather, you make the choice at object-creation time and it becomes more of a design choice. Obviously the downside is that there’s slightly more glue code to make sure components can communicate effectively. But I’d take that hit over the issue of debugging deep inheritance hierarchies every time.
The thing that convinced me about all this, was back in the day, we were making worms and we’d gotten up to about 120 weapons, with a rather nuts inheritance hierarchy. It was so crazy that one wrong call and your “poke” would let off a nuke (well, ok, not quite, but it had plenty of bugs like that). I felt like there was a better way and componentization was the answer. Units of code that have narrowly defined scope and functionality, composited together at runtime.
Sorry for the essay. Just trying to say why its important.
I appreciate the essay! I’ve been learning programming the wrong way that I think many devs do: learn how to make the game do something, and retrospectively learn why the way I’m actually doing it is a terrible, horrible thing-I really appreciate the clarifications!
For communicating between components, is there any specific reason not to use events? I’m really, super-uncomfortable using setters to make narrowly scoped modules talk to each other, and it seems like it would be potentially less buggy to do something like making each attack in the Combat module get its enemy, then fire off some event RotateMe(float) that Movement was listening for, and either notify animation with an event or just directly set a flag to queue up whatever attack animation Combat wants. I know this is going to be a performance hit compared to doing everything through getters/setters, but since there’s a lot of complex interaction that could possibly occur (what happens when the player tries to jump, dodge, punch, and crouch at the same time?), I’m trying to streamline everything into a single state machine that Movement can curate and manage priorities in.
Also yikes, Worms organized weapons in an inheritance hierarchy? I had always assumed there was some generic Weapons method that had animations, behavior, and everything else, and every single item was an isolated subclass of weapons.