Skip to content

Attachable Components

What You'll Learn

  • Creating attachable components for equipment items
  • Implementing attachment interface contracts
  • Managing attachment state and relationships
  • Using tag-based compatibility systems
  • Handling network replication for attachables
  • Integrating with container slot systems

Quick Start

// Add attachable component to equipment actor
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Attachable")
class UMounteaAttachableComponent* AttachableComponent;

// Attach to compatible container
bool Success = AttachableComponent->Execute_AttachToContainer(AttachableComponent, TargetContainer);

// Check attachment state
bool IsAttached = AttachableComponent->Execute_GetState(AttachableComponent) == EAttachmentState::EAS_Attached;

Result

Equipment items that can intelligently attach to compatible slots with automatic state management and network synchronization.

Component Architecture

Core Structure

UMounteaAttachableComponent implements the attachable interface:

class UMounteaAttachableComponent : public UActorComponent, public IMounteaAdvancedAttachmentAttachableInterface
{
    // Identity
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Mountea|Attachable")
    FName Id;                                    // Unique identifier

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Mountea|Attachable")
    FText DisplayName;                           // UI display name

    // Compatibility
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Mountea|Attachable")
    FGameplayTagContainer Tags;                  // Compatibility tags

    // State
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Mountea|Attachable")
    EAttachmentState State;                      // Current attachment state

    // Relationships
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Mountea")
    TScriptInterface<IMounteaAdvancedAttachmentContainerInterface> AttachedTo;  // Current container
};

Attachment States

enum class EAttachmentState : uint8
{
    EAS_Detached,    // Not attached to any container
    EAS_Attached     // Currently attached to a container
};

Component Setup

Basic Configuration

// Equipment actor with attachable component
AEquipmentItem::AEquipmentItem()
{
    // Create attachable component
    AttachableComponent = CreateDefaultSubobject<UMounteaAttachableComponent>(TEXT("AttachableComponent"));

    // Configure identity
    AttachableComponent->Id = "UniqueItemId";
    AttachableComponent->DisplayName = NSLOCTEXT("Equipment", "SwordName", "Iron Sword");

    // Set compatibility tags
    FGameplayTagContainer ItemTags;
    ItemTags.AddTag(FGameplayTag::RequestGameplayTag("Equipment.Weapon.Melee.Sword"));
    AttachableComponent->Tags = ItemTags;

    // Initial state
    AttachableComponent->State = EAttachmentState::EAS_Detached;
}

Interface Implementation

The component implements all required interface methods:

// Identity accessors
virtual FName GetId_Implementation() const override { return Id; }
virtual void SetId_Implementation(const FName& NewId) override;

virtual FText GetDisplayName_Implementation() const override { return DisplayName; }
virtual void SetDisplayName_Implementation(const FText& NewDisplayName) override;

// Tag management
virtual FGameplayTagContainer GetTags_Implementation() const override { return Tags; }
virtual void SetTags_Implementation(const FGameplayTagContainer& NewTags) override;

// State management
virtual EAttachmentState GetState_Implementation() const override { return State; }
virtual void SetState_Implementation(const EAttachmentState NewState) override;

// Attachment operations
virtual bool AttachToSlot_Implementation(const TScriptInterface<IMounteaAdvancedAttachmentContainerInterface>& Target, const FName& SlotId) override;
virtual bool AttachToContainer_Implementation(const TScriptInterface<IMounteaAdvancedAttachmentContainerInterface>& Target) override;
virtual bool Detach_Implementation() override;

Property Management

Identity Properties

void UMounteaAttachableComponent::SetId_Implementation(const FName& NewId)
{
    if (Id != NewId)
        Id = NewId;
}

void UMounteaAttachableComponent::SetDisplayName_Implementation(const FText& NewDisplayName)
{
    if (!NewDisplayName.EqualTo(DisplayName))
        DisplayName = NewDisplayName;
}

Tag System

void UMounteaAttachableComponent::SetTags_Implementation(const FGameplayTagContainer& NewTags)
{
    Tags.Reset();
    Tags.AppendTags(NewTags);
}

