From 4677d04b747b9557e73711e8be44ce085c7f617e Mon Sep 17 00:00:00 2001 From: cit110 <840418418@qq.com> Date: Sun, 26 Apr 2026 15:45:03 +0800 Subject: [PATCH] Add core game mode framework --- Config/DefaultGame.ini | 4 + Config/DefaultPHYCore.ini | 10 ++ .../PHY/Private/Characters/PHYAICharacter.cpp | 4 + .../Private/Game/PHYGameFrameworkSettings.cpp | 22 ++++ Source/PHY/Private/Game/PHYGameModeBase.cpp | 116 ++++++++++++++++++ Source/PHY/Private/Game/PHYGameState.cpp | 36 ++++++ Source/PHY/Private/Game/PHYHUD.cpp | 28 +++++ .../Public/Game/PHYGameFrameworkSettings.h | 63 ++++++++++ Source/PHY/Public/Game/PHYGameModeBase.h | 54 ++++++++ Source/PHY/Public/Game/PHYGameState.h | 43 +++++++ Source/PHY/Public/Game/PHYHUD.h | 39 ++++++ 11 files changed, 419 insertions(+) create mode 100644 Source/PHY/Private/Game/PHYGameFrameworkSettings.cpp create mode 100644 Source/PHY/Private/Game/PHYGameModeBase.cpp create mode 100644 Source/PHY/Private/Game/PHYGameState.cpp create mode 100644 Source/PHY/Private/Game/PHYHUD.cpp create mode 100644 Source/PHY/Public/Game/PHYGameFrameworkSettings.h create mode 100644 Source/PHY/Public/Game/PHYGameModeBase.h create mode 100644 Source/PHY/Public/Game/PHYGameState.h create mode 100644 Source/PHY/Public/Game/PHYHUD.h diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index 65b060d..26a1e4e 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -6,3 +6,7 @@ CommonButtonAcceptKeyHandling=TriggerClick ProjectID=E7C26E1F4D195F0DBE49C2A3E5017988 CopyrightNotice= +[/Script/EngineSettings.GameMapsSettings] +GlobalDefaultGameMode=/Script/PHY.PHYGameModeBase +GlobalDefaultServerGameMode=/Script/PHY.PHYGameModeBase + diff --git a/Config/DefaultPHYCore.ini b/Config/DefaultPHYCore.ini index 540395d..60060d5 100644 --- a/Config/DefaultPHYCore.ini +++ b/Config/DefaultPHYCore.ini @@ -3,3 +3,13 @@ ConfigVersion=1 bMultiplayerFirst=True bPreferCodeAndConfig=True TargetPlatformName=Win64 + +[/Script/PHY.PHYGameFrameworkSettings] +DefaultPawnClass="/Script/PHY.PHYPlayerCharacter" +PlayerControllerClass="/Script/PHY.PHYPlayerController" +PlayerStateClass="/Script/PHY.PHYPlayerState" +GameStateClass="/Script/PHY.PHYGameState" +HUDClass="/Script/PHY.PHYHUD" +SpectatorClass="/Script/Engine.SpectatorPawn" +bUseSeamlessTravel=True +bStartPlayersAsSpectators=False diff --git a/Source/PHY/Private/Characters/PHYAICharacter.cpp b/Source/PHY/Private/Characters/PHYAICharacter.cpp index 3b1f5b8..f711892 100644 --- a/Source/PHY/Private/Characters/PHYAICharacter.cpp +++ b/Source/PHY/Private/Characters/PHYAICharacter.cpp @@ -10,10 +10,14 @@ #include "Class/PHYClassComponent.h" #include "Class/PHYClassSettings.h" #include "GGA_AbilitySystemComponent.h" +#include "AI/PHYAIController.h" APHYAICharacter::APHYAICharacter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + AIControllerClass = APHYAIController::StaticClass(); + AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned; + AbilitySystemComponent = CreateDefaultSubobject(TEXT("AbilitySystemComponent")); AbilitySystemComponent->SetIsReplicated(true); AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed); diff --git a/Source/PHY/Private/Game/PHYGameFrameworkSettings.cpp b/Source/PHY/Private/Game/PHYGameFrameworkSettings.cpp new file mode 100644 index 0000000..94f2f14 --- /dev/null +++ b/Source/PHY/Private/Game/PHYGameFrameworkSettings.cpp @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Game/PHYGameFrameworkSettings.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameFrameworkSettings) + +#include "Characters/PHYPlayerCharacter.h" +#include "Game/PHYGameState.h" +#include "Game/PHYHUD.h" +#include "GameFramework/SpectatorPawn.h" +#include "Player/PHYPlayerController.h" +#include "Player/PHYPlayerState.h" + +UPHYGameFrameworkSettings::UPHYGameFrameworkSettings() +{ + DefaultPawnClass = APHYPlayerCharacter::StaticClass(); + PlayerControllerClass = APHYPlayerController::StaticClass(); + PlayerStateClass = APHYPlayerState::StaticClass(); + GameStateClass = APHYGameState::StaticClass(); + HUDClass = APHYHUD::StaticClass(); + SpectatorClass = ASpectatorPawn::StaticClass(); +} diff --git a/Source/PHY/Private/Game/PHYGameModeBase.cpp b/Source/PHY/Private/Game/PHYGameModeBase.cpp new file mode 100644 index 0000000..15d6bd1 --- /dev/null +++ b/Source/PHY/Private/Game/PHYGameModeBase.cpp @@ -0,0 +1,116 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Game/PHYGameModeBase.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameModeBase) + +#include "Characters/PHYPlayerCharacter.h" +#include "Game/PHYGameFrameworkSettings.h" +#include "Game/PHYGameState.h" +#include "Game/PHYHUD.h" +#include "GameFramework/SpectatorPawn.h" +#include "Player/PHYPlayerController.h" +#include "Player/PHYPlayerState.h" + +namespace +{ + template + void ApplyConfiguredClass(const TSoftClassPtr& ConfiguredClass, TSubclassOf& TargetClass) + { + if (ConfiguredClass.IsNull()) + { + return; + } + + if (UClass* LoadedClass = ConfiguredClass.LoadSynchronous()) + { + TargetClass = LoadedClass; + } + } +} + +APHYGameModeBase::APHYGameModeBase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + DefaultPawnClass = APHYPlayerCharacter::StaticClass(); + PlayerControllerClass = APHYPlayerController::StaticClass(); + PlayerStateClass = APHYPlayerState::StaticClass(); + GameStateClass = APHYGameState::StaticClass(); + HUDClass = APHYHUD::StaticClass(); + SpectatorClass = ASpectatorPawn::StaticClass(); + + bUseSeamlessTravel = true; + bStartPlayersAsSpectators = false; +} + +void APHYGameModeBase::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) +{ + ApplyGameFrameworkSettings(); + + Super::InitGame(MapName, Options, ErrorMessage); +} + +void APHYGameModeBase::InitGameState() +{ + Super::InitGameState(); + + SetGameFrameworkReady(false); +} + +void APHYGameModeBase::StartPlay() +{ + Super::StartPlay(); + + SetGameFrameworkReady(true); +} + +void APHYGameModeBase::PostLogin(APlayerController* NewPlayer) +{ + Super::PostLogin(NewPlayer); + + // 玩家登录后的队伍、职业、存档恢复由后续对应系统在这里挂接。 +} + +void APHYGameModeBase::Logout(AController* Exiting) +{ + // 玩家退出前的持久化和队伍清理由后续对应系统在这里挂接。 + Super::Logout(Exiting); +} + +UClass* APHYGameModeBase::GetDefaultPawnClassForController_Implementation(AController* InController) +{ + (void)InController; + return DefaultPawnClass ? DefaultPawnClass.Get() : Super::GetDefaultPawnClassForController_Implementation(InController); +} + +APHYGameState* APHYGameModeBase::GetPHYGameState() const +{ + return GetGameState(); +} + +void APHYGameModeBase::ApplyGameFrameworkSettings() +{ + const UPHYGameFrameworkSettings* Settings = GetDefault(); + if (!Settings) + { + return; + } + + ApplyConfiguredClass(Settings->DefaultPawnClass, DefaultPawnClass); + ApplyConfiguredClass(Settings->PlayerControllerClass, PlayerControllerClass); + ApplyConfiguredClass(Settings->PlayerStateClass, PlayerStateClass); + ApplyConfiguredClass(Settings->GameStateClass, GameStateClass); + ApplyConfiguredClass(Settings->HUDClass, HUDClass); + ApplyConfiguredClass(Settings->SpectatorClass, SpectatorClass); + + bUseSeamlessTravel = Settings->bUseSeamlessTravel; + bStartPlayersAsSpectators = Settings->bStartPlayersAsSpectators; +} + +void APHYGameModeBase::SetGameFrameworkReady(const bool bNewReady) +{ + if (APHYGameState* PHYGameState = GetPHYGameState()) + { + PHYGameState->SetGameFrameworkReady(bNewReady); + } +} diff --git a/Source/PHY/Private/Game/PHYGameState.cpp b/Source/PHY/Private/Game/PHYGameState.cpp new file mode 100644 index 0000000..1d5a7ee --- /dev/null +++ b/Source/PHY/Private/Game/PHYGameState.cpp @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Game/PHYGameState.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameState) + +#include "Net/UnrealNetwork.h" + +APHYGameState::APHYGameState(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicates = true; +} + +void APHYGameState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ThisClass, bGameFrameworkReady); +} + +void APHYGameState::SetGameFrameworkReady(const bool bNewReady) +{ + if (!HasAuthority()) + { + return; + } + + bGameFrameworkReady = bNewReady; + OnRep_GameFrameworkReady(); +} + +void APHYGameState::OnRep_GameFrameworkReady() +{ + // 预留给 HUD、UI 或战局阶段系统监听框架就绪状态。 +} diff --git a/Source/PHY/Private/Game/PHYHUD.cpp b/Source/PHY/Private/Game/PHYHUD.cpp new file mode 100644 index 0000000..07f26a7 --- /dev/null +++ b/Source/PHY/Private/Game/PHYHUD.cpp @@ -0,0 +1,28 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Game/PHYHUD.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYHUD) + +APHYHUD::APHYHUD(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void APHYHUD::BeginPlay() +{ + Super::BeginPlay(); + + InitializeHUDForPlayer(GetOwningPlayerController()); +} + +void APHYHUD::InitializeHUDForPlayer(APlayerController* OwningPlayerController) +{ + if (!OwningPlayerController || bHUDInitialized) + { + return; + } + + // UI 专家后续在这里接入 GenericUISystem/CommonUI 的根层级。 + bHUDInitialized = true; +} diff --git a/Source/PHY/Public/Game/PHYGameFrameworkSettings.h b/Source/PHY/Public/Game/PHYGameFrameworkSettings.h new file mode 100644 index 0000000..455295c --- /dev/null +++ b/Source/PHY/Public/Game/PHYGameFrameworkSettings.h @@ -0,0 +1,63 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "UObject/SoftObjectPtr.h" +#include "PHYGameFrameworkSettings.generated.h" + +class AGameStateBase; +class AHUD; +class APawn; +class APlayerController; +class APlayerState; +class ASpectatorPawn; + +/** + * @brief PHY 游戏框架默认类配置。 + * + * GameMode 会优先使用这里配置的类,未配置时回退到项目 C++ 原生骨架。 + * 这样后续需要替换为蓝图子类时,只需要改配置,不需要改业务代码。 + */ +UCLASS(Config=PHYCore, DefaultConfig) +class PHY_API UPHYGameFrameworkSettings : public UObject +{ + GENERATED_BODY() + +public: + /** @brief 构造默认 C++ 框架类引用。 */ + UPHYGameFrameworkSettings(); + + /** @brief 默认玩家 Pawn 类。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game") + TSoftClassPtr DefaultPawnClass; + + /** @brief 默认玩家控制器类。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game") + TSoftClassPtr PlayerControllerClass; + + /** @brief 默认玩家状态类。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game") + TSoftClassPtr PlayerStateClass; + + /** @brief 默认游戏状态类。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game") + TSoftClassPtr GameStateClass; + + /** @brief 默认 HUD 类。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game") + TSoftClassPtr HUDClass; + + /** @brief 默认旁观者 Pawn 类。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game") + TSoftClassPtr SpectatorClass; + + /** @brief 是否启用无缝旅行,默认按多人优先启用。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game") + bool bUseSeamlessTravel = true; + + /** @brief 是否让玩家默认以旁观者身份开始。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Game") + bool bStartPlayersAsSpectators = false; +}; diff --git a/Source/PHY/Public/Game/PHYGameModeBase.h b/Source/PHY/Public/Game/PHYGameModeBase.h new file mode 100644 index 0000000..737026a --- /dev/null +++ b/Source/PHY/Public/Game/PHYGameModeBase.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "PHYGameModeBase.generated.h" + +class APHYGameState; + +/** + * @brief PHY 游戏模式根类。 + * + * GameMode 是服务端玩法框架入口,负责绑定默认 Pawn、Controller、PlayerState、 + * GameState 和 HUD。具体玩法系统仍通过 Character、ASC、输入和组件完成。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYGameModeBase : public AGameModeBase +{ + GENERATED_BODY() + +public: + /** @brief 构造默认项目框架类。 */ + APHYGameModeBase(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief 初始化地图参数并应用项目框架配置。 */ + virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; + + /** @brief GameState 初始化后同步项目框架状态。 */ + virtual void InitGameState() override; + + /** @brief 游戏正式开始时标记框架就绪。 */ + virtual void StartPlay() override; + + /** @brief 玩家登录后执行项目级初始化扩展点。 */ + virtual void PostLogin(APlayerController* NewPlayer) override; + + /** @brief 玩家退出时执行项目级清理扩展点。 */ + virtual void Logout(AController* Exiting) override; + + /** @brief 按控制器返回默认 Pawn 类,后续可扩展职业或模式差异。 */ + virtual UClass* GetDefaultPawnClassForController_Implementation(AController* InController) override; + + /** @brief 获取强类型 PHY GameState。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Game") + APHYGameState* GetPHYGameState() const; + +protected: + /** @brief 从 UPHYGameFrameworkSettings 应用可配置框架类。 */ + virtual void ApplyGameFrameworkSettings(); + + /** @brief 标记 GameState 框架是否就绪。 */ + virtual void SetGameFrameworkReady(bool bNewReady); +}; diff --git a/Source/PHY/Public/Game/PHYGameState.h b/Source/PHY/Public/Game/PHYGameState.h new file mode 100644 index 0000000..5c593c4 --- /dev/null +++ b/Source/PHY/Public/Game/PHYGameState.h @@ -0,0 +1,43 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameStateBase.h" +#include "PHYGameState.generated.h" + +/** + * @brief PHY 游戏状态。 + * + * 当前只承载项目框架是否完成初始化的复制状态,后续战局阶段、队伍比分、 + * 复活队列等多人状态都应优先汇总到这里,而不是散落在关卡蓝图。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYGameState : public AGameStateBase +{ + GENERATED_BODY() + +public: + /** @brief 构造游戏状态并启用复制。 */ + APHYGameState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief 声明需要复制的属性。 */ + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + /** @brief 查询游戏框架是否已由服务端标记为就绪。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Game") + bool IsGameFrameworkReady() const { return bGameFrameworkReady; } + + /** @brief 服务端设置游戏框架就绪状态。 */ + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="PHY|Game") + void SetGameFrameworkReady(bool bNewReady); + +protected: + /** @brief 客户端接收框架就绪状态后的扩展点。 */ + UFUNCTION() + virtual void OnRep_GameFrameworkReady(); + + /** @brief 服务端确认 GameMode 已完成基础类和玩家流程初始化。 */ + UPROPERTY(ReplicatedUsing=OnRep_GameFrameworkReady, BlueprintReadOnly, Category="PHY|Game") + bool bGameFrameworkReady = false; +}; diff --git a/Source/PHY/Public/Game/PHYHUD.h b/Source/PHY/Public/Game/PHYHUD.h new file mode 100644 index 0000000..797390c --- /dev/null +++ b/Source/PHY/Public/Game/PHYHUD.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/HUD.h" +#include "PHYHUD.generated.h" + +/** + * @brief PHY HUD 根入口。 + * + * 首期只提供 C++ HUD 生命周期入口。真正 UI 由后续 GenericUISystem/CommonUI + * 专家接入,避免在 GameMode 或 PlayerController 中直接堆 UMG 装配逻辑。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYHUD : public AHUD +{ + GENERATED_BODY() + +public: + /** @brief 构造 HUD 根入口。 */ + APHYHUD(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief HUD 开始运行时初始化玩家 UI 入口。 */ + virtual void BeginPlay() override; + + /** @brief 初始化当前玩家的 HUD 逻辑。 */ + UFUNCTION(BlueprintCallable, Category="PHY|UI") + virtual void InitializeHUDForPlayer(APlayerController* OwningPlayerController); + + /** @brief 查询 HUD 是否已经完成初始化。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|UI") + bool IsHUDInitialized() const { return bHUDInitialized; } + +protected: + /** @brief HUD 是否已经完成基础初始化。 */ + UPROPERTY(Transient, BlueprintReadOnly, Category="PHY|UI") + bool bHUDInitialized = false; +};