提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
GameMode是服务器端的权威对象,负责游戏规则和逻辑,而GameState是同步到所有客户端的,用来保存游戏的状态信息。所以,如果客户端需要访问某些数据,比如分数、时间等,这些数据应该存储在GameState中,而不是GameMode,因为GameMode只在服务器存在,客户端无法直接访问。
在虚幻引擎中,GameMode 和 GameState 是网络同步机制中的两个核心类。它们的核心区别在于:
GameMode:仅在服务器端存在,负责管理游戏规则和逻辑(如生成玩家、判断胜负)。客户端无法直接访问 GameMode。
GameState:存在于服务器和所有客户端,保存需要同步的全局游戏状态(如比分、剩余时间、玩家列表)。服务器更新 GameState 后会自动同步到客户端。
因此,如果客户端需要访问某些数据(例如 GameMode 中的变量),必须将这些数据转移到 GameState 中,并通过网络同步机制(属性复制或 RPC)实现客户端访问。
假设你的 GameMode 中有一个需要同步的变量 GlobalScore,以下是迁移步骤:
// MyGameState.h
#pragma once
#include "GameFramework/GameStateBase.h"
#include "MyGameState.generated.h"
UCLASS()
class MYPROJECT_API AMyGameState : public AGameStateBase
{
GENERATED_BODY()
public:
// 需要同步的变量
UPROPERTY(Replicated, BlueprintReadOnly, Category = "GameState")
int32 GlobalScore;
// 设置属性复制规则
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
// MyGameState.cpp
#include "MyGameState.h"
#include "Net/UnrealNetwork.h"
void AMyGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyGameState, GlobalScore); // 注册 GlobalScore 为同步变量
}
// MyGameMode.h
#pragma once
#include "GameFramework/GameModeBase.h"
#include "MyGameMode.generated.h"
UCLASS()
class MYPROJECT_API AMyGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
virtual void StartPlay() override;
// 更新全局分数(服务器逻辑)
void UpdateGlobalScore(int32 NewScore);
};
// MyGameMode.cpp
#include "MyGameMode.h"
#include "MyGameState.h"
void AMyGameMode::StartPlay()
{
Super::StartPlay();
// 初始化全局分数(示例)
UpdateGlobalScore(100);
}
void AMyGameMode::UpdateGlobalScore(int32 NewScore)
{
// 获取 GameState 并更新分数
AMyGameState* MyGameState = GetGameState<AMyGameState>();
if (MyGameState)
{
MyGameState->GlobalScore = NewScore;
}
}
无论是服务器还是客户端,都可以通过 GetWorld()->GetGameState() 获取 GameState 实例:
// 任意客户端或服务器代码中
void AMyPlayerController::Client_ShowScore()
{
// 获取 GameState
AMyGameState* MyGameState = GetWorld()->GetGameState<AMyGameState>();
if (MyGameState)
{
// 直接读取同步后的变量
int32 CurrentScore = MyGameState->GlobalScore;
UE_LOG(LogTemp, Warning, TEXT("Global Score: %d"), CurrentScore);
}
}
如果需要变量变化时触发客户端逻辑(如更新 UI),可以使用 ReplicatedUsing:
// MyGameState.h
UCLASS()
class AMyGameState : public AGameStateBase
{
GENERATED_BODY()
UPROPERTY(ReplicatedUsing=OnRep_GlobalScore)
int32 GlobalScore;
UFUNCTION()
void OnRep_GlobalScore(); // 变量同步时客户端回调
};
// MyGameState.cpp
void AMyGameState::OnRep_GlobalScore()
{
// 客户端收到 GlobalScore 更新
UE_LOG(LogTemp, Warning, TEXT("Score Updated: %d"), GlobalScore);
// 更新 UI 或其他客户端逻辑
}
如果某些数据不适合直接通过 GameState 同步(例如需要即时触发的动作),可使用 RPC:
// MyGameMode.cpp
void AMyGameMode::UpdateGlobalScore(int32 NewScore)
{
AMyGameState* MyGameState = GetGameState<AMyGameState>();
if (MyGameState)
{
MyGameState->GlobalScore = NewScore;
// 多播 RPC 通知所有客户端
MyGameState->Multicast_OnScoreUpdated(NewScore);
}
}
// MyGameState.h
UCLASS()
class AMyGameState : public AGameStateBase
{
GENERATED_BODY()
UFUNCTION(NetMulticast, Unreliable)
void Multicast_OnScoreUpdated(int32 NewScore);
};
// MyGameState.cpp
void AMyGameState::Multicast_OnScoreUpdated_Implementation(int32 NewScore)
{
// 客户端执行逻辑(如播放音效)
UGameplayStatics::PlaySound2D(this, ScoreUpdateSound);
}
场景 | 推荐方案 | 代码示例 |
---|---|---|
客户端需要读取全局状态 | 将数据迁移到 GameState + 属性复制 | DOREPLIFETIME + OnRep 回调 |
客户端需要即时响应事件 | 服务器调用多播 RPC | UFUNCTION(NetMulticast) |
服务器需要权威修改数据 | 在 GameMode 中操作 GameState | GetGameState() + 直接赋值 |
在修改 GameState 中的变量时,确保逻辑仅在服务器执行:
if (HasAuthority()) // 或 GetOwnerRole() == ROLE_Authority
{
// 安全修改变量
}
对高频更新的变量使用 Replicated + RepNotify 代替 RPC。
对非关键数据使用 Unreliable RPC 减少带宽占用。
客户端可能稍晚于服务器收到数据更新,需通过插值或预测机制提升体验(参考前文回滚机制)。
通过以上方法,你可以将原本只能在服务器访问的 GameMode 数据安全同步到客户端,并确保多人游戏的逻辑一致性。