Started Replicating Properties. Now Clients can't move. Don't understand why

Hello all,

TL;DR Set Up Replication on the first of my character variables, suddenly client characters are not able to move. Server (Host) character can move just fine.

I didn’t realize how difficult it was going to be to convert a single-player game to a multiplayer game since I started producing one. So… basically, I’ve been working on a multiplayer game but treating it as a single player game in terms of code. I have a few core systems in already, but nothing really serious. I recently realized that almost nothing in my game will work as a multiplayer experience without setting up replication and RPCs, so I have been trying to modify my code to support my originally intended multiplayer functionality.

The first thing that I tackled in this was to make sure that all of my game play relevant projectiles are replicated to clients. To do this, I renamed the OnFire() function (I started this project from a C++ 1st Person Template) to ClientOnFire(), and moved the logic from this function into a ServerOnFire() function (which is called only on the server. This allowed all projectiles to be spawned on the server.

This was working all fine and good. So I decided to tackle some player variable replication next. I started with the Equipped_Ammo_Remaining variable. I successfully set this up to replicate (ie. It compiles successfully). However, upon testing the game, I now have a fairly serious problem. After making this change, my player controlled characters can no longer move on clients. The server character can move just fine. but no client characters can. When using a listen server, the server character moves properly, but the client characters cannot. When using a dedicated server, none of the characters (all on clients) can move.

I’m really stumped here. I am certain that the change to Equipped_Ammo_Remaining
(including the addition of “void AMonkeyNutsCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const” ) has caused my client characters to be unable to move, but I honestly have no idea why.

I’d appreciate any help with this that I can get. Thanks in advance for any response.

Code for my Character Class is shown below.

MonkeyNutsCharacter.h

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "MonkeyNuts.h"
#include "UnrealNetwork.h"
#include "WeaponTypes.h"
#include "MNPickup.h"
#include "MNWeaponPickup.h"
#include "MNCharacterMovementComponent.h"
#include "MonkeyNutsProjectile.h"
#include "MonkeyNutsCharacter.generated.h"

UCLASS(config=Game)
class AMonkeyNutsCharacter : public ACharacter
{
	GENERATED_UCLASS_BODY()


//////////***************Component Properties*******************//////////

…//Insert Component initialization stuff
//////////******************************************************//////////

//***Turning Variables**
	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
		float BaseTurnRate;

	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
		float BaseLookUpRate;

//***Weapon and Projectile**
	/** Gun muzzle's offset from the characters location */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
		FVector GunOffset;

	/** Projectile class to spawn */
	UPROPERTY(EditDefaultsOnly, Category = Projectile)
		TSubclassOf<class AMonkeyNutsProjectile> ProjectileClass;

	/** Sound to play each time we fire */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
		USoundBase* FireSound;

	/** AnimMontage to play each time we fire */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
		UAnimMontage* FireAnimation;
	
//**********Function Prototypes**********

//////////***************Return Player Var Methods**************//////////

///Insert Get Private Variable Functions Here
//////////******************************************************//////////
//TICK FUNCTION Override for the player
UFUNCTION()
void Tick(float DeltaSeconds) override;

	//Allows the character source to receive OnActorBeginOverlap Events
		virtual void ReceiveActorBeginOverlap(AActor* OtherActor) override;
	//Allows the character source to receive OnActorB=EndOverlap Events
		virtual void ReceiveActorEndOverlap(AActor* OtherActor) override;
	//Allows the character to recieve OnBeginPlayEvents
		virtual void ReceiveBeginPlay() override;

	UFUNCTION(BlueprintCallable, Category = PlayerStatus)
	//Allows the character to check for interactable objects when overlapping other objects
		void CheckForInteractables(int32 OnorOff);

	//Returns what the character is currently looking at
	UFUNCTION(BlueprintCallable, Category = PlayerStatus)
		AActor* GetLookingAt();
	//Returns the Closest Interactable to the character
	UFUNCTION(BlueprintCallable, Category = PlayerStatus)
		AMNInteractable* GetClosestInteractable();

	UFUNCTION()
		void RunATrace();
	UPROPERTY()
		AActor* InteractableInRange;

		void PickUpWeapon(AMNWeaponPickup* WeaponPU);
private:

//////////***************Primary Player Variables***************//////////
	//Character Health
	UPROPERTY(EditAnywhere, Instanced, Category = PlayerStatus)
		int32 PlayerHealth;
	//The Character's Currently Equipped Weapon
	UPROPERTY(EditAnywhere, Instanced, Category = PlayerEquipment)
		TEnumAsByte<EWeaponTypes::Type> EquippedWeapon;
	//The Character's Currently Equipped Backup Weapon
	UPROPERTY(EditAnywhere, Instanced, Category = PlayerEquipment)
		TEnumAsByte<EWeaponTypes::Type> BackupWeapon;
	//Stores which carried weapon is currently equipped. 1 for Primary, 2 for Secondary.
	UPROPERTY(EditAnywhere, Instanced, Category = PlayerEquipment)
		int32 CurrentWeapon;
//////////******************************************************//////////


	//The Closest Interactable to The Character
		AMNInteractable* ClosestInteractable;
	//The What the Character Is Currently looking at.
		AActor* ThingImLookingAt;

//////////***************Player Ammunition Variables***************//////////
	//Total Amount of Ammo Stockpiled for Current Weapon
	UPROPERTY(EditAnywhere, Replicated, Instanced, Category = PlayerEquipment)
		int32 Equipped_Ammo_Remaining;
	///INSERT OTHER Variable Properties similar to Equipped_Ammo_Remaining, bbut these are not replicated. 
//////////******************************************************//////////

protected:
	void Interact();
	void SwitchWeapon();

	/** Handler for a touch input beginning. */
	void TouchStarted(const ETouchIndex::Type FingerIndex, const FVector Location);

	/** Fires a projectile. */
	void ClientOnFire();

	//Server Driven Fire Function
	UFUNCTION(Reliable, Server, WithValidation)
	void ServerOnFire();

	/** Handles moving forward/backward */
	void ClientMoveForward(float Value);

	UFUNCTION(Reliable, Server, WithValidation)
	void ServerMoveForward(float Value);

	/** Handles stafing movement, left and right */
	void ClientMoveRight(float Value);

	void ServerMoveRight(float Value);

	/*
	 * Called via input to turn at a given rate.
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */

	void TurnAtRate(float Rate);

	/**
	 * Called via input to turn look up/down at a given rate.
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void LookUpAtRate(float Rate);


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

MonkeyNutsCharacter.cpp

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

#include "MonkeyNuts.h"
#include "UnrealNetwork.h"
#include "WeaponTypes.h"
#include "MNWeaponPickup.h"
#include "MonkeyNutsCharacter.h"
#include "Math.h"
#include "MNWeaponInfo.h"


//////////////////////////////////////////////////////////////////////////
// AMonkeyNutsCharacter


AMonkeyNutsCharacter::AMonkeyNutsCharacter(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP.SetDefaultSubobjectClass<UMNCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
{
	//***Basic Properties***
	bReplicates = true;
	bOnlyRelevantToOwner = false;
	bReplicateInstigator = true;
	bReplicateMovement = true;
	// set our turn rates for input
	BaseTurnRate = 45.f;
	BaseLookUpRate = 45.f;

	/** CharacterMovement component used by walking/running/flying avatars not using rigid body physics */
	UPROPERTY(Category = Character, VisibleAnywhere, BlueprintReadOnly)
	TSubobjectPtr<class UMNCharacterMovementComponent> CharacterMovement;

	// Default offset from the character location for projectiles to spawn
	GunOffset = FVector(100.0f, 30.0f, 10.0f);

	//***Component Properties***

	// Set size for collision capsule
	CapsuleComponent->InitCapsuleSize(42.f, 96.0f);
	
	// Create a CameraComponent	
	FirstPersonCameraComponent = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("FirstPersonCamera"));
	FirstPersonCameraComponent->AttachParent = CapsuleComponent;
	FirstPersonCameraComponent->RelativeLocation = FVector(0, 0, 128.f); // Position the camera
	//RootComponent = FirstPersonCameraComponent;
	// Create a mesh component that will be used when being viewed from a '1st person' view (when controlling this pawn)
	Mesh1P = PCIP.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("CharacterMesh1P"));
	Mesh1P->SetOnlyOwnerSee(true);			// only the owning player will see this mesh
	Mesh1P->AttachParent = FirstPersonCameraComponent;
	//Mesh1P->AttachTo(FirstPersonCameraComponent);
	//Mesh1P->RelativeLocation = FVector(0.f, 0.f, -150.f);
	Mesh1P->bCastDynamicShadow = false;
	Mesh1P->CastShadow = false;
	//Mesh1P->AlwaysLoadOnClient;


	//Create a mesh component that will be used when being veiwed by other players.
	Mesh3P = PCIP.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("CharacterMesh3P"));
	Mesh3P->SetOwnerNoSee(true);
	Mesh3P->SetOnlyOwnerSee(false);
	Mesh3P->AttachParent = FirstPersonCameraComponent;
	//Mesh3P->AttachTo(FirstPersonCameraComponent);
	//Mesh3P->RelativeLocation = FVector(0.f, 0.f, -200.f);
	Mesh3P->bCastDynamicShadow = true;
	Mesh3P->CastShadow = true;

	// Note: The ProjectileClass and the skeletal mesh/anim blueprints for Mesh1P are set in the
	// derived blueprint asset named MyCharacter (to avoid direct content references in C++)

	//***Public Player Variables***
	PlayerHealth = 100;

	EquippedWeapon = EWeaponTypes::Type::Pistol;
	BackupWeapon = EWeaponTypes::Type::Unarmed;
	Equipped_Ammo_Remaining = 100000;
	Backup_Ammo_Remaining = 100000;
	Equipped_AmmoClip_Remaining = 100000;
	Equipped_AmmoClip_Max = 100000;
	Backup_AmmoClip_Remaining = 100000;
	Backup_AmmoClip_Max = 100000;
	Grenade_Ammo = 2;

}

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

void AMonkeyNutsCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	// set up gameplay key bindings
	check(InputComponent);

	InputComponent->BindAction("Jump", IE_Pressed, this, &AMonkeyNutsCharacter::Jump);
	InputComponent->BindAction("Interact", IE_Pressed, this, &AMonkeyNutsCharacter::Interact);
	InputComponent->BindAction("SwitchWeapons", IE_Pressed, this, &AMonkeyNutsCharacter::SwitchWeapon);
	InputComponent->BindAction("Fire", IE_Pressed, this, &AMonkeyNutsCharacter::ClientOnFire);
	InputComponent->BindTouch(EInputEvent::IE_Pressed, this, &AMonkeyNutsCharacter::TouchStarted);

	InputComponent->BindAxis("MoveForward", this, &AMonkeyNutsCharacter::ClientMoveForward);
	InputComponent->BindAxis("MoveRight", this, &AMonkeyNutsCharacter::ClientMoveRight);
	
	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
	// "turn" handles devices that provide an absolute delta, such as a mouse.
	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
	InputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
	InputComponent->BindAxis("TurnRate", this, &AMonkeyNutsCharacter::TurnAtRate);
	InputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
	InputComponent->BindAxis("LookUpRate", this, &AMonkeyNutsCharacter::LookUpAtRate);
}



