Actor component SetHiddenInGame not working when called in client net multicast function

Hello I am having an issue where the SetHiddenInGame function does not hide a UStaticMeshComponent when executed in a net multicast function which was called from the server and executed on the client.

I have reproduced this behavior in a standalone project in both UE4 4.14 and 4.15. The strange thing about this is that a netmulticast function I create in blueprints to call SetHiddenInGame on the mesh component works fine but not when I use my C++ built function.

Any ideas? I intend to upload my test project after I get out of work today. I’ll post up more of my findings and test scenarios then. Thanks for being so awesome Epic devs and support! :slight_smile:

Edit:
Here’s the link to the project as promised!
?dl=0

So here’s some more details about what is contained within the project files.
To reproduce the “bug”

  1. Rebuild the VS project files
  2. Startup the project’s editor
  3. From the “Play” button’s dropdown menu, select 2 or more players
  4. Observe the cube above the actor’s head on the Server as it hides and unhides for all pawns in the level every 2 seconds.
  5. Observe that the same behavior is not occurring for all clients

Now, for a little more detail on what I have included in the project:

  • This project was based off the Sidescroller template, so find the main SideScrollerCharacter Blueprint where the 2 second timer loop is occurring in the Event Graph
  • The C++ class “ActorShield” contains the UStaticMeshComponent called ‘mesh’ is located which I am unable to hide with the SetHiddenInGame() function
  • The C++ class “MyProjectCharacter.cpp” is where the “NetMulticast_TestFunction_Implementation()” function is located which is executed from the server by means of the blueprint “SideScrollerCharacter” which derives from this class.

I have a bit of clutter in here that is not connected in the Event Graph of the blueprint class. These were left for experimenting different scenarios such as:

  • calling the SetHiddenInGame function from the “Event NetMulticast_TestFunction_ClientWorkaround” Blueprint Implementable Event which does not work to hide the “actorShield’s mesh”

  • calling the “CustomEvent_0” blueprint created event which is a netmulticast and it CAN correctly hide and unhide the “actorShield’s mesh”

After all the testing I have done I came to the conclusion that either:

  1. I am coding the c++ NetMulticast function incorrectly
  2. I am incorrectly calling the c++ NetMulticast function
  3. There is a bug in UE4 4.14 and 4.15 which causes the clients to ignore the calls (or reset after calling) the boolean value of bHiddenInGame of the UStaticMeshComponent

Let me know if you have any questions or need me to clarify anything, I apologize that this isn’t the cleanest project, I’m sure I could simplify this further if necessary but I’ve been pulling my hair out over this one for days and I’m hoping someone can cut me a bit of slack :slight_smile: If not let me know what I should do in future posts.

Edit 2:
Thanks to Chris’ help (see his answer below) he identified an issue where I had forgot to set the character’s “shield” variable with the Replicated tag in my var’s UPROPERTY. Once I did that I realized that issue from my main project was due to having the shield Replicated AND the shield’s mesh variable Replicated. Once this is done you will see the issue come up again. What is the reasoning behind this behavior in UE4 Networking?

Here’s the .h and .cpp files in use here:

ActorShield.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

UCLASS()
class MYPROJECT_API AActorShield : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AActorShield();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(EditDefaultsOnly, Category = "Shield")
		UStaticMeshComponent* mesh;

	UPROPERTY(EditDefaultsOnly, Category = "Shield")
		USphereComponent* sphere;
};

ActorShield.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyProject.h"
#include "ActorShield.h"


// Sets default values
AActorShield::AActorShield()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	sphere = CreateDefaultSubobject<USphereComponent>(TEXT("Root"));
	if (sphere)
		RootComponent = sphere;

	mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Shield"));
	if (mesh)
		mesh->SetupAttachment(RootComponent);
}

// Called when the game starts or when spawned
void AActorShield::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AActorShield::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

MyProjectCharacter.h

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Character.h"
#include "MyProjectCharacter.generated.h"

class AActorShield;

UCLASS(config=Game)
class AMyProjectCharacter : public ACharacter
{
	GENERATED_BODY()

	/** Side view camera */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* SideViewCameraComponent;

	/** Camera boom positioning the camera beside the character */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class USpringArmComponent* CameraBoom;

protected:

	virtual void BeginPlay() override;
	
	/** Called for side to side input */
	void MoveRight(float Val);

	/** Handle touch inputs. */
	void TouchStarted(const ETouchIndex::Type FingerIndex, const FVector Location);

	/** Handle touch stop event. */
	void TouchStopped(const ETouchIndex::Type FingerIndex, const FVector Location);

	// APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
	// End of APawn interface


public:
	AMyProjectCharacter();

	/** Returns SideViewCameraComponent subobject **/
	FORCEINLINE class UCameraComponent* GetSideViewCameraComponent() const { return SideViewCameraComponent; }
	/** Returns CameraBoom subobject **/
	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }

	UPROPERTY(EditDefaultsOnly, Category = "MyVars")
		AActorShield* shield;

	UPROPERTY(EditDefaultsOnly, Category = "MyVars")
		TSubclassOf<AActorShield> shieldClass;

	UFUNCTION(NetMulticast, Reliable, BlueprintCallable)
		void NetMulticast_TestFunction();

	UFUNCTION(BlueprintImplementableEvent, BlueprintCallable)
		void NetMulticast_TestFunction_ClientWorkaround();

	void WriteMyGameLog(FString Msg);
};

