即使拥有有效的 NetOwner,客户端也无法调用 RPC 服务器

问题描述 投票:0回答:1

这是我为多人游戏构建的第一个具有复制功能的 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"));
    }
    
}
c++ replication rpc multiplayer unreal-engine5
1个回答
0
投票

对于客户端到服务器的 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);
}
© www.soinside.com 2019 - 2024. All rights reserved.