Camera & Camera Manager behaving oddly in Multiplayer

EDIT: Since discovered it’s not the camera manager, it’s a matter of references to the Pawn. See last post.

Wondering if someone can help me with this. I’m having an issue at the moment where playing a Multiplayer game in the editor causes the game to spawn two Camera Actors at the world origin, and my clients are all using those cameras instead of the correct ones. What’s strange, is that when I unpossess and select my Player Controllers, I can see that the player controllers are looking through the correct camera in the small preview windows.

I have a custom Player Controller that has a Spring arm and a Camera component as part of it, and on BeginPlay or posession etc, I’m telling the camera to attach itself to the Pawn it possesses. This part works, and I can tell that from the preview windows, it also works fine in Single Player. The reason I’m doing this, is because I want each player controlled to manage it’s camera based on the pawn it’s possessing, and I don’t want to create camera and spring-arm components for every pawn I could possess. This enables me to do things like change to third and first person views seamlessly along with other things.

My custom Camera Manager class changes the ‘UpdateViewTarget’ function to first search for a camera on my PlayerController before doing anything else, which I know work in Singleplayer. Here’s the altered function. Non-C++ people, don’t be scared away by the code!



void ABZGame_CameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime)


{
	if ((PendingViewTarget.Target != NULL) && BlendParams.bLockOutgoing && OutVT.Equal(ViewTarget))
	{
		return;
	}

	FMinimalViewInfo OrigPOV = OutVT.POV;
	OutVT.POV.FOV = DefaultFOV;
	OutVT.POV.OrthoWidth = DefaultOrthoWidth;
	OutVT.POV.bConstrainAspectRatio = false;
	OutVT.POV.ProjectionMode = this->bIsOrthographic ? ECameraProjectionMode::Orthographic : ECameraProjectionMode::Perspective;
	OutVT.POV.PostProcessBlendWeight = 1.0f;
	bool bDoNotApplyModifiers = false;

	ABZGame_PlayerController* BZGame_Controller = Cast<ABZGame_PlayerController>(GetOwningPlayerController());
	if (BZGame_Controller != NULL)
	{
		UCameraComponent* ViewCam = BZGame_Controller->GetViewCamera();

		//BZGame_Controller->GetViewCamera()->AttachTo(BZGame_Controller->GetCamSpringArm(), USpringArmComponent::SocketName);
		OutVT.POV.Location = ViewCam->GetComponentLocation();
		OutVT.POV.Rotation = ViewCam->GetComponentRotation();
		OutVT.POV.FOV = ViewCam->FieldOfView;
		OutVT.POV.AspectRatio = ViewCam->AspectRatio;
		OutVT.POV.bConstrainAspectRatio = ViewCam->bConstrainAspectRatio;
		OutVT.POV.ProjectionMode = ViewCam->ProjectionMode;
		OutVT.POV.OrthoWidth = ViewCam->OrthoWidth;
		OutVT.POV.PostProcessBlendWeight = ViewCam->PostProcessBlendWeight;

		if (BZGame_Controller->GetViewCamera()->PostProcessBlendWeight > 0.0f)
		{
			OutVT.POV.PostProcessSettings = ViewCam->PostProcessSettings;
		}

		if (!bDoNotApplyModifiers || this->bAlwaysApplyModifiers)
		{
			ApplyCameraModifiers(DeltaTime, OutVT.POV);
		}

		SetActorLocationAndRotation(OutVT.POV.Location, OutVT.POV.Rotation, false);
		UpdateCameraLensEffects(OutVT);
	}
	else
	{
		Super::UpdateViewTarget(OutVT, DeltaTime);
	}
}


In multiplayer, my cameras for all of the clients immediately switch to the one that the game spawns at the world origin by default. ShooterGame also spawns these cameras, but it uses the Pawn as a view Target I believe.

So… any ideas? I’ve trawled through answerhub and the forums to no avail so far. I suspect the issue is stemming either from the Player Controller or from the Camera Mananger. I also tried to use the ‘SetViewTarget’ function and pass in the PlayerController, but doing that causes the Manager to use my Pawn’s location and rotation instead. Very bizarre.

A bit of further experimentation reveals that I do seem to be using the Clients version of the PlayerController View Camera, but it’s not being attached to the correct Pawn on the client. This indicates to me that the issue isn’t with my Camera Mananger, but the way in which I’m attaching the camera’s spring arm to the newly possessed pawn.

So, here’s the way I’ve done it right now. I’m assuming I’m doing this the wrong way…



/* Server Handles Attaching Camera Arm To Pawn */
bool ABZGame_PlayerController::ServerAttachCameraToPawn_Validate()
{
	return true;
}

void ABZGame_PlayerController::ServerAttachCameraToPawn_Implementation()
{
	AttachCameraToPawn();
}

void ABZGame_PlayerController::AttachCameraToPawn()
{
	if (Role < ROLE_Authority)
	{
		ServerAttachCameraToPawn();
	}
	else
	{
		if (GetPawn())
		{
			GetCamSpringArm()->AttachTo(GetPawn()->GetRootComponent());
			GetCamSpringArm()->SetAttachedActor(GetPawn());
			OnViewTypeUpdate();
		}

		UBZGame_GameObjectComponent* NewGameObj = GetGameObjectComponentFromPawn();
		//ASSERTV(NewGameObj != NULL, *FString::Printf(TEXT("Invalid Game Object For Player Controller")));
		if (NewGameObj)
		{
			NewGameObj->SetOwningController(this);
			NewGameObj->SetHudReference(Cast<ABZGame_InGameHUD>(this->GetHUD()));
		}
	}
}