void AMonkeyNutsCharacter::ClientOnFire()
{
	if (Role <= ROLE_Authority){
		AMonkeyNutsCharacter::ServerOnFire();
	}
}

void AMonkeyNutsCharacter::ServerOnFire_Implementation(){
	if (ProjectileClass != NULL)
	{
		const FRotator SpawnRotation = GetControlRotation();
		// MuzzleOffset is in camera space, so transform it to world space before offsetting from the character location to find the final muzzle position
		const FVector SpawnLocation = GetActorLocation() + SpawnRotation.RotateVector(GunOffset);

		UWorld* const World = GetWorld();
		if (World != NULL)
		{
			// spawn the projectile at the muzzle
			World->SpawnActor<AMonkeyNutsProjectile>(ProjectileClass, SpawnLocation, SpawnRotation);
		}
	}

	// try and play the sound if specified
	if (FireSound != NULL)
	{
		UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation());
	}

	// try and play a firing animation if specified
	if (FireAnimation != NULL)
	{
		// Get the animation object for the arms mesh
		UAnimInstance* AnimInstance = Mesh1P->GetAnimInstance();
		if (AnimInstance != NULL)
		{
			AnimInstance->Montage_Play(FireAnimation, 1.f);
		}
	}
}
bool AMonkeyNutsCharacter::ServerOnFire_Validate(){
	return true;
}

