Mesh component replicates to server, but not back to clients

Hi.
I was having trouble with replication in my project, so I created a mini project just to get the replication stuff right.
So, here’s my setup:
I created a C++ project based off of the third person template. Default player character… etc
I added an input action that basically performs a trace hit and determines whether it hit a “pickable” item (a class that I created).
if it did, it calls a function on that item called “used()” which basically hides the mesh component from view (it’s for an inventory system).

When I run it as a single player, it works great. I get to an item, press G, the item’s gone.
But when I try to play with 2 players, and a dedicated server (in the editor), it doesn’t work correctly.
The action happens on the server. I know this because after performing the action, I can actually walk through the item as if it wasn’t there but it’s still showing.
However, all the players (including myself) still see the mesh. It’s just not replicating to all the actions.

Here are the main points of my code:

ReplicationCharacter.h
I have these functions defined:

void Grab();	
UFUNCTION(Server, Reliable, WithValidation)
	void ServerGrab();
void ServerGrab_Implementation();
bool ServerGrab_Validate();

ReplicationCharacter.cpp

void AReplicationCharacter::Grab()
{
    //It only performs this action on the server. Hence the "else" part. This is just for easier debugging
   //If I remove the else so that the code still executes on the client, it does work but it still doesn't replicate to the other players
	if (Role < ROLE_Authority)
	{
		ServerGrab();
	}
	else
	{
		FHitResult Target = PerformTractHit(); //This is just a simple function that perform a trace hit and returns the result
		if (APickable * ItemTarget = Cast<APickable>(Target.GetActor()))
		{
			if (ItemTarget)
			{
				ItemTarget->Used();
			}
		}
	}
}

void AReplicationCharacter::ServerGrab_Implementation()
{
	Grab();
}

bool AReplicationCharacter::ServerGrab_Validate()
{
	return true;
}

And now for the Pickable class:
Pickable.h
It has a static mesh variable which I then use to hold the static mesh. I set it to replicated. The rest is not important

UPROPERTY(Replicated, VisibleAnywhere, Category = "Mesh")
	UStaticMeshComponent * MeshComp;
virtual void BeginPlay() override;

void Used();

And the .cpp file:

APickable::APickable(const FObjectInitializer& ObjectInitializer)
{
	PrimaryActorTick.bCanEverTick = true;
	MeshComp = ObjectInitializer.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("Mesh"));
	MeshComp->SetIsReplicated(true);
	 = MeshComp;
	bReplicates = true;
	bReplicateMovement = true;
}
    void APickable::Used()
    {
    	if (Role == ROLE_Authority)
    	{
    		MeshComp->SetHiddenInGame(true);
    		MeshComp->SetSimulatePhysics(false);
    		SetActorEnableCollision(false);
    	}
    }
    
    
    void APickable::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
    {
    	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
   
    	DOREPLIFETIME(APickable, MeshComp);
    }

A bit of a tl;dr. Sorry about that, but if anybody could tell me where I went wrong, It’d be great.

ps, as you may’ve guessed, I’m new to replication in UE4.

Thanks!

Hey Lord Northern,

Here is an example of how you could do this:

[GrabActor.h]

#pragma once

#include "GameFramework/Actor.h"
#include "GrabActor.generated.h"

UCLASS()
class AH481676_API AGrabActor : public AActor
{
	GENERATED_BODY()
	
public:	
	AGrabActor();
	virtual void BeginPlay() override;
	virtual void Tick( float DeltaSeconds ) override;
    
    UPROPERTY( EditDefaultsOnly, Category="Grab" )
    UStaticMeshComponent *Mesh;	
};

[GrabActor.cpp]

#include "AH481676.h"
#include "GrabActor.h"

AGrabActor::AGrabActor()
{
	PrimaryActorTick.bCanEverTick = true;

    // Set mesh as root and simulate physics (so it rolls and whatever)
    Mesh = CreateDefaultSubobject<UStaticMeshComponent>( TEXT("Mesh") );
    Mesh->SetSimulatePhysics( true );
    SetRootComponent( Mesh );
         
    bReplicates = true;
    bReplicateMovement = true;
}

void AGrabActor::BeginPlay()
{
	Super::BeginPlay();
	
}
void AGrabActor::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );

}

[TCharacter.h]

#pragma once

#include "GameFramework/Character.h"
#include "TCharacter.generated.h"

UCLASS()
class AH481676_API ATCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	ATCharacter();
	virtual void BeginPlay() override;
	virtual void Tick( float DeltaSeconds ) override;
	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

    void Forward( float Amount );
    void Right( float Amount );
    void StartGrab( );
    void StopGrab( );
    
    UFUNCTION( Server, Reliable, WithValidation )
    void ServerGrab( );
    
    UFUNCTION( Server, Reliable, WithValidation )
    void StopServerGrab( );
    
    UPROPERTY( replicated )
    AActor *GrabbedActor;
	
    UPROPERTY( EditDefaultsOnly, Category = "GrabLocation" )
    USceneComponent *GrabHoldLocation;    	
};

