第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Abilities/GGA_GameplayAbility.h"
#include "GGA_GamePhaseAbility.generated.h"
/**
* UGGA_GamePhaseAbility
*
* The base gameplay ability for any ability that is used to change the active game phase.
*/
UCLASS(Abstract, HideCategories = Input)
class UGGA_GamePhaseAbility : public UGGA_GameplayAbility
{
GENERATED_BODY()
public:
UGGA_GamePhaseAbility(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
const FGameplayTag& GetGamePhaseTag() const { return GamePhaseTag; }
#if WITH_EDITOR
#if ENGINE_MINOR_VERSION > 2
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
#endif
#endif
protected:
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
protected:
// Defines the game phase that this game phase ability is part of. So for example,
// if your game phase is GamePhase.RoundStart, then it will cancel all sibling phases.
// So if you had a phase such as GamePhase.WaitingToStart that was active, starting
// the ability part of RoundStart would end WaitingToStart. However to get nested behaviors
// you can also nest the phases. So for example, GamePhase.Playing.NormalPlay, is a sub-phase
// of the parent GamePhase.Playing, so changing the sub-phase to GamePhase.Playing.SuddenDeath,
// would stop any ability tied to GamePhase.Playing.*, but wouldn't end any ability
// tied to the GamePhase.Playing phase.
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GGA|GamePhase")
FGameplayTag GamePhaseTag;
};

View File

@@ -0,0 +1,7 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Logging/LogMacros.h"
DECLARE_LOG_CATEGORY_EXTERN(LogGGA_GamePhase, Log, All);

View File

@@ -0,0 +1,103 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "GameplayAbilitySpecHandle.h"
#include "GameplayTagContainer.h"
#include "Subsystems/WorldSubsystem.h"
#include "GGA_GamePhaseSubsystem.generated.h"
class UGGA_GamePhaseAbility;
DECLARE_DYNAMIC_DELEGATE_OneParam(FGGamePhaseDynamicDelegate, const UGGA_GamePhaseAbility*, Phase);
DECLARE_DELEGATE_OneParam(FGGamePhaseDelegate, const UGGA_GamePhaseAbility* Phase);
DECLARE_DYNAMIC_DELEGATE_OneParam(FGGamePhaseTagDynamicDelegate, const FGameplayTag&, PhaseTag);
DECLARE_DELEGATE_OneParam(FGGamePhaseTagDelegate, const FGameplayTag& PhaseTag);
// Match rule for message receivers
UENUM(BlueprintType)
enum class EGGA_PhaseTagMatchType : uint8
{
// An exact match will only receive messages with exactly the same channel
// (e.g., registering for "A.B" will match a broadcast of A.B but not A.B.C)
ExactMatch,
// A partial match will receive any messages rooted in the same channel
// (e.g., registering for "A.B" will match a broadcast of A.B as well as A.B.C)
PartialMatch
};
/** Subsystem for managing game phases using gameplay tags in a nested manner, which allows parent and child
* phases to be active at the same time, but not sibling phases.
* Example: Game.Playing and Game.Playing.WarmUp can coexist, but Game.Playing and Game.ShowingScore cannot.
* When a new phase is started, any active phases that are not ancestors will be ended.
* Example: if Game.Playing and Game.Playing.CaptureTheFlag are active when Game.Playing.PostGame is started,
* Game.Playing will remain active, while Game.Playing.CaptureTheFlag will end.
*/
UCLASS()
class UGGA_GamePhaseSubsystem : public UWorldSubsystem
{
GENERATED_BODY()
public:
UGGA_GamePhaseSubsystem();
//virtual void PostInitialize() override;
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
void StartPhase(TSubclassOf<UGGA_GamePhaseAbility> PhaseAbility, FGGamePhaseDelegate PhaseEndedCallback = FGGamePhaseDelegate());
//TODO Return a handle so folks can delete these. They will just grow until the world resets.
//TODO Should we just occasionally clean these observers up? It's not as if everyone will properly unhook them even if there is a handle.
void WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EGGA_PhaseTagMatchType MatchType, const FGGamePhaseTagDelegate& WhenPhaseActive);
void WhenPhaseEnds(FGameplayTag PhaseTag, EGGA_PhaseTagMatchType MatchType, const FGGamePhaseTagDelegate& WhenPhaseEnd);
UFUNCTION(BlueprintCallable, Category = "GGA|GamePhase", BlueprintAuthorityOnly, BlueprintPure = false, meta = (AutoCreateRefTerm = "PhaseTag"))
bool IsPhaseActive(const FGameplayTag& PhaseTag) const;
protected:
virtual bool DoesSupportWorldType(const EWorldType::Type WorldType) const override;
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "GGA|GamePhase", meta = (DisplayName="Start Phase", AutoCreateRefTerm = "PhaseEnded"))
void K2_StartPhase(TSubclassOf<UGGA_GamePhaseAbility> Phase, const FGGamePhaseDynamicDelegate& PhaseEnded);
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "GGA|GamePhase", meta = (DisplayName = "When Phase Starts or Is Active", AutoCreateRefTerm = "WhenPhaseActive"))
void K2_WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EGGA_PhaseTagMatchType MatchType, FGGamePhaseTagDynamicDelegate WhenPhaseActive);
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "GGA|GamePhase", meta = (DisplayName = "When Phase Ends", AutoCreateRefTerm = "WhenPhaseEnd"))
void K2_WhenPhaseEnds(FGameplayTag PhaseTag, EGGA_PhaseTagMatchType MatchType, FGGamePhaseTagDynamicDelegate WhenPhaseEnd);
void OnBeginPhase(const UGGA_GamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle);
void OnEndPhase(const UGGA_GamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle);
private:
struct FGGamePhaseEntry
{
public:
FGameplayTag PhaseTag;
FGGamePhaseDelegate PhaseEndedCallback;
};
TMap<FGameplayAbilitySpecHandle, FGGamePhaseEntry> ActivePhaseMap;
struct FGPhaseObserver
{
public:
bool IsMatch(const FGameplayTag& ComparePhaseTag) const;
FGameplayTag PhaseTag;
EGGA_PhaseTagMatchType MatchType = EGGA_PhaseTagMatchType::ExactMatch;
FGGamePhaseTagDelegate PhaseCallback;
};
TArray<FGPhaseObserver> PhaseStartObservers;
TArray<FGPhaseObserver> PhaseEndObservers;
friend class UGGA_GamePhaseAbility;
};