【UE4C++-ActionRougelike-19】多人游戏网络同步
一、联网宝箱
1、交互组件RPC函数
在SInteractionComponent.h中添加在服务器上执行的RPC函数,用于客户端请求交互。
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class ACTIONROGUELIKE_API USInteractionComponent : public UActorComponent
{
public:
// 声明一个主动交互函数
void PrimaryInteract();
protected:
// 在服务器上执行的RPC函数,用于客户端请求进行交互
UFUNCTION(Server, Reliable)
void ServerInteract(AActor* InFocus);
}
在SInteractionComponent.cpp中交互时发送RPC调用请求,并实现RPC函数ServerInteract
void USInteractionComponent::PrimaryInteract()
{
// 发送RPC调用请求
ServerInteract(FocusedActor);
}
// 定义在服务器上执行的RPC函数
void USInteractionComponent::ServerInteract_Implementation(AActor* InFocus)
{
// 检查是否选中了交互对象
if (InFocus == nullptr)
{
GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, "No Focus Actor to interact.");
return;
}
// 获取拥有该组件的Actor对象所属的Pawn对象
APawn* MyPawn = Cast<APawn>(GetOwner());
// 调用Interact函数进行交互
ISInteractionInterface::Execute_Interact(InFocus, MyPawn);
}
2、修改宝箱
在STreasureChestItem.h中添加一个状态变量bLidOpened表示宝箱是否打开,并且使用RepNotify标记实现状态同步;再定义回调函数OnRep_LidOpened用于状态变量bLidOpened变换后调用
UCLASS()
class ACTIONROGUELIKE_API ASTreasureChestItem : public AActor, public ISInteractionInterface
{
public:
// Sets default values for this actor's properties
ASTreasureChestItem();
//宝箱打开时盖子角度
UPROPERTY(EditAnywhere)
float OpenPitch;
//重写互动接口的互动方法
void Interact_Implementation(APawn* InstigatorPawn) override;
protected:
//表示盖子是否打开,并通过RepNotify标记实现了状态同步,RepNotify是一个属性标记,用于表示在属性更新时应该调用哪个函数。
//ReplicatedUsing 参数指定一个名为 OnRep_LidOpened 的函数,当该属性的值在网络上改变时会调用该函数,以使客户端和服务器上的值保持同步。
UPROPERTY(ReplicatedUsing="OnRep_LidOpened", BlueprintReadOnly)
bool bLidOpened;
// OnRep函数在客户端和服务器同步变量后调用
UFUNCTION()
void OnRep_LidOpened();
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* BaseMesh;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UStaticMeshComponent* LidMesh;
};
在STreasureChestItem.cpp的构造函数中开启复制,并实现回调函数OnRep_LidOpened
ASTreasureChestItem::ASTreasureChestItem()
{
//创建宝箱底部Mesh
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>("BaseMesh");
RootComponent = BaseMesh;
//创建宝箱盖子Mesh
LidMesh = CreateDefaultSubobject<UStaticMeshComponent>("LidMesh");
LidMesh->SetupAttachment(BaseMesh);
//打开宝箱时盖子倾斜角度
OpenPitch = 110.0f;
// 开启复制
SetReplicates(true);
}
void ASTreasureChestItem::Interact_Implementation(APawn* InstigatorPawn)
{
// 切换盖子状态
bLidOpened = !bLidOpened;
// 本地客户端也要同步宝箱盖子状态
OnRep_LidOpened();
}
void ASTreasureChestItem::OnRep_LidOpened()
{
// 根据盖子状态设置盖子的旋转角度
float CurrPitch = bLidOpened ? OpenPitch : 0.0f;
LidMesh->SetRelativeRotation(FRotator(CurrPitch, 0, 0));
}
// 在GetLifetimeReplicatedProps函数中声明需要同步的属性
void ASTreasureChestItem::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// 将 bLidOpened 属性添加到需要复制的属性列表中
DOREPLIFETIME(ASTreasureChestItem, bLidOpened);
}
3、效果演示
4、总结:
4.1 RPC
概念:RPC(Remote Procedure Call,远程过程调用)是一种用于多人游戏中网络同步的机制,是在本地调用但在其他机器(不同于执行调用的机器)上远程执行的函数,它可以在服务器和客户端之间传递函数调用。
使用:要将一个函数声明为 RPC,您只需将
Server
、Client
或NetMulticast
关键字添加到UFUNCTION
声明。- Server:从客户端调用到服务器的RPC。
- Client:从服务器调用到客户端的RPC。
- Multicast:从服务器调用到所有客户端的RPC。
注意事项:
- Actor属性:RPC只能在具有网络复制功能的对象上使用,即执行RPC的对象必须是一个继承自AActor的类,且必须被复制。
- 函数实现:需要在客户端和服务器端都实现RPC函数
- 可靠性:默认情况下RPC是不可靠的。要确保在远程机器上执行 RPC 调用,可以指定
Reliable
关键字
4.2 属性复制
概念:属性复制是UE中一种实现网络同步的机制。通过标记属性为可复制,UE能够自动同步服务器和客户端之间的这些属性,以保持游戏状态一致。
使用:
- 在需要复制的属性的UPROPERTY宏中添加Replicated修饰符
- 实现一个名为GetLifetimeReplicatedProps的函数,以告诉UE引擎哪些属性需要复制。在这个函数中,使用DOREPLIFETIME宏指定需要复制的属性
- 为Actor开启复制(SetReplicates(true)、bReplicates=true)
注意事项:
- 客户端只能读取被同步的属性,不能修改。
- 只为关键属性启用复制,以减少网络传输和处理开销。
二、联网属性和用户界面
1、客户端上的权限
- PlayerController:客户端拥有与其本地玩家相关的PlayerController实例。客户端可以通过这个实例处理输入和控制游戏角色(Pawn)。
- PlayerState:客户端可以访问所有客户端玩家的PlayerState实例,但只能读取这些信息。客户端无法直接修改其他客户端玩家的PlayerState。如果需要修改玩家状态信息,需要通过向服务器发送RPC请求来实现。
- GameState:客户端可以拥有GameState实例。GameState包含全局游戏状态信息,如游戏进度、剩余时间等。服务器负责更新GameState并同步到客户端。
- GameMode:客户端不拥有GameMode实例。GameMode仅在服务器上存在,负责管理游戏的核心逻辑。客户端无法直接访问或修改GameMode,必须通过发送RPC请求或通知服务器来与GameMode进行交互。
- Pawn:客户端可以拥有Pawn(游戏角色)实例。客户端通常控制一个与其本地玩家相关的Pawn,通过PlayerController来处理输入和控制Pawn。客户端也可以看到其他玩家的Pawn,但无法直接控制它们。
2、联网属性和用户界面
2.1 属性复制
在MyPick和MyProjectileBase的构造函数中添加SetReplicates(true);使得这两个Actor及其子类能够同步到客户端
2.2 同步血量
在MyAttributeComponent.h中为生命值和最大生命值添加Replicated标记,用来复制这两个属性;添加多播函数MulticastHealthChanged用来告知客户端血量变化
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class ACTIONROGUELIKE_API UMyAttributeComponent : public UActorComponent
{
protected:
//当前生命值,可在编辑器中修改,默认只在服务器同步
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Replicated, Category = "Attributes")
float Health;
//最大生命值,可在编辑器中修改,默认只在服务器同步
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Replicated, Category = "Attributes")
float MaxHealth;
// 用于多播当前生命值变化的函数
UFUNCTION(NetMulticast, Reliable)
void MulticastHealthChanged(AActor* InstigatorActor, float NewHealth, float Delta);
};
在MyAttributeComponent.cpp的构造函数中设置组件可复制;在生命值变化函数中调用多播函数MulticastHealthChanged通知客户端血量的变化;实现多播函数;重写GetLifetimeReplicatedProps方法,设置该组件可以被网络同步的属性
UMyAttributeComponent::UMyAttributeComponent()
{
MaxHealth = 100;
Health = MaxHealth;
//用于设置组件的默认Replication行为
SetIsReplicatedByDefault(true);
}
bool UMyAttributeComponent::ApplyHealthChange(AActor* InstigatorActor, float Delta)
{
// 广播触发委托
//OnHealthChange.Broadcast(InstigatorActor,this, Health,ActualDelta);
//如果实际变化量不为0,则通知客户端
if (ActualDelta != 0.0f)
{
MulticastHealthChanged(InstigatorActor, Health, ActualDelta);
}
}
//多播函数,用于通知客户端生命值发生变化
void UMyAttributeComponent::MulticastHealthChanged_Implementation(AActor* InstigatorActor, float NewHealth, float Delta)
{
//广播生命值变化事件
OnHealthChange.Broadcast(InstigatorActor, this, NewHealth, Delta);
}
//重写父类的函数,用于设置该组件可以被网络同步
void UMyAttributeComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
//声明要同步的属性
DOREPLIFETIME(UMyAttributeComponent, Health);
DOREPLIFETIME(UMyAttributeComponent, MaxHealth);
//DOREPLIFETIME_CONDITION(UMyAttributeComponent, MaxHealth, COND_InitialOnly);
}
2.3 同步用户UI
在PlayerCharacter_BP中添加血量条Widget
3、同步Action
在MyActionComponent中增加服务器RPC函数ServerStartAction,使得客户端也能够在服务器上启动某个动作。
//MyActionComponent.h
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class ACTIONROGUELIKE_API UMyActionComponent : public UActorComponent
{
protected:
// 声明一个可靠的服务器 RPC 函数,用于在服务器上启动动作
UFUNCTION(Server, Reliable)
void ServerStartAction(AActor* Instigator, FName ActionName);
}
//MyActionComponent.cpp
UMyActionComponent::UMyActionComponent()
{
PrimaryComponentTick.bCanEverTick = true;
SetIsReplicatedByDefault(true);
}
bool UMyActionComponent::StartActionByName(AActor* Instigator, FName ActionName)
{
// 遍历 Actions 数组,查找并运行指定名称ActionName的动作
for (UMyAction* Action : Actions)
{
if (Action && Action->ActionName == ActionName)
{
// 如果当前组件不在服务器上,向服务器发送 RPC 启动动作
if (!GetOwner()->HasAuthority())
{
ServerStartAction(Instigator, ActionName);
}
// 启动动作
Action->StartAction(Instigator);
return true;
}
}
return false;
}
// 实现服务器 RPC 函数 ServerStartAction,调用 StartActionByName 启动动作
void UMyActionComponent::ServerStartAction_Implementation(AActor* Instigator, FName ActionName)
{
StartActionByName(Instigator, ActionName);
}
4、效果演示
三、蓝图同步
1、爆炸油桶
设置爆炸油桶定时结束后爆炸,触发MulticastExplode。
设置爆炸事件的Replicates方式为多播