Need help with prediction in CharacterMovementComponent

Hi,

I’ve read through some examples and the wiki entry but there is too much that isn’t making sense when I try and put my own abilities in which aren’t so basic.

They ‘roughly’ work on a dedicated server and each client sees the other client use the ability, but it’s filled with corrections throughout the ability. I’m worried I might not be entering the right data into savedmoves.

In this case the ability is a boost/sprint, there is a float accumulated via input, when it hits 1.0 it sets bWantsBoost = true, and a timer is set to the duration of the ability. The ability then sets a speed multiplier on tick (that is used by CalcVelocity). However as mentioned, it’s being constantly corrected throughout even at 100ms simulated latency.

Here is some summarized code. Would be great if someone can tell me what I’m missing.

CharacterMovement header:


// OMITTED ABOVE

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Movement")
	float SpeedMultiplier = 1.f;

	float BoostTimer = 0.f;
	float BoostCooldownTimer = 0.f;
	uint8 bWantsBoost : 1;
	bool bIsBoosting = false;

	// ---------------------------------------------
	// NETCODE
	// ---------------------------------------------

	virtual void OnMovementUpdated(float DeltaTime, const FVector& OldLocation, const FVector& OldVelocity) override;

	virtual void UpdateFromCompressedFlags(uint8 Flags) override;

	virtual class FNetworkPredictionData_Client* GetPredictionData_Client() const override;
};


class FSavedMove_Rider : public FSavedMove_Character
{
public:
	typedef FSavedMove_Character Super;

	uint8 bSavedWantsBoost : 1;

	virtual void Clear() override;

	virtual uint8 GetCompressedFlags() const override;

	virtual bool CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InPawn, float MaxDelta) const override;

	virtual void SetMoveFor(ACharacter* C, float InDeltaTime, FVector const& NewAccel, class FNetworkPredictionData_Client_Character & ClientData) override;

	virtual void PrepMoveFor(ACharacter* C) override;
};

class FNetworkPredictionData_Client_Rider : public FNetworkPredictionData_Client_Character
{
public:
	FNetworkPredictionData_Client_Rider(const UCharacterMovementComponent& ClientMovement);

	typedef FNetworkPredictionData_Client_Character Super;

	virtual FSavedMovePtr AllocateNewMove() override;
};

**Netcode Class File: **


#include "Wool.h"
#include "PMovement.h"
#include "PCharacter.h"


void FSavedMove_Rider::Clear()
{
	Super::Clear();

	bSavedWantsBoost = false;
}

void UPMovement::OnMovementUpdated(float DeltaTime, const FVector& OldLocation, const FVector& OldVelocity)
{
	Super::OnMovementUpdated(DeltaTime, OldLocation, OldVelocity);

	if (!CharacterOwner)
	{
		return;
	}

	if (bWantsBoost)
	{
		bIsBoosting = bWantsBoost;

		HandleBoost();
	}

	if (BoostTimer > 0.f)
	{
		BoostTimer -= DeltaTime;
	}

	if (BoostCooldownTimer > 0.f)
	{
		BoostCooldownTimer -= DeltaTime;
	}
}

uint8 FSavedMove_Rider::GetCompressedFlags() const
{
	uint8 Result = Super::GetCompressedFlags();

	if (bSavedWantsBoost)
	{
		Result |= FLAG_Custom_0;
	}

	return Result;
}

void UPMovement::UpdateFromCompressedFlags(uint8 Flags)
{
	Super::UpdateFromCompressedFlags(Flags);

	bWantsBoost = (Flags&FSavedMove_Character::FLAG_Custom_0) != 0;
}

bool FSavedMove_Rider::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InPawn, float MaxDelta) const
{
	if (bSavedWantsBoost != ((FSavedMove_Rider*)&NewMove)->bSavedWantsBoost)
	{
		return false;
	}

	return Super::CanCombineWith(NewMove, InPawn, MaxDelta);
}


void FSavedMove_Rider::SetMoveFor(ACharacter* Character, float InDeltaTime, FVector const& NewAccel, class FNetworkPredictionData_Client_Character & ClientData)
{
	Super::SetMoveFor(Character, InDeltaTime, NewAccel, ClientData);

	UPMovement* CharMov = Cast<UPMovement>(Character->GetCharacterMovement());

	if (CharMov)
	{
		bSavedWantsBoost = CharMov->bWantsBoost;
	}
}

