C++ - Attach camera to bone/socket?

Hello,

My C++ character class is named “VRSoldier”.
My skeletal mesh for this class is named “Soldier_ru_01”.

I have multiple questions :

1)
I would like to attach my VR camera to the head of my skeletal mesh, with C++.
I haven’t found documentation less than 5 years old and deprecated…
My current constructor in VRSoldier.cpp is :

// Sets default values
AVRSoldier::AVRSoldier()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// On cherche le skeletal mesh qui sera utilisé dans notre conteneur pour l'instant vide
	static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_SoldierMesh(TEXT("SkeletalMesh'/Game/Soldier_ru_01/Meshes/Soldier_ru_01.Soldier_ru_01'"));
	// On assigne le skeletal mesh à notre conteneur
	SoldierMesh = GetMesh();
	SoldierMesh->SetSkeletalMesh(SK_SoldierMesh.Object);
	
	// Create a first person mesh component for the owning player.
	//SoldierMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Soldier_ru_01"));
	
	VRCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("VRCamera"));

	//Attach the VR camera to the SoldierMesh.
	VRCamera->AttachToComponent(SoldierMesh, FAttachmentTransformRules::SnapToTargetIncludingScale);
	VRCamera->bLockToHmd = 1;

	// Make sure the mesh casts shadows
	SoldierMesh->bCastDynamicShadow = true;
	SoldierMesh->CastShadow = true;

}

The declarations (public) in VRSoldier.h are :

        // Soldier mesh
	UPROPERTY(EditAnywhere, Category = Mesh)
		class USkeletalMeshComponent* SoldierMesh;


	// VR camera.
	UPROPERTY(VisibleAnywhere)
		class UCameraComponent* VRCamera;

So I want to attach the camera to the head bone, or to its child socket named “headSocket” that I made.
It is probably related to AttachToComponent() but I don’t know how to do it.

2)
Currently, the Camera should be a child of the mesh considering I wrote this :

VRCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("VRCamera"));

	//Attach the VR camera to the SoldierMesh.
	VRCamera->AttachToComponent(SoldierMesh, FAttachmentTransformRules::SnapToTargetIncludingScale);

But for some reason, when I open the Blueprint class derived from my VRSoldier C++ class, the VRCamera is at the same level as the mesh, instead of being a child?

3)
When I place my Blueprint class in the level, for an unknown reason the camera is far from the mesh & capsule, however by default in the blueprint editor it’s shown inside of the capsule, and I manually moved it to the eyes height…

Why am I able to move it with the mouse, by the way, considering I defined it in C++ so I can only set the scale in the blueprint editor right panel, nothing else?

4)
I am currently using AttachToComponent, but I have seen AttachTo being used in old versions. Is it deprecated now?

5)
I have seen an old deprecated C++ [tutorial][5] where Epic is placing the mesh as a child of the camera. However, all modern examples I saw had the camera as a child of the mesh. Which version is better considering I want to use a VR camera, and create an aimspace to get the body to react to the camera? I am using a full body to create a “true FPP” character, and I will later implement inversed kinematic to control the arms with the motioncontrollers.

6)
I have set VRCamera->bLockToHmd = 1; (= LockToHeadMountDisplay) in the character class constructor. So I expected this option to be non-accessible from the derived blueprint class editor, but it is still accessible. Why?

Note :

I am trying to do all of this in C++ in despite of the problems associated with hard-coding references to assets, because

  • I want to learn doing it in C++.
  • I hate spaghettis.
  • I wasn’t able to define a socket for my camera considering it is Inherited, because I added it in the C++ class instead of adding it in the blueprint editor.
  • I don’t know how to create a function in my character class to attach objects to the hand, if both the skeletal mesh and it’s bones/sockets, and the MotionController component, are added later in the character blueprint. It would use references that do not exist…

About this Q&A :

I would like to know why Rama says

hardcoded asset references can break during packaging

He also explains that :

