Hey all! This seems like something that should be pretty simple, and yet here I am after a full day of making zero progress, about to tear my hair out.
I have a base class set up for items that can be picked up and moved around (“grabbables”). In this class, I’m using a control boolean (bInteractable) to determine whether a player can currently pick up the item. This way I can turn the ability to interact with them off when, for example, another player is holding them. This all works as intended on a standalone build, and bInteractable replicates as intended from a listen server to a client. However, no matter what I try, no matter how many examples online I’ve followed, bInteractable just refuses to replicate when I change it on a client. It changes on the client that calls it, but the change doesn’t appear on the server or any other clients.
Relevant bits of code are here:
In GrabbableBase.h
#include "Net/UnrealNetwork.h"
UPROPERTY(Replicated)
bool bInteractable; // Whether this actor can currently be grabbed
/* This function is from an interface, hence the use of the _Implementation suffix. This works as intended and AFAICT has nothing to do with my issue */
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Interaction")
void SetInteractable(bool bUpdateInteractable);
virtual void SetInteractable_Implementation(bool bUpdateInteractable) override;
UFUNCTION(Server, Reliable, WithValidation)
void Server_SetInteractable(bool bUpdateInteractable);
virtual bool Server_SetInteractable_Validate(bool bUpdateInteractable);
virtual void Server_SetInteractable_Implementation(bool bUpdateInteractable);
In GrabbableBase.cpp
#include "Net/UnrealNetwork.h"
AGrabbableBase::AGrabbableBase()
{
bInteractable = true;
bReplicates = true;
}
void AGrabbableBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AGrabbableBase, bInteractable);
}
void AGrabbableBase::SetInteractable_Implementation(bool bUpdateInteractable)
{
if(HasAuthority())
{
bInteractable = bUpdateInteractable;
}
else
{
Server_SetInteractable_Implementation(bUpdateInteractable);
}
}
bool AGrabbableBase::Server_SetInteractable_Validate(bool bUpdateInteractable)
{
return true;
}
void AGrabbableBase::Server_SetInteractable_Implementation(boolbUpdateInteractable)
{
bInteractable = bUpdateInteractable;
// Debug message
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("SERVER FUNCTION CALLED SUCCESSFULLY!"));
}
I must have followed half a dozen different guides and other examples on this, and they all seem to suggest this is correct.
As I understand it, what SHOULD be happening here is when a grabbable item has its SetInteractable function called, it checks to see whether it exists on the server or a client. If it’s on the server, it sets bInteractable to whatever was passed into it. If it’s a client, it calls Server_SetInteractable (as far as I can tell through use of debug messages, this part works). Either way, what should then happen is bInteractable gets changed on the server, which because bInteractable is set up to replicate, should in turn replicate down to the other clients.
And yet this last part doesn’t happen. I’m still able to interact with grabbables that other players are holding (unless that player is the server). I also wrote this extremely hacky set of conditional checks below to test it out. When the server is holding a grabbable, it displays false for the server and all clients (the intended behavior). When a client is holding one, it displays false for that client and true for everyone else (not the intended behavior).
Running on tick in GrabbableBase.cpp as a debug test
if (HasAuthority())
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("SERVER:"));
if (bInteractable == true)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("True"));
}
else if (bInteractable == false)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("False"));
}
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("CLIENT:"));
if (bInteractable == true)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("True"));
}
else if (bInteractable == false)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("False"));
}
}
Soo… ideas? By all accounts it doesn’t seem like I’m missing anything, and yet I clearly am because this isn’t working. Any help would be MASSIVELY appreciated!