I am very new to Unreal, so apologies if I’m making a simple mistake.
I’ve been working on a small multiplayer prototype, where simply a character will run across a pickup, and a bool will be changed on an associated Component, to either true or false, and then that component will swap that characters mesh material to a different material. In the below code, I’ve changed this to simply be triggered by the character itself, through button press.
I’ve tried as many ways as I could find online to do this, and only one is working, but it’s not great. This working method is checking which material is on the character mesh, rather than using the bool (which doesn’t seem to work). Could someone show me what I’m doing wrong for this not to work, and let me know what I need to do to get this working? I also get the feeling that I’m doing this the hard way, so any direction for how to better program multiplayer with Unreal would also be appreciated.
I’ve removed parts of the code which are not related to my question, so to simplify what you can see.
PlayerCharacter.h:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "PlayerCharacter.generated.h"
class UStaticMeshComponent;
UCLASS()
class Game_API PlayerCharacter : public ACharacter
{
GENERATED_BODY()
public:
APlayerCharacter();
public:
UPROPERTY(EditAnywhere)
class USkeletalMeshComponent* CharacterMesh;
protected:
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
class UPlayerShieldComponent* ShieldComp;
public:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
void ToggleShield();
UFUNCTION(Server, Reliable, WithValidation)
void Server_ToggleShield();
};
PlayerCharacter.cpp:
#include "Character/PlayerCharacter.h"
#include "GameFramework/Controller.h"
#include "Components/StaticMeshComponent.h"
#include "Components/PlayerShieldComponent.h"
#include "Net/UnrealNetwork.h"
APlayerCharacter::APlayerCharacter()
{
PrimaryActorTick.bCanEverTick = true;
CharacterMesh = Cast<USkeletalMeshComponent>(GetDefaultSubobjectByName("CharacterMesh0"));
ShieldComp = CreateDefaultSubobject<UPlayerShieldComponent>(TEXT("ShieldComp"));
}
void APlayerCharacter::BeginPlay()
{
Super::BeginPlay();
}
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &APlayerCharacter::ToggleShield);
}
void APlayerCharacter::ToggleShield()
{
if (GetLocalRole() == ROLE_Authority)
{
if (ShieldComp)
{
ShieldComp->DoToggleShield();
}
}
else
{
Server_ToggleShield();
}
}
bool APlayerCharacter::Server_ToggleShield_Validate()
{
return true;
}
void APlayerCharacter::Server_ToggleShield_Implementation()
{
if (UPlayerShieldComponent* sc = Cast<UPlayerShieldComponent>(GetDefaultSubobjectByName("ShieldComp")))
{
sc->DoToggleShield();
}
}
PlayerShieldComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Character/PlayerCharacter.h"
#include "PlayerShieldComponent.generated.h"
UCLASS(ClassGroup = (Game), meta = (BlueprintSpawnableComponent))
class Game_API UPlayerShieldComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPlayerShieldComponent();
UPROPERTY(Replicated, BlueprintReadOnly)
bool bShieldIsOn;
protected:
virtual void BeginPlay() override;
UFUNCTION(NetMulticast, Reliable, WithValidation)
void Multi_ToggleShield();
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Materials)
class UMaterialInstance* OffMaterial;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Materials)
class UMaterialInstance* OnMaterial;
void DoToggleShield();
};
PlayerShieldComponent.cpp
#include "Components/PlayerShieldComponent.h"
#include "Net/UnrealNetwork.h"
#include "Components/StaticMeshComponent.h"
#include "Character/PlayerCharacter.h"
// Sets default values for this component's properties
UPlayerShieldComponent::UPlayerShieldComponent()
{
PrimaryComponentTick.bCanEverTick = false;
OnMaterial = CreateDefaultSubobject<UMaterialInstance>(TEXT("On Material"));
OffMaterial = CreateDefaultSubobject<UMaterialInstance>(TEXT("Off Material"));
bShieldIsOn = false;
}
void UPlayerShieldComponent::BeginPlay()
{
Super::BeginPlay();
}
void UPlayerShieldComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UPlayerShieldComponent, bShieldIsOn);
}
void UPlayerShieldComponent::DoToggleShield()
{
if (GetOwnerRole() == ROLE_Authority)
{
bShieldIsOn = !bShieldIsOn;
Multi_ToggleShield(); // From the server.
}
}
void UPlayerShieldComponent::Multi_ToggleShield_Implementation()
{
if (APlayerCharacter* character = Cast<APlayerCharacter>(GetOwner()))
{
if (USkeletalMeshComponent* cm = Cast<USkeletalMeshComponent>(character->GetDefaultSubobjectByName("CharacterMesh0")))
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, character->GetFName().ToString());
if (cm->GetMaterial(0) == OnMaterial)
{
cm->SetMaterial(0, OffMaterial);
}
else
{
cm->SetMaterial(0, OnMaterial);
}
}
}
}
bool UPlayerShieldComponent::Multi_ToggleShield_Validate()
{
return true;
}