void FSavedMove_Rider::PrepMoveFor(ACharacter* Character)
{
	Super::PrepMoveFor(Character);

	UPMovement* CharMov = Cast<UPMovement>(Character->GetCharacterMovement());
	
	if (CharMov)
	{
		CharMov->bWantsBoost = bSavedWantsBoost;
	}
}

FNetworkPredictionData_Client_Rider::FNetworkPredictionData_Client_Rider(const UCharacterMovementComponent& ClientMovement)
	: Super(ClientMovement)
{

}

FSavedMovePtr FNetworkPredictionData_Client_Rider::AllocateNewMove()
{
	return FSavedMovePtr(new FSavedMove_Rider());
}

class FNetworkPredictionData_Client* UPMovement::GetPredictionData_Client() const
{
	check(CharacterOwner != NULL);
	check(PawnOwner->Role < ROLE_Authority);

	if (!ClientPredictionData)
	{
		UPMovement* MutableThis = const_cast<UPMovement*>(this);
		MutableThis->ClientPredictionData = new FNetworkPredictionData_Client_Rider(*this);
		MutableThis->ClientPredictionData->MaxSmoothNetUpdateDist = 92.f;
		MutableThis->ClientPredictionData->NoSmoothNetUpdateDist = 140.f;
	}

	return ClientPredictionData;
}


Movement Code:


void UPMovement::HandleBoost()
{
	if(bIsBoosting)
	{
		const EBoostState State = GetBoostState();

		const float AbilityDuration = SpeedBoostAccelTime + SpeedBoostDuration + SpeedBoostDecelTime;

		switch (State)
		{
			case EBoostState::BS_Accel:
			{
				const float Alpha = UH::MapRange(BoostTimer, AbilityDuration, AbilityDuration - SpeedBoostAccelTime);

				if (SpeedBoostAccelCurve)
				{
					SpeedMultiplier = FMath::Lerp(1.f, SpeedBoostMultiplier, SpeedBoostAccelCurve->GetFloatValue(Alpha));
				}
				else
				{
					SpeedMultiplier = FMath::Lerp(1.f, SpeedBoostMultiplier, Alpha);

				}
				break;
			}
			case EBoostState::BS_Boost:
			{
				SpeedMultiplier = SpeedBoostMultiplier;
				break;
			}
			case EBoostState::BS_Decel:
			{
				const float Alpha = UH::MapRange(BoostTimer, 
												 AbilityDuration - SpeedBoostAccelTime - SpeedBoostDuration,
												 0.f, true);

				if (SpeedBoostDecelCurve)
				{
					SpeedMultiplier = FMath::Lerp(SpeedBoostMultiplier, SpeedBoostDemultiplier,
												  SpeedBoostDecelCurve->GetFloatValue(Alpha));

				}
				else
				{
					SpeedMultiplier = FMath::Lerp(SpeedBoostMultiplier, SpeedBoostDemultiplier, Alpha);

				}
				break;
			}
			case EBoostState::BS_None:
			{
				SpeedMultiplier = 1.f;
				BoostCooldownTimer = SpeedBoostCooldown;
			}
			break;
		}
	}
}

EBoostState UPMovement::GetBoostState() const
{
		const float AbilityDuration = SpeedBoostAccelTime + SpeedBoostDuration + SpeedBoostDecelTime;

		if (BoostTimer > AbilityDuration - SpeedBoostAccelTime)
		{
			return EBoostState::BS_Accel;
		}
		else if (BoostTimer > AbilityDuration - (SpeedBoostAccelTime + SpeedBoostDuration))
		{
			return EBoostState::BS_Boost;
		}
		else if (BoostTimer > AbilityDuration - (SpeedBoostAccelTime + SpeedBoostDuration + SpeedBoostDecelTime))
		{
			return EBoostState::BS_Decel;
		}
		else
		{
			return EBoostState::BS_None;
		}

	return EBoostState::BS_None;
}


Did you ever figure this out?

As I’m trying to set various movement speeds and accelerations fairly often in my game and not having much luck :frowning: