完成复活逻辑

This commit is contained in:
dd
2026-06-04 01:57:25 +08:00
parent 8306418f13
commit 531a648a88
15 changed files with 447 additions and 4 deletions
+1 -1
View File
@@ -14,4 +14,4 @@ NetIndexFirstBitSegment=16
+GameplayTagList=(Tag="Attributes.Item.FireBall",DevComment="")
+GameplayTagList=(Tag="Attributes.Vital.Health",DevComment="")
+GameplayTagList=(Tag="Item.Held.Fireball",DevComment="")
+GameplayTagList=(Tag="State.Dead",DevComment="")
Binary file not shown.
Binary file not shown.
Binary file not shown.
+133
View File
@@ -5,9 +5,22 @@
#include "AbilitySystemComponent.h"
#include "Abilities/GameplayAbility.h"
#include "Components/CapsuleComponent.h"
#include "Components/MainAbilitySystemComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/World.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameplayEffect.h"
#include "MainAttributeSet.h"
#include "TimerManager.h"
namespace
{
FGameplayTag GetDeadTag()
{
return FGameplayTag::RequestGameplayTag(FName(TEXT("State.Dead")));
}
}
// Sets default values
ABotCharacter::ABotCharacter()
@@ -30,6 +43,17 @@ void ABotCharacter::BeginPlay()
InitAbilityActorInfo();
}
void ABotCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UnbindHealthChangedDelegate();
if (UWorld* World = GetWorld())
{
World->GetTimerManager().ClearTimer(RespawnTimer);
}
Super::EndPlay(EndPlayReason);
}
void ABotCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
@@ -63,7 +87,9 @@ void ABotCharacter::InitAbilityActorInfo()
return;
}
UnbindHealthChangedDelegate();
AbilitySystemComponent->InitAbilityActorInfo(this, this);
BindHealthChangedDelegate();
if (HasAuthority())
{
@@ -72,6 +98,39 @@ void ABotCharacter::InitAbilityActorInfo()
}
}
void ABotCharacter::BindHealthChangedDelegate()
{
if (!AbilitySystemComponent || HealthChangedHandle.IsValid())
{
return;
}
HealthChangedHandle = AbilitySystemComponent
->GetGameplayAttributeValueChangeDelegate(UMainAttributeSet::GetHealthAttribute())
.AddUObject(this, &ThisClass::OnHealthChanged);
}
void ABotCharacter::UnbindHealthChangedDelegate()
{
if (!AbilitySystemComponent || !HealthChangedHandle.IsValid())
{
return;
}
AbilitySystemComponent
->GetGameplayAttributeValueChangeDelegate(UMainAttributeSet::GetHealthAttribute())
.Remove(HealthChangedHandle);
HealthChangedHandle.Reset();
}
void ABotCharacter::OnHealthChanged(const FOnAttributeChangeData& Data)
{
if (HasAuthority() && Data.NewValue <= 0.f)
{
HandleDeath();
}
}
void ABotCharacter::GiveDefaultAbilities()
{
if (bHasGivenDefaultAbilities || !AbilitySystemComponent)
@@ -110,3 +169,77 @@ void ABotCharacter::ApplyDefaultAttributeEffect()
}
}
void ABotCharacter::HandleDeath()
{
if (bHasHandledDeath)
{
return;
}
bHasHandledDeath = true;
if (AbilitySystemComponent)
{
AbilitySystemComponent->SetLooseGameplayTagCount(GetDeadTag(), 1, EGameplayTagReplicationState::TagOnly);
}
MulticastHandleDeath();
if (UWorld* World = GetWorld())
{
if (RespawnDelay <= 0.f)
{
HandleRespawn();
}
else
{
World->GetTimerManager().SetTimer(RespawnTimer, this, &ThisClass::HandleRespawn, RespawnDelay, false);
}
}
}
void ABotCharacter::MulticastHandleDeath_Implementation()
{
if (bHasNotifiedDeath)
{
return;
}
bHasNotifiedDeath = true;
GetCharacterMovement()->DisableMovement();
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
GetMesh()->SetHiddenInGame(true, true);
BP_OnDeath();
}
void ABotCharacter::HandleRespawn()
{
if (!HasAuthority())
{
return;
}
if (AbilitySystemComponent)
{
AbilitySystemComponent->SetLooseGameplayTagCount(GetDeadTag(), 0, EGameplayTagReplicationState::TagOnly);
const float MaxHealth = AbilitySystemComponent->GetNumericAttribute(UMainAttributeSet::GetMaxHealthAttribute());
AbilitySystemComponent->SetNumericAttributeBase(UMainAttributeSet::GetHealthAttribute(), MaxHealth);
}
bHasHandledDeath = false;
bHasNotifiedDeath = false;
MulticastRestoreRespawnState();
}
void ABotCharacter::MulticastRestoreRespawnState_Implementation()
{
bHasNotifiedDeath = false;
GetCharacterMovement()->SetMovementMode(MOVE_Walking);
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
GetMesh()->SetHiddenInGame(false, true);
}
+52 -2
View File
@@ -3,12 +3,21 @@
#include "MainAttributeSet.h"
#include "GameplayEffectExtension.h"
#include "Net/UnrealNetwork.h"
namespace
{
FGameplayTag GetDeadTag()
{
return FGameplayTag::RequestGameplayTag(FName(TEXT("State.Dead")));
}
}
UMainAttributeSet::UMainAttributeSet()
{
InitHealth(1000.f);
InitMaxHealth(1000.f);
InitHealth(50.f);
InitMaxHealth(50.f);
}
void UMainAttributeSet::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
@@ -18,6 +27,47 @@ void UMainAttributeSet::GetLifetimeReplicatedProps(TArray<class FLifetimePropert
DOREPLIFETIME_CONDITION_NOTIFY(UMainAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
}
void UMainAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
}
else if (Attribute == GetMaxHealthAttribute())
{
NewValue = FMath::Max(NewValue, 0.f);
}
}
void UMainAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
if (GetHealth() <= 0.f)
{
if (UAbilitySystemComponent* ASC = GetOwningAbilitySystemComponent())
{
AActor* OwningActor = ASC->GetOwnerActor();
if (OwningActor && OwningActor->HasAuthority())
{
ASC->SetLooseGameplayTagCount(GetDeadTag(), 1, EGameplayTagReplicationState::TagOnly);
}
}
}
}
else if (Data.EvaluatedData.Attribute == GetMaxHealthAttribute())
{
SetMaxHealth(FMath::Max(GetMaxHealth(), 0.f));
SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
}
}
void UMainAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth) const
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UMainAttributeSet, Health, OldHealth);
+24
View File
@@ -4,6 +4,7 @@
#include "CoreMinimal.h"
#include "AbilitySystemInterface.h"
#include "Engine/TimerHandle.h"
#include "GameFramework/Character.h"
#include "BotCharacter.generated.h"
@@ -11,6 +12,7 @@ class UAbilitySystemComponent;
class UAttributeSet;
class UGameplayAbility;
class UGameplayEffect;
struct FOnAttributeChangeData;
UCLASS()
class PVPDEMO_API ABotCharacter : public ACharacter, public IAbilitySystemInterface
@@ -29,6 +31,7 @@ public:
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GAS")
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
@@ -42,6 +45,12 @@ protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="GAS")
TSubclassOf<UGameplayEffect> DefaultAttributeEffect;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Respawn", meta=(ClampMin="0", Units="s"))
float RespawnDelay = 2.f;
UFUNCTION(BlueprintImplementableEvent, Category="Death")
void BP_OnDeath();
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
@@ -51,9 +60,24 @@ public:
private:
void InitAbilityActorInfo();
void BindHealthChangedDelegate();
void UnbindHealthChangedDelegate();
void OnHealthChanged(const FOnAttributeChangeData& Data);
void GiveDefaultAbilities();
void ApplyDefaultAttributeEffect();
void HandleDeath();
void HandleRespawn();
UFUNCTION(NetMulticast, Reliable)
void MulticastHandleDeath();
UFUNCTION(NetMulticast, Reliable)
void MulticastRestoreRespawnState();
bool bHasGivenDefaultAbilities = false;
bool bHasAppliedDefaultAttributeEffect = false;
bool bHasHandledDeath = false;
bool bHasNotifiedDeath = false;
FDelegateHandle HealthChangedHandle;
FTimerHandle RespawnTimer;
};
+2
View File
@@ -18,6 +18,8 @@ public:
UMainAttributeSet();
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
+113
View File
@@ -4,18 +4,30 @@
#include "Engine/LocalPlayer.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/Controller.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Engine/World.h"
#include "InputActionValue.h"
#include "MainAttributeSet.h"
#include "MainPlayerState.h"
#include "PvPDemo.h"
#include "PvPDemoGameMode.h"
#include "AbilitySystemComponent.h"
#include "AttributeSet.h"
#include "Kismet/KismetSystemLibrary.h"
namespace
{
FGameplayTag GetDeadTag()
{
return FGameplayTag::RequestGameplayTag(FName(TEXT("State.Dead")));
}
}
APvPDemoCharacter::APvPDemoCharacter()
{
@@ -71,6 +83,13 @@ void APvPDemoCharacter::OnRep_PlayerState()
InitAbilityActorInfo();
}
void APvPDemoCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UnbindHealthChangedDelegate();
Super::EndPlay(EndPlayReason);
}
void APvPDemoCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
// Set up action bindings
@@ -173,11 +192,14 @@ void APvPDemoCharacter::InitAbilityActorInfo()
{
AMainPlayerState* MainPlayerState = GetPlayerState<AMainPlayerState>();
check(MainPlayerState);
UnbindHealthChangedDelegate();
// 设置ASC的所有者,OwnerActor是所有者,AvatarActor是游戏中的形象
MainPlayerState->GetAbilitySystemComponent()->InitAbilityActorInfo(MainPlayerState, this);
AbilitySystemComponent = MainPlayerState->GetAbilitySystemComponent();
AttributeSet = MainPlayerState->GetAttributeSet();
BindHealthChangedDelegate();
if (HasAuthority() && AbilitySystemComponent && HeldItem)
{
@@ -187,6 +209,97 @@ void APvPDemoCharacter::InitAbilityActorInfo()
}
}
void APvPDemoCharacter::BindHealthChangedDelegate()
{
if (!AbilitySystemComponent || HealthChangedHandle.IsValid())
{
return;
}
HealthChangedHandle = AbilitySystemComponent
->GetGameplayAttributeValueChangeDelegate(UMainAttributeSet::GetHealthAttribute())
.AddUObject(this, &ThisClass::OnHealthChanged);
}
void APvPDemoCharacter::UnbindHealthChangedDelegate()
{
if (!AbilitySystemComponent || !HealthChangedHandle.IsValid())
{
return;
}
AbilitySystemComponent
->GetGameplayAttributeValueChangeDelegate(UMainAttributeSet::GetHealthAttribute())
.Remove(HealthChangedHandle);
HealthChangedHandle.Reset();
}
void APvPDemoCharacter::OnHealthChanged(const FOnAttributeChangeData& Data)
{
if (HasAuthority() && Data.NewValue <= 0.f)
{
HandleDeath();
}
}
void APvPDemoCharacter::HandleDeath()
{
if (bHasHandledDeath)
{
return;
}
bHasHandledDeath = true;
if (AbilitySystemComponent)
{
AbilitySystemComponent->SetLooseGameplayTagCount(GetDeadTag(), 1, EGameplayTagReplicationState::TagOnly);
}
if (UWorld* World = GetWorld())
{
if (APvPDemoGameMode* GameMode = World->GetAuthGameMode<APvPDemoGameMode>())
{
GameMode->RequestPlayerRespawn(GetController(), this, RespawnDelay);
}
}
MulticastHandleDeath();
}
void APvPDemoCharacter::MulticastHandleDeath_Implementation()
{
if (bHasNotifiedDeath)
{
return;
}
bHasNotifiedDeath = true;
GetCharacterMovement()->DisableMovement();
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
GetMesh()->SetHiddenInGame(true, true);
BP_OnDeath();
}
void APvPDemoCharacter::RestoreRespawnState()
{
if (!HasAuthority())
{
return;
}
MulticastRestoreRespawnState();
}
void APvPDemoCharacter::MulticastRestoreRespawnState_Implementation()
{
GetCharacterMovement()->SetMovementMode(MOVE_Walking);
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
GetMesh()->SetHiddenInGame(false, true);
}
bool APvPDemoCharacter::AddHeldItemTag(FGameplayTag Tag)
{
if (!HasAuthority())
+25
View File
@@ -14,6 +14,7 @@ class UInputAction;
class UAbilitySystemComponent;
class UAttributeSet;
struct FInputActionValue;
struct FOnAttributeChangeData;
class UGameplayAbility;
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
@@ -79,6 +80,7 @@ protected:
/** Initialize input action bindings */
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
protected:
@@ -109,6 +111,10 @@ public:
// UFUNCTION(BlueprintCallable, Category="Input")
// virtual void DoAttack();
protected:
UFUNCTION(BlueprintImplementableEvent, Category="Death")
void BP_OnDeath();
public:
/** Returns CameraBoom subobject **/
@@ -119,14 +125,33 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Items")
TSubclassOf<UGameplayAbility> HeldItem;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Respawn", meta=(ClampMin="0", Units="s"))
float RespawnDelay = 2.f;
void RestoreRespawnState();
private:
void InitAbilityActorInfo();
void BindHealthChangedDelegate();
void UnbindHealthChangedDelegate();
void OnHealthChanged(const FOnAttributeChangeData& Data);
void HandleDeath();
UFUNCTION(NetMulticast, Reliable)
void MulticastHandleDeath();
UFUNCTION(NetMulticast, Reliable)
void MulticastRestoreRespawnState();
UFUNCTION(BlueprintCallable)
bool AddHeldItemTag(FGameplayTag Tag);
UFUNCTION(BlueprintCallable)
bool RemoveHeldItemTag(FGameplayTag Tag);
FDelegateHandle HealthChangedHandle;
bool bHasHandledDeath = false;
bool bHasNotifiedDeath = false;
};
+85
View File
@@ -2,7 +2,92 @@
#include "PvPDemoGameMode.h"
#include "AbilitySystemComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/Pawn.h"
#include "MainAttributeSet.h"
#include "MainPlayerState.h"
#include "PvPDemoCharacter.h"
#include "TimerManager.h"
namespace
{
FGameplayTag GetDeadTag()
{
return FGameplayTag::RequestGameplayTag(FName(TEXT("State.Dead")));
}
}
APvPDemoGameMode::APvPDemoGameMode()
{
// stub
}
void APvPDemoGameMode::RequestPlayerRespawn_Implementation(AController* Controller, APawn* DeadPawn, float DelaySeconds)
{
if (!Controller)
{
return;
}
const TWeakObjectPtr<AController> ControllerWeak = Controller;
const TWeakObjectPtr<APawn> DeadPawnWeak = DeadPawn;
FTimerDelegate RespawnDelegate;
RespawnDelegate.BindWeakLambda(this, [this, ControllerWeak, DeadPawnWeak]()
{
RespawnPlayer(ControllerWeak.Get(), DeadPawnWeak.Get());
});
FTimerHandle RespawnTimer;
if (DelaySeconds <= 0.f)
{
RespawnDelegate.ExecuteIfBound();
return;
}
GetWorldTimerManager().SetTimer(RespawnTimer, RespawnDelegate, DelaySeconds, false);
}
void APvPDemoGameMode::RespawnPlayer(AController* Controller, APawn* DeadPawn)
{
if (!Controller)
{
return;
}
ResetRespawnAttributes(Controller);
APawn* OldPawn = DeadPawn;
if (OldPawn && Controller->GetPawn() == OldPawn)
{
Controller->UnPossess();
}
RestartPlayer(Controller);
if (APvPDemoCharacter* RespawnedCharacter = Cast<APvPDemoCharacter>(Controller->GetPawn()))
{
RespawnedCharacter->RestoreRespawnState();
}
if (OldPawn && OldPawn != Controller->GetPawn() && Controller->GetPawn())
{
OldPawn->Destroy();
}
}
void APvPDemoGameMode::ResetRespawnAttributes(AController* Controller) const
{
const AMainPlayerState* MainPlayerState = Controller ? Controller->GetPlayerState<AMainPlayerState>() : nullptr;
UAbilitySystemComponent* ASC = MainPlayerState ? MainPlayerState->GetAbilitySystemComponent() : nullptr;
if (!ASC)
{
return;
}
ASC->SetLooseGameplayTagCount(GetDeadTag(), 0, EGameplayTagReplicationState::TagOnly);
const float MaxHealth = ASC->GetNumericAttribute(UMainAttributeSet::GetMaxHealthAttribute());
ASC->SetNumericAttributeBase(UMainAttributeSet::GetHealthAttribute(), MaxHealth);
}
+12 -1
View File
@@ -6,6 +6,9 @@
#include "GameFramework/GameModeBase.h"
#include "PvPDemoGameMode.generated.h"
class AController;
class APawn;
/**
* Simple GameMode for a third person game
*/
@@ -18,7 +21,15 @@ public:
/** Constructor */
APvPDemoGameMode();
UFUNCTION(BlueprintAuthorityOnly, BlueprintNativeEvent, Category="Respawn")
void RequestPlayerRespawn(AController* Controller, APawn* DeadPawn, float DelaySeconds);
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="Respawn")
void RespawnPlayer(AController* Controller, APawn* DeadPawn);
private:
void ResetRespawnAttributes(AController* Controller) const;
};