Edit: MOAR UPDATE. Turns out the Spring Arm isn’t being attached to the pawn, but on the Client ONLY. The server is attaching both… So I need to do something Client-Side to attach the spring arm…

And finally I have it working BUT it causes a crash when using a Dedicated Server, and it only works for my Character Pawn class for the client. The other much larger pawn seems to stop it from working.

I have to do a hefty amount of calls for this, I call the ‘AttachCameraToPawn’ function on Possess and BeginPlay (I thought Possess would be enough, but not doing it in BeginPlay stops it working for clients). This function then checks if we have authority or not; if we do, it calls a NetMulticast even to actually attach the CameraArm, if we don’t, it calls a Server-Only Reliable function to call the Multi-Cast events, and also tells itself to perform the attachment.

I’m new to network programming, and this clearly isn’t the fix because it only works 50% of the time, and using it with dedicated server causes a full crash (something about cant cast a nullptr to LocalPlayer?). May as well move this to the C++ forum is a mod is around. I’ve removed the _Validate() functions from the CPP but this is how it looks so far. If anybody could give me a crash-course in how to do network commands properly I’m all ears!

PlayerController.h



	UFUNCTION(Reliable, Server, WithValidation)
	void ServerAttachCameraToPawn();
	UFUNCTION(Reliable, NetMulticast, WithValidation)
	void MultiAttachCameraToPawn();
	UFUNCTION()
	void AttachCameraToPawn();
	void PerformAttach();


PlayerController.cpp



ABZGame_PlayerController::ABZGame_PlayerController(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	PlayerCameraManagerClass = ABZGame_CameraManager::StaticClass();
	CheatClass = UBZGame_CheatManager::StaticClass();

	/* Default View Setting - Will Later Default To The Players Options Settings From an INI File */
	CameraViewSetting = EBZGame_CameraViewSetting::ECVS_FirstPerson;

	PlayerControllerTeam = 0;
	bAllowGameActions = true;

	/* Create Spring Arm & Camera */
	CamSpringArm = ObjectInitializer.CreateDefaultSubobject<UBZGame_AssignableSpringArm>(this, TEXT("AssignableSpringArm"));
	GetCamSpringArm()->TargetArmLength = 0.f;
	GetCamSpringArm()->bUsePawnControlRotation = false;
	GetCamSpringArm()->bInheritPitch = true;
	GetCamSpringArm()->bInheritYaw = true;
	GetCamSpringArm()->bInheritRoll = true;
	GetCamSpringArm()->bEnableCameraLag = false;
	GetCamSpringArm()->bEnableCameraRotationLag = false;
	GetCamSpringArm()->bHiddenInGame = false;

	ViewCamera = ObjectInitializer.CreateDefaultSubobject<UCameraComponent>(this, TEXT("ViewCamera"));
	GetViewCamera()->AttachTo(GetCamSpringArm(), UBZGame_AssignableSpringArm::SocketName);
	GetViewCamera()->SetRelativeLocation(FVector(0.f, 0.f, 0.f), false);
	GetViewCamera()->bHiddenInGame = false;

	ArrowComp = ObjectInitializer.CreateDefaultSubobject<UArrowComponent>(this, TEXT("Arrow"));
	GetArrowComp()->SetArrowColor_New(FLinearColor(1.0f, 0.5f, 0.0f, 0.f));
	GetArrowComp()->AttachTo(GetViewCamera());
	GetArrowComp()->bHiddenInGame = false;

	bGameEndedFrame = false;
	bHasSentStartEvents = false;

	bHidden = false;
	bHiddenEd = false;
	bReplicates = true;
}

/* Initialization/Begin Play. */
void ABZGame_PlayerController::BeginPlay()
{
	Super::BeginPlay();

	/* For Clients */
	AttachCameraToPawn();
}

void ABZGame_PlayerController::Possess(APawn* aPawn)
{
	Super::Possess(aPawn);

	AttachCameraToPawn();
}

void ABZGame_PlayerController::AttachCameraToPawn()
{
	if (Role != ROLE_Authority)
	{
		ServerAttachCameraToPawn();
	}
	else if (Role == ROLE_Authority)
	{
		MultiAttachCameraToPawn();
	}
}

void ABZGame_PlayerController::ServerAttachCameraToPawn_Implementation()
{
	MultiAttachCameraToPawn();
	PerformAttach();
}

void ABZGame_PlayerController::MultiAttachCameraToPawn_Implementation()
{
	PerformAttach();
}

void ABZGame_PlayerController::PerformAttach()
{
	if (GetPawn())
	{
		GetCamSpringArm()->SetAttachedActor(GetPawn());
		GetCamSpringArm()->AttachTo(GetPawn()->GetRootComponent());
		OnViewTypeUpdate();

		UBZGame_GameObjectComponent* NewGameObj = GetGameObjectComponentFromPawn();
		ASSERTV(NewGameObj, *FString::Printf(TEXT("Invalid Game Object For Player Controller")));
		if (NewGameObj)
		{
			NewGameObj->SetOwningController(this);
			NewGameObj->SetHudReference(Cast<ABZGame_InGameHUD>(GetHUD()));
	 	}
	}
}

UBZGame_GameObjectComponent* ABZGame_PlayerController::GetGameObjectComponentFromPawn()
{
	if (GetPawn())
	{
		return Cast<UBZGame_GameObjectComponent>(GetPawn()->FindComponentByClass(UBZGame_GameObjectComponent::StaticClass()));
	}
	else
	{
		return NULL;
	}
}


Edit: Any pre-existing pawns in the level also cause some barmy results. Clients and Servers possessing the same pawns, Clients being deleted from memory etc. From memory I think I have to set the pawn to use from the Pawn itself… though there’s surely a better more solid way of doing this.