[TCharacter.cpp]

#include "AH481676.h"
#include "TCharacter.h"
#include "GrabActor.h"

ATCharacter::ATCharacter()
{
    // Need tick
	PrimaryActorTick.bCanEverTick = true;
    
    // Location attached to mesh that will serve as the location where the grabbed object
    // will stay when being held
    GrabHoldLocation = CreateDefaultSubobject<USceneComponent>( TEXT("GrabLocation") );
    GrabHoldLocation->SetupAttachment( GetMesh( ) );
}

void ATCharacter::GetLifetimeReplicatedProps( TArray< FLifetimeProperty > & OutLifetimeProps ) const
{
    // Replicate to client so they can set the position when being held
    DOREPLIFETIME( ATCharacter, GrabbedActor );
    
    Super::GetLifetimeReplicatedProps( OutLifetimeProps );
}

void ATCharacter::BeginPlay()
{
	Super::BeginPlay();
	
}

void ATCharacter::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );
    
    // If GrabbedActor is set, it is being held
    if( GrabbedActor )
    {
        // Make sure location is valid
        if( GrabHoldLocation )
        {
            // Set GrabbedActor to location of GrabHoldLocation (world space)
            GrabbedActor->SetActorLocation( GrabHoldLocation->GetComponentLocation( ) );
        }
    }
}

// input
void ATCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    Super::SetupPlayerInputComponent(InputComponent);
    
    InputComponent->BindAxis( "Forward", this, &ATCharacter::Forward );
    InputComponent->BindAxis( "Right", this, &ATCharacter::Right );
    InputComponent->BindAxis( "Up", this, &ACharacter::AddControllerPitchInput );
    InputComponent->BindAxis( "Look", this, &ACharacter::AddControllerYawInput );
    
    InputComponent->BindAction( "Jump", IE_Pressed, this, &ACharacter::Jump );
    
    // Input setup for Grab / Let go
    InputComponent->BindAction( "Grab", IE_Pressed, this, &ATCharacter::StartGrab );
    InputComponent->BindAction( "Grab", IE_Released, this, &ATCharacter::StopGrab );
}

void ATCharacter::Forward( float Amount )
{
    if( Amount != 0.f )
    {
        AddMovementInput( GetActorForwardVector( ), Amount );
    }
}

void ATCharacter::Right( float Amount )
{
    if( Amount != 0.f )
    {
        AddMovementInput( GetActorRightVector( ), Amount );
    }
}

// Try to grab object
void ATCharacter::StartGrab( )
{
    // Assumption is dedicated server
    ServerGrab( );   
}

// Let go of object (if holding anything)
void ATCharacter::StopGrab( )
{
    // Assumption is dedicated server
    StopServerGrab( );
}

bool ATCharacter::ServerGrab_Validate( )
{
    return true;
}

// Server side, grab object
void ATCharacter::ServerGrab_Implementation( )
{
    // Start trace location
    FVector StartTraceLocation = GetActorLocation( ) + FVector( 0.f, 0.f, 64.f );
    // End trace location
    FVector EndTraceLocation = StartTraceLocation + ( GetControlRotation( ).Vector( ).GetSafeNormal( ) * 32768 );
    // Hit result for trace
    FHitResult Hit;
    
    // We want to ignore character in trace
    FCollisionQueryParams Params;
    Params.AddIgnoredActor( this );
    
    // If we arent holding anything
    if( GrabbedActor == nullptr )
    {
        // Try a trace
        if( GetWorld( )->LineTraceSingleByChannel( Hit, StartTraceLocation, 
                                                  EndTraceLocation, ECollisionChannel::ECC_WorldDynamic, 
                                                  Params ) )
        {
            // If trace hit something, make sure the actor it hit is valid
            if( Hit.GetActor( ) )
            {
                // Cast the hit actor into the AGrabActor - We dont want to grab another object
                AGrabActor *Grab = Cast<AGrabActor>( Hit.GetActor( ) );
                if( Grab )
                {
                    // Set GrabActor ( as server this will replicate to clients )
                    GrabbedActor = Grab;
                }
            }
        }
    }
}

bool ATCharacter::StopServerGrab_Validate( )
{
    return true;
}

// Drop GrabbedActor
void ATCharacter::StopServerGrab_Implementation( )
{
    GrabbedActor = nullptr;
}

Make sure in your project header file, to include “UnrealNetwork.h”.

As an example, if you game is “MyGame”, open MyGame.h and add:

#include "UnrealNetwork.h"

Also, as a follow up, picking up the ball is a little odd because of the trace. You have to stand like this:

106240-481676_traceballpickup.png

Alright! It worked wonders!

Thanks!