Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [1.2.1] - 2025-11-26

### Updated

- `UModularPawnOrchestratorComponent` transitions to the state `ModularGameplayTag_InitState_GameplayReady` when the components of both the pawn and the player controller are in the state `ModularGameplayTag_InitState_DataInitialized`
- `UModularPawnOrchestratorComponent` sends the event `NAME_PawnOrchestratorReady` when it transitions to the state `ModularGameplayTag_InitState_GameplayReady`

## [1.2.0] - 2025-11-20

Expand Down
2 changes: 1 addition & 1 deletion ModularGameplayActors.uplugin
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.2.0",
"VersionName": "1.2.1",
"FriendlyName": "Modular Gameplay Actors",
"Description": "Base classes designed to be used with the Modular Gameplay plugin.",
"Category": "Gameplay",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Unreal Engine](https://img.shields.io/badge/Unreal%20Engine-5.3%2B-blue.svg)](https://unrealengine.com/)
[![Version](https://img.shields.io/badge/version-1.2.0-green.svg)](https://github.com/yourusername/yourplugin/releases)
[![Version](https://img.shields.io/badge/version-1.2.1-green.svg)](https://github.com/yourusername/yourplugin/releases)

## Overview

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "ModularGameplayActorsFunctionLibrary.h"

#include "Components/GameFrameworkComponentManager.h"
#include "Components/GameFrameworkInitStateInterface.h"

void UModularGameplayActorsFunctionLibrary::CheckDefaultInitializationForImplementersOnActor(AActor* Actor, FName ExcludingFeature /*= NAME_None*/)
{
if (UGameFrameworkComponentManager* Manager = UGameFrameworkComponentManager::GetForActor(Actor))
{
TArray<UObject*> Implementers;
Manager->GetAllFeatureImplementers(Implementers, Actor, FGameplayTag(), ExcludingFeature);

for (UObject* Implementer : Implementers)
{
if (IGameFrameworkInitStateInterface* ImplementerInterface = Cast<IGameFrameworkInitStateInterface>(Implementer))
{
ImplementerInterface->CheckDefaultInitialization();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,111 +1,118 @@
#include "ModularPawnOrchestratorComponent.h"

#include "ModularGameplayTags.h"
#include "Components/GameFrameworkComponentManager.h"
#include "GameFramework/Controller.h"
#include "ModularGameplayTags.h"
#include "Net/UnrealNetwork.h"

const FName UModularPawnOrchestratorComponent::NAME_ActorFeatureName( "ModularPawnOrchestratorComponent" );
const FName UModularPawnOrchestratorComponent::NAME_ActorFeatureName("ModularPawnOrchestratorComponent");
const FName UModularPawnOrchestratorComponent::NAME_PawnOrchestratorReady("PawnOrchestratorReady");

UModularPawnOrchestratorComponent::UModularPawnOrchestratorComponent( const FObjectInitializer & ObjectInitializer ) :
Super( ObjectInitializer )
UModularPawnOrchestratorComponent::UModularPawnOrchestratorComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryComponentTick.bStartWithTickEnabled = false;
PrimaryComponentTick.bCanEverTick = false;
PrimaryComponentTick.bStartWithTickEnabled = false;
PrimaryComponentTick.bCanEverTick = false;

SetIsReplicatedByDefault( true );
SetIsReplicatedByDefault(true);
}

void UModularPawnOrchestratorComponent::OnPawnReadyToInitialize_UnRegister( const FSimpleMulticastDelegate::FDelegate & Delegate )
void UModularPawnOrchestratorComponent::OnPawnReadyToInitialize_UnRegister(const FSimpleMulticastDelegate::FDelegate& Delegate)
{
OnPawnReadyToInitialize.Remove( Delegate.GetHandle() );
OnPawnReadyToInitialize.Remove(Delegate.GetHandle());
}

FName UModularPawnOrchestratorComponent::GetFeatureName() const
{
return NAME_ActorFeatureName;
return NAME_ActorFeatureName;
}

bool UModularPawnOrchestratorComponent::CanChangeInitState( UGameFrameworkComponentManager * Manager, FGameplayTag CurrentState, FGameplayTag DesiredState ) const
bool UModularPawnOrchestratorComponent::CanChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState) const
{
check( Manager != nullptr );

auto * Pawn = GetPawn< APawn >();

if (!CurrentState.IsValid() && DesiredState == ModularGameplayTag_InitState_Spawned)
{
return Pawn != nullptr;
}
if (CurrentState == ModularGameplayTag_InitState_Spawned && DesiredState == ModularGameplayTag_InitState_DataAvailable)
{
const auto bHasAuthority = Pawn->HasAuthority();
const auto bIsLocallyControlled = Pawn->IsLocallyControlled();

if (bHasAuthority || bIsLocallyControlled)
{
// Check for being possessed by a controller.
if (GetController< AController >() == nullptr)
{
return false;
}
}

return true;
}
if (CurrentState == ModularGameplayTag_InitState_DataAvailable && DesiredState == ModularGameplayTag_InitState_DataInitialized)
{
// Transition to initialize if all features have their data available
return Manager->HaveAllFeaturesReachedInitState( Pawn, ModularGameplayTag_InitState_DataAvailable );
}
if (CurrentState == ModularGameplayTag_InitState_DataInitialized && DesiredState == ModularGameplayTag_InitState_GameplayReady)
{
return true;
}

return false;
check(Manager != nullptr);

auto* Pawn = GetPawn<APawn>();

if (!CurrentState.IsValid() && DesiredState == ModularGameplayTag_InitState_Spawned)
{
return Pawn != nullptr;
}
if (CurrentState == ModularGameplayTag_InitState_Spawned && DesiredState == ModularGameplayTag_InitState_DataAvailable)
{
const auto bHasAuthority = Pawn->HasAuthority();
const auto bIsLocallyControlled = Pawn->IsLocallyControlled();

if (bHasAuthority || bIsLocallyControlled)
{
// Check for being possessed by a controller.
if (GetController<AController>() == nullptr)
{
return false;
}
}

return true;
}
if (CurrentState == ModularGameplayTag_InitState_DataAvailable && DesiredState == ModularGameplayTag_InitState_DataInitialized)
{
return Manager->HaveAllFeaturesReachedInitState(Pawn, ModularGameplayTag_InitState_DataAvailable);
}
if (CurrentState == ModularGameplayTag_InitState_DataInitialized && DesiredState == ModularGameplayTag_InitState_GameplayReady)
{
// Check on both the pawn and its player controller because we want everything to be initialized, including inputs (which are handlded by the player controller0
return Manager->HaveAllFeaturesReachedInitState(Pawn, ModularGameplayTag_InitState_DataInitialized) &&
Manager->HaveAllFeaturesReachedInitState(GetController<AController>(), ModularGameplayTag_InitState_DataInitialized);
}

return false;
}

void UModularPawnOrchestratorComponent::HandleChangeInitState( UGameFrameworkComponentManager * Manager, FGameplayTag CurrentState, FGameplayTag DesiredState )
void UModularPawnOrchestratorComponent::HandleChangeInitState(UGameFrameworkComponentManager* Manager, FGameplayTag CurrentState, FGameplayTag DesiredState)
{
// This is currently all handled by other components listening to this state change
if (CurrentState == ModularGameplayTag_InitState_DataInitialized && DesiredState == ModularGameplayTag_InitState_GameplayReady)
{
// Send the information that the pawn (and its controller) are fully initialized.
// This is required for example by the ModularPawnGameplayMode system which needs not only the pawn, but also the ability system and inputs to be fully initialized
UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(GetPawn<APawn>(), NAME_PawnOrchestratorReady);
}
}

void UModularPawnOrchestratorComponent::OnActorInitStateChanged( const FActorInitStateChangedParams & Params )
void UModularPawnOrchestratorComponent::OnActorInitStateChanged(const FActorInitStateChangedParams& Params)
{
// If another feature is now in DataAvailable, see if we should transition to DataInitialized
if (Params.FeatureName != NAME_ActorFeatureName)
{
if (Params.FeatureState == ModularGameplayTag_InitState_DataAvailable)
{
CheckDefaultInitialization();
}
}
// If another feature is now in DataAvailable, see if we should transition to DataInitialized
if (Params.FeatureName != NAME_ActorFeatureName)
{
if (Params.FeatureState == ModularGameplayTag_InitState_DataAvailable)
{
CheckDefaultInitialization();
}
}
}

UModularPawnOrchestratorComponent * UModularPawnOrchestratorComponent::FindPawnOrchestratorComponent( const AActor * Actor )
UModularPawnOrchestratorComponent* UModularPawnOrchestratorComponent::FindPawnOrchestratorComponent(const AActor* Actor)
{
return Actor
? Actor->FindComponentByClass< UModularPawnOrchestratorComponent >()
: nullptr;
return Actor
? Actor->FindComponentByClass<UModularPawnOrchestratorComponent>()
: nullptr;
}

void UModularPawnOrchestratorComponent::OnRegister()
{
Super::OnRegister();
Super::OnRegister();

const auto * Pawn = GetPawn< APawn >();
if (!ensureAlwaysMsgf( ( Pawn != nullptr ), TEXT( "ModularPawnOrchestratorComponent on [%s] can only be added to Pawn actors." ), *GetNameSafe( GetOwner() ) ))
{
return;
}
const auto* Pawn = GetPawn<APawn>();
if (!ensureAlwaysMsgf((Pawn != nullptr), TEXT("ModularPawnOrchestratorComponent on [%s] can only be added to Pawn actors."), *GetNameSafe(GetOwner())))
{
return;
}

TArray< UActorComponent * > PawnExtensionComponents;
Pawn->GetComponents( StaticClass(), PawnExtensionComponents );
ensureAlwaysMsgf( ( PawnExtensionComponents.Num() == 1 ), TEXT( "Only one ModularPawnOrchestratorComponent should exist on [%s]." ), *GetNameSafe( GetOwner() ) );
TArray<UActorComponent*> PawnExtensionComponents;
Pawn->GetComponents(StaticClass(), PawnExtensionComponents);
ensureAlwaysMsgf((PawnExtensionComponents.Num() == 1), TEXT("Only one ModularPawnOrchestratorComponent should exist on [%s]."), *GetNameSafe(GetOwner()));
}

void UModularPawnOrchestratorComponent::BindToRequiredOnActorInitStateChanged()
{
// Listen for changes to all features
BindOnActorInitStateChanged( NAME_None, FGameplayTag(), false );
// Listen for changes to all features
BindOnActorInitStateChanged(NAME_None, FGameplayTag(), false);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"

#include "ModularGameplayActorsFunctionLibrary.generated.h"

/**
* Helper functions
*/
UCLASS()
class MODULARGAMEPLAYACTORS_API UModularGameplayActorsFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()

public:
/** This will call CheckDefaultInitialization on all other feature implementers using this interface on a specific actor, useful to update the state of any dependencies */
static void CheckDefaultInitializationForImplementersOnActor(AActor* Actor, FName ExcludingFeature = NAME_None);
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class MODULARGAMEPLAYACTORS_API UModularPawnOrchestratorComponent : public UModu

/** The name of this overall feature, this one depends on the other named component features */
static const FName NAME_ActorFeatureName;
static const FName NAME_PawnOrchestratorReady;

protected:
void OnRegister() override;
Expand Down
Loading