MyProjectCharacter.cpp

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "MyProject.h"
#include "GameFramework/Character.h"
#include "Engine.h"
#include "ActorShield.h"
#include "MyProjectCharacter.h"

AMyProjectCharacter::AMyProjectCharacter()
{
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	// Don't rotate when the controller rotates.
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// Create a camera boom attached to the root (capsule)
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent);
	CameraBoom->bAbsoluteRotation = true; // Rotation of the character should not affect rotation of boom
	CameraBoom->bDoCollisionTest = false;
	CameraBoom->TargetArmLength = 500.f;
	CameraBoom->SocketOffset = FVector(0.f,0.f,75.f);
	CameraBoom->RelativeRotation = FRotator(0.f,180.f,0.f);

	// Create a camera and attach to boom
	SideViewCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("SideViewCamera"));
	SideViewCameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
	SideViewCameraComponent->bUsePawnControlRotation = false; // We don't want the controller rotating the camera

	// Configure character movement
	GetCharacterMovement()->bOrientRotationToMovement = true; // Face in the direction we are moving..
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 720.0f, 0.0f); // ...at this rotation rate
	GetCharacterMovement()->GravityScale = 2.f;
	GetCharacterMovement()->AirControl = 0.80f;
	GetCharacterMovement()->JumpZVelocity = 1000.f;
	GetCharacterMovement()->GroundFriction = 3.f;
	GetCharacterMovement()->MaxWalkSpeed = 600.f;
	GetCharacterMovement()->MaxFlySpeed = 600.f;

	// Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) 
	// are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)
}

void AMyProjectCharacter::BeginPlay()
{
	Super::BeginPlay();
	if (HasAuthority() && GetWorld() && shieldClass)
	{
		FActorSpawnParameters spawnParams = FActorSpawnParameters();
		spawnParams.bNoFail = true;
		shield = GetWorld()->SpawnActor<AActorShield>(shieldClass, GetActorLocation(), FRotator::ZeroRotator, spawnParams);
		if (shield)
		{
			shield->AttachToActor(this, FAttachmentTransformRules::KeepWorldTransform);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
// Input

void AMyProjectCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	// set up gameplay key bindings
	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
	PlayerInputComponent->BindAxis("MoveRight", this, &AMyProjectCharacter::MoveRight);

	PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyProjectCharacter::TouchStarted);
	PlayerInputComponent->BindTouch(IE_Released, this, &AMyProjectCharacter::TouchStopped);
}

void AMyProjectCharacter::MoveRight(float Value)
{
	// add movement in that direction
	AddMovementInput(FVector(0.f,-1.f,0.f), Value);
}

void AMyProjectCharacter::TouchStarted(const ETouchIndex::Type FingerIndex, const FVector Location)
{
	// jump on any touch
	Jump();
}

void AMyProjectCharacter::TouchStopped(const ETouchIndex::Type FingerIndex, const FVector Location)
{
	StopJumping();
}

void AMyProjectCharacter::NetMulticast_TestFunction_Implementation()
{
	WriteMyGameLog("This is NetMulticast_TestFunction");
	//GetMesh()->SetHiddenInGame(!GetMesh()->bHiddenInGame);
	if (shield && shield->mesh)
		shield->mesh->SetHiddenInGame(!shield->mesh->bHiddenInGame);
	NetMulticast_TestFunction_ClientWorkaround();
}

void AMyProjectCharacter::WriteMyGameLog(FString Msg)
{
	if (GEngine)
	{
		if (GEngine->GetNetMode(GetWorld()) == ENetMode::NM_Client)
		{
			// set this one to log so it shows grey
			UE_LOG(LogTemp, Log, TEXT("CLIENT - %s"), *Msg);
		}
		else if (GEngine->GetNetMode(GetWorld()) == ENetMode::NM_ListenServer || GEngine->GetNetMode(GetWorld()) == ENetMode::NM_DedicatedServer)
		{
			// set this one to error so it shows red.
			UE_LOG(LogTemp, Error, TEXT("SERVER - %s"), *Msg);
		}
	}
}

There’s nothing wrong at all with your multicast function, you just need to replicate the shield variable.

UPROPERTY(EditDefaultsOnly, replicated, Category = "MyVars")
	AActorShield* shield;

void AMyProjectCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(AMyProjectCharacter, shield);
}

Thanks Chris! You are correct that adding the replication to the shield variable fixes the issue in this standalone project. However, I am also performing this variable replication in my main project. :frowning:

Good news is that it dawned on me what the problem was once I added the missing replication code. :slight_smile:

I am actually performing the replication on this “AActorShield” in my main project. BUT, I also am setting the mesh variable to replicated in my tags which is where the root of my problem is coming from! So if you set both the character’s shield to replicated AND set the shield’s “mesh” component to replicated then you will see this same behavior again.

This is where I figured I was having some sort of misconception about how the replication functionality of UE4 works. Why is this the case? I’ll update the project with these details once I get some time to do so later today.

Thanks again for this extra push to get me to where I needed to be Chris. After further testing and going back to my original project, it appears I was having problems because I was attempting to use the “SetHiddenInGame” function to hide and unhide the AActorShield’s UStaticMeshComponent when I should have been using “SetVisibility” instead. I now have much more consistent results and this issue has gone away.

I’m still a bit confused though about having both the Character’s shield pointer variable set with the Replicated tag, and the AActorShield’s UStaticMeshComponent mesh variable set with the Replicated tag and getting this same broken functionality out of this test project I have uploaded.

I’ll pick this issue up again later should I need to start replicating my mesh in my main project :slight_smile: