Animation works on client, not on server...

A bit of an old thread, but I ran into this issue today, and Google was not helpful at all. It did point me to this thread, but there’s no actual solution here. So I’ll outline the bug/problem I’ve found that causes this and my work around.

The problem: If you have a derived character with more than one skeletal mesh, only the first (the Mesh member) will animate on the server if it’s a client. It’s related to this check in SkeletalMeshComponent::ShouldTickPose()

This is actually pretty gross. I have no idea why SkeletalMeshComponent is directly referring to a gameplay class. Bad programmer, no cookie.



// Remote Clients on the Server will always tick animations as updates from the client comes in. To use Client's delta time.
const bool bRemoteClientOnServer = CharacterOwner && (CharacterOwner->Role == ROLE_Authority) && CharacterOwner->Controller && !CharacterOwner->Controller->IsLocalController();


When the components go to tick, if this component is part of a ACharacter derived class, and it’s remotely controlled and it’s on a server, it doesn’t get the normal tick. CharacterMovementComponent has the responsibility to do it, but doesn’t. It only assumes that it has one mesh, the Mesh member, and updates that and only that.

Unfortunately the ACharacter and UCharacterMovementComponent classes are quite tightly coupled and monstrously large, so a good general solution isn’t easy.

However, I’ve fixed MY issue. There’s a couple other calls to TickPose() in UCharacterMovementComponent that aren’t addressed with this fix, so I may have to come back to this issue. I ended up having to create a custom character movement component and overriding one method.



void URobotCMC::MoveAutonomous(float ClientTimeStamp, float DeltaTime, uint8 CompressedFlags, const FVector& NewAccel)
{
	if (!HasValidData())
	{
		return;
	}

	CharacterOwner->UpdateFromCompressedFlags(CompressedFlags);
	CharacterOwner->CheckJumpInput(DeltaTime);

	Acceleration = ConstrainInputAcceleration(NewAccel);
	AnalogInputModifier = ComputeAnalogInputModifier();
	PerformMovement(DeltaTime);

	CharacterOwner->ClearJumpInput();

	// If not playing root motion, tick animations after physics. We do this here to keep events, notifies, states and transitions in sync with client updates.
	if (!CharacterOwner->bClientUpdating && !CharacterOwner->IsPlayingRootMotion() && CharacterOwner->Mesh)
	{
                // My hack, tighly coupling this component to my derived character class, sadly. I'd really like a good solution.
		ARobotCharacter *robot = Cast<ARobotCharacter>(CharacterOwner);

		if (robot != NULL)
		{
                        // Tick my other components.
			if (robot->TorsoComponent)
			{
				robot->TorsoComponent->TickPose(DeltaTime);
			}

			if (robot->HullComponent)
			{
				robot->HullComponent->TickPose(DeltaTime);
			}
		}

		CharacterOwner->Mesh->TickPose(DeltaTime);
	}
}



So DarkHalf, you were right - the issue was related to you having an unexpected number of skeletal meshes on your derived character, and having the blueprint somewhere other than the default mesh.

I hope this helps someone. I am very tempted to refactor the ACharacter and movement component to be a little less grody, but the maintenance overhead involved is scary.