bool UMounteaAttachableComponent::HasTag_Implementation(const FGameplayTag& Tag) const
{
    return Tags.HasTag(Tag);
}

bool UMounteaAttachableComponent::MatchesTags_Implementation(const FGameplayTagContainer& OtherTags, const bool bRequireAll) const
{
    return bRequireAll ? Tags.HasAll(OtherTags) : Tags.HasAny(OtherTags);
}

State Management

void UMounteaAttachableComponent::SetState_Implementation(const EAttachmentState NewState)
{
    if (NewState != State)
        State = NewState;
}

bool UMounteaAttachableComponent::IsValidAttachable_Implementation() const
{
    return !DisplayName.IsEmpty() && !Id.IsNone();
}

bool UMounteaAttachableComponent::CanAttach_Implementation() const
{
    return IsValidAttachable() && State != EAttachmentState::EAS_Attached;
}

Attachment Operations

Specific Slot Attachment

bool UMounteaAttachableComponent::AttachToSlot_Implementation(
    const TScriptInterface<IMounteaAdvancedAttachmentContainerInterface>& Target, 
    const FName& SlotId)
{
    if (!Execute_CanAttach(this) || !IsValid(Target.GetObject()))
        return false;

    // Attempt attachment through container
    if (!Target->Execute_TryAttach(Target.GetObject(), SlotId, this))
        return false;

    // Update state on success
    AttachedTo = Target;
    Execute_SetState(this, EAttachmentState::EAS_Attached);
    return true;
}

Smart Container Attachment

bool UMounteaAttachableComponent::AttachToContainer_Implementation(
    const TScriptInterface<IMounteaAdvancedAttachmentContainerInterface>& Target)
{
    if (!CanAttach() || !Target.GetObject())
        return false;

    // Find compatible slot automatically
    const FName foundSlotId = Target->Execute_FindFirstFreeSlotWithTags(Target.GetObject(), Tags);
    if (foundSlotId.IsNone())
        return false;

    return Execute_AttachToSlot(this, Target, foundSlotId);
}

Detachment

bool UMounteaAttachableComponent::Detach_Implementation()
{
    if (State != EAttachmentState::EAS_Attached || !AttachedTo.GetObject())
        return false;

    // Find current slot
    const FName slotId = AttachedTo->Execute_GetSlotIdForAttachable(AttachedTo.GetObject(), this);
    if (!slotId.IsNone())
        AttachedTo->Execute_TryDetach(AttachedTo.GetObject(), slotId);

    // Clear relationship
    AttachedTo = nullptr;
    State = EAttachmentState::EAS_Detached;
    return true;
}

Tag Compatibility

Tag Hierarchies

// Weapon compatibility example
Tags = "Equipment.Weapon.Melee.Sword";

// Compatible with slots tagged:
// "Equipment"                    // Any equipment
// "Equipment.Weapon"             // Any weapon  
// "Equipment.Weapon.Melee"       // Any melee weapon
// "Equipment.Weapon.Melee.Sword" // Swords specifically

// Not compatible with:
// "Equipment.Weapon.Ranged"      // Different weapon type
// "Equipment.Armor"              // Different equipment type

Multi-Tag Compatibility

// Multi-compatible item
FGameplayTagContainer MultiTags;
MultiTags.AddTag(FGameplayTag::RequestGameplayTag("Equipment.Weapon.Melee"));
MultiTags.AddTag(FGameplayTag::RequestGameplayTag("Size.Medium"));
MultiTags.AddTag(FGameplayTag::RequestGameplayTag("Material.Steel"));
AttachableComponent->SetTags_Implementation(MultiTags);

// Matches slots requiring:
// - Any melee weapon
// - Medium-sized items
// - Steel equipment
// - Combinations of the above

Conditional Compatibility

bool CheckAdvancedCompatibility(const TScriptInterface<IMounteaAdvancedAttachmentContainerInterface>& Target, const FName& SlotId)
{
    auto Slot = Target->Execute_GetSlot(Target.GetObject(), SlotId);
    if (!Slot)
        return false;

    // Basic tag matching
    if (!Slot->MatchesTags(Tags, false))
        return false;

    // Advanced compatibility checks
    if (HasTag(FGameplayTag::RequestGameplayTag("Weapon.TwoHanded")))
    {
        // Two-handed weapons need both main and off-hand slots free
        auto OffHandSlot = Target->Execute_GetSlot(Target.GetObject(), "OffHand");
        return OffHandSlot && OffHandSlot->IsEmpty();
    }

    return true;
}

