这是我为多人游戏构建的第一个具有复制功能的 Actor,所以如果您认为我应该使用另一种技术,请告诉我。
我正在使用两个播放器运行 UE5.4,并且作为网络连接,我使用监听服务器。
所以我正在尝试制作一个简单的门,您可以与之交互以打开或关闭它。当您与门交互时,客户端调用名为 ServerInteract 的服务器 RPC,该 RPC 会更新门 Actor 的 bOpen 属性。此属性被标记为 ReplicatedUsing,因此应在所有客户端上复制它,并在所有客户端上调用 OnRep 函数。此 OnRep 函数根据 bOpen 状态播放打开或关闭动画。 我的问题是,当我与客户端 0(也是主机/服务器)的门交互时,复制工作完美。但是,当我与客户端 1(不是主机而是一个简单客户端)交互时,即使参与者的所有者具有有效的连接,ServerInteract RPC 甚至不会被调用。
您可以在视频中看到一个测试,其中的日志清楚地表明了我的观点。 youtube 上的视频
门.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "../InteractiveActor.h"
#include "Door.generated.h"
UCLASS()
class ESCAPEGAME_API ADoor : public AInteractiveActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADoor();
// Handle interaction
virtual void ServerInteract_Implementation(APawn* InteractionSender) override;
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION(BlueprintCallable)
bool ToggleDoor();
UFUNCTION()
void OnRep_bOpen();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Door)
UAnimSequence* DoorOpening_Animation;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Door)
UAnimSequence* DoorClosing_Animation;
UPROPERTY(ReplicatedUsing=OnRep_bOpen ,EditAnywhere, BlueprintReadOnly, Category = Door)
bool bOpen;
public:
UPROPERTY(EditAnywhere, category = Door)
USkeletalMeshComponent* DoorSkeletalMesh;
UPROPERTY(EditAnywhere, category = Door)
USceneComponent* DoorRoot;
};
门.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Door.h"
#include "Net/UnrealNetwork.h"
// Sets default values
ADoor::ADoor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
DoorRoot = CreateDefaultSubobject<USceneComponent>(TEXT("DoorRoot"));
SetRootComponent(DoorRoot);
DoorSkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>("DoorMesh");
DoorSkeletalMesh->SetupAttachment(DoorRoot);
// Initiate variables
bOpen = false;
bReplicates = true;
}
// Called when the game starts or when spawned
void ADoor::BeginPlay()
{
Super::BeginPlay();
}
bool ADoor::ToggleDoor()
{
UE_LOG(LogTemp, Warning, TEXT("ToggleDoor : This actor has %s"), ( HasAuthority() ? TEXT("authority") : TEXT("no authority") ));
bOpen = !bOpen;
if (HasAuthority())
{
// if bOpen changed to true play opening animation if it changed to false play closing animation.
DoorSkeletalMesh->PlayAnimation(bOpen ? DoorOpening_Animation : DoorClosing_Animation, false);
}
return bOpen;
}
void ADoor::OnRep_bOpen()
{
UE_LOG(LogTemp, Warning, TEXT("OnRep_bOpen Has authority : %s"), (HasAuthority() ? TEXT("yes") : TEXT("no")));
UE_LOG(LogTemp, Warning, TEXT("bOpen : %s"), (bOpen ? TEXT("yes") : TEXT("no")));
// if bOpen changed to true play opening animation if it changed to false play closing animation.
DoorSkeletalMesh->PlayAnimation(bOpen ? DoorOpening_Animation : DoorClosing_Animation, false);
}
void ADoor::ServerInteract_Implementation(APawn* player)
{
Super::ServerInteract_Implementation(player);
UE_LOG(LogTemp, Warning, TEXT("Server : ServerInteract"));
ToggleDoor();
}
void ADoor::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ADoor, bOpen);
}
InteractiveActor.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InteractiveActor.generated.h"
UCLASS(Abstract)
class ESCAPEGAME_API AInteractiveActor : public AActor
{
GENERATED_BODY()
public:
// Interact function
UFUNCTION(Server, Reliable, WithValidation)
virtual void ServerInteract(APawn* InteractionSender);
virtual void ServerInteract_Implementation(APawn* InteractionSender);
virtual bool ServerInteract_Validate(APawn* InteractionSender);
};
InteractiveActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "InteractiveActor.h"
void AInteractiveActor::ServerInteract_Implementation(APawn* InteractionSender)
{
UE_LOG(LogTemp, Warning, TEXT("ServerInteract with %s, Has authority: %s"), *InteractionSender->GetName(), (HasAuthority() ? TEXT("Yes") : TEXT("No")));
}
bool AInteractiveActor::ServerInteract_Validate(APawn* InteractionSender)
{
return true;
}
玩家角色交互函数,当按下 f 时调用。
void AEscapeGameCharacter::Interact()
{
UE_LOG(LogTemp, Warning, TEXT("AEscapeGameCharacter::Interact"));
FVector StartLocation = FirstPersonCameraComponent->GetComponentLocation();
FVector EndLocation = FirstPersonCameraComponent->GetForwardVector() * 200 + StartLocation;
FHitResult Hit;
GetWorld()->LineTraceSingleByChannel(Hit, StartLocation, EndLocation, ECC_Visibility);
DrawDebugLine(GetWorld(), StartLocation, EndLocation, FColor::Red, false, 5, 0, 5);
if (!Hit.bBlockingHit) { return; }
if (Hit.GetActor()->IsA(AInteractiveActor::StaticClass()))
{
AInteractiveActor* InteractiveActor = Cast<AInteractiveActor>(Hit.GetActor());
if (AEscapeGamePlayerController* controller = Cast<AEscapeGamePlayerController>(GetController()))
{
UE_LOG(LogTemp, Warning, TEXT("Net Rep Responsible Owner: %s"), (HasNetOwner() ? TEXT("Yes") : TEXT("No")));
InteractiveActor->SetOwner(this);
InteractiveActor->ServerInteract(this);
}
} else
{
UE_LOG(LogTemp, Warning, TEXT("Hitted something"));
}
}
对于客户端到服务器的 RPC,所有者必须是尝试调用 RPC 的客户端。
一般来说,放置在关卡中的角色(例如门)永远不属于任何特定玩家。
我看到您正在尝试从Interact功能中设置Owner,但是Owner不能从客户端设置,它必须从Authority设置,所以它在那里没有效果。它可能看起来有效(例如,如果您在调用 SetOwner 后打印所有者,它将显示),但由于所有者未在服务器端修改,因此它不会接受 RPC。
您应该稍微修改代码,通过真正的玩家拥有的类(例如角色)路由 RPC,然后在服务器端转发到角色代码,而不是尝试修改水平放置的角色的所有者.
您可以很容易地调整代码来做到这一点。
AInteractiveActor 不执行 RPC,它只需要一个入口点
UFUNCTION()
virtual void OnInteract(APawn* Sender)
{
if (HasAuthority())
{
// server interaction
}
else
{
// client interaction (optional - can be used for prediction)
}
}
将 RPC 逻辑移至您的角色类
UFUNCTION(Server, Reliable, WithValidation)
virtual void ServerInteract(AInteractiveActor* Target);
virtual void ServerInteract_Implementation(AInteractiveActor* Target);
virtual void ServerInteract_Validate(AInteractiveActor* Target);
// in function Interact()
if (Hit.GetActor()->IsA(AInteractiveActor::StaticClass()))
{
AInteractiveActor* InteractiveActor = Cast<AInteractiveActor>(Hit.GetActor());
if (!HasAuthority())
InteractiveActor->OnInteract(this); //optional, can be used for prediction
ServerInteract(InteractiveActor);
}
// end function Interact
void AEscapeGameCharacter::ServerInteract_Implementation(AInteractiveActor* Target)
{
if (Target)
Target->OnInteract(this);
}
此答案由UE5官方论坛的Chatouille撰写。感谢他的帮助。
最终角色.h
// Handle all interact action from player
void Interact();
UFUNCTION(Server, Reliable)
void ServerInteract(AInteractiveActor* Target);
void ServerInteract_Implementation(AInteractiveActor* Target);
最终角色.cpp
void AEscapeGameCharacter::Interact()
{
UE_LOG(LogTemp, Warning, TEXT("AEscapeGameCharacter::Interact"));
FVector StartLocation = FirstPersonCameraComponent->GetComponentLocation();
FVector EndLocation = FirstPersonCameraComponent->GetForwardVector() * 200 + StartLocation;
FHitResult Hit;
GetWorld()->LineTraceSingleByChannel(Hit, StartLocation, EndLocation, ECC_Visibility);
DrawDebugLine(GetWorld(), StartLocation, EndLocation, FColor::Red, false, 5, 0, 5);
if (!Hit.bBlockingHit) { return; }
if (Hit.GetActor()->IsA(AInteractiveActor::StaticClass()))
{
AInteractiveActor* InteractiveActor = Cast<AInteractiveActor>(Hit.GetActor());
UE_LOG(LogTemp, Warning, TEXT("Net Rep Responsible Owner: %s"), (HasNetOwner() ? TEXT("Yes") : TEXT("No")));
ServerInteract(InteractiveActor);
} else
{
UE_LOG(LogTemp, Warning, TEXT("Hitted something"));
}
}
void AEscapeGameCharacter::ServerInteract_Implementation(AInteractiveActor* Target)
{
if (Target)
{
Target->OnInteract(this);
}
}
最终InteractiveActor.h
UFUNCTION(BlueprintCallable)
virtual void OnInteract(APawn* Sender);
最终之门.h
// Handle interaction
virtual void OnInteract(APawn* Sender) override;
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION()
bool ToggleDoor();
UFUNCTION()
void OnRep_bOpen();
最终之门.cpp
bool ADoor::ToggleDoor()
{
UE_LOG(LogTemp, Warning, TEXT("ToggleDoor : This actor has %s"), ( HasAuthority() ? TEXT("authority") : TEXT("no authority") ));
bOpen = !bOpen;
if (HasAuthority())
{
// if bOpen changed to true play opening animation if it changed to false play closing animation.
DoorSkeletalMesh->PlayAnimation(bOpen ? DoorOpening_Animation : DoorClosing_Animation, false);
}
return bOpen;
}
void ADoor::OnRep_bOpen()
{
UE_LOG(LogTemp, Warning, TEXT("OnRep_bOpen Has authority : %s"), (HasAuthority() ? TEXT("yes") : TEXT("no")));
UE_LOG(LogTemp, Warning, TEXT("bOpen : %s"), (bOpen ? TEXT("yes") : TEXT("no")));
// if bOpen changed to true play opening animation if it changed to false play closing animation.
DoorSkeletalMesh->PlayAnimation(bOpen ? DoorOpening_Animation : DoorClosing_Animation, false);
}
void ADoor::OnInteract(APawn* Sender)
{
Super::OnInteract(Sender);
UE_LOG(LogTemp, Warning, TEXT("Server : ServerInteract"));
ToggleDoor();
}
void ADoor::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ADoor, bOpen);
}