Design paradigm: Composition vs Inheritance vs Interface(?) in Unreal with example. Need help

I know this thread will not lead to any concrete solution because there is more than one way to skin a cat. The reason I posted this thread is because finding decent information on Composition in Unreal is like finding a Unicorn. It doesn’t exist. It is very popular in Unity and Unreal has many of the same tools for Composition as Unity post-4.7. So I’m curious why there is not more information about blueprintable components for Unreal.

Let’s take the classic example of things in a game being Interactable.

Inheritance



class AUsableActor : public AActor { // some virtual functions and properties to Interact() with Actor. }

class ABaseItem : public AUsableActor { // override Interact() to Pick up item perhaps }

class ABaseDoor : public AUsableActor { // override Interact() to Open door }


Interfaces



class AUsableItem : public AActor, public IInteractable { // implement Interact from IInteractable to pick up item }

class AUsableDoor : public AActor, public IInteractable { // implement Interact from IInteractable to open door }


Finally… UE4 Composition



class UInteractableComponent : public UActorComponent {}
class UInteractableItemComponent : public UInteractableComponent {}
class UInteractableDoorComponent : public UInteractableComponent {}

// Equipable Item component to make this more interesting
class UEquipableItemComponent : public UActorComponent {}

// Item Storage (like a backpack) allowing item to store other items
class UItemStorageComponent : public UActorComponent {}


// Base Item class
class ABaseItem : public AActor
{
     UInteractableItemComponent* ItemComp; // Allows and handles item pickup...puts item in character inventory
     UEquipableItemComponent* EquipComp; // Allows and handles item equip through inventory
     UItemStorageComponent* StorageComp; // Allows item to hold other items...can be placed on World item boxes as well to be looted.
     UOutlineOnFocusComponent* FocusComp; // Allows character focus handler component to outline on mouse over 

    //Adding these components would make something like an equippable backpack. Each component should function independently and without care of the others or the item it is connected to.
}



You get the idea…I’m sure there are advantages and disadvantages to all 3. I’m really not sure what the ideal approach is. I really like component based design but these require more upfront engineering it seems. Discuss…

2 Likes

This is my preferred approach when coming to extending game objects.

Actor Child-Classes
Used for discrete types of objects. For example, ‘Powerup’, ‘Pawn’, ‘Vehicle’, ‘Character’. Stores common behaviour for that types and child types. Exposes a lot of blueprint default properties for customizing appearance and basic properties.

Components
Used for appending properties, variables or identical behaviour to actors of varying types. Examples:

A ‘Health’ variable that is modified by the owning actor. ‘Health’ is a universal variable and could be shared across multiple actors.
Storing an array of ‘Inventory’ such as weapons or ‘abilities’, and providing functionality to start and stop using them. Both Characters and Vehicles might want to carry weapons!

Interfaces
Used for when actors of different types need to perform some given behaviour, but that behaviour can be customized per-object that uses the interface. For example:

*A ‘Killable object’ interface, which provides the following functions:

  • HasBeenKilled()
  • Kill()

Actors and Components of different types may want to be ‘killed’. Each object can decide whether it has been killed with different parameters, and can be killed in different ways. (E.g, character might ragdoll, vehicle might explode).*


Personally, for ‘Useable’ items I like interfaces. Why? Because each actor can define what ‘Using’ it really means.

Is it a pickup? Add it to inventory.
Is it an inventory item? Use the item.
Is it a weapon? Fire the weapon.
Is it a door? Open the Door.

Does the ‘usable’ items need to have an interface for the player? Create a UUseableUI component that allows you to change the UI Icon, UI Text etc.

You can also create whatever callbacks functions you need, and force objects using those interfaces to implement that behaviour.

4 Likes

I’m trying to build an “Escape from Tarkov” style inventory to really investigate what I can do with a heavy component centered design while learning UMG system. An inventory like this works in this way I imagine:

(for a backpack)
-Character has an InventoryComponent where the EquipSlots reside. Items are not directly stored here.
-ItemActor has a PickupComponent which is a child of an InteractComponent. This allows you to look for an InteractComponent to determine if something can be Interacted with…ie DoorActor/ItemActor/PlayerActor/ContainerActor all child of AActor…
* PickupComponent wll allow this item to go into an Inventory. Holds all properties related to Inventory; item size, weight, if it’s stackable, etc.*
-ItemActor has an EquipComponent which will show “Equip” action through context menus in the UI and tell InventoryComponent where it should be equipped.
-ItemActor has an ItemStorageComponent which will hold “contents” or references to the ItemActors in inventory.

ItemActor in this case is a very bland class housing only a Name property. You can make many different types of Items by combining different custom UActorComponents.

What I do not like in game systems design, is things like Epic’s implementation of ACharacter and UCharacterMovementComponent. The Character and CharacterMovementComponent are completely coupled and dependent on each other. You cannot use CharacterMovementComponent on anything other than derived classes of ACharacter. Character even has basic “DoCrouch” style logic while the Component itself has the actual Crouch implementation. In my dream world, the MovementComonent would move or crouch the Object without caring what it is. The Object shouldn’t even need to know about the Component. Maybe even receive player input events instead of Input being performed on the Character itself cutting out the need to call events ON CharacterMovement from Character…

An example of this would be “WeaponHandler” component attached to your Character which holds input event for “Reload”. Essentially, a character without a WeaponHandler does not know how to Reload or Shoot a gun.

1 Like

Yeah this point in particular is quite a common one. Unfortunately due to the complex nature of networked movement, CMC and the Character are very heavily intertwined. A few folks including myself have made some considerable effort in trying to break the component away from the character and try to use it as a more generic component, which would be much better. Unfortunately it would break a lot of backward compatibility.

I’ve been doing Networked Movement comp’s for years now so I think I’ve learned a few tricks - when I actually have a solution I’m happy with I’ll gladly share.

2 Likes

Well I successfully decoupled non-movement related input from the Character. Attaching a “CharacterInteractionComponent” to anything should attempt to bind Input action. Building this Component driven system has been surprisingly easy actually. With a 4 month old you can imagine that I don’t have much time. I managed to build a repository of components this weekend with BPs that handle:

Item storage
Inventory Items
Character Inventory
Interactions
Equipable Actors
Focusing actors with Post process outliner

I now need to convert these to C++ and build a UI to really test some of the other functionality.

I definitely prefer Composition over Inheritance after doing this exercise. I didn’t actually need any interfaces because the character is not interacting with the Actors directly. Only thing I do not like are all of the “GetComponentByClass” calls and checking if they are Valid before doing anything.

GetComonentByClass(UInventoryItemComponent::StaticClass()) to check if the Actor is an inventory item.
GetComonentByClass(UEquipableItemComponent::StaticClass()) to check if the Actor can be equpped and where to equip it

You get the drift…