Integration Patterns

Inventory Integration

// Create attachable from inventory item
UMounteaAttachableComponent* CreateAttachableFromItem(const FInventoryItem& Item)
{
    if (!Item.IsItemValid())
        return nullptr;

    // Spawn equipment actor
    UClass* ActorClass = Item.GetTemplate()->SpawnActor.LoadSynchronous();
    if (!ActorClass)
        return nullptr;

    AActor* EquipmentActor = GetWorld()->SpawnActor<AActor>(ActorClass);
    if (!EquipmentActor)
        return nullptr;

    // Configure attachable component
    auto AttachableComp = EquipmentActor->FindComponentByClass<UMounteaAttachableComponent>();
    if (AttachableComp)
    {
        AttachableComp->SetId_Implementation(Item.GetGuid().ToString()); 
        AttachableComp->SetDisplayName_Implementation(Item.GetItemName());
        AttachableComp->SetTags_Implementation(Item.GetTemplate()->Tags);
    }

    return AttachableComp;
}

Equipment Action Integration

// Equipment action using attachable
bool ProcessEquipAction(const FInventoryItem& Item, UMounteaEquipmentComponent* Equipment)
{
    // Create attachable from item
    auto AttachableComp = CreateAttachableFromItem(Item);
    if (!AttachableComp)
        return false;

    // Attempt smart attachment
    bool Success = AttachableComp->Execute_AttachToContainer(AttachableComp, Equipment);

    if (!Success)
    {
        // Clean up on failure
        AttachableComp->GetOwner()->Destroy();
        return false;
    }

    return true;
}

Advanced Features

Custom Attachment Logic

class UAdvancedAttachableComponent : public UMounteaAttachableComponent
{
protected:
    virtual bool AttachToContainer_Implementation(const TScriptInterface<IMounteaAdvancedAttachmentContainerInterface>& Target) override
    {
        // Custom pre-attachment logic
        if (!PerformPreAttachmentChecks(Target))
            return false;

        // Standard attachment
        bool Success = Super::AttachToContainer_Implementation(Target);

        if (Success)
        {
            // Custom post-attachment logic
            PerformPostAttachmentSetup(Target);
        }

        return Success;
    }

private:
    bool PerformPreAttachmentChecks(const TScriptInterface<IMounteaAdvancedAttachmentContainerInterface>& Target)
    {
        // Example: Check character level requirements
        if (auto Character = Cast<ACharacter>(Target->Execute_GetOwningActor(Target.GetObject())))
        {
            if (auto LevelComp = Character->FindComponentByClass<ULevelComponent>())
            {
                return LevelComp->GetLevel() >= GetRequiredLevel();
            }
        }
        return true;
    }
};

Dynamic Tag Management

void UpdateDynamicTags()
{
    FGameplayTagContainer NewTags = Tags;

    // Add condition-based tags
    if (IsEnchanted())
        NewTags.AddTag(FGameplayTag::RequestGameplayTag("State.Enchanted"));

    if (IsDamaged())
        NewTags.AddTag(FGameplayTag::RequestGameplayTag("State.Damaged"));

    if (IsUpgraded())
        NewTags.AddTag(FGameplayTag::RequestGameplayTag("State.Upgraded"));

    SetTags_Implementation(NewTags);
}

Attachment Callbacks

// Override state changes for custom behavior
virtual void SetState_Implementation(const EAttachmentState NewState) override
{
    EAttachmentState OldState = State;
    Super::SetState_Implementation(NewState);

    // React to state changes
    if (OldState != NewState)
    {
        OnAttachmentStateChanged(OldState, NewState);
    }
}

void OnAttachmentStateChanged(EAttachmentState OldState, EAttachmentState NewState)
{
    if (NewState == EAttachmentState::EAS_Attached)
    {
        // Attached to container
        ApplyAttachmentEffects();
        OnAttached.Broadcast();
    }
    else if (NewState == EAttachmentState::EAS_Detached)
    {
        // Detached from container
        RemoveAttachmentEffects();
        OnDetached.Broadcast();
    }
}

Performance Optimization

Efficient Tag Operations

// Cache frequently used tags
class UOptimizedAttachableComponent : public UMounteaAttachableComponent
{
private:
    mutable TMap<FGameplayTag, bool> TagCache;

public:
    virtual bool HasTag_Implementation(const FGameplayTag& Tag) const override
    {
        if (bool* CachedResult = TagCache.Find(Tag))
            return *CachedResult;

        bool Result = Super::HasTag_Implementation(Tag);
        TagCache.Add(Tag, Result);
        return Result;
    }

    virtual void SetTags_Implementation(const FGameplayTagContainer& NewTags) override
    {
        Super::SetTags_Implementation(NewTags);
        TagCache.Empty(); // Invalidate cache
    }
};

Memory Management

// Clean up attachable references
void CleanupAttachable()
{
    // Detach if attached
    if (State == EAttachmentState::EAS_Attached)
    {
        Execute_Detach(this);
    }

    // Clear references
    AttachedTo = nullptr;

    // Reset state
    State = EAttachmentState::EAS_Detached;
}

Common Patterns

Pattern 1: Equipment Sets

// Track equipment set membership
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Equipment Set")
FString EquipmentSetName;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Equipment Set")
int32 SetPieceIndex;

void CheckSetBonus()
{
    if (State == EAttachmentState::EAS_Attached && !EquipmentSetName.IsEmpty())
    {
        // Find other set pieces in same container
        auto Container = AttachedTo.GetObject();
        int32 SetPiecesEquipped = CountEquippedSetPieces(Container, EquipmentSetName);

        // Apply set bonuses based on piece count
        ApplySetBonus(EquipmentSetName, SetPiecesEquipped);
    }
}

Pattern 2: Conditional Attachment

// Override CanAttach for special requirements
virtual bool CanAttach_Implementation() const override
{
    // Base validation
    if (!Super::CanAttach_Implementation())
        return false;

    // Level requirement
    if (RequiredLevel > 0)
    {
        // Check if player meets level requirement
        return CheckPlayerLevel(RequiredLevel);
    }

    // Class restriction
    if (!AllowedClasses.IsEmpty())
    {
        return CheckPlayerClass(AllowedClasses);
    }

    return true;
}

Pattern 3: Attachment Notifications

// Event delegates for attachment changes
UPROPERTY(BlueprintAssignable, Category="Attachable Events")
FOnAttachableStateChanged OnAttached;

UPROPERTY(BlueprintAssignable, Category="Attachable Events")
FOnAttachableStateChanged OnDetached;

// Broadcast events on state changes
virtual void SetState_Implementation(const EAttachmentState NewState) override
{
    EAttachmentState OldState = State;
    Super::SetState_Implementation(NewState);

    if (OldState != NewState)
    {
        if (NewState == EAttachmentState::EAS_Attached)
            OnAttached.Broadcast(this);
        else if (NewState == EAttachmentState::EAS_Detached)
            OnDetached.Broadcast(this);
    }
}

Troubleshooting

Attachment Failures

  • Verify tag compatibility between attachable and slots
  • Check if target container is valid
  • Ensure CanAttach returns true

State Desync

  • Validate state changes follow proper flow
  • Check container relationship consistency
  • Verify interface implementation completeness

Tag Matching Problems

  • Review tag hierarchy design
  • Check for typos in tag names
  • Validate tag registration

Memory Leaks

  • Clean up container references properly
  • Detach before destroying attachables
  • Clear cached data appropriately

Best Practices

Design Guidelines

  • Unique IDs: Ensure attachable IDs are unique within context
  • Meaningful Tags: Use descriptive, hierarchical tag structures
  • State Consistency: Always maintain state-relationship consistency
  • Interface Compliance: Implement all required interface methods
  • Performance: Cache expensive operations and validate optimization

Next Steps