Background stutter

Hello everyone.

I’m making a simple 2D game and want to implement some moving backgrounds.

I have ABackground class that has to follow camera component attached to the character.
Since basic code is not very big, I’ll post it here in its entirety.

ABackground::ABackground()
{
	PrimaryActorTick.bCanEverTick = true;
	PrimaryActorTick.bStartWithTickEnabled = true;
	PrimaryActorTick.TickGroup = TG_PrePhysics;

	Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
	RootComponent = Root;

	SpriteComponent = CreateDefaultSubobject<UPaperGroupedSpriteComponent>(TEXT("Sprite"));
	SpriteComponent->SetupAttachment(RootComponent);

	Offset = -FVector::RightVector;
}

void ABackground::BeginPlay()
{
	Super::BeginPlay();

	if (Character)
	{
		Camera = Character->GetCamera();

		if (Camera)
		{
			FVector NewLocation = Offset;
			CameraPosition = Camera->GetComponentTransform().GetTranslation().X;
			NewLocation.X += CameraPosition;
			RootComponent->SetWorldLocation(NewLocation);
		}
	}
}

void ABackground::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (Camera)
	{
		FVector NewLocation = RootComponent->GetComponentLocation();
		float dx = (Camera->GetComponentTransform().GetTranslation().X - CameraPosition) * ParallaxSpeed;
		NewLocation.X += dx;
		CameraPosition = Camera->GetComponentTransform().GetTranslation().X;
		RootComponent->SetWorldLocation(NewLocation);
	}
}

Well, it works pretty much as I’d expect, but there is one issue: movement is very inconsistent. When I set ParallaxSpeed value to 1, I want my background to be completely static relatively to the camera, and, well, it’s not. It shakes and stutters all the time during movement. I tried to use FMath::FInterp, but it doesn’t help.

Does anyone have any ideas?

You are not taking the DeltaTime into account. When you animate you will normally account for the frame time, this is so that you can crate a smooth animation. In your case I’m a bit skeptic about your ParallaxSpeed, does it mean units per second? if yes you ParallaxSpeed must be multiplied per DeltaTime, this way it will in fact indicate how much you will move per second. It makes it all more consistent.

So a ParallaxSpeed of 1 would be mean move it 1 unit each second. To stop movement ParallaxSpeed should be set to 0.

Note: I’m not fixing your code here, just outlining some holes in your proposed solution.

Hello, thank you for your answer.

ParallaxSpeed sets the speed of background relatively the camera’s speed. Since camera’s movement is already smoothed, I do not need to take DeltaTime into account. Basically you may even ignore it, just think it is 1. And if it is 1, background speed equals camera speed, so the code will just move the background at the exact camera’s location with fixed offset added.

Actually, I can’t understand why stutter occurs, because relative positions of camera and background are completely fixed each frame (given that ParallaxSpeed is 1).

The basic idea is this: no matter how fast, in what way and where the camera moves, I want my background to be completely fixed relatively to it when ParallaxSpeed is set to 1. Camera may teleport or whatever, background have to be there always. That’s why I’m just setting background location based on camera location and I’m kinda surprised it doesn’t work as intended.

Details about the camera:
It is connected by Spring Arm to my character, but I’m not sure if it’s important.

Decided to post a simpler version of code to explain the issue I’m having.

void ABackground::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (Camera)
	{
		FVector NewLocation = RootComponent->GetComponentLocation();
		NewLocation.X = Camera->GetComponentTransform().GetTranslation().X;
		RootComponent->SetWorldLocation(NewLocation);
	}
}

Simple as that. All I want to achieve is to maintain position of background relative to the camera without actual attachment.

The code, obviously, works. It does maintain relative positions, but background always jitters during camera’s movement.

It feels like the camera updates the picture before background’s location was set. I tried setting TickGroup of the background befure TickGroup of the camera – no luck.

By the way, with inconsistent framerate stuttering gets worse, with visible “teleportation” of the background.

With FPS capped to 60 fps and constant speed of camera there is no stuttering whatsoever, but it starts occuring when camera’s speed changes.

Weird enough, with 120 fps jitter is again visible even if camera’s speed is constant.

Can you try to set your actor tick to be dependent on the cameras tick? I’ll try to post an example tomorrow (on the phone atm xD).

I’m not sure how to do it, but I’d think I need to make my actor tick before image updated, not vice versa.

Hi again.
I kinda solved the problem. I made a new inherited from AActor class, that follows given target on given distance with given relative speed. Created two instances of this new class and placed a primaty camera into one of them (before it was attached to my character), another one serves as a container for a background sprite(later on I’ll just make another class with extended functionality for it). They both follow my character and there is no stutter between them at all. I suppose stutter is still present on the character himself, but I was not able to see it because of animation.

This is how you add a tick dependency:

The DependencyActor has to tick before us, you can do the opposite too.

PrimaryActorTick.AddPrerequisite(DependencyActor, DependencyActor->PrimaryActorTick);

I attach actors to player camera this way:

in begin play or when you want :

if (attachActorToPlayerCamera != nullptr) { // the actor to attach to plyr cam
		APlayerController* pc = 
               (GetWorld() != nullptr) ? GetWorld()->GetFirstPlayerController() : nullptr;
		APlayerChar* plyr0 = (pc != nullptr) ? Cast<APlayerChar>(pc->GetPawn()) : nullptr;
		if (plyr0 != nullptr) {
			auto cam = plyr0->GetSideViewCameraComponent();
			if (cam != nullptr) {
				attachActorToPlayerCamera->AttachToComponent(cam,   
                                      FAttachmentTransformRules::SnapToTargetNotIncludingScale);
                            // here set relative location you want
			}
		}
	}