c++unreal-engine4unreal-gameplay-ability-system

How can I create a UE4 UCLASS base class that uses CreateDefaultSubobject() but allow sub-classes to change the type of the sub-object?


I am writing a re-usable RPG framework with the UE4 Gameplay Ability System. As part of the framework, I want to provide a character base class that games can override. The base class should instantiate both the Ability System Component (ASC) and Attribute Set (AS) of the character as sub-objects, preferably via the CreateDefaultSubobject(), which, as you may know, can only be invoked in the constructor. The problem is that I need the type of the ASC and AS to be something that sub-classes can override, so if they have a more specific ASC and AC that can plug it in.

Here are the things I've tried/constraints I need to satisfy:

How can I satisfy these constraints and make it possible for child-classes to provider their own types for the ASC and AS?


Solution

  • The solution here was to create a factory class that is templated, and then have the constructor take the factory in as a parameter. The factory class has methods that create the ASC and AS; the constructor invokes those methods and passes-in the character, allowing the ASC and AS to be created as part of the body of the constructor.

    Here's what the factory class looks like:

    /**
     * Type of object for instantiating the ASC and attribute set for this type of character.
     *
     * This is in a separate object to allow these types to be parameterized/templated so sub-classes can swap out the type
     * of ASC and attribute set just by supplying different types in their constructors.
     */
    template<class AscType, class AttributeSetType>
    class TMyCharacterComponentFactory
    {
    public:
        /**
         * Creates an Ability System Component (ASC) for this character.
         *
         * The ASC is automatically created as a default sub-object of the character, with the name
         * "AbilitySystemComponent".
         *
         * @param Character
         *  The character for which the ASC will be created.
         *
         * @return
         *  The new ASC.
         */
        static AscType* CreateAbilitySystemComponent(AMyCharacterBase* Character)
        {
            AscType* AbilitySystemComponent = Character->CreateDefaultSubobject<AscType>(TEXT("AbilitySystemComponent"));
    
            AbilitySystemComponent->SetIsReplicated(true);
    
            return AbilitySystemComponent;
        }
    
        /**
         * Creates an Attribute Set for this character.
         *
         * The attribute set is automatically created as a default sub-object of the character, with the name
         * "AttributeSet".
         *
         * @param Character
         *  The character for which the attribute set will be created.
         *
         * @return
         *  The new attribute set.
         */
        static AttributeSetType* CreateAttributeSet(AMyCharacterBase* Character)
        {
            return Character->CreateDefaultSubobject<AttributeSetType>(TEXT("AttributeSet"));
        }
    };
    

    With this in place, here's what the base class looks like (note that the constructor that takes-in the factory object must be defined in the header of the class to ensure that the compiler can generate a constructor with the correct template parameters at compile-time):

    #include <GameFramework/Character.h>
    #include <AbilitySystemInterface.h>
    #include <UObject/ConstructorHelpers.h>
    
    #include "Abilities/MyAbilitySystemComponent.h"
    #include "Abilities/MyAttributeSet.h"
    #include "MyCharacterBase.generated.h"
    
    // Forward declaration; this is defined at the end of the file.
    template<class AscType, class AttributeSetType>
    class TMyCharacterComponentFactory;
    
    /**
     * Base class for both playable and non-playable characters.
     */
    UCLASS()
    class MY_API AMyCharacterBase : public ACharacter, public IAbilitySystemInterface
    {
        GENERATED_BODY()
    
    public:
        /**
         * Default constructor for AMyCharacterBase.
         */
        explicit AMyCharacterBase();
    
    protected:
        /**
         * The Ability System Component (ASC) used for interfacing this character with the Gameplay Abilities System (GAS).
         */
        UPROPERTY()
        UMyAbilitySystemComponent* AbilitySystemComponent;
    
        /**
         * The attributes of this character.
         */
        UPROPERTY()
        UMyAttributeSet* AttributeSet;
    
        /**
         * Constructor for sub-classes to specify the type of the ASC and Attribute Set.
         *
         * @param ComponentFactory
         *   The factory component to use for generating the ASC and Attribute Set.
         */
        template<class AscType, class AttributeSetType>
        AMyCharacterBase(TMyCharacterComponentFactory<AscType, AttributeSetType> ComponentFactory)
        {
            this->AbilitySystemComponent = ComponentFactory.CreateAbilitySystemComponent(this);
            this->AttributeSet           = ComponentFactory.CreateAttributeSet(this);
        }
    };
    
    // In the CPP file
    AMyCharacterBase::AMyCharacterBase() :
        AMyCharacterBase(TMyCharacterComponentFactory<UMyAbilitySystemComponent, UMyAttributeSet>())
    {
    }
    
    

    Here's what a base class that extends this and provides its own type for the ASC and AS looks like:

    #include "MyCharacterBase.h"
    #include "Abilities/OtherAbilitySystemComponent.h"
    #include "Abilities/OtherAttributeSet.h"
    #include "AnOtherCharacterBase.generated.h"
    
    UCLASS()
    class OTHER_API AnOtherCharacterBase : public AMyCharacterBase
    {
        GENERATED_BODY()
    
    public:
        // Constructor and overrides
        AnOtherCharacterBase();
    }
    
    
    // In the CPP file
    AnOtherCharacterBase::AnOtherCharacterBase() :
        AMyCharacterBase(TMyCharacterComponentFactory<OtherAbilitySystemComponent, OtherAttributeSet>())
    {
    }