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!
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”
- Rebuild the VS project files
- Startup the project’s editor
- From the “Play” button’s dropdown menu, select 2 or more players
- 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.
- 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:
- I am coding the c++ NetMulticast function incorrectly
- I am incorrectly calling the c++ NetMulticast function
- 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 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);
}
}
}