第一次提交
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class GenericGameSystem : ModuleRules
|
||||
{
|
||||
public GenericGameSystem(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new[]
|
||||
{
|
||||
"Core", "SmartObjectsModule"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"ModularGameplay",
|
||||
"NetCore",
|
||||
"PhysicsCore",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"GameplayTags",
|
||||
"UMG",
|
||||
"TargetingSystem",
|
||||
"GameplayTasks",
|
||||
"GameplayAbilities",
|
||||
"GameplayBehaviorsModule",
|
||||
"SmartObjectsModule",
|
||||
"GameplayBehaviorSmartObjectsModule"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GGS_LogChannels.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GameplayBehavior.h"
|
||||
#include "GameplayTask.h"
|
||||
#include "Abilities/GameplayAbility.h"
|
||||
#include "GameplayTask.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogGGS)
|
||||
|
||||
FString GetGGSLogContextString(const UObject* ContextObject)
|
||||
{
|
||||
ENetRole Role = ROLE_None;
|
||||
FString RoleName = TEXT("None");
|
||||
FString Name = "None";
|
||||
|
||||
if (const AActor* Actor = Cast<AActor>(ContextObject))
|
||||
{
|
||||
Role = Actor->GetLocalRole();
|
||||
Name = Actor->GetName();
|
||||
}
|
||||
else if (const UActorComponent* Component = Cast<UActorComponent>(ContextObject))
|
||||
{
|
||||
Role = Component->GetOwnerRole();
|
||||
Name = Component->GetOwner()->GetName();
|
||||
}
|
||||
else if (const UGameplayBehavior* Behavior = Cast<UGameplayBehavior>(ContextObject))
|
||||
{
|
||||
Role = Behavior->GetAvatar()->GetLocalRole();
|
||||
Name = Behavior->GetAvatar()->GetName();
|
||||
}
|
||||
else if (const UGameplayTask* Task = Cast<UGameplayTask>(ContextObject))
|
||||
{
|
||||
Role = Task->GetAvatarActor()->GetLocalRole();
|
||||
Name = Task->GetAvatarActor()->GetName();
|
||||
}
|
||||
else if (const UGameplayAbility* Ability = Cast<UGameplayAbility>(ContextObject))
|
||||
{
|
||||
Role = Ability->GetAvatarActorFromActorInfo()->GetLocalRole();
|
||||
Name = Ability->GetAvatarActorFromActorInfo()->GetName();
|
||||
}
|
||||
else if (IsValid(ContextObject))
|
||||
{
|
||||
Name = ContextObject->GetName();
|
||||
}
|
||||
|
||||
if (Role != ROLE_None)
|
||||
{
|
||||
RoleName = (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
|
||||
}
|
||||
return FString::Printf(TEXT("[%s] (%s)"), *RoleName, *Name);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
#include "GenericGameSystem.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FGenericGameSystemModule"
|
||||
|
||||
void FGenericGameSystemModule::StartupModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FGenericGameSystemModule::ShutdownModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FGenericGameSystemModule, GenericGameSystem)
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/Abilities/GGS_GameplayAbility_Interaction.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GGS_LogChannels.h"
|
||||
#include "SmartObjectBlueprintFunctionLibrary.h"
|
||||
#include "Interaction/GGS_InteractionDefinition.h"
|
||||
#include "Interaction/GGS_InteractionSystemComponent.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
|
||||
UGGS_GameplayAbility_Interaction::UGGS_GameplayAbility_Interaction()
|
||||
{
|
||||
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
|
||||
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateYes;
|
||||
}
|
||||
|
||||
void UGGS_GameplayAbility_Interaction::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
const FGameplayEventData* TriggerEventData)
|
||||
{
|
||||
InteractionSystem = UGGS_InteractionSystemComponent::GetInteractionSystemComponent(ActorInfo->AvatarActor.Get());
|
||||
if (InteractionSystem == nullptr)
|
||||
{
|
||||
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
|
||||
return;
|
||||
}
|
||||
InteractionSystem->OnInteractableActorChangedEvent.AddDynamic(this, &ThisClass::OnInteractActorChanged);
|
||||
|
||||
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
|
||||
}
|
||||
|
||||
void UGGS_GameplayAbility_Interaction::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
bool bReplicateEndAbility, bool bWasCancelled)
|
||||
{
|
||||
if (UGGS_InteractionSystemComponent* UserComponent = UGGS_InteractionSystemComponent::GetInteractionSystemComponent(ActorInfo->AvatarActor.Get()))
|
||||
{
|
||||
UserComponent->OnInteractableActorChangedEvent.RemoveDynamic(this, &ThisClass::OnInteractActorChanged);
|
||||
}
|
||||
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
|
||||
}
|
||||
|
||||
bool UGGS_GameplayAbility_Interaction::TryClaimInteraction(int32 Index, FSmartObjectClaimHandle& ClaimedHandle)
|
||||
{
|
||||
USmartObjectSubsystem* Subsystem = USmartObjectSubsystem::GetCurrent(GetWorld());
|
||||
|
||||
check(Subsystem!=nullptr)
|
||||
const TArray<FGGS_InteractionOption>& InteractionInstances = InteractionSystem->GetInteractionOptions();
|
||||
if (!InteractionInstances.IsValidIndex(Index))
|
||||
{
|
||||
GGS_CLOG(Error, "Interaction at index(%d) not exist!!", Index)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (InteractionInstances[Index].Definition == nullptr)
|
||||
{
|
||||
GGS_CLOG(Error, "Interaction at index(%d) has invalid definition!", Index)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (InteractionInstances[Index].SlotState != ESmartObjectSlotState::Free)
|
||||
{
|
||||
GGS_CLOG(Error, "Interaction(%s) was Claimed/Occupied!", *InteractionInstances[Index].Definition->Text.ToString())
|
||||
return false;
|
||||
}
|
||||
|
||||
const FGGS_InteractionOption& CurrentOption = InteractionInstances[Index];
|
||||
|
||||
FSmartObjectClaimHandle NewlyClaimedHandle = USmartObjectBlueprintFunctionLibrary::MarkSmartObjectSlotAsClaimed(GetWorld(), CurrentOption.RequestResult.SlotHandle, GetAvatarActorFromActorInfo());
|
||||
|
||||
// A valid claimed handle can point to an object that is no longer part of the simulation
|
||||
if (!Subsystem->IsClaimedSmartObjectValid(NewlyClaimedHandle))
|
||||
{
|
||||
GGS_CLOG(Error, "Interaction(%s) refers to an object that is no longer available.!", *InteractionInstances[Index].Definition->Text.ToString())
|
||||
return false;
|
||||
}
|
||||
|
||||
ClaimedHandle = NewlyClaimedHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void UGGS_GameplayAbility_Interaction::OnInteractActorChanged_Implementation(AActor* OldActor, AActor* NewActor)
|
||||
{
|
||||
}
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
EDataValidationResult UGGS_GameplayAbility_Interaction::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
if (ReplicationPolicy != EGameplayAbilityReplicationPolicy::ReplicateYes)
|
||||
{
|
||||
Context.AddError(FText::FromString(TEXT("Core Interaction ability must be Replicated to allow client->server communications via RPC.")));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
if (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalOnly || NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::ServerOnly)
|
||||
{
|
||||
Context.AddError(FText::FromString(TEXT("Core Interaction ability must not be Local/Server only.")));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
if (!AbilityTriggers.IsEmpty())
|
||||
{
|
||||
Context.AddError(FText::FromString(TEXT("Core Interaction ability doesn't allow event triggering!")));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
if (InstancingPolicy != EGameplayAbilityInstancingPolicy::InstancedPerActor)
|
||||
{
|
||||
Context.AddError(FText::FromString(TEXT("Core Interaction ability's instancing policy must be InstancedPerActor")));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/Behaviors/GGS_GameplayBehaviorConfig_InteractionWithAbility.h"
|
||||
#include "Interaction/Behaviors/GGS_GameplayBehavior_InteractionWithAbility.h"
|
||||
|
||||
UGGS_GameplayBehaviorConfig_InteractionWithAbility::UGGS_GameplayBehaviorConfig_InteractionWithAbility()
|
||||
{
|
||||
BehaviorClass = UGGS_GameplayBehavior_InteractionWithAbility::StaticClass();
|
||||
}
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
EDataValidationResult UGGS_GameplayBehaviorConfig_InteractionWithAbility::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
if (BehaviorClass == nullptr || !BehaviorClass->GetClass()->IsChildOf(UGGS_GameplayBehavior_InteractionWithAbility::StaticClass()))
|
||||
{
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/Behaviors/GGS_GameplayBehavior_InteractionWithAbility.h"
|
||||
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "AbilitySystemGlobals.h"
|
||||
#include "GGS_LogChannels.h"
|
||||
#include "Abilities/GameplayAbility.h"
|
||||
#include "Interaction/Behaviors/GGS_GameplayBehaviorConfig_InteractionWithAbility.h"
|
||||
#include "Interaction/GGS_InteractionSystemComponent.h"
|
||||
|
||||
bool UGGS_GameplayBehavior_InteractionWithAbility::Trigger(AActor& InAvatar, const UGameplayBehaviorConfig* Config, AActor* SmartObjectOwner)
|
||||
{
|
||||
bTransientIsTriggering = true;
|
||||
bTransientIsActive = false;
|
||||
TransientAvatar = &InAvatar;
|
||||
TransientSmartObjectOwner = SmartObjectOwner;
|
||||
|
||||
UGGS_InteractionSystemComponent* InteractionSystem = InAvatar.FindComponentByClass<UGGS_InteractionSystemComponent>();
|
||||
|
||||
if (!InteractionSystem)
|
||||
{
|
||||
GGS_CLOG(Error, "Missing InteractionSystem Component!")
|
||||
return false;
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* Asc = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(&InAvatar);
|
||||
if (!Asc)
|
||||
{
|
||||
GGS_CLOG(Error, "Missing Ability System Component!")
|
||||
return false;
|
||||
}
|
||||
|
||||
TSubclassOf<UGameplayAbility> AbilityClass{nullptr};
|
||||
int32 AbilityLevel = 0;
|
||||
if (!CheckValidAbilitySetting(Config, AbilityClass, AbilityLevel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FGameplayAbilitySpec* Handle = Asc->FindAbilitySpecFromClass(AbilityClass))
|
||||
{
|
||||
GGS_CLOG(Error, "Try granting repeated interaction ability of class:%s, which is not supported!", *AbilityClass->GetName())
|
||||
return false;
|
||||
}
|
||||
|
||||
GrantedAbilityClass = AbilityClass;
|
||||
|
||||
AbilityEndedDelegateHandle = Asc->OnAbilityEnded.AddUObject(this, &ThisClass::OnAbilityEndedCallback);
|
||||
|
||||
//Ability trigger by event when activation polciy=ServerInitied won't work.
|
||||
AbilitySpecHandle = Asc->K2_GiveAbilityAndActivateOnce(AbilityClass, AbilityLevel);
|
||||
|
||||
if (!AbilitySpecHandle.IsValid())
|
||||
{
|
||||
GGS_CLOG(Error, "Can't active ability of class:%s! Check ability settings!", *AbilityClass->GetName())
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special case: behavior already interrupted
|
||||
if (bBehaviorWasInterrupted && AbilitySpecHandle.IsValid() && !bAbilityEnded)
|
||||
{
|
||||
Asc->ClearAbility(AbilitySpecHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AbilitySpecHandle.IsValid() && bAbilityEnded)
|
||||
{
|
||||
GGS_CLOG(Verbose, "Instantly executed interaction ability:%s,handle%s", *AbilityClass->GetName(), *AbilitySpecHandle.ToString())
|
||||
EndBehavior(InAvatar, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
GGS_CLOG(Verbose, "Granted and activate interaction ability:%s,handle%s", *AbilityClass->GetName(), *AbilitySpecHandle.ToString())
|
||||
|
||||
//TODO what happens when avatar or target get destoryied?
|
||||
// SOOwner销毁的时候, 需要Abort当前行为, 目的是清除赋予的Ability
|
||||
// SmartObjectOwner->OnDestroyed.AddDynamic(this, &ThisClass::OnSmartObjectOwnerDestroyed);
|
||||
GGS_CLOG(Verbose, "Interaction begins with ability:%s", *AbilityClass->GetName())
|
||||
|
||||
bTransientIsTriggering = false;
|
||||
bTransientIsActive = true;
|
||||
return bTransientIsActive;
|
||||
}
|
||||
|
||||
void UGGS_GameplayBehavior_InteractionWithAbility::EndBehavior(AActor& Avatar, const bool bInterrupted)
|
||||
{
|
||||
GGS_CLOG(Verbose, "Interaction behavior ended %s", bInterrupted?TEXT("due to interruption!"):TEXT("normally!"))
|
||||
|
||||
// clear ability stuff.
|
||||
if (UAbilitySystemComponent* Asc = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(&Avatar))
|
||||
{
|
||||
if (AbilityEndedDelegateHandle.IsValid())
|
||||
{
|
||||
Asc->OnAbilityEnded.Remove(AbilityEndedDelegateHandle);
|
||||
AbilityEndedDelegateHandle.Reset();
|
||||
}
|
||||
|
||||
// Special case: behavior interrupting active ability, so cancel ability.
|
||||
if (bInterrupted && bTransientIsActive && !bAbilityEnded && AbilitySpecHandle.IsValid())
|
||||
{
|
||||
if (const FGameplayAbilitySpec* Spec = Asc->FindAbilitySpecFromHandle(AbilitySpecHandle))
|
||||
{
|
||||
GGS_CLOG(Verbose, "Cancel ability(%s) because behavior was interrupted.", *Spec->Ability.GetClass()->GetName())
|
||||
Asc->CancelAbilityHandle(AbilitySpecHandle);
|
||||
}
|
||||
}
|
||||
|
||||
if (bInterrupted && !bTransientIsActive && AbilitySpecHandle.IsValid())
|
||||
{
|
||||
Asc->ClearAbility(AbilitySpecHandle);
|
||||
}
|
||||
}
|
||||
|
||||
Super::EndBehavior(Avatar, bInterrupted);
|
||||
|
||||
bBehaviorWasInterrupted = bInterrupted;
|
||||
}
|
||||
|
||||
bool UGGS_GameplayBehavior_InteractionWithAbility::CheckValidAbilitySetting(const UGameplayBehaviorConfig* Config, TSubclassOf<UGameplayAbility>& OutAbilityClass, int32& OutAbilityLevel)
|
||||
{
|
||||
// Ability class validation.
|
||||
const UGGS_GameplayBehaviorConfig_InteractionWithAbility* InteractionConfig = Cast<const UGGS_GameplayBehaviorConfig_InteractionWithAbility>(Config);
|
||||
if (!InteractionConfig)
|
||||
{
|
||||
GGS_CLOG(Error, "Invalid GameplayBehaviorConfig! expect Config be type of %s.", *UGGS_GameplayBehaviorConfig_InteractionWithAbility::StaticClass()->GetName())
|
||||
return false;
|
||||
}
|
||||
|
||||
const TSubclassOf<UGameplayAbility> AbilityClass = InteractionConfig->AbilityToGrant.LoadSynchronous();
|
||||
if (!AbilityClass)
|
||||
{
|
||||
GGS_CLOG(Error, "Invalid AbilityToGrant Class!")
|
||||
return false;
|
||||
}
|
||||
OutAbilityClass = AbilityClass;
|
||||
OutAbilityLevel = InteractionConfig->AbilityLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGGS_GameplayBehavior_InteractionWithAbility::OnAbilityEndedCallback(const FAbilityEndedData& EndedData)
|
||||
{
|
||||
if (bAbilityEnded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// check for ability granted by this behavior.
|
||||
if (EndedData.AbilitySpecHandle == AbilitySpecHandle || EndedData.AbilityThatEnded->GetClass() == GrantedAbilityClass)
|
||||
{
|
||||
bAbilityEnded = true;
|
||||
bAbilityWasCancelled = EndedData.bWasCancelled;
|
||||
|
||||
// Special case: behavior already active and abilities ended, ending behavior normally.
|
||||
if (!bTransientIsTriggering && bTransientIsActive)
|
||||
{
|
||||
GGS_CLOG(Verbose, "Interaction ability(%s) %s.", *EndedData.AbilityThatEnded.GetClass()->GetName(),
|
||||
EndedData.bWasCancelled?TEXT("was canceled"):TEXT("ended normally"))
|
||||
EndBehavior(*GetAvatar(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/GGS_InteractableInterface.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IGGS_InteractableInterface functions that are not pure virtual.
|
||||
|
||||
FText IGGS_InteractableInterface::GetInteractionDisplayNameText_Implementation() const
|
||||
{
|
||||
if (UObject* Object = _getUObject())
|
||||
{
|
||||
return FText::FromString(GetNameSafe(Object));
|
||||
}
|
||||
return FText::GetEmpty();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/GGS_InteractionDefinition.h"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/GGS_InteractionStructLibrary.h"
|
||||
#include "Interaction/GGS_InteractionDefinition.h"
|
||||
|
||||
FString FGGS_InteractionOption::ToString() const
|
||||
{
|
||||
return FString::Format(TEXT("{0} {1} {2}"), {
|
||||
Definition ? Definition->Text.ToString() : TEXT("Null Definition"), SlotState == ESmartObjectSlotState::Free ? TEXT("Valid") : TEXT("Invalid"), SlotIndex
|
||||
});
|
||||
}
|
||||
|
||||
bool operator==(const FGGS_InteractionOption& Lhs, const FGGS_InteractionOption& RHS)
|
||||
{
|
||||
return Lhs.Definition == RHS.Definition
|
||||
&& Lhs.RequestResult == RHS.RequestResult
|
||||
&& Lhs.SlotIndex == RHS.SlotIndex
|
||||
&& Lhs.SlotState == RHS.SlotState;
|
||||
}
|
||||
|
||||
bool operator!=(const FGGS_InteractionOption& Lhs, const FGGS_InteractionOption& RHS)
|
||||
{
|
||||
return !(Lhs == RHS);
|
||||
}
|
||||
|
||||
bool operator<(const FGGS_InteractionOption& Lhs, const FGGS_InteractionOption& RHS)
|
||||
{
|
||||
return Lhs.SlotIndex < RHS.SlotIndex;
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/GGS_InteractionSystemComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameplayBehaviorSmartObjectBehaviorDefinition.h"
|
||||
#include "GGS_LogChannels.h"
|
||||
#include "SmartObjectComponent.h"
|
||||
#include "SmartObjectSubsystem.h"
|
||||
#include "Interaction/GGS_InteractableInterface.h"
|
||||
#include "Interaction/GGS_SmartObjectFunctionLibrary.h"
|
||||
#include "Interaction/GGS_InteractionStructLibrary.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Net/Core/PushModel/PushModel.h"
|
||||
|
||||
// Sets default values for this component's properties
|
||||
UGGS_InteractionSystemComponent::UGGS_InteractionSystemComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
SetIsReplicatedByDefault(true);
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
FDoRepLifetimeParams Params;
|
||||
Params.bIsPushBased = true;
|
||||
Params.Condition = COND_OwnerOnly;
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, InteractableActor, Params);
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, NumsOfInteractableActors, Params);
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, InteractingOption, Params);
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, InteractionOptions, Params);
|
||||
}
|
||||
|
||||
UGGS_InteractionSystemComponent* UGGS_InteractionSystemComponent::GetInteractionSystemComponent(const AActor* Actor)
|
||||
{
|
||||
return IsValid(Actor) ? Actor->FindComponentByClass<UGGS_InteractionSystemComponent>() : nullptr;
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::CycleInteractableActors_Implementation(bool bNext)
|
||||
{
|
||||
if (bInteracting || InteractableActors.Num() <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int32 Index = InteractableActor != nullptr ? InteractableActors.IndexOfByKey(InteractableActor) : 0;
|
||||
if (!InteractableActors.IsValidIndex(Index)) //一个都没
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (bNext)
|
||||
{
|
||||
Index = FMath::Clamp(Index + 1, 0, InteractableActors.Num());
|
||||
}
|
||||
else
|
||||
{
|
||||
Index = FMath::Clamp(Index - 1, 0, InteractableActors.Num());
|
||||
}
|
||||
if (InteractableActors.IsValidIndex(Index) && InteractableActors[Index] != nullptr && InteractableActors[Index] !=
|
||||
InteractableActor)
|
||||
{
|
||||
SetInteractableActor(InteractableActors[Index]);
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::SearchInteractableActors()
|
||||
{
|
||||
OnSearchInteractableActorsEvent.Broadcast();
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::SetInteractableActors(TArray<AActor*> NewActors)
|
||||
{
|
||||
if (!GetOwner()->HasAuthority())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InteractableActors = NewActors;
|
||||
SetInteractableActorsNum(InteractableActors.Num());
|
||||
OnInteractableActorsChanged();
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::SetInteractableActorsNum(int32 NewNum)
|
||||
{
|
||||
if (NewNum != NumsOfInteractableActors)
|
||||
{
|
||||
const int32 PrevNum = NumsOfInteractableActors;
|
||||
NumsOfInteractableActors = NewNum;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, NumsOfInteractableActors, this)
|
||||
OnInteractableActorsNumChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::SetInteractableActor(AActor* InActor)
|
||||
{
|
||||
if (InActor != InteractableActor)
|
||||
{
|
||||
AActor* OldActor = InteractableActor;
|
||||
InteractableActor = InActor;
|
||||
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractableActor, this)
|
||||
OnInteractableActorChanged(OldActor);
|
||||
}
|
||||
}
|
||||
|
||||
FSmartObjectRequestFilter UGGS_InteractionSystemComponent::GetSmartObjectRequestFilter_Implementation()
|
||||
{
|
||||
return DefaultRequestFilter;
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::StartInteraction(int32 NewIndex)
|
||||
{
|
||||
if (bInteracting)
|
||||
{
|
||||
GGS_CLOG(Warning, "Can't start interaction(%d) while already interacting(%d)", NewIndex, InteractingOption)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InteractionOptions.IsValidIndex(NewIndex))
|
||||
{
|
||||
GGS_CLOG(Warning, "Try start invalid interaction(%d)", NewIndex)
|
||||
return;
|
||||
}
|
||||
|
||||
int32 Prev = InteractingOption;
|
||||
InteractingOption = NewIndex;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractingOption, this);
|
||||
OnInteractingOptionChanged(Prev);
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::EndInteraction()
|
||||
{
|
||||
if (!bInteracting)
|
||||
{
|
||||
//GGS_CLOG(Warning, TEXT("no need to end interaction when there's no any active interaction."))
|
||||
return;
|
||||
}
|
||||
|
||||
int32 Prev = InteractingOption;
|
||||
InteractingOption = INDEX_NONE;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractingOption, this);
|
||||
OnInteractingOptionChanged(Prev);
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::InstantInteraction(int32 NewIndex)
|
||||
{
|
||||
if (bInteracting)
|
||||
{
|
||||
GGS_CLOG(Warning, "Can't trigger instant interaction(%d) while already interacting(%d)", NewIndex, InteractingOption)
|
||||
return;
|
||||
}
|
||||
if (!InteractionOptions.IsValidIndex(NewIndex))
|
||||
{
|
||||
GGS_CLOG(Warning, "Try trigger invalid interaction(%d)", NewIndex)
|
||||
return;
|
||||
}
|
||||
|
||||
int32 Prev = InteractingOption;
|
||||
InteractingOption = NewIndex;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractingOption, this);
|
||||
OnInteractingOptionChanged(Prev);
|
||||
|
||||
int32 Prev2 = InteractingOption;
|
||||
InteractingOption = INDEX_NONE;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractingOption, this);
|
||||
OnInteractingOptionChanged(Prev2);
|
||||
}
|
||||
|
||||
bool UGGS_InteractionSystemComponent::IsInteracting() const
|
||||
{
|
||||
return bInteracting;
|
||||
}
|
||||
|
||||
int32 UGGS_InteractionSystemComponent::GetInteractingOption() const
|
||||
{
|
||||
return InteractingOption;
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::OnInteractableActorChanged(AActor* OldActor)
|
||||
{
|
||||
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
|
||||
{
|
||||
RefreshOptionsForActor();
|
||||
}
|
||||
|
||||
if (IsValid(OldActor) && OldActor->GetClass()->ImplementsInterface(UGGS_InteractableInterface::StaticClass()))
|
||||
{
|
||||
IGGS_InteractableInterface::Execute_OnInteractionDeselected(OldActor, GetOwner());
|
||||
}
|
||||
|
||||
if (IsValid(InteractableActor) && InteractableActor->GetClass()->ImplementsInterface(UGGS_InteractableInterface::StaticClass()))
|
||||
{
|
||||
IGGS_InteractableInterface::Execute_OnInteractionSelected(InteractableActor, GetOwner());
|
||||
}
|
||||
|
||||
OnInteractableActorChangedEvent.Broadcast(OldActor, InteractableActor);
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::OnInteractableActorsNumChanged()
|
||||
{
|
||||
OnInteractableActorNumChangedEvent.Broadcast(NumsOfInteractableActors);
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::OnInteractableActorsChanged_Implementation()
|
||||
{
|
||||
if (!bInteracting)
|
||||
{
|
||||
// update potential actor.
|
||||
if (!IsValid(InteractableActor) || !InteractableActors.Contains(InteractableActor))
|
||||
{
|
||||
if (InteractableActors.IsValidIndex(0) && IsValid(InteractableActors[0]))
|
||||
{
|
||||
SetInteractableActor(InteractableActors[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetInteractableActor(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (bNewActorHasPriority)
|
||||
{
|
||||
if (IsValid(InteractableActor) && InteractableActors.IsValidIndex(0) && InteractableActors[0] != InteractableActor)
|
||||
{
|
||||
SetInteractableActor(InteractableActors[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::OnSmartObjectEventCallback(const FSmartObjectEventData& EventData)
|
||||
{
|
||||
check(InteractableActor != nullptr);
|
||||
|
||||
for (int32 i = 0; i < InteractionOptions.Num(); i++)
|
||||
{
|
||||
const FGGS_InteractionOption& Option = InteractionOptions[i];
|
||||
if (EventData.SmartObjectHandle == Option.RequestResult.SmartObjectHandle && EventData.SlotHandle == Option.RequestResult.SlotHandle)
|
||||
{
|
||||
if (EventData.Reason == ESmartObjectChangeReason::OnOccupied || EventData.Reason == ESmartObjectChangeReason::OnReleased || EventData.Reason == ESmartObjectChangeReason::OnClaimed)
|
||||
{
|
||||
RefreshOptionsForActor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::OnInteractionOptionsChanged()
|
||||
{
|
||||
for (FGGS_InteractionOption& InteractionOption : InteractionOptions)
|
||||
{
|
||||
GGS_CLOG(Verbose, "Available Options:%s", *InteractionOption.ToString())
|
||||
}
|
||||
OnInteractionOptionsChangedEvent.Broadcast();
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::OnInteractingOptionChanged_Implementation(int32 PrevOptionIndex)
|
||||
{
|
||||
bool bPrevInteracting = bInteracting;
|
||||
bInteracting = InteractingOption != INDEX_NONE;
|
||||
|
||||
if (IsValid(InteractableActor) && InteractableActor->GetClass()->ImplementsInterface(UGGS_InteractableInterface::StaticClass()))
|
||||
{
|
||||
if (!bPrevInteracting && bInteracting)
|
||||
{
|
||||
IGGS_InteractableInterface::Execute_OnInteractionStarted(InteractableActor, GetOwner(), InteractingOption);
|
||||
}
|
||||
if (bPrevInteracting && !bInteracting)
|
||||
{
|
||||
IGGS_InteractableInterface::Execute_OnInteractionEnded(InteractableActor, GetOwner(), PrevOptionIndex);
|
||||
}
|
||||
}
|
||||
|
||||
OnInteractingStateChangedEvent.Broadcast(bInteracting);
|
||||
}
|
||||
|
||||
void UGGS_InteractionSystemComponent::RefreshOptionsForActor()
|
||||
{
|
||||
USmartObjectSubsystem* Subsystem = USmartObjectSubsystem::GetCurrent(GetWorld());
|
||||
|
||||
if (!Subsystem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// getting new options for current interact actor.
|
||||
TArray<FGGS_InteractionOption> NewOptions;
|
||||
{
|
||||
TArray<FSmartObjectRequestResult> Results;
|
||||
if (IsValid(InteractableActor) && UGGS_SmartObjectFunctionLibrary::FindSmartObjectsWithInteractionEntranceInActor(GetSmartObjectRequestFilter(), InteractableActor, Results, GetOwner()))
|
||||
{
|
||||
for (int32 i = 0; i < Results.Num(); i++)
|
||||
{
|
||||
FGGS_InteractionOption Option;
|
||||
UGGS_InteractionDefinition* FoundDefinition;
|
||||
if (UGGS_SmartObjectFunctionLibrary::FindInteractionDefinitionFromSmartObjectSlot(this, Results[i].SlotHandle, FoundDefinition))
|
||||
{
|
||||
Option.Definition = FoundDefinition;
|
||||
Option.SlotState = Subsystem->GetSlotState(Results[i].SlotHandle);
|
||||
Option.RequestResult = Results[i];
|
||||
Option.SlotIndex = i;
|
||||
Option.BehaviorDefinition = Subsystem->GetBehaviorDefinitionByRequestResult(Results[i], USmartObjectBehaviorDefinition::StaticClass());
|
||||
NewOptions.Add(Option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check any options changed.
|
||||
bool bOptionsChanged = false;
|
||||
{
|
||||
if (NewOptions.Num() == InteractionOptions.Num())
|
||||
{
|
||||
NewOptions.Sort();
|
||||
|
||||
for (int OptionIndex = 0; OptionIndex < NewOptions.Num(); OptionIndex++)
|
||||
{
|
||||
const FGGS_InteractionOption& NewOption = NewOptions[OptionIndex];
|
||||
const FGGS_InteractionOption& CurrentOption = InteractionOptions[OptionIndex];
|
||||
|
||||
if (NewOption != CurrentOption)
|
||||
{
|
||||
bOptionsChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bOptionsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bOptionsChanged)
|
||||
{
|
||||
// unregister event callbacks for existing options.
|
||||
for (int32 i = 0; i < InteractionOptions.Num(); i++)
|
||||
{
|
||||
auto& Handle = InteractionOptions[i].RequestResult.SlotHandle;
|
||||
if (SlotCallbacks.Contains(Handle))
|
||||
{
|
||||
if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Handle))
|
||||
{
|
||||
OnEventDelegate->Remove(SlotCallbacks[Handle]);
|
||||
SlotCallbacks.Remove(Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (FGGS_InteractionOption& Option : InteractionOptions)
|
||||
{
|
||||
if (SlotCallbacks.Contains(Option.RequestResult.SlotHandle))
|
||||
{
|
||||
if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Option.RequestResult.SlotHandle))
|
||||
{
|
||||
OnEventDelegate->Remove(SlotCallbacks[Option.RequestResult.SlotHandle]);
|
||||
SlotCallbacks.Remove(Option.RequestResult.SlotHandle);
|
||||
}
|
||||
}
|
||||
// if (Option.DelegateHandle.IsValid())
|
||||
// {
|
||||
// if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Option.RequestResult.SlotHandle))
|
||||
// {
|
||||
// OnEventDelegate->Remove(Option.DelegateHandle);
|
||||
// Option.DelegateHandle.Reset();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
InteractionOptions = NewOptions;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractionOptions, this)
|
||||
|
||||
GGS_CLOG(Verbose, "Interaction options changed, nums of options:%d", InteractionOptions.Num())
|
||||
|
||||
// register slot event callbacks.
|
||||
// for (int32 i = 0; i < InteractionOptions.Num(); i++)
|
||||
// {
|
||||
// FGGS_InteractionOption& Option = InteractionOptions[i];
|
||||
// if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Option.RequestResult.SlotHandle))
|
||||
// {
|
||||
// Option.DelegateHandle = OnEventDelegate->AddUObject(this, &ThisClass::OnSmartObjectEventCallback);
|
||||
// }
|
||||
// }
|
||||
|
||||
for (int32 i = 0; i < InteractionOptions.Num(); i++)
|
||||
{
|
||||
auto& Handle = InteractionOptions[i].RequestResult.SlotHandle;
|
||||
if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Handle))
|
||||
{
|
||||
FDelegateHandle DelegateHandle = OnEventDelegate->AddUObject(this, &ThisClass::OnSmartObjectEventCallback);
|
||||
SlotCallbacks.Emplace(Handle, DelegateHandle);
|
||||
}
|
||||
}
|
||||
|
||||
OnInteractionOptionsChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/GGS_SmartObjectFunctionLibrary.h"
|
||||
#include "GameplayBehaviorSmartObjectBehaviorDefinition.h"
|
||||
#include "GameplayBehaviorConfig.h"
|
||||
#include "SmartObjectBlueprintFunctionLibrary.h"
|
||||
#include "SmartObjectDefinition.h"
|
||||
#include "Engine/World.h"
|
||||
#include "SmartObjectSubsystem.h"
|
||||
#include "Interaction/GGS_InteractionDefinition.h"
|
||||
|
||||
UGameplayBehaviorConfig* UGGS_SmartObjectFunctionLibrary::GetGameplayBehaviorConfig(const USmartObjectBehaviorDefinition* BehaviorDefinition)
|
||||
{
|
||||
if (const UGameplayBehaviorSmartObjectBehaviorDefinition* Definition = Cast<UGameplayBehaviorSmartObjectBehaviorDefinition>(BehaviorDefinition))
|
||||
{
|
||||
return Definition->GameplayBehaviorConfig;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGGS_SmartObjectFunctionLibrary::FindGameplayBehaviorConfig(const USmartObjectBehaviorDefinition* BehaviorDefinition, TSubclassOf<UGameplayBehaviorConfig> DesiredClass,
|
||||
UGameplayBehaviorConfig*& OutConfig)
|
||||
{
|
||||
if (UClass* RealClass = DesiredClass)
|
||||
{
|
||||
if (UGameplayBehaviorConfig* Config = GetGameplayBehaviorConfig(BehaviorDefinition))
|
||||
{
|
||||
if (Config->GetClass()->IsChildOf(RealClass))
|
||||
{
|
||||
OutConfig = Config;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGGS_SmartObjectFunctionLibrary::FindSmartObjectsWithInteractionEntranceInActor(const FSmartObjectRequestFilter& Filter, AActor* SearchActor, TArray<FSmartObjectRequestResult>& OutResults,
|
||||
const AActor* UserActor)
|
||||
{
|
||||
if (!IsValid(SearchActor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TArray<FSmartObjectRequestResult> Results;
|
||||
USmartObjectBlueprintFunctionLibrary::FindSmartObjectsInActor(Filter, SearchActor, Results, UserActor);
|
||||
if (Results.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// filter results which has definiton entry.
|
||||
for (int32 i = 0; i < Results.Num(); i++)
|
||||
{
|
||||
UGGS_InteractionDefinition* FoundDefinition;
|
||||
if (FindInteractionDefinitionFromSmartObjectSlot(SearchActor, Results[i].SlotHandle, FoundDefinition))
|
||||
{
|
||||
OutResults.Add(Results[i]);
|
||||
}
|
||||
}
|
||||
return !OutResults.IsEmpty();
|
||||
}
|
||||
|
||||
bool UGGS_SmartObjectFunctionLibrary::FindInteractionDefinitionFromSmartObjectSlot(UObject* WorldContext, FSmartObjectSlotHandle SmartObjectSlotHandle, UGGS_InteractionDefinition*& OutDefinition)
|
||||
{
|
||||
if (WorldContext && WorldContext->GetWorld() && SmartObjectSlotHandle.IsValid())
|
||||
{
|
||||
if (USmartObjectSubsystem* Subsystem = WorldContext->GetWorld()->GetSubsystem<USmartObjectSubsystem>())
|
||||
{
|
||||
Subsystem->ReadSlotData(SmartObjectSlotHandle, [ &OutDefinition](FConstSmartObjectSlotView SlotView)
|
||||
{
|
||||
if (const FGGS_SmartObjectInteractionEntranceData* Entry = SlotView.GetDefinitionDataPtr<FGGS_SmartObjectInteractionEntranceData>())
|
||||
{
|
||||
if (!Entry->DefinitionDA.IsNull())
|
||||
{
|
||||
OutDefinition = Entry->DefinitionDA.LoadSynchronous();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return OutDefinition != nullptr;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/Targeting/GGS_TargetingFilterTask_InteractionSmartObjects.h"
|
||||
#include "Interaction/GGS_InteractionSystemComponent.h"
|
||||
#include "Interaction/GGS_SmartObjectFunctionLibrary.h"
|
||||
|
||||
bool UGGS_TargetingFilterTask_InteractionSmartObjects::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (AActor* Actor = TargetData.HitResult.GetActor())
|
||||
{
|
||||
if (UGGS_InteractionSystemComponent* InteractionSys = UGGS_InteractionSystemComponent::GetInteractionSystemComponent(SourceContext->SourceActor))
|
||||
{
|
||||
TArray<FSmartObjectRequestResult> Results;
|
||||
|
||||
return !UGGS_SmartObjectFunctionLibrary::FindSmartObjectsWithInteractionEntranceInActor(InteractionSys->GetSmartObjectRequestFilter(), Actor, Results, InteractionSys->GetOwner());
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Interaction/Tasks/GGS_AbilityTask_UseSmartObjectWithGameplayBehavior.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameplayBehavior.h"
|
||||
#include "GameplayBehaviorConfig.h"
|
||||
#include "GameplayBehaviorSmartObjectBehaviorDefinition.h"
|
||||
#include "GameplayBehaviorSubsystem.h"
|
||||
#include "GGS_LogChannels.h"
|
||||
#include "SmartObjectComponent.h"
|
||||
#include "SmartObjectSubsystem.h"
|
||||
|
||||
UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||
{
|
||||
bBehaviorFinished = false;
|
||||
}
|
||||
|
||||
UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior* UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::UseSmartObjectWithGameplayBehavior(UGameplayAbility* OwningAbility,
|
||||
FSmartObjectClaimHandle ClaimHandle, ESmartObjectClaimPriority ClaimPriority)
|
||||
{
|
||||
if (OwningAbility == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior* MyTask = NewAbilityTask<UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior>(OwningAbility);
|
||||
if (MyTask == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
MyTask->SetClaimHandle(ClaimHandle);
|
||||
return MyTask;
|
||||
}
|
||||
|
||||
void UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::Activate()
|
||||
{
|
||||
Super::Activate();
|
||||
bool bSuccess = false;
|
||||
ON_SCOPE_EXIT
|
||||
{
|
||||
if (!bSuccess)
|
||||
{
|
||||
EndTask();
|
||||
}
|
||||
};
|
||||
|
||||
if (!ensureMsgf(ClaimedHandle.IsValid(), TEXT("SmartObject handle must be valid at this point.")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
APawn* Pawn = Cast<APawn>(GetAvatarActor());
|
||||
if (Pawn == nullptr)
|
||||
{
|
||||
GGS_CLOG(Error, "Pawn required to use GameplayBehavior with claim handle: %s.", *LexToString(ClaimedHandle));
|
||||
return;
|
||||
}
|
||||
USmartObjectSubsystem* SmartObjectSubsystem = USmartObjectSubsystem::GetCurrent(Pawn->GetWorld());
|
||||
if (!ensureMsgf(SmartObjectSubsystem != nullptr, TEXT("SmartObjectSubsystem must be accessible at this point.")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// A valid claimed handle can point to an object that is no longer part of the simulation
|
||||
if (!SmartObjectSubsystem->IsClaimedSmartObjectValid(ClaimedHandle))
|
||||
{
|
||||
GGS_CLOG(Log, "Claim handle: %s refers to an object that is no longer available.", *LexToString(ClaimedHandle));
|
||||
return;
|
||||
}
|
||||
|
||||
// Register a callback to be notified if the claimed slot became unavailable
|
||||
SmartObjectSubsystem->RegisterSlotInvalidationCallback(ClaimedHandle, FOnSlotInvalidated::CreateUObject(this, &UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::OnSlotInvalidated));
|
||||
|
||||
bSuccess = StartInteraction();
|
||||
}
|
||||
|
||||
bool UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::StartInteraction()
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
USmartObjectSubsystem* SmartObjectSubsystem = USmartObjectSubsystem::GetCurrent(World);
|
||||
if (!ensure(SmartObjectSubsystem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const UGameplayBehaviorSmartObjectBehaviorDefinition* SmartObjectGameplayBehaviorDefinition = SmartObjectSubsystem->MarkSlotAsOccupied<
|
||||
UGameplayBehaviorSmartObjectBehaviorDefinition>(ClaimedHandle);
|
||||
const UGameplayBehaviorConfig* GameplayBehaviorConfig = SmartObjectGameplayBehaviorDefinition != nullptr ? SmartObjectGameplayBehaviorDefinition->GameplayBehaviorConfig : nullptr;
|
||||
GameplayBehavior = GameplayBehaviorConfig != nullptr ? GameplayBehaviorConfig->GetBehavior(*World) : nullptr;
|
||||
if (GameplayBehavior == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const USmartObjectComponent* SmartObjectComponent = SmartObjectSubsystem->GetSmartObjectComponent(ClaimedHandle);
|
||||
AActor& InteractorActor = *GetAvatarActor();
|
||||
AActor* InteracteeActor = SmartObjectComponent ? SmartObjectComponent->GetOwner() : nullptr;
|
||||
const bool bBehaviorActive = UGameplayBehaviorSubsystem::TriggerBehavior(*GameplayBehavior, InteractorActor, GameplayBehaviorConfig, InteracteeActor);
|
||||
// Behavior can be successfully triggered AND ended synchronously. We are only interested to register callback when still running
|
||||
if (bBehaviorActive)
|
||||
{
|
||||
OnBehaviorFinishedNotifyHandle = GameplayBehavior->GetOnBehaviorFinishedDelegate().AddUObject(this, &UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::OnSmartObjectBehaviorFinished);
|
||||
}
|
||||
|
||||
return bBehaviorActive;
|
||||
}
|
||||
|
||||
void UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::OnSmartObjectBehaviorFinished(UGameplayBehavior& Behavior, AActor& Avatar, const bool bInterrupted)
|
||||
{
|
||||
// Adding an ensure in case the assumptions change in the future.
|
||||
ensure(GetAvatarActor() != nullptr);
|
||||
|
||||
// make sure we handle the right pawn - we can get this notify for a different
|
||||
// Avatar if the behavior sending it out is not instanced (CDO is being used to perform actions)
|
||||
if (GetAvatarActor() == &Avatar)
|
||||
{
|
||||
Behavior.GetOnBehaviorFinishedDelegate().Remove(OnBehaviorFinishedNotifyHandle);
|
||||
bBehaviorFinished = true;
|
||||
EndTask();
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::OnDestroy(bool bInOwnerFinished)
|
||||
{
|
||||
if (ClaimedHandle.IsValid())
|
||||
{
|
||||
USmartObjectSubsystem* SmartObjectSubsystem = USmartObjectSubsystem::GetCurrent(GetWorld());
|
||||
check(SmartObjectSubsystem);
|
||||
SmartObjectSubsystem->MarkSlotAsFree(ClaimedHandle);
|
||||
SmartObjectSubsystem->UnregisterSlotInvalidationCallback(ClaimedHandle);
|
||||
ClaimedHandle.Invalidate();
|
||||
}
|
||||
|
||||
if (TaskState != EGameplayTaskState::Finished)
|
||||
{
|
||||
if (GameplayBehavior != nullptr && bBehaviorFinished)
|
||||
{
|
||||
OnSucceeded.Broadcast();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnFailed.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
Super::OnDestroy(bInOwnerFinished);
|
||||
}
|
||||
|
||||
void UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior::OnSlotInvalidated(const FSmartObjectClaimHandle& ClaimHandle, const ESmartObjectSlotState State)
|
||||
{
|
||||
if (!bBehaviorFinished && GameplayBehavior != nullptr)
|
||||
{
|
||||
check(GetAvatarActor());
|
||||
GameplayBehavior->GetOnBehaviorFinishedDelegate().Remove(OnBehaviorFinishedNotifyHandle);
|
||||
GameplayBehavior->AbortBehavior(*GetAvatarActor());
|
||||
}
|
||||
EndTask();
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Ragdoll/GGS_RagdollComponent.h"
|
||||
#include "GGS_LogChannels.h"
|
||||
#include "TimerManager.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Engine/SkinnedAsset.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Net/Core/PushModel/PushModel.h"
|
||||
|
||||
|
||||
// Sets default values for this component's properties
|
||||
UGGS_RagdollComponent::UGGS_RagdollComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||
// off to improve performance if you don't need them.
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
SetIsReplicatedByDefault(true);
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
const FGGS_RagdollState& UGGS_RagdollComponent::GetRagdollState() const
|
||||
{
|
||||
return RagdollState;
|
||||
}
|
||||
|
||||
bool UGGS_RagdollComponent::IsRagdollAllowedToStart() const
|
||||
{
|
||||
if (!IsValid(MeshComponent))
|
||||
{
|
||||
GGS_CLOG(Warning, "Missing skeletal mesh component for the Ragdoll to work.")
|
||||
return false;
|
||||
}
|
||||
if (bRagdolling)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FBodyInstance* PelvisBodyInstance = MeshComponent->GetBodyInstance(PelvisBoneName);
|
||||
FBodyInstance* SpineBodyInstance = MeshComponent->GetBodyInstance(SpineBoneName);
|
||||
if (PelvisBodyInstance == nullptr || SpineBodyInstance == nullptr)
|
||||
{
|
||||
GGS_CLOG(Warning, "A physics asset with the %s and %s bones are required for the Ragdoll to work.(Also ensure mesh component has collision enabled)", *PelvisBoneName.ToString(),
|
||||
*SpineBoneName.ToString())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::SetMeshComponent_Implementation(USkeletalMeshComponent* InMeshComponent)
|
||||
{
|
||||
MeshComponent = InMeshComponent;
|
||||
}
|
||||
|
||||
bool UGGS_RagdollComponent::IsRagdolling_Implementation() const
|
||||
{
|
||||
return bRagdolling;
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::StartRagdoll()
|
||||
{
|
||||
if (GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy || !IsRagdollAllowedToStart())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
|
||||
{
|
||||
MulticastStartRagdoll();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ACharacter* Character = Cast<ACharacter>(GetOwner()))
|
||||
{
|
||||
Character->GetCharacterMovement()->FlushServerMoves();
|
||||
}
|
||||
ServerStartRagdoll();
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::ServerStartRagdoll_Implementation()
|
||||
{
|
||||
if (IsRagdollAllowedToStart())
|
||||
{
|
||||
MulticastStartRagdoll();
|
||||
GetOwner()->ForceNetUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::MulticastStartRagdoll_Implementation()
|
||||
{
|
||||
LocalStartRagdoll();
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::LocalStartRagdoll()
|
||||
{
|
||||
if (!IsRagdollAllowedToStart())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MeshComponent->bUpdateJointsFromAnimation = true; // Required for the flail animation to work properly.
|
||||
|
||||
if (!MeshComponent->IsRunningParallelEvaluation() && !MeshComponent->GetBoneSpaceTransforms().IsEmpty())
|
||||
{
|
||||
MeshComponent->UpdateRBJointMotors();
|
||||
}
|
||||
|
||||
// Stop any active montages.
|
||||
|
||||
static constexpr auto BlendOutDuration{0.2f};
|
||||
|
||||
MeshComponent->GetAnimInstance()->Montage_Stop(BlendOutDuration);
|
||||
|
||||
if (IsValid(CharacterOwner))
|
||||
{
|
||||
// Disable movement corrections and reset network smoothing.
|
||||
CharacterOwner->GetCharacterMovement()->NetworkSmoothingMode = ENetworkSmoothingMode::Disabled;
|
||||
CharacterOwner->GetCharacterMovement()->bIgnoreClientMovementErrorChecksAndCorrection = true;
|
||||
}
|
||||
|
||||
// Detach the mesh so that character transformation changes will not affect it in any way.
|
||||
|
||||
MeshComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
|
||||
|
||||
// Disable capsule collision and enable mesh physics simulation.
|
||||
|
||||
UPrimitiveComponent* RootPrimitive = Cast<UPrimitiveComponent>(GetOwner()->GetRootComponent());
|
||||
{
|
||||
RootPrimitive->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
}
|
||||
|
||||
MeshComponent->SetCollisionObjectType(ECC_PhysicsBody);
|
||||
MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
MeshComponent->SetSimulatePhysics(true);
|
||||
|
||||
// This is required for the ragdoll to behave properly when any body instance is set to simulated in a physics asset.
|
||||
// TODO Check the need for this in future engine versions.
|
||||
MeshComponent->ResetAllBodiesSimulatePhysics();
|
||||
|
||||
const auto* PelvisBody{MeshComponent->GetBodyInstance(PelvisBoneName)};
|
||||
FVector PelvisLocation;
|
||||
|
||||
FPhysicsCommand::ExecuteRead(PelvisBody->ActorHandle, [this, &PelvisLocation](const FPhysicsActorHandle& ActorHandle)
|
||||
{
|
||||
PelvisLocation = FPhysicsInterface::GetTransform_AssumesLocked(ActorHandle, true).GetLocation();
|
||||
RagdollState.Velocity = FPhysicsInterface::GetLinearVelocity_AssumesLocked(ActorHandle);
|
||||
});
|
||||
|
||||
RagdollState.PullForce = 0.0f;
|
||||
|
||||
if (bLimitInitialRagdollSpeed)
|
||||
{
|
||||
// Limit the ragdoll's speed for a few frames, because for some unclear reason,
|
||||
// it can get a much higher initial speed than the character's last speed.
|
||||
|
||||
// TODO Find a better solution or wait for a fix in future engine versions.
|
||||
|
||||
static constexpr auto MinSpeedLimit{200.0f};
|
||||
|
||||
RagdollState.SpeedLimitFrameTimeRemaining = 8;
|
||||
RagdollState.SpeedLimit = FMath::Max(MinSpeedLimit, UE_REAL_TO_FLOAT(GetOwner()->GetVelocity().Size()));
|
||||
|
||||
ConstraintRagdollSpeed();
|
||||
}
|
||||
|
||||
if (PawnOwner->GetLocalRole() >= ROLE_Authority)
|
||||
{
|
||||
SetRagdollTargetLocation(FVector::ZeroVector);
|
||||
}
|
||||
|
||||
if (PawnOwner->IsLocallyControlled() || (PawnOwner->GetLocalRole() >= ROLE_Authority && !IsValid(PawnOwner->GetController())))
|
||||
{
|
||||
SetRagdollTargetLocation(PelvisLocation);
|
||||
}
|
||||
|
||||
// Clear the character movement mode and set the locomotion action to Ragdoll.
|
||||
|
||||
if (IsValid(CharacterOwner))
|
||||
{
|
||||
CharacterOwner->GetCharacterMovement()->SetMovementMode(MOVE_None);
|
||||
}
|
||||
bRagdolling = true;
|
||||
OnRagdollStarted();
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::OnRagdollStarted_Implementation()
|
||||
{
|
||||
OnRagdollStartedEvent.Broadcast();
|
||||
}
|
||||
|
||||
bool UGGS_RagdollComponent::IsRagdollAllowedToStop() const
|
||||
{
|
||||
return bRagdolling;
|
||||
}
|
||||
|
||||
bool UGGS_RagdollComponent::StopRagdoll()
|
||||
{
|
||||
if (GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy || !IsRagdollAllowedToStop())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
|
||||
{
|
||||
MulticastStopRagdoll();
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerStopRagdoll();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::ServerStopRagdoll_Implementation()
|
||||
{
|
||||
if (IsRagdollAllowedToStop())
|
||||
{
|
||||
MulticastStopRagdoll();
|
||||
GetOwner()->ForceNetUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::MulticastStopRagdoll_Implementation()
|
||||
{
|
||||
LocalStopRagdoll();
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::LocalStopRagdoll()
|
||||
{
|
||||
if (!IsRagdollAllowedToStop())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MeshComponent->SnapshotPose(RagdollState.FinalRagdollPose);
|
||||
|
||||
const auto PelvisTransform{MeshComponent->GetSocketTransform(PelvisBoneName)};
|
||||
const auto PelvisRotation{PelvisTransform.Rotator()};
|
||||
|
||||
// Disable mesh physics simulation and enable capsule collision.
|
||||
|
||||
MeshComponent->bUpdateJointsFromAnimation = false;
|
||||
|
||||
MeshComponent->SetSimulatePhysics(false);
|
||||
MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
MeshComponent->SetCollisionObjectType(ECC_Pawn);
|
||||
|
||||
UPrimitiveComponent* RootPrimitive = Cast<UPrimitiveComponent>(GetOwner()->GetRootComponent());
|
||||
{
|
||||
RootPrimitive->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
}
|
||||
|
||||
|
||||
bool bGrounded;
|
||||
const auto NewActorLocation{RagdollTraceGround(bGrounded)};
|
||||
|
||||
// Determine whether the ragdoll is facing upward or downward and set the actor rotation accordingly.
|
||||
|
||||
const auto bRagdollFacingUpward{FMath::UnwindDegrees(PelvisRotation.Roll) <= 0.0f};
|
||||
|
||||
auto NewActorRotation{GetOwner()->GetActorRotation()};
|
||||
NewActorRotation.Yaw = bRagdollFacingUpward ? PelvisRotation.Yaw - 180.0f : PelvisRotation.Yaw;
|
||||
|
||||
GetOwner()->SetActorLocationAndRotation(NewActorLocation, NewActorRotation, false, nullptr, ETeleportType::TeleportPhysics);
|
||||
|
||||
// Attach the mesh back and restore its default relative location.
|
||||
|
||||
const auto& ActorTransform{GetOwner()->GetActorTransform()};
|
||||
|
||||
FVector BaseTranslationOffset{FVector::Zero()};
|
||||
FQuat BaseRotationOffset;
|
||||
if (IsValid(CharacterOwner))
|
||||
{
|
||||
BaseTranslationOffset = CharacterOwner->GetBaseTranslationOffset();
|
||||
BaseRotationOffset = CharacterOwner->GetBaseRotationOffset();
|
||||
}
|
||||
MeshComponent->SetWorldLocationAndRotationNoPhysics(ActorTransform.TransformPositionNoScale(BaseTranslationOffset),
|
||||
ActorTransform.TransformRotation(BaseRotationOffset).Rotator());
|
||||
|
||||
MeshComponent->AttachToComponent(RootPrimitive, FAttachmentTransformRules::KeepWorldTransform);
|
||||
|
||||
if (MeshComponent->ShouldUseUpdateRateOptimizations())
|
||||
{
|
||||
// Disable URO for one frame to force the animation blueprint to update and get rid of the incorrect mesh pose.
|
||||
|
||||
MeshComponent->bEnableUpdateRateOptimizations = false;
|
||||
|
||||
GetWorldTimerManager().SetTimerForNextTick(FTimerDelegate::CreateWeakLambda(this, [this]
|
||||
{
|
||||
MeshComponent->bEnableUpdateRateOptimizations = true;
|
||||
}));
|
||||
}
|
||||
|
||||
// Restore the pelvis transform to the state it was in before we changed
|
||||
// the character and mesh transforms to keep its world transform unchanged.
|
||||
|
||||
const auto& ReferenceSkeleton{MeshComponent->GetSkinnedAsset()->GetRefSkeleton()};
|
||||
|
||||
const auto PelvisBoneIndex{ReferenceSkeleton.FindBoneIndex(PelvisBoneName)};
|
||||
if (PelvisBoneIndex >= 0)
|
||||
{
|
||||
// We expect the pelvis bone to be the root bone or attached to it, so we can safely use the mesh transform here.
|
||||
RagdollState.FinalRagdollPose.LocalTransforms[PelvisBoneIndex] = PelvisTransform.GetRelativeTransform(MeshComponent->GetComponentTransform());
|
||||
}
|
||||
|
||||
bRagdolling = false;
|
||||
|
||||
if (IsValid(CharacterOwner))
|
||||
{
|
||||
if (bGrounded)
|
||||
{
|
||||
CharacterOwner->GetCharacterMovement()->SetMovementMode(CharacterOwner->GetCharacterMovement()->DefaultLandMovementMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
CharacterOwner->GetCharacterMovement()->SetMovementMode(MOVE_Falling);
|
||||
CharacterOwner->GetCharacterMovement()->Velocity = RagdollState.Velocity;
|
||||
}
|
||||
}
|
||||
|
||||
OnRagdollEnded(bGrounded);
|
||||
|
||||
if (bGrounded && bPlayGetupMontageAfterRagdollEndedOnGround)
|
||||
{
|
||||
if (UAnimMontage* SelectedMontage = SelectGetUpMontage(bRagdollFacingUpward))
|
||||
{
|
||||
MeshComponent->GetAnimInstance()->Montage_Play(SelectedMontage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::OnRagdollEnded_Implementation(bool bGrounded)
|
||||
{
|
||||
OnRagdollEndedEvent.Broadcast(bGrounded);
|
||||
// If the ragdoll is on the ground, set the movement mode to walking and play a get up montage. If not, set
|
||||
// the movement mode to falling and update the character movement velocity to match the last ragdoll velocity.
|
||||
|
||||
// AlsCharacterMovement->SetMovementModeLocked(false);
|
||||
//
|
||||
|
||||
//
|
||||
// SetLocomotionAction(FGameplayTag::EmptyTag);
|
||||
//
|
||||
// if (bGrounded && MeshComponent->GetAnimInstance()->Montage_Play(SelectGetUpMontage(bRagdollFacingUpward)) > 0.0f)
|
||||
// {
|
||||
// AlsCharacterMovement->SetInputBlocked(true);
|
||||
//
|
||||
// SetLocomotionAction(AlsLocomotionActionTags::GettingUp);
|
||||
// }
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::SetRagdollTargetLocation(const FVector& NewTargetLocation)
|
||||
{
|
||||
if (RagdollTargetLocation != NewTargetLocation)
|
||||
{
|
||||
RagdollTargetLocation = NewTargetLocation;
|
||||
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, RagdollTargetLocation, this)
|
||||
|
||||
if (GetOwner()->GetLocalRole() == ROLE_AutonomousProxy)
|
||||
{
|
||||
ServerSetRagdollTargetLocation(RagdollTargetLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::ServerSetRagdollTargetLocation_Implementation(const FVector_NetQuantize& NewTargetLocation)
|
||||
{
|
||||
SetRagdollTargetLocation(NewTargetLocation);
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::RefreshRagdoll(float DeltaTime)
|
||||
{
|
||||
if (!bRagdolling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we are dealing with physics here, we should not use functions such as USkinnedMeshComponent::GetSocketTransform() as
|
||||
// they may return an incorrect result in situations like when the animation blueprint is not ticking or when URO is enabled.
|
||||
|
||||
const auto* PelvisBody{MeshComponent->GetBodyInstance(PelvisBoneName)};
|
||||
FVector PelvisLocation;
|
||||
|
||||
FPhysicsCommand::ExecuteRead(PelvisBody->ActorHandle, [this, &PelvisLocation](const FPhysicsActorHandle& ActorHandle)
|
||||
{
|
||||
PelvisLocation = FPhysicsInterface::GetTransform_AssumesLocked(ActorHandle, true).GetLocation();
|
||||
RagdollState.Velocity = FPhysicsInterface::GetLinearVelocity_AssumesLocked(ActorHandle);
|
||||
});
|
||||
|
||||
const auto bLocallyControlled{PawnOwner->IsLocallyControlled() || (PawnOwner->GetLocalRole() >= ROLE_Authority && !IsValid(PawnOwner->GetController()))};
|
||||
|
||||
if (bLocallyControlled)
|
||||
{
|
||||
SetRagdollTargetLocation(PelvisLocation);
|
||||
}
|
||||
|
||||
// Prevent the capsule from going through the ground when the ragdoll is lying on the ground.
|
||||
|
||||
// While we could get rid of the line trace here and just use RagdollTargetLocation
|
||||
// as the character's location, we don't do that because the camera depends on the
|
||||
// capsule's bottom location, so its removal will cause the camera to behave erratically.
|
||||
|
||||
bool bGrounded;
|
||||
PawnOwner->SetActorLocation(RagdollTraceGround(bGrounded), false, nullptr, ETeleportType::TeleportPhysics);
|
||||
|
||||
// Zero target location means that it hasn't been replicated yet, so we can't apply the logic below.
|
||||
|
||||
if (!bLocallyControlled && !RagdollTargetLocation.IsZero())
|
||||
{
|
||||
// Apply ragdoll location corrections.
|
||||
|
||||
static constexpr auto PullForce{750.0f};
|
||||
static constexpr auto InterpolationHalfLife{1.2f};
|
||||
|
||||
RagdollState.PullForce = FMath::Lerp(RagdollState.PullForce, PullForce, DamperExactAlpha(DeltaTime, InterpolationHalfLife));
|
||||
|
||||
const auto HorizontalSpeedSquared{RagdollState.Velocity.SizeSquared2D()};
|
||||
|
||||
const auto PullForceBoneName{
|
||||
HorizontalSpeedSquared > FMath::Square(300.0f) ? SpineBoneName : PelvisBoneName
|
||||
};
|
||||
|
||||
auto* PullForceBody{MeshComponent->GetBodyInstance(PullForceBoneName)};
|
||||
|
||||
FPhysicsCommand::ExecuteWrite(PullForceBody->ActorHandle, [this](const FPhysicsActorHandle& ActorHandle)
|
||||
{
|
||||
if (!FPhysicsInterface::IsRigidBody(ActorHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto PullForceVector{
|
||||
RagdollTargetLocation - FPhysicsInterface::GetTransform_AssumesLocked(ActorHandle, true).GetLocation()
|
||||
};
|
||||
|
||||
static constexpr auto MinPullForceDistance{5.0f};
|
||||
static constexpr auto MaxPullForceDistance{50.0f};
|
||||
|
||||
if (PullForceVector.SizeSquared() > FMath::Square(MinPullForceDistance))
|
||||
{
|
||||
FPhysicsInterface::AddForce_AssumesLocked(
|
||||
ActorHandle, PullForceVector.GetClampedToMaxSize(MaxPullForceDistance) * RagdollState.PullForce, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Use the speed to scale ragdoll joint strength for physical animation.
|
||||
|
||||
static constexpr auto ReferenceSpeed{1000.0f};
|
||||
static constexpr auto Stiffness{25000.0f};
|
||||
|
||||
const auto SpeedAmount{Clamp01(UE_REAL_TO_FLOAT(RagdollState.Velocity.Size() / ReferenceSpeed))};
|
||||
|
||||
MeshComponent->SetAllMotorsAngularDriveParams(SpeedAmount * Stiffness, 0.0f, 0.0f);
|
||||
|
||||
// Limit the speed of ragdoll bodies.
|
||||
|
||||
if (RagdollState.SpeedLimitFrameTimeRemaining > 0)
|
||||
{
|
||||
RagdollState.SpeedLimitFrameTimeRemaining -= 1;
|
||||
|
||||
ConstraintRagdollSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
FVector UGGS_RagdollComponent::RagdollTraceGround(bool& bGrounded) const
|
||||
{
|
||||
auto RagdollLocation{!RagdollTargetLocation.IsZero() ? FVector{RagdollTargetLocation} : GetOwner()->GetActorLocation()};
|
||||
|
||||
ACharacter* Character = Cast<ACharacter>(GetOwner());
|
||||
if (!IsValid(Character))
|
||||
return RagdollLocation;
|
||||
|
||||
// We use a sphere sweep instead of a simple line trace to keep capsule
|
||||
// movement consistent between Ragdoll and regular character movement.
|
||||
|
||||
const auto CapsuleRadius{Character->GetCapsuleComponent()->GetScaledCapsuleRadius()};
|
||||
const auto CapsuleHalfHeight{Character->GetCapsuleComponent()->GetScaledCapsuleHalfHeight()};
|
||||
|
||||
const FVector TraceStart{RagdollLocation.X, RagdollLocation.Y, RagdollLocation.Z + 2.0f * CapsuleRadius};
|
||||
const FVector TraceEnd{RagdollLocation.X, RagdollLocation.Y, RagdollLocation.Z - CapsuleHalfHeight + CapsuleRadius};
|
||||
|
||||
const auto CollisionChannel{Character->GetCharacterMovement()->UpdatedComponent->GetCollisionObjectType()};
|
||||
|
||||
FCollisionQueryParams QueryParameters{TEXT("RagdollTraceGround"), false, GetOwner()};
|
||||
FCollisionResponseParams CollisionResponses;
|
||||
Character->GetCharacterMovement()->InitCollisionParams(QueryParameters, CollisionResponses);
|
||||
|
||||
FHitResult Hit;
|
||||
bGrounded = GetWorld()->SweepSingleByChannel(Hit, TraceStart, TraceEnd, FQuat::Identity,
|
||||
CollisionChannel, FCollisionShape::MakeSphere(CapsuleRadius),
|
||||
QueryParameters, CollisionResponses);
|
||||
|
||||
// #if ENABLE_DRAW_DEBUG
|
||||
// UAlsDebugUtility::DrawSweepSingleSphere(GetWorld(), TraceStart, TraceEnd, CapsuleRadius,
|
||||
// bGrounded, Hit, {0.0f, 0.25f, 1.0f},
|
||||
// {0.0f, 0.75f, 1.0f}, 0.0f);
|
||||
// #endif
|
||||
|
||||
return {
|
||||
RagdollLocation.X, RagdollLocation.Y,
|
||||
bGrounded
|
||||
? Hit.Location.Z + CapsuleHalfHeight - CapsuleRadius + UCharacterMovementComponent::MIN_FLOOR_DIST
|
||||
: RagdollLocation.Z
|
||||
};
|
||||
}
|
||||
|
||||
void UGGS_RagdollComponent::ConstraintRagdollSpeed() const
|
||||
{
|
||||
MeshComponent->ForEachBodyBelow(NAME_None, true, false, [this](FBodyInstance* Body)
|
||||
{
|
||||
FPhysicsCommand::ExecuteWrite(Body->ActorHandle, [this](const FPhysicsActorHandle& ActorHandle)
|
||||
{
|
||||
if (!FPhysicsInterface::IsRigidBody(ActorHandle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto Velocity{FPhysicsInterface::GetLinearVelocity_AssumesLocked(ActorHandle)};
|
||||
if (Velocity.SizeSquared() <= FMath::Square(RagdollState.SpeedLimit))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Velocity.Normalize();
|
||||
Velocity *= RagdollState.SpeedLimit;
|
||||
|
||||
FPhysicsInterface::SetLinearVelocity_AssumesLocked(ActorHandle, Velocity);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
UAnimMontage* UGGS_RagdollComponent::SelectGetUpMontage_Implementation(bool bRagdollFacingUpward)
|
||||
{
|
||||
if (GetUpBackMontage.IsNull() || GetUpFrontMontage.IsNull())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return bRagdollFacingUpward ? GetUpBackMontage.LoadSynchronous() : GetUpFrontMontage.LoadSynchronous();
|
||||
}
|
||||
|
||||
|
||||
// Called when the game starts
|
||||
void UGGS_RagdollComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
PawnOwner = GetPawnChecked<APawn>();
|
||||
CharacterOwner = GetPawn<ACharacter>();
|
||||
|
||||
if (CharacterOwner)
|
||||
{
|
||||
MeshComponent = CharacterOwner->GetMesh();
|
||||
}
|
||||
|
||||
if (!MeshComponent)
|
||||
{
|
||||
MeshComponent = GetOwner()->FindComponentByClass<USkeletalMeshComponent>();
|
||||
}
|
||||
if (!MeshComponent)
|
||||
{
|
||||
GGS_CLOG(Warning, "Require skeletal mesh component for the Ragdoll to work.")
|
||||
}
|
||||
}
|
||||
|
||||
float UGGS_RagdollComponent::DamperExactAlpha(float DeltaTime, float HalfLife)
|
||||
{
|
||||
return 1.0f - FMath::InvExpApprox(0.6931471805599453f / (HalfLife + UE_SMALL_NUMBER) * DeltaTime);
|
||||
}
|
||||
|
||||
float UGGS_RagdollComponent::Clamp01(float Value)
|
||||
{
|
||||
return Value > 0.0f
|
||||
? Value < 1.0f
|
||||
? Value
|
||||
: 1.0f
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
|
||||
void UGGS_RagdollComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
FDoRepLifetimeParams Parameters;
|
||||
Parameters.bIsPushBased = true;
|
||||
|
||||
Parameters.Condition = COND_SkipOwner;
|
||||
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, RagdollTargetLocation, Parameters)
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void UGGS_RagdollComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
RefreshRagdoll(DeltaTime);
|
||||
// ...
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Ragdoll/GGS_RagdollStructLibrary.h"
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Utilities/GGS_SocketRelationshipMapping.h"
|
||||
#include "Engine/StreamableRenderAsset.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Animation/Skeleton.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
bool UGGS_SocketRelationshipMapping::FindSocketAdjustment(const USkeletalMeshComponent* InParentMeshComponent, const UStreamableRenderAsset* InMeshAsset, FName InSocketName,
|
||||
FGGS_SocketAdjustment& OutAdjustment) const
|
||||
{
|
||||
if (InParentMeshComponent == nullptr || InMeshAsset == nullptr || InSocketName.IsNone())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
USkeleton* Skeleton = InParentMeshComponent->GetSkeletalMeshAsset()->GetSkeleton();
|
||||
if (!Skeleton)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FString SkeletonName = Skeleton->GetName();
|
||||
|
||||
for (const FGGS_SocketRelationship& Relationship : Relationships)
|
||||
{
|
||||
UStreamableRenderAsset* Key{nullptr};
|
||||
if (!Relationship.MeshAsset.IsNull())
|
||||
{
|
||||
Key = Relationship.MeshAsset.LoadSynchronous();
|
||||
}
|
||||
if (!Key || Key->GetName() != InMeshAsset->GetName())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int32 i = Relationship.Adjustments.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
const FGGS_SocketAdjustment& Adjustment = Relationship.Adjustments[i];
|
||||
bool bMatchSkeleton = Adjustment.ForSkeletons.IsEmpty() ? true : Adjustment.ForSkeletons.Contains(SkeletonName);
|
||||
if (bMatchSkeleton && Adjustment.SocketName == InSocketName)
|
||||
{
|
||||
OutAdjustment = Adjustment;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGGS_SocketRelationshipMapping::FindSocketAdjustmentInMappings(TArray<TSoftObjectPtr<UGGS_SocketRelationshipMapping>> InMappings, const USkeletalMeshComponent* InParentMeshComponent,
|
||||
const UStreamableRenderAsset* InMeshAsset, FName InSocketName,
|
||||
FGGS_SocketAdjustment& OutAdjustment)
|
||||
{
|
||||
for (TSoftObjectPtr<UGGS_SocketRelationshipMapping> Mapping : InMappings)
|
||||
{
|
||||
if (Mapping.IsNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (const UGGS_SocketRelationshipMapping* LoadedMapping = Mapping.LoadSynchronous())
|
||||
{
|
||||
if (LoadedMapping->FindSocketAdjustment(InParentMeshComponent, InMeshAsset, InSocketName, OutAdjustment))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
void UGGS_SocketRelationshipMapping::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
for (FGGS_SocketRelationship& Relationship : Relationships)
|
||||
{
|
||||
if (Relationship.MeshAsset.IsNull())
|
||||
{
|
||||
Relationship.EditorFriendlyName = TEXT("Invalid!");
|
||||
}
|
||||
else
|
||||
{
|
||||
UStreamableRenderAsset* MeshAsset = Relationship.MeshAsset.LoadSynchronous();
|
||||
Relationship.EditorFriendlyName = MeshAsset->GetName();
|
||||
for (FGGS_SocketAdjustment& Adjustment : Relationship.Adjustments)
|
||||
{
|
||||
if (Adjustment.SocketName == NAME_None)
|
||||
{
|
||||
Adjustment.EditorFriendlyName = "Empty adjustments!";
|
||||
}
|
||||
if (Adjustment.ForSkeletons.IsEmpty())
|
||||
{
|
||||
Adjustment.EditorFriendlyName = Adjustment.SocketName.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
FString SkeletonNames;
|
||||
for (const FString& ForSkeleton : Adjustment.ForSkeletons)
|
||||
{
|
||||
SkeletonNames = SkeletonNames.Append(ForSkeleton);
|
||||
}
|
||||
Adjustment.EditorFriendlyName = FString::Format(TEXT("{0} on {1}"), {Adjustment.SocketName.ToString(), SkeletonNames});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
|
||||
/**
|
||||
* Gets the context string for logging purposes.
|
||||
* 获取用于日志记录的上下文字符串。
|
||||
* @param ContextObject The object providing the context (optional). 提供上下文的对象(可选)。
|
||||
* @return The context string. 上下文字符串。
|
||||
*/
|
||||
FString GetGGSLogContextString(const UObject* ContextObject = nullptr);
|
||||
|
||||
/**
|
||||
* Log category for generic game system messages.
|
||||
* 通用游戏系统消息的日志类别。
|
||||
*/
|
||||
GENERICGAMESYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGGS, Log, All);
|
||||
|
||||
/**
|
||||
* Macro for logging generic game system messages.
|
||||
* 用于记录通用游戏系统消息的宏。
|
||||
* @details Logs messages with function name and formatted message. 记录包含函数名和格式化消息的日志。
|
||||
*/
|
||||
#define GGS_LOG(Verbosity, Format, ...) \
|
||||
{ \
|
||||
UE_LOG(LogGGS, Verbosity, TEXT("%S: %s"),__FUNCTION__, *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro for context-based logging for generic game system.
|
||||
* 用于通用游戏系统的基于上下文的日志记录宏。
|
||||
* @details Logs messages with function name, context, and formatted message. 记录包含函数名、上下文和格式化消息的日志。
|
||||
*/
|
||||
#define GGS_CLOG(Verbosity, Format, ...) \
|
||||
{ \
|
||||
UE_LOG(LogGGS, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGGSLogContextString(this), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro for context-based logging with an explicit owner.
|
||||
* 使用显式拥有者进行基于上下文的日志记录宏。
|
||||
* @details Logs messages with function name, owner context, and formatted message. 记录包含函数名、拥有者上下文和格式化消息的日志。
|
||||
*/
|
||||
#define GGS_OWNED_CLOG(LogOwner, Verbosity, Format, ...) \
|
||||
{ \
|
||||
UE_LOG(LogGGS, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGGSLogContextString(LogOwner), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FGenericGameSystemModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SmartObjectRuntime.h"
|
||||
#include "Abilities/GameplayAbility.h"
|
||||
#include "GGS_GameplayAbility_Interaction.generated.h"
|
||||
|
||||
class UGGS_InteractionSystemComponent;
|
||||
class USmartObjectComponent;
|
||||
|
||||
/**
|
||||
* Core gameplay ability for handling interactions.
|
||||
* 处理交互的核心游戏技能。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class GENERICGAMESYSTEM_API UGGS_GameplayAbility_Interaction : public UGameplayAbility
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the interaction gameplay ability.
|
||||
* 交互游戏技能构造函数。
|
||||
*/
|
||||
UGGS_GameplayAbility_Interaction();
|
||||
|
||||
/**
|
||||
* Activates the interaction ability.
|
||||
* 激活交互技能。
|
||||
* @param Handle The ability specification handle. 技能规格句柄。
|
||||
* @param ActorInfo Information about the actor using the ability. 使用技能的Actor信息。
|
||||
* @param ActivationInfo Information about the ability activation. 技能激活信息。
|
||||
* @param TriggerEventData Optional event data triggering the ability. 触发技能的可选事件数据。
|
||||
*/
|
||||
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
const FGameplayEventData* TriggerEventData) override;
|
||||
|
||||
/**
|
||||
* Ends the interaction ability.
|
||||
* 结束交互技能。
|
||||
* @param Handle The ability specification handle. 技能规格句柄。
|
||||
* @param ActorInfo Information about the actor using the ability. 使用技能的Actor信息。
|
||||
* @param ActivationInfo Information about the ability activation. 技能激活信息。
|
||||
* @param bReplicateEndAbility Whether to replicate the end ability call. 是否同步结束技能调用。
|
||||
* @param bWasCancelled Whether the ability was cancelled. 技能是否被取消。
|
||||
*/
|
||||
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility,
|
||||
bool bWasCancelled) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Attempts to claim an interaction with a smart object.
|
||||
* 尝试认领与智能对象的交互。
|
||||
* @param Index The interaction option index. 交互选项索引。
|
||||
* @param ClaimedHandle The claimed smart object handle (output). 认领的智能对象句柄(输出)。
|
||||
* @return True if the interaction was claimed successfully, false otherwise. 如果交互成功认领返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Interaction", meta=(ExpandBoolAsExecs=ReturnValue))
|
||||
bool TryClaimInteraction(int32 Index, FSmartObjectClaimHandle& ClaimedHandle);
|
||||
|
||||
/**
|
||||
* Called when the interactable actor changes.
|
||||
* 可交互演员变更时调用。
|
||||
* @param OldActor The previous interactable actor. 之前的可交互演员。
|
||||
* @param NewActor The new interactable actor. 新的可交互演员。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category="GCS|Interaction")
|
||||
void OnInteractActorChanged(AActor* OldActor, AActor* NewActor);
|
||||
|
||||
/**
|
||||
* Reference to the interaction system component.
|
||||
* 交互系统组件的引用。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category="GCS|Interaction")
|
||||
TObjectPtr<UGGS_InteractionSystemComponent> InteractionSystem{nullptr};
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Validates data in the editor.
|
||||
* 在编辑器中验证数据。
|
||||
* @param Context The data validation context. 数据验证上下文。
|
||||
* @return The validation result. 验证结果。
|
||||
*/
|
||||
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayBehaviorConfig.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GGS_GameplayBehaviorConfig_InteractionWithAbility.generated.h"
|
||||
|
||||
class UGameplayAbility;
|
||||
class UUserWidget;
|
||||
|
||||
/**
|
||||
* Configuration for ability-based interaction behavior.
|
||||
* 基于技能的交互行为配置。
|
||||
*/
|
||||
UCLASS(DisplayName="Gameplay Behavior Config Interaction (GGS)")
|
||||
class GENERICGAMESYSTEM_API UGGS_GameplayBehaviorConfig_InteractionWithAbility : public UGameplayBehaviorConfig
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the interaction behavior config.
|
||||
* 交互行为配置构造函数。
|
||||
*/
|
||||
UGGS_GameplayBehaviorConfig_InteractionWithAbility();
|
||||
|
||||
/**
|
||||
* The ability to grant and activate when interaction begins.
|
||||
* 交互开始时赋予并激活的技能。
|
||||
* @note Must be instanced and not LocalOnly. Does not support event-triggered abilities.
|
||||
* @注意 必须是实例化的技能,不能是LocalOnly。不支持事件触发的技能。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Interaction")
|
||||
TSoftClassPtr<UGameplayAbility> AbilityToGrant;
|
||||
|
||||
/**
|
||||
* The level of the ability, used for visual distinctions.
|
||||
* 技能等级,用于视觉区分。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction")
|
||||
int32 AbilityLevel{0};
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Validates data in the editor.
|
||||
* 在编辑器中验证数据。
|
||||
* @param Context The data validation context. 数据验证上下文。
|
||||
* @return The validation result. 验证结果。
|
||||
*/
|
||||
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayAbilitySpecHandle.h"
|
||||
#include "GameplayBehavior.h"
|
||||
#include "Abilities/GameplayAbilityTypes.h"
|
||||
#include "GGS_GameplayBehavior_InteractionWithAbility.generated.h"
|
||||
|
||||
/**
|
||||
* Gameplay behavior for ability-based interactions.
|
||||
* 基于技能的交互游戏行为。
|
||||
*/
|
||||
UCLASS(DisplayName="GameplayBehavior_Interaction (GGS)", NotBlueprintable)
|
||||
class GENERICGAMESYSTEM_API UGGS_GameplayBehavior_InteractionWithAbility : public UGameplayBehavior
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Triggers the interaction behavior.
|
||||
* 触发交互行为。
|
||||
* @param InAvatar The avatar actor. 化身演员。
|
||||
* @param Config The behavior config. 行为配置。
|
||||
* @param SmartObjectOwner The smart object owner. 智能对象拥有者。
|
||||
* @return True if the behavior was triggered successfully, false otherwise. 如果行为成功触发返回true,否则返回false。
|
||||
*/
|
||||
virtual bool Trigger(AActor& InAvatar, const UGameplayBehaviorConfig* Config, AActor* SmartObjectOwner) override;
|
||||
|
||||
/**
|
||||
* Ends the behavior.
|
||||
* 结束行为。
|
||||
* @param Avatar The avatar actor. 化身演员。
|
||||
* @param bInterrupted Whether the behavior was interrupted. 行为是否被中断。
|
||||
*/
|
||||
virtual void EndBehavior(AActor& Avatar, const bool bInterrupted) override;
|
||||
|
||||
/**
|
||||
* Checks the validity of the ability settings.
|
||||
* 检查技能设置的有效性。
|
||||
* @param Config The behavior config. 行为配置。
|
||||
* @param OutAbilityClass The ability class (output). 技能类(输出)。
|
||||
* @param OutAbilityLevel The ability level (output). 技能等级(输出)。
|
||||
* @return True if the settings are valid, false otherwise. 如果设置有效返回true,否则返回false。
|
||||
*/
|
||||
bool CheckValidAbilitySetting(const UGameplayBehaviorConfig* Config, TSubclassOf<UGameplayAbility>& OutAbilityClass, int32& OutAbilityLevel);
|
||||
|
||||
/**
|
||||
* The ability class granted for the interaction.
|
||||
* 为交互授予的技能类。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TSubclassOf<UGameplayAbility> GrantedAbilityClass{nullptr};
|
||||
|
||||
/**
|
||||
* Handle for the granted ability spec.
|
||||
* 授予技能规格的句柄。
|
||||
*/
|
||||
FGameplayAbilitySpecHandle AbilitySpecHandle;
|
||||
|
||||
/**
|
||||
* Indicates if the behavior was interrupted.
|
||||
* 表示行为是否被中断。
|
||||
*/
|
||||
bool bBehaviorWasInterrupted = false;
|
||||
|
||||
/**
|
||||
* Indicates if the ability has ended.
|
||||
* 表示技能是否已结束。
|
||||
*/
|
||||
bool bAbilityEnded = false;
|
||||
|
||||
/**
|
||||
* Indicates if the ability was cancelled.
|
||||
* 表示技能是否被取消。
|
||||
*/
|
||||
bool bAbilityWasCancelled = false;
|
||||
|
||||
/**
|
||||
* Delegate handle for ability end notification.
|
||||
* 技能结束通知的委托句柄。
|
||||
*/
|
||||
FDelegateHandle AbilityEndedDelegateHandle;
|
||||
|
||||
/**
|
||||
* Called when the ability ends.
|
||||
* 技能结束时调用。
|
||||
* @param EndedData The ability end data. 技能结束数据。
|
||||
*/
|
||||
virtual void OnAbilityEndedCallback(const FAbilityEndedData& EndedData);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "GGS_InteractableInterface.generated.h"
|
||||
|
||||
/**
|
||||
* Interface for actors to handle interaction events.
|
||||
* 处理交互事件的演员接口。
|
||||
*/
|
||||
UINTERFACE()
|
||||
class GENERICGAMESYSTEM_API UGGS_InteractableInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation class for interactable actors.
|
||||
* 可交互演员的实现类。
|
||||
*/
|
||||
class GENERICGAMESYSTEM_API IGGS_InteractableInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Retrieves the display name for the interactable actor.
|
||||
* 获取可交互演员的显示名称。
|
||||
* @return The display name. 显示名称。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GGS|Interaction")
|
||||
FText GetInteractionDisplayName() const;
|
||||
virtual FText GetInteractionDisplayNameText_Implementation() const;
|
||||
|
||||
/**
|
||||
* Called when the actor is selected by the interaction system.
|
||||
* 演员被交互系统选中时调用。
|
||||
* @param Instigator The instigating actor, usually the player. 发起者,通常是玩家。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GGS|Interaction")
|
||||
void OnInteractionSelected(AActor* Instigator);
|
||||
|
||||
/**
|
||||
* Called when the actor is deselected by the interaction system.
|
||||
* 演员被交互系统取消选中时调用。
|
||||
* @param Instigator The instigating actor. 发起者。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GGS|Interaction")
|
||||
void OnInteractionDeselected(AActor* Instigator);
|
||||
|
||||
/**
|
||||
* Called when interaction with the actor starts.
|
||||
* 与演员交互开始时调用。
|
||||
* @param Instigator The instigating actor. 发起者。
|
||||
* @param Index The interaction option index. 交互选项索引。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GGS|Interaction")
|
||||
void OnInteractionStarted(AActor* Instigator, int32 Index);
|
||||
|
||||
/**
|
||||
* Called when interaction with the actor ends.
|
||||
* 与演员交互结束时调用。
|
||||
* @param Instigator The instigating actor. 发起者。
|
||||
* @param Index The interaction option index. 交互选项索引。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GGS|Interaction")
|
||||
void OnInteractionEnded(AActor* Instigator, int32 Index);
|
||||
|
||||
/**
|
||||
* Called when an interaction option is selected.
|
||||
* 交互选项被选中时调用。
|
||||
* @param Instigator The instigating actor. 发起者。
|
||||
* @param OptionIndex The selected option index. 选中的选项索引。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GGS|Interaction")
|
||||
void OnInteractionOptionSelected(AActor* Instigator, int32 OptionIndex);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GGS_InteractionDefinition.generated.h"
|
||||
|
||||
/**
|
||||
* Base class for interaction settings, used in smart object interaction entrances.
|
||||
* 交互设置基类,用于智能对象交互入口。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class GENERICGAMESYSTEM_API UGGS_InteractionDefinition : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Display text for the interaction.
|
||||
* 交互的显示文本。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Interaction")
|
||||
FText Text;
|
||||
|
||||
/**
|
||||
* Sub-text for the interaction.
|
||||
* 交互的子文本。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Interaction")
|
||||
FText SubText;
|
||||
|
||||
/**
|
||||
* Input action that triggers the interaction.
|
||||
* 触发交互的输入动作。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Interaction", meta = (RowType = "/Script/CommonUI.CommonInputActionDataBase"))
|
||||
FDataTableRowHandle TriggeringInputAction;
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#include "SmartObjectRuntime.h"
|
||||
#include "SmartObjectSubsystem.h"
|
||||
#include "SmartObjectTypes.h"
|
||||
#if ENGINE_MINOR_VERSION >= 6
|
||||
#include "SmartObjectRequestTypes.h"
|
||||
#endif
|
||||
#include "Engine/DataTable.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GGS_InteractionStructLibrary.generated.h"
|
||||
|
||||
class UGGS_InteractionSystemComponent;
|
||||
class UAbilitySystemComponent;
|
||||
class UGGS_InteractionDefinition;
|
||||
|
||||
/**
|
||||
* Structure wrapping an interaction definition for smart object interaction.
|
||||
* 封装智能对象交互的交互定义结构。
|
||||
*/
|
||||
USTRUCT(DisplayName="Interaction Entrance")
|
||||
struct GENERICGAMESYSTEM_API FGGS_SmartObjectInteractionEntranceData : public FSmartObjectDefinitionData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Interaction definition containing static data for player interaction.
|
||||
* 包含玩家交互静态数据的交互定义。
|
||||
* @note Replicated across the network.
|
||||
* @注意 通过网络同步。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="Interaction", meta=(DisplayName="Definition"))
|
||||
TSoftObjectPtr<UGGS_InteractionDefinition> DefinitionDA{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure representing an interaction option.
|
||||
* 表示交互选项的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICGAMESYSTEM_API FGGS_InteractionOption
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Interaction definition associated with this option.
|
||||
* 与此选项关联的交互定义。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Interaction")
|
||||
TObjectPtr<UGGS_InteractionDefinition> Definition{nullptr};
|
||||
|
||||
/**
|
||||
* Smart object request result for this option. Not replicated.
|
||||
* 此选项的智能对象请求结果。未网络同步。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, NotReplicated, Category="Interaction")
|
||||
FSmartObjectRequestResult RequestResult;
|
||||
|
||||
/**
|
||||
* Smart object behavior definition for this option.
|
||||
* 此选项的智能对象行为定义。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, NotReplicated, Category="Interaction")
|
||||
TObjectPtr<const USmartObjectBehaviorDefinition> BehaviorDefinition;
|
||||
|
||||
/**
|
||||
* Index of the associated smart object slot, used for UI sorting.
|
||||
* 关联智能对象槽的索引,用于UI排序。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Interaction")
|
||||
int32 SlotIndex{-1};
|
||||
|
||||
/**
|
||||
* State of the associated smart object slot, used for UI input rules.
|
||||
* 关联智能对象槽的状态,用于UI输入规则。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Interaction")
|
||||
ESmartObjectSlotState SlotState{ESmartObjectSlotState::Free};
|
||||
|
||||
/**
|
||||
* Equality operator for comparing interaction options.
|
||||
* 交互选项的相等比较运算符。
|
||||
*/
|
||||
friend bool operator==(const FGGS_InteractionOption& Lhs, const FGGS_InteractionOption& RHS);
|
||||
|
||||
/**
|
||||
* Inequality operator for comparing interaction options.
|
||||
* 交互选项的不等比较运算符。
|
||||
*/
|
||||
friend bool operator!=(const FGGS_InteractionOption& Lhs, const FGGS_InteractionOption& RHS);
|
||||
|
||||
/**
|
||||
* Less-than operator for sorting interaction options by slot index.
|
||||
* 按槽索引排序交互选项的比较运算符。
|
||||
*/
|
||||
friend bool operator<(const FGGS_InteractionOption& Lhs, const FGGS_InteractionOption& RHS);
|
||||
|
||||
/**
|
||||
* Converts the interaction option to a string representation.
|
||||
* 将交互选项转换为字符串表示。
|
||||
* @return String representation of the option. 选项的字符串表示。
|
||||
*/
|
||||
FString ToString() const;
|
||||
};
|
||||
@@ -0,0 +1,332 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GGS_InteractionStructLibrary.h"
|
||||
#include "SmartObjectSubsystem.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GGS_InteractionSystemComponent.generated.h"
|
||||
|
||||
class UCommonUserWidget;
|
||||
class UGameplayBehavior;
|
||||
|
||||
/**
|
||||
* Delegate for interaction events.
|
||||
* 交互事件的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FInteractionEventSignature);
|
||||
|
||||
/**
|
||||
* Delegate for changes in the interactable actor.
|
||||
* 可交互演员变更的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FInteractableActorChangedSignature, AActor*, OldActor, AActor*, NewActor);
|
||||
|
||||
/**
|
||||
* Delegate for changes in the interacting state.
|
||||
* 交互状态变更的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FInteractingStateChangedSignature, bool, bInteracting);
|
||||
|
||||
/**
|
||||
* Delegate for changes in the number of interactable actors.
|
||||
* 可交互演员数量变更的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FInteractableActorNumChangedSignature, int32, ActorsNum);
|
||||
|
||||
/**
|
||||
* Component for managing interactions with smart objects.
|
||||
* 管理与智能对象交互的组件。
|
||||
*/
|
||||
UCLASS(Blueprintable, BlueprintType, ClassGroup=(GGS), meta=(BlueprintSpawnableComponent))
|
||||
class GENERICGAMESYSTEM_API UGGS_InteractionSystemComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the interaction system component.
|
||||
* 交互系统组件构造函数。
|
||||
*/
|
||||
UGGS_InteractionSystemComponent();
|
||||
|
||||
/**
|
||||
* Retrieves lifetime replicated properties.
|
||||
* 获取生命周期内同步的属性。
|
||||
* @param OutLifetimeProps The replicated properties. 同步的属性。
|
||||
*/
|
||||
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
/**
|
||||
* Retrieves the interaction system component from an actor.
|
||||
* 从演员获取交互系统组件。
|
||||
* @param Actor The actor to query. 要查询的演员。
|
||||
* @return The interaction system component. 交互系统组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GGS|InteractionSystem", meta=(DefaultToSelf="Actor"))
|
||||
static UGGS_InteractionSystemComponent* GetInteractionSystemComponent(const AActor* Actor);
|
||||
|
||||
/**
|
||||
* Cycles through interactable actors.
|
||||
* 循环切换可交互演员。
|
||||
* @param bNext Whether to cycle to the next actor. 是否切换到下一个演员。
|
||||
*/
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category="GGS|InteractionSystem")
|
||||
void CycleInteractableActors(bool bNext);
|
||||
|
||||
/**
|
||||
* Triggers a search for potential interactable actors.
|
||||
* 触发潜在可交互演员的搜索。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GGS|InteractionSystem")
|
||||
void SearchInteractableActors();
|
||||
|
||||
/**
|
||||
* Sets a new array of interactable actors.
|
||||
* 设置新的可交互演员数组。
|
||||
* @param NewActors The new interactable actors. 新的可交互演员。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GGS|InteractionSystem")
|
||||
void SetInteractableActors(TArray<AActor*> NewActors);
|
||||
|
||||
/**
|
||||
* Sets the number of interactable actors.
|
||||
* 设置可交互演员的数量。
|
||||
* @param NewNum The new number of interactable actors. 可交互演员的新数量。
|
||||
*/
|
||||
void SetInteractableActorsNum(int32 NewNum);
|
||||
|
||||
/**
|
||||
* Retrieves the array of interactable actors.
|
||||
* 获取可交互演员数组。
|
||||
* @return The interactable actors. 可交互演员。
|
||||
*/
|
||||
TArray<AActor*> GetInteractableActors() const { return InteractableActors; }
|
||||
|
||||
/**
|
||||
* Retrieves the number of interactable actors.
|
||||
* 获取可交互演员的数量。
|
||||
* @return The number of interactable actors. 可交互演员数量。
|
||||
*/
|
||||
int32 GetNumOfInteractableActors() const { return NumsOfInteractableActors; }
|
||||
|
||||
/**
|
||||
* Sets the current interactable actor.
|
||||
* 设置当前可交互演员。
|
||||
* @param InActor The actor to set. 要设置的演员。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GGS|InteractionSystem")
|
||||
void SetInteractableActor(AActor* InActor);
|
||||
|
||||
/**
|
||||
* Retrieves the current interactable actor.
|
||||
* 获取当前可交互演员。
|
||||
* @return The interactable actor. 可交互演员。
|
||||
*/
|
||||
AActor* GetInteractableActor() const { return InteractableActor; }
|
||||
|
||||
/**
|
||||
* Delegate for when the interactable actor changes.
|
||||
* 可交互演员变更时的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FInteractableActorChangedSignature OnInteractableActorChangedEvent;
|
||||
|
||||
/**
|
||||
* Delegate for when the number of interactable actors changes.
|
||||
* 可交互演员数量变更时的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FInteractableActorNumChangedSignature OnInteractableActorNumChangedEvent;
|
||||
|
||||
/**
|
||||
* Delegate for when the interacting state changes.
|
||||
* 交互状态变更时的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FInteractingStateChangedSignature OnInteractingStateChangedEvent;
|
||||
|
||||
/**
|
||||
* Delegate for when the interaction options change.
|
||||
* 交互选项变更时的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FInteractionEventSignature OnInteractionOptionsChangedEvent;
|
||||
|
||||
/**
|
||||
* Delegate for when a search for interactable actors is triggered.
|
||||
* 触发可交互演员搜索时的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FInteractionEventSignature OnSearchInteractableActorsEvent;
|
||||
|
||||
/**
|
||||
* Retrieves the smart object request filter.
|
||||
* 获取智能对象请求过滤器。
|
||||
* @return The smart object request filter. 智能对象请求过滤器。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GGS|InteractionSystem")
|
||||
FSmartObjectRequestFilter GetSmartObjectRequestFilter();
|
||||
virtual FSmartObjectRequestFilter GetSmartObjectRequestFilter_Implementation();
|
||||
|
||||
/**
|
||||
* Starts an interaction with the specified option index.
|
||||
* 开始与指定选项索引的交互。
|
||||
* @param NewIndex The interaction option index. 交互选项索引。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GGS|InteractionSystem")
|
||||
virtual void StartInteraction(int32 NewIndex = 0);
|
||||
|
||||
/**
|
||||
* Ends the current interaction.
|
||||
* 结束当前交互。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GGS|InteractionSystem")
|
||||
virtual void EndInteraction();
|
||||
|
||||
/**
|
||||
* Performs an instant interaction with the specified option index.
|
||||
* 执行与指定选项索引的即时交互。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GGS|InteractionSystem")
|
||||
void InstantInteraction(int32 NewIndex = 0);
|
||||
|
||||
/**
|
||||
* Checks if an interaction is in progress.
|
||||
* 检查是否正在进行交互。
|
||||
* @return True if interacting, false otherwise. 如果正在交互返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GGS|InteractionSystem")
|
||||
bool IsInteracting() const;
|
||||
|
||||
/**
|
||||
* Retrieves the current interacting option index.
|
||||
* 获取当前交互选项索引。
|
||||
* @return The interacting option index. 交互选项索引。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GGS|InteractionSystem")
|
||||
int32 GetInteractingOption() const;
|
||||
|
||||
/**
|
||||
* Retrieves the current interaction options.
|
||||
* 获取当前交互选项。
|
||||
* @return The interaction options. 交互选项。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GGS|InteractionSystem")
|
||||
const TArray<FGGS_InteractionOption>& GetInteractionOptions() const { return InteractionOptions; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called when the interactable actor changes.
|
||||
* 可交互演员变更时调用。
|
||||
* @param OldActor The previous interactable actor. 之前的可交互演员。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnInteractableActorChanged(AActor* OldActor);
|
||||
|
||||
/**
|
||||
* Called when the number of interactable actors changes.
|
||||
* 可交互演员数量变更时调用。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnInteractableActorsNumChanged();
|
||||
|
||||
/**
|
||||
* Called when the potential interactable actors changes.
|
||||
* 可交互演员变更时调用。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category="GGS|InteractionSystem")
|
||||
void OnInteractableActorsChanged();
|
||||
|
||||
/**
|
||||
* Called when a smart object event occurs.
|
||||
* 智能对象事件发生时调用。
|
||||
* @param EventData The smart object event data. 智能对象事件数据。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnSmartObjectEventCallback(const FSmartObjectEventData& EventData);
|
||||
|
||||
/**
|
||||
* Called when interaction options change.
|
||||
* 交互选项变更时调用。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnInteractionOptionsChanged();
|
||||
|
||||
/**
|
||||
* Called when the interacting option index changes.
|
||||
* 交互选项索引变更时调用。
|
||||
* @param PrevOptionIndex The previous option index. 之前的选项索引。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category="GGS|InteractionSystem")
|
||||
void OnInteractingOptionChanged(int32 PrevOptionIndex);
|
||||
|
||||
/**
|
||||
* Refreshes interaction options based on smart object request results.
|
||||
* 根据智能对象请求结果刷新交互选项。
|
||||
*/
|
||||
virtual void RefreshOptionsForActor();
|
||||
|
||||
/**
|
||||
* Array of potential interactable actors. Not replicated.
|
||||
* 潜在可交互演员数组。未网络同步。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GGS|InteractionSystem")
|
||||
TArray<TObjectPtr<AActor>> InteractableActors;
|
||||
|
||||
/**
|
||||
* Number of potential interactable actors, replicated to owning client.
|
||||
* 潜在可交互演员数量,同步到拥有客户端。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing=OnInteractableActorsNumChanged, Category="GGS|InteractionSystem")
|
||||
int32 NumsOfInteractableActors{0};
|
||||
|
||||
/**
|
||||
* Current selected interactable actor, replicated for owner only.
|
||||
* 当前选中的可交互演员,仅针对拥有者同步。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GGS|InteractionSystem", ReplicatedUsing=OnInteractableActorChanged)
|
||||
TObjectPtr<AActor> InteractableActor;
|
||||
|
||||
/**
|
||||
* Default filter for searching interactable smart objects.
|
||||
* 搜索可交互智能对象的默认过滤器。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GGS|InteractionSystem")
|
||||
FSmartObjectRequestFilter DefaultRequestFilter;
|
||||
|
||||
/**
|
||||
* If checked, whenever potential interactable actors changes, the first actor in the list will be selected as currency interactable actor.
|
||||
* 如果勾选,始终使用潜在交互演员中的第一个作为当前选择。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing=OnInteractableActorsNumChanged, Category="GGS|InteractionSystem")
|
||||
bool bNewActorHasPriority{false};
|
||||
|
||||
/**
|
||||
* Current available interaction options, replicated for owner only.
|
||||
* 当前可用的交互选项,仅针对拥有者同步。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GGS|InteractionSystem", ReplicatedUsing=OnInteractionOptionsChanged)
|
||||
TArray<FGGS_InteractionOption> InteractionOptions;
|
||||
|
||||
/**
|
||||
* Indicates if an interaction is in progress.
|
||||
* 表示是否正在进行交互。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GGS|InteractionSystem")
|
||||
bool bInteracting{false};
|
||||
|
||||
/**
|
||||
* Current interacting option index (-1 if no interaction).
|
||||
* 当前交互选项索引(无交互时为-1)。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GGS|InteractionSystem", ReplicatedUsing=OnInteractingOptionChanged)
|
||||
int32 InteractingOption{INDEX_NONE};
|
||||
|
||||
/**
|
||||
* Map of smart object slot handles to delegate handles.
|
||||
* 智能对象槽句柄到委托句柄的映射。
|
||||
*/
|
||||
TMap<FSmartObjectSlotHandle, FDelegateHandle> SlotCallbacks;
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GGS_InteractionStructLibrary.h"
|
||||
#include "SmartObjectSubsystem.h"
|
||||
#include "SmartObjectTypes.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "GGS_SmartObjectFunctionLibrary.generated.h"
|
||||
|
||||
class UGameplayInteractionSmartObjectBehaviorDefinition;
|
||||
class UGameplayBehaviorSmartObjectBehaviorDefinition;
|
||||
class UGameplayBehaviorConfig;
|
||||
class USmartObjectBehaviorDefinition;
|
||||
|
||||
/**
|
||||
* Blueprint function library for smart object interactions.
|
||||
* 智能对象交互的蓝图函数库。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICGAMESYSTEM_API UGGS_SmartObjectFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Retrieves the gameplay behavior config from a smart object behavior definition.
|
||||
* 从智能对象行为定义获取游戏行为配置。
|
||||
* @param BehaviorDefinition The smart object behavior definition. 智能对象行为定义。
|
||||
* @return The gameplay behavior config. 游戏行为配置。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GGS|SmartObject")
|
||||
static UGameplayBehaviorConfig* GetGameplayBehaviorConfig(const USmartObjectBehaviorDefinition* BehaviorDefinition);
|
||||
|
||||
/**
|
||||
* Finds a specific gameplay behavior config by class.
|
||||
* 按类查找特定游戏行为配置。
|
||||
* @param BehaviorDefinition The smart object behavior definition. 智能对象行为定义。
|
||||
* @param DesiredClass The desired config class. 期望的配置类。
|
||||
* @param OutConfig The found config (output). 找到的配置(输出)。
|
||||
* @return True if the config was found, false otherwise. 如果找到配置返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GGS|SmartObject", meta=(DeterminesOutputType="DesiredClass", DynamicOutputParam="OutConfig", ExpandBoolAsExecs="ReturnValue"))
|
||||
static bool FindGameplayBehaviorConfig(const USmartObjectBehaviorDefinition* BehaviorDefinition, TSubclassOf<UGameplayBehaviorConfig> DesiredClass, UGameplayBehaviorConfig*& OutConfig);
|
||||
|
||||
/**
|
||||
* Searches for smart object slots with interaction entrances on an actor.
|
||||
* 在演员上搜索带有交互入口的智能对象槽。
|
||||
* @param Filter The search filter. 搜索过滤器。
|
||||
* @param SearchActor The actor to search. 要搜索的演员。
|
||||
* @param OutResults The found smart object slot candidates (output). 找到的智能对象槽候选(输出)。
|
||||
* @param UserActor Optional actor for additional data in condition evaluation. 用于条件评估的可选演员。
|
||||
* @return True if at least one candidate was found, false otherwise. 如果找到至少一个候选返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure = False, Category = "GGS|SmartObject", Meta = (ReturnDisplayName = "bSuccess"))
|
||||
static bool FindSmartObjectsWithInteractionEntranceInActor(const FSmartObjectRequestFilter& Filter, AActor* SearchActor, TArray<FSmartObjectRequestResult>& OutResults,
|
||||
const AActor* UserActor = nullptr);
|
||||
|
||||
/**
|
||||
* Finds the interaction definition for a smart object slot.
|
||||
* 查找智能对象槽的交互定义。
|
||||
* @param WorldContext The world context object. 世界上下文对象。
|
||||
* @param SmartObjectSlotHandle The smart object slot handle. 智能对象槽句柄。
|
||||
* @param OutDefinition The interaction definition (output). 交互定义(输出)。
|
||||
* @return True if the definition was found, false otherwise. 如果找到定义返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GGS|SmartObject", meta=(WorldContext="WorldContext", ExpandBoolAsExecs="ReturnValue"))
|
||||
static bool FindInteractionDefinitionFromSmartObjectSlot(UObject* WorldContext, FSmartObjectSlotHandle SmartObjectSlotHandle, UGGS_InteractionDefinition*& OutDefinition);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Tasks/TargetingFilterTask_BasicFilterTemplate.h"
|
||||
#include "GGS_TargetingFilterTask_InteractionSmartObjects.generated.h"
|
||||
|
||||
/**
|
||||
* Filter task for selecting interactable smart objects.
|
||||
* 选择可交互智能对象的过滤任务。
|
||||
*/
|
||||
UCLASS(meta=(DisplayName="(GGS)FilterTask:InteractionSmartObject"))
|
||||
class GENERICGAMESYSTEM_API UGGS_TargetingFilterTask_InteractionSmartObjects : public UTargetingFilterTask_BasicFilterTemplate
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Determines if a target should be filtered based on interaction criteria.
|
||||
* 根据交互标准确定是否过滤目标。
|
||||
* @param TargetingHandle The targeting request handle. 目标请求句柄。
|
||||
* @param TargetData The target data. 目标数据。
|
||||
* @return True if the target should be filtered, false otherwise. 如果目标应被过滤返回true,否则返回false。
|
||||
*/
|
||||
virtual bool ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const override;
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Abilities/Tasks/AbilityTask.h"
|
||||
#include "SmartObjectRuntime.h"
|
||||
#include "SmartObjectTypes.h"
|
||||
#include "GGS_AbilityTask_UseSmartObjectWithGameplayBehavior.generated.h"
|
||||
|
||||
class UGameplayBehavior;
|
||||
|
||||
/**
|
||||
* Ability task for using a smart object with gameplay behavior.
|
||||
* 使用智能对象和游戏行为的技能任务。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICGAMESYSTEM_API UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior : public UAbilityTask
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the ability task.
|
||||
* 技能任务构造函数。
|
||||
*/
|
||||
UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
/**
|
||||
* Creates an ability task to use a smart object with gameplay behavior.
|
||||
* 创建使用智能对象和游戏行为的技能任务。
|
||||
* @param OwningAbility The owning gameplay ability. 拥有的游戏技能。
|
||||
* @param ClaimHandle The smart object claim handle. 智能对象认领句柄。
|
||||
* @param ClaimPriority The claim priority. 认领优先级。
|
||||
* @return The created ability task. 创建的技能任务。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GGS|Interaction", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
|
||||
static UGGS_AbilityTask_UseSmartObjectWithGameplayBehavior* UseSmartObjectWithGameplayBehavior(UGameplayAbility* OwningAbility, FSmartObjectClaimHandle ClaimHandle,
|
||||
ESmartObjectClaimPriority ClaimPriority = ESmartObjectClaimPriority::Normal);
|
||||
|
||||
/**
|
||||
* Sets the smart object claim handle.
|
||||
* 设置智能对象认领句柄。
|
||||
* @param Handle The claim handle. 认领句柄。
|
||||
*/
|
||||
void SetClaimHandle(const FSmartObjectClaimHandle& Handle) { ClaimedHandle = Handle; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Activates the ability task.
|
||||
* 激活技能任务。
|
||||
*/
|
||||
virtual void Activate() override;
|
||||
|
||||
/**
|
||||
* Called when the task is destroyed.
|
||||
* 任务销毁时调用。
|
||||
* @param bInOwnerFinished Whether the owner finished the task. 拥有者是否完成任务。
|
||||
*/
|
||||
virtual void OnDestroy(bool bInOwnerFinished) override;
|
||||
|
||||
/**
|
||||
* Starts the interaction with the smart object.
|
||||
* 开始与智能对象的交互。
|
||||
* @return True if the interaction started successfully, false otherwise. 如果交互成功开始返回true,否则返回false。
|
||||
*/
|
||||
bool StartInteraction();
|
||||
|
||||
/**
|
||||
* Called when the smart object behavior finishes.
|
||||
* 智能对象行为完成时调用。
|
||||
* @param Behavior The gameplay behavior. 游戏行为。
|
||||
* @param Avatar The avatar actor. 化身演员。
|
||||
* @param bInterrupted Whether the behavior was interrupted. 行为是否被中断。
|
||||
*/
|
||||
void OnSmartObjectBehaviorFinished(UGameplayBehavior& Behavior, AActor& Avatar, const bool bInterrupted);
|
||||
|
||||
/**
|
||||
* Called when the smart object slot is invalidated.
|
||||
* 智能对象槽失效时调用。
|
||||
* @param ClaimHandle The claim handle. 认领句柄。
|
||||
* @param State The slot state. 槽状态。
|
||||
*/
|
||||
void OnSlotInvalidated(const FSmartObjectClaimHandle& ClaimHandle, const ESmartObjectSlotState State);
|
||||
|
||||
/**
|
||||
* Delegate for when the interaction succeeds.
|
||||
* 交互成功时的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FGenericGameplayTaskDelegate OnSucceeded;
|
||||
|
||||
/**
|
||||
* Delegate for when the interaction fails.
|
||||
* 交互失败时的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FGenericGameplayTaskDelegate OnFailed;
|
||||
|
||||
/**
|
||||
* The gameplay behavior for the interaction.
|
||||
* 交互的游戏行为。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UGameplayBehavior> GameplayBehavior;
|
||||
|
||||
/**
|
||||
* The claimed smart object handle.
|
||||
* 认领的智能对象句柄。
|
||||
*/
|
||||
FSmartObjectClaimHandle ClaimedHandle;
|
||||
|
||||
/**
|
||||
* Delegate handle for behavior finished notification.
|
||||
* 行为完成通知的委托句柄。
|
||||
*/
|
||||
FDelegateHandle OnBehaviorFinishedNotifyHandle;
|
||||
|
||||
/**
|
||||
* Indicates if the behavior has finished.
|
||||
* 表示行为是否已完成。
|
||||
*/
|
||||
bool bBehaviorFinished;
|
||||
};
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GGS_RagdollStructLibrary.h"
|
||||
#include "Components/PawnComponent.h"
|
||||
|
||||
#include "GGS_RagdollComponent.generated.h"
|
||||
|
||||
class USkeletalMeshComponent;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGGS_RagdollStartedSignature);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGGS_RagdollEndedSignature, bool, bGrounded);
|
||||
|
||||
UCLASS(ClassGroup=(GGS), Blueprintable, meta=(BlueprintSpawnableComponent))
|
||||
class GENERICGAMESYSTEM_API UGGS_RagdollComponent : public UPawnComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this component's properties
|
||||
UGGS_RagdollComponent(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
|
||||
const FGGS_RagdollState& GetRagdollState() const;
|
||||
|
||||
bool IsRagdollAllowedToStart() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "GGS|Ragdoll")
|
||||
void SetMeshComponent(USkeletalMeshComponent* InMeshComponent);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "GGS|Ragdoll")
|
||||
bool IsRagdolling() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "GGS|Ragdoll")
|
||||
void StartRagdoll();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "GGS|Ragdoll")
|
||||
virtual void LocalStartRagdoll();
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="Event")
|
||||
FGGS_RagdollStartedSignature OnRagdollStartedEvent;
|
||||
UPROPERTY(BlueprintAssignable, Category="Event")
|
||||
FGGS_RagdollEndedSignature OnRagdollEndedEvent;
|
||||
|
||||
private:
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerStartRagdoll();
|
||||
|
||||
UFUNCTION(NetMulticast, Reliable)
|
||||
void MulticastStartRagdoll();
|
||||
|
||||
protected:
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "GGS|Ragdoll")
|
||||
void OnRagdollStarted();
|
||||
|
||||
public:
|
||||
bool IsRagdollAllowedToStop() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "GGS|Ragdoll", Meta = (ReturnDisplayName = "Success"))
|
||||
bool StopRagdoll();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "GGS|Ragdoll")
|
||||
virtual void LocalStopRagdoll();
|
||||
|
||||
private:
|
||||
UFUNCTION(Server, Reliable)
|
||||
void ServerStopRagdoll();
|
||||
|
||||
UFUNCTION(NetMulticast, Reliable)
|
||||
void MulticastStopRagdoll();
|
||||
|
||||
protected:
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "GGS|Ragdoll")
|
||||
UAnimMontage* SelectGetUpMontage(bool bRagdollFacingUpward);
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "GGS|Ragdoll")
|
||||
void OnRagdollEnded(bool bGrounded);
|
||||
virtual void OnRagdollEnded_Implementation(bool bGrounded);
|
||||
|
||||
private:
|
||||
void SetRagdollTargetLocation(const FVector& NewTargetLocation);
|
||||
|
||||
UFUNCTION(Server, Unreliable)
|
||||
void ServerSetRagdollTargetLocation(const FVector_NetQuantize& NewTargetLocation);
|
||||
|
||||
void RefreshRagdoll(float DeltaTime);
|
||||
|
||||
FVector RagdollTraceGround(bool& bGrounded) const;
|
||||
|
||||
void ConstraintRagdollSpeed() const;
|
||||
|
||||
protected:
|
||||
// Called when the game starts
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
float DamperExactAlpha(float DeltaTime, float HalfLife);
|
||||
|
||||
float Clamp01(float Value);
|
||||
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ragdoll Settings")
|
||||
FName PelvisBoneName{TEXT("pelvis")};
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Ragdoll Settings")
|
||||
FName SpineBoneName{TEXT("spine_03")};
|
||||
|
||||
// If checked, the ragdoll's speed will be limited by the character's last speed for a few frames
|
||||
// after activation. This hack is used to prevent the ragdoll from getting a very high initial speed
|
||||
// at unstable FPS, which can be reproduced by jumping and activating the ragdoll at the same time.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ragdoll Settings")
|
||||
uint8 bLimitInitialRagdollSpeed : 1 {true};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ragdoll Settings")
|
||||
bool bPlayGetupMontageAfterRagdollEndedOnGround{false};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ragdoll Settings", meta=(EditCondition="bPlayGetupMontageAfterRagdollEndedOnGround"))
|
||||
TSoftObjectPtr<UAnimMontage> GetUpFrontMontage;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ragdoll Settings", meta=(EditCondition="bPlayGetupMontageAfterRagdollEndedOnGround"))
|
||||
TSoftObjectPtr<UAnimMontage> GetUpBackMontage;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Ragdoll State", Transient)
|
||||
bool bRagdolling{false};
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Ragdoll State", Transient, Replicated)
|
||||
FVector_NetQuantize RagdollTargetLocation{ForceInit};
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Ragdoll State", Transient)
|
||||
FGGS_RagdollState RagdollState;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, Category = "Ragdoll State", Transient)
|
||||
TObjectPtr<USkeletalMeshComponent> MeshComponent{nullptr};
|
||||
|
||||
UPROPERTY(VisibleAnywhere, Category = "Ragdoll State", Transient)
|
||||
TObjectPtr<APawn> PawnOwner{nullptr};
|
||||
|
||||
UPROPERTY(VisibleAnywhere, Category = "Ragdoll State", Transient)
|
||||
TObjectPtr<ACharacter> CharacterOwner{nullptr};
|
||||
|
||||
public:
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
// Called every frame
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Animation/PoseSnapshot.h"
|
||||
#include "GGS_RagdollStructLibrary.generated.h"
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICGAMESYSTEM_API FGGS_RagdollState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GGS")
|
||||
FVector Velocity{ForceInit};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GGS", Meta = (ForceUnits = "N"))
|
||||
float PullForce{0.0f};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GGS", Meta = (ClampMin = 0))
|
||||
int32 SpeedLimitFrameTimeRemaining{0};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GGS", Meta = (ClampMin = 0, ForceUnits = "cm/s"))
|
||||
float SpeedLimit{0.0f};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GGS")
|
||||
FPoseSnapshot FinalRagdollPose;
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GGS_SocketRelationshipMapping.generated.h"
|
||||
|
||||
/**
|
||||
* Structure for socket adjustments.
|
||||
* 插槽调整结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICGAMESYSTEM_API FGGS_SocketAdjustment
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Array of skeleton names for the adjustment.
|
||||
* 调整适用的骨骼名称数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GGS")
|
||||
TArray<FString> ForSkeletons;
|
||||
|
||||
/**
|
||||
* Name of the socket.
|
||||
* 插槽名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GGS")
|
||||
FName SocketName{NAME_None};
|
||||
|
||||
/**
|
||||
* Relative transform for the socket.
|
||||
* 插槽的相对变换。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GGS")
|
||||
FTransform RelativeTransform;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Editor-friendly name for the adjustment.
|
||||
* 调整的编辑器友好名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GGS", meta=(EditCondition=false, EditConditionHides))
|
||||
FString EditorFriendlyName;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for socket relationships.
|
||||
* 插槽关系结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICGAMESYSTEM_API FGGS_SocketRelationship
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Mesh asset associated with the relationship.
|
||||
* 与关系关联的网格资产。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GGS")
|
||||
TSoftObjectPtr<UStreamableRenderAsset> MeshAsset;
|
||||
|
||||
/**
|
||||
* Array of socket adjustments for the mesh.
|
||||
* 网格的插槽调整数组。
|
||||
* @note Will look from bottom to top; 从下往上查询。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GGS", meta=(TitleProperty="EditorFriendlyName"))
|
||||
TArray<FGGS_SocketAdjustment> Adjustments;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Editor-friendly name for the relationship.
|
||||
* 关系的编辑器友好名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GGS", meta=(EditCondition=false, EditConditionHides))
|
||||
FString EditorFriendlyName;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Data asset for defining socket relationships for mesh attachments.
|
||||
* 定义网格附件插槽关系的数据资产。
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class GENERICGAMESYSTEM_API UGGS_SocketRelationshipMapping : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Finds a socket adjustment for a given mesh and socket.
|
||||
* 查找给定网格和插槽的插槽调整。
|
||||
* @param InParentMeshComponent The parent mesh component. 父网格组件。
|
||||
* @param InMeshAsset The mesh asset. 网格资产。
|
||||
* @param InSocketName The socket name. 插槽名称。
|
||||
* @param OutAdjustment The found socket adjustment (output). 找到的插槽调整(输出)。
|
||||
* @return True if an adjustment was found, false otherwise. 如果找到调整返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure=False, Category="GGS|Utilities")
|
||||
bool FindSocketAdjustment(const USkeletalMeshComponent* InParentMeshComponent, const UStreamableRenderAsset* InMeshAsset, FName InSocketName,
|
||||
FGGS_SocketAdjustment& OutAdjustment) const;
|
||||
|
||||
/**
|
||||
* Finds a socket adjustment across multiple mappings.
|
||||
* 在多个映射中查找插槽调整。
|
||||
* @param InMappings The socket relationship mappings. 插槽关系映射。
|
||||
* @param InParentMeshComponent The parent mesh component. 父网格组件。
|
||||
* @param InMeshAsset The mesh asset. 网格资产。
|
||||
* @param InSocketName The socket name. 插槽名称。
|
||||
* @param OutAdjustment The found socket adjustment (output). 找到的插槽调整(输出)。
|
||||
* @return True if an adjustment was found, false otherwise. 如果找到调整返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GGS|Utilities")
|
||||
static bool FindSocketAdjustmentInMappings(TArray<TSoftObjectPtr<UGGS_SocketRelationshipMapping>> InMappings, const USkeletalMeshComponent* InParentMeshComponent,
|
||||
const UStreamableRenderAsset* InMeshAsset, FName InSocketName,
|
||||
FGGS_SocketAdjustment& OutAdjustment);
|
||||
|
||||
/**
|
||||
* Array of socket relationships.
|
||||
* 插槽关系数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GGS", meta=(TitleProperty="EditorFriendlyName"))
|
||||
TArray<FGGS_SocketRelationship> Relationships;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Pre-save processing for editor.
|
||||
* 编辑器预保存处理。
|
||||
*/
|
||||
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||
#endif
|
||||
};
|
||||
Reference in New Issue
Block a user