If you do indeed have a blueprint of the related class, you can just add the asset link in your .h file directly rather than hardcoding the lookup, which can and frequently does fail during packaging.

So this is the question 7) :
How can I add the asset link in my .h file directly? I suppose it’s by copying the reference SkeletalMesh'/Game/Soldier_ru_01/Meshes/Soldier_ru_01.Soldier_ru_01' ?
Then, how should I add it in the header? With an include?
Does it allow me to then work with this skeletal mesh, and remove :

static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_SoldierMesh(TEXT("SkeletalMesh'/Game/Soldier_ru_01/Meshes/Soldier_ru_01.Soldier_ru_01'"));

?

To resolve the question 2) , should I use TSubclassOf instead of AttachToComponent?

Hello,

I solved the questions 2) & 3) by replacing VRCamera->AttachToComponent(SoldierMesh, FAttachmentTransformRules::SnapToTargetNotIncludingScale); by VRCamera->SetupAttachment(SoldierMesh);.

Now the hierarchy is good, and the camera is not far away from the character when I place the blueprint in the level.

300939-hierarchy.png

300950-camera.png

I think I solved the question 1) by writing VRCamera->SetupAttachment(SoldierMesh, TEXT("headSocket"));.
I think it is solved but I am not sure because my camera’s origin has been moved to the head after compiling, however when I create a blueprint from this C++ class the blueprint editor does not show this camera is fixed to a socket…

The remaining questions are 4, 5, 6, 7.

4) Yes, AttachTo is deprecated.

Salut, j’ai remarqué que tu étais francais. Pourrais tu m’apporter de l’aide pour mon projet d’études ?
Actuellement je dois m’occuper de paramétrer la camera en mode fps ?
J’espere que tu repondras

De quoi as tu besoin?

Besoin d’aide afin de choisir comment et où m’orienter pour avoir un résultat. Je suis nouveau donc j’ai besoin dapprendre

Adding to this thread as I encountered the same issue with attaching my Camera to the head socket of my Mesh in the constructor. I believe this is a runtime issue. I managed to solve it by moving most of the logic to PostInitializeComponents function. Here’s an example of my code:

character.cpp:

AVampireHunterCharacter::AVampireHunterCharacter()
{
...
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(GetMesh());
}

This code segment works correctly, and I can see in the editor that the camera is properly attached to my mesh component. However, when I specify the socket name, I encounter the same issue mentioned earlier in the thread, where the camera points at the character’s feet. To debug, I attempted to validate the socket’s existence by printing out all sockets for the mesh. I added the following code inside my constructor:

if (GetMesh())
	{
		TArray<FName> SocketNames = GetMesh()->GetAllSocketNames();
		UE_LOG(LogTemplateCharacter, Warning, TEXT("Mesh exists obtaining all sockets:"));
		for (const FName &SocketName : SocketNames)
		{

			UE_LOG(LogTemplateCharacter, Warning, TEXT("Socket: %s"), *SocketName.ToString());
		}
		if (SocketNames.Num() == 0)
		{
			UE_LOG(LogTemplateCharacter, Warning, TEXT("No sockets"));
		}
	}
	else
	{
		UE_LOG(LogTemplateCharacter, Warning, TEXT("No Mesh"));
	}

Result:

LogTemplateCharacter: Warning: Mesh exists obtaining all sockets:
LogTemplateCharacter: Warning: No sockets
LogTemplateCharacter: Warning: Mesh exists obtaining all sockets:
LogTemplateCharacter: Warning: No sockets
LogTemplateCharacter: Warning: Mesh exists obtaining all sockets:
LogTemplateCharacter: Warning: No sockets

This explains why we experience this unexpected behavior. While the mesh exists when attaching the camera to it in the constructor, the sockets are not yet initialized. I discovered the PostInitializeComponents method, which allows us to execute logic after the components have been fully initialized.

I updated my Character.h and Character.cpp files to log the results in the PostInitializeComponents function.