void AMonkeyNutsCharacter::TouchStarted(const ETouchIndex::Type FingerIndex, const FVector Location)
{
	// only fire for first finger down
	if (FingerIndex == 0)
	{
		ClientOnFire();
	}
}

void AMonkeyNutsCharacter::ClientMoveForward(float Value)
{
	if (Role <= ROLE_Authority){
		AMonkeyNutsCharacter::ServerMoveForward(Value);
	}
	else if (Role == ROLE_Authority){
		if (Value != 0.0f){
			// add movement in that direction
			AddMovementInput(GetActorForwardVector(), Value);
		}
	}
	else{
		//Unhandled Exception
	}
	
}

void AMonkeyNutsCharacter::ServerMoveForward_Implementation(float Value){
	if (Value != 0.0f){
			//UE_LOG(LogTemp, Warning, TEXT("ServerMoveForward_Implementation Called. Value: %n"), Value);
			// add movement in that direction
			AddMovementInput(GetActorForwardVector(), Value);
	}
}
bool AMonkeyNutsCharacter::ServerMoveForward_Validate(float Value){
	return true;
}

void AMonkeyNutsCharacter::ClientMoveRight(float Value)
{
	if (Value != 0.0f)
	{
		// add movement in that direction
		AddMovementInput(GetActorRightVector(), Value);
	}
}

void AMonkeyNutsCharacter::TurnAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void AMonkeyNutsCharacter::LookUpAtRate(float Rate)
{
	// calculate delta for this frame from the rate information
	AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

//////////////////////////////////////////////////////////////////////////
///Tick









//Ammo Value Retrieval Functions
int32 AMonkeyNutsCharacter::GetEquippedAmmo_Remaining(){
	return Equipped_Ammo_Remaining;
}
int32 AMonkeyNutsCharacter::GetEquippedAmmoClip_Remaining(){
	return Equipped_AmmoClip_Remaining;
}
int32 AMonkeyNutsCharacter::GetEquippedAmmoClip_Max(){
	return Equipped_AmmoClip_Max;
}

int32 AMonkeyNutsCharacter::GetBackupAmmo_Remaining(){
	return Backup_Ammo_Remaining;
}
int32 AMonkeyNutsCharacter::GetBackupAmmoClip_Remaining(){
	return Backup_AmmoClip_Remaining;
}
int32 AMonkeyNutsCharacter::GetBackupAmmoClip_Max(){
	return Backup_AmmoClip_Max;
}

int32 AMonkeyNutsCharacter::GetGrenade_Ammo(){
	return Grenade_Ammo;
}

//Other Functions

void AMonkeyNutsCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const{
	DOREPLIFETIME(AMonkeyNutsCharacter, Equipped_Ammo_Remaining);

}

Hey there,
When you supply the GetLifetimeReplicatedProps function you are overriding all the character replication stuff already built in to the engine.

You need to add a call at the start of your function to Super::GetLifetimeReplicatedProps like so:

void AMonkeyNutsCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
     Super::GetLifetimeReplicatedProps(OutLifetimeProps);
     DOREPLIFETIME(AMonkeyNutsCharacter, Equipped_Ammo_Remaining);
 }
3 Likes

Thanks for that. That worked wonderfully. I feel kinda dumb that that’s all it was though. Thanks for the fast response. Wasn’t expecting that.

thank you