.h:

...
public:
	void PostInitializeComponents() override;

.cpp:

void AVampireHunterCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	if (GetMesh())
	{
		TArray<FName> SocketNames = GetMesh()->GetAllSocketNames();
		UE_LOG(LogTemplateCharacter, Warning, TEXT("Mesh exists obtaining all sockets:"));
		for (const FName &SocketName : SocketNames)
		{

			UE_LOG(LogTemplateCharacter, Warning, TEXT("Socket: %s"), *SocketName.ToString());
		}
		if (SocketNames.Num() == 0)
		{
			UE_LOG(LogTemplateCharacter, Warning, TEXT("No sockets"));
		}
	}
	else
	{
		UE_LOG(LogTemplateCharacter, Warning, TEXT("No Mesh"));
	}
}

Result:

LogTemplateCharacter: Warning: Mesh exists obtaining all sockets:
LogTemplateCharacter: Warning: Socket: weapon_r_muzzle
LogTemplateCharacter: Warning: Socket: foot_r_Socket
LogTemplateCharacter: Warning: Socket: foot_l_Socket
LogTemplateCharacter: Warning: Socket: headSocket
LogTemplateCharacter: Warning: Socket: root
LogTemplateCharacter: Warning: Socket: pelvis
LogTemplateCharacter: Warning: Socket: spine_01
LogTemplateCharacter: Warning: Socket: spine_02
LogTemplateCharacter: Warning: Socket: spine_03
LogTemplateCharacter: Warning: Socket: spine_04
LogTemplateCharacter: Warning: Socket: spine_05
LogTemplateCharacter: Warning: Socket: neck_01
LogTemplateCharacter: Warning: Socket: neck_02
LogTemplateCharacter: Warning: Socket: head
LogTemplateCharacter: Warning: Socket: clavicle_l
LogTemplateCharacter: Warning: Socket: upperarm_l
LogTemplateCharacter: Warning: Socket: lowerarm_l
LogTemplateCharacter: Warning: Socket: lowerarm_twist_02_l
LogTemplateCharacter: Warning: Socket: lowerarm_twist_01_l
LogTemplateCharacter: Warning: Socket: hand_l
...

Seems we now have access to all the sockets to the mesh. Update function to now hold code:

void AVampireHunterCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	FollowCamera->SetupAttachment(GetMesh(), TEXT("head"));
}

Does not like when using SetupAttachment here, I get the following error from UE5:

LogOutputDevice: Error: === Handled ensure: ===
LogOutputDevice: Error: Ensure condition failed: !bRegistered  [File:D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\Components\SceneComponent.cpp] [Line: 1958] 
LogOutputDevice: Error: SetupAttachment should only be used to initialize AttachParent and AttachSocketName for a future AttachToComponent. Once a component is registered you must use AttachToComponent. Owner [/Engine/Transient.World_0:PersistentLevel.BP_ThirdPersonCharacter_C_0], InParent [CharacterMesh0], InSocketName [head]

My workaround is to use SetupAttachment to attach the camera to the mesh in the constructor, as this works as intended. Then, in this function, I use AttachToComponent to attach it to the socket. Full result:

.cpp:

AVampireHunterCharacter::AVampireHunterCharacter()
{
	...
	// Create a follow camera
	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(GetMesh());

}

// This function is needed as sockets are not present during construction
void AVampireHunterCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	FollowCamera->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, TEXT("head"));
	FollowCamera->bUsePawnControlRotation = true;
	FollowCamera->SetRelativeLocation(FVector(0, 15, 0));
}

Note that I added the relative location to prevent the camera from clipping through the character. However, this change does not reflect in the Editor. Enabling the flag or modifying the location values has no visible effect in the detail panel, but after a live code sync, the changes are updated when running the game.

Hope this helps someone else, as I wanted to achieve this via C++ and not by Blueprints. Note version of Unreal I am on is 5.5.1