NEED HELP: Character - Replacing Default Components

It’s just… couple of issues I’m facing. Trying to replace default character components with my own ones, and fighting with second.

If:

ACndCharacter_Master::ACndCharacter_Master(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer
		.SetDefaultSubobjectClass<UCndComp_Character_Capsule>(CndKN_CompNames.Character.Capsule)
		.SetDefaultSubobjectClass<UCndComp_Character_Mesh>(CndKN_CompNames.Character.Mesh)
		.SetDefaultSubobjectClass<UCndComp_Character_Move>(CndKN_CompNames.Character.Move)
	)

{

BPF_Init_SetupComponents();

}

BPF_Init_SetupComponents()
{

	if (!CO_CndCharacter_Mesh)
	{
		// CO_CndCharacter_Mesh = CreateOptionalDefaultSubobject<UCndComp_Character_Mesh>(CndKN_CompNames.Character.Mesh);

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Mesh - Created!"));
		}

	}

	if (!CO_CndCharacter_Move)
	{

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Move - Created!"));

			UE_LOG(LogTemp, Warning, TEXT("CND_DEBUG - Character_Master: Is Valid - CO_CndCharacter_Move: %s"),
				CO_CndCharacter_Move ? TEXT("Valid") : TEXT("NULL"));

		}

	}

	if (!CO_CndCharacter_Capsule)
	{

		// CO_CndCharacter_Capsule = GetCapsuleComponent();

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Capsule - Created!"));
		}

	}

}

Then:
No override happens, defaults components are set.

If:
Blueprint opened (Parent Class: CndCharacter_Master)
Then:
Components are created 5 times.

Bumping it, cuz I’m still trying to get rid of those default components.

The class inherits ACharacter.
Already tried using Initializer with Super and FObjectInitializer, but still creates these unwanted default duplicates. Fighting with this for over a day!

FYI: CndKN_CompNames = Structure holding FNames for easier component naming organization.

CndCharacter_Master.cpp

ACndCharacter_Master::ACndCharacter_Master(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{

BPF_Init_SetupComponents();

}
void ACndCharacter_Master::BPF_Init_SetupComponents()
{

	CO_CndCharacter_Capsule = CreateDefaultSubobject<UCndComp_Character_Capsule>(CndKN_CompNames.Character.Capsule);
	if (CO_CndCharacter_Capsule)
	{
		CO_CndCharacter_Capsule->SetCapsuleHalfHeight(90.0f);
		CO_CndCharacter_Capsule->SetCapsuleRadius(40.0f);
		RootComponent = CO_CndCharacter_Capsule;

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Capsule - Created!"));
		}

	}
	

	CO_CndCharacter_Mesh = CreateDefaultSubobject<UCndComp_Character_Mesh>(CndKN_CompNames.Character.Mesh);

	if (CO_CndCharacter_Mesh)
	{

		CO_CndCharacter_Mesh->SetupAttachment(CO_CndCharacter_Capsule);

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Mesh - Created!"));
		}


	}

	CO_CndCharacter_Move = CreateDefaultSubobject<UCndComp_Character_Move>(CndKN_CompNames.Character.Move);	

	if (CO_CndCharacter_Move)
	{

		CO_CndCharacter_Move->UpdatedComponent = CO_CndCharacter_Capsule;

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Move - Created!"));

			UE_LOG(LogTemp, Warning, TEXT("CND_DEBUG - Character_Master: Is Valid - CO_CndCharacter_Move: %s"),
				CO_CndCharacter_Move ? TEXT("Valid") : TEXT("NULL"));

		}

	}

}

Doing it the way you did it in your first post is the right way, your logging in that first post makes little sense though because you’re checking the validity of pointers that will never be set. Initialization code will use the classes you provided and store those components in the same variables as it usually would (ACharacter::Mesh, ACharacter::Capsule, ACharacter::CharacterMovement).

There MIGHT be a bug with existing blueprint classes and changing the components like this, so to make sure the first step works I’d create a new blueprint class if I were you. If that works, and your existing blueprint doesn’t, either migrate the rest of the BP logic or try to figure out why it doesn’t work (maybe reparenting fixes it?).

Your second approach will ofc still create the default components and, while you COULD destroy them and reassign the pointers to your components instead, I would recommend against it because the first approach is the intended one.

They still appear, even after I made some changes to this to initializer. Yes, I made a new blueprint.

ACndCharacter_Master::ACndCharacter_Master(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer
		.SetDefaultSubobjectClass<UCndComp_Character_Capsule>(CndKN_CompNames.Character.Capsule)
		.SetDefaultSubobjectClass<UCndComp_Character_Mesh>(CndKN_CompNames.Character.Mesh)
		.SetDefaultSubobjectClass<UCndComp_Character_Move>(CndKN_CompNames.Character.Move)
	)

Could you try using MeshComponentName, CharacterMovementComponentName, and CapsuleComponentName (they’re all members of ACharacter) for your SetDefaultSubobjectClass names and see if that changes anything? The default components might not override properly if you’re not using matching names.

As if that’ll help.

Gone back to default constructor.

And added these to BPF_Init_SetupComponents(), but now cause the engine to crash, now that I did the deep clean (removed and regenerated binaries and etc).


ACndCharacter_Master::ACndCharacter_Master()
{

BPF_Init_SetupComponents()

}

ACndCharacter_Master::BPF_Init_SetupComponents()
{
	GetCapsuleComponent()->DestroyComponent();
	GetMesh()->DestroyComponent();
	GetCharacterMovement()->DestroyComponent();
}

Oh, and out of curiosity, have you actually tried fighting with ACharacter derive by yourself?

I actually have, on released projects, and I’m currently working on a AAA project where we’re doing this for basically all base classes.

It’s a bit hard to debug this with partial context and no access to a debugger though, so it would help a lot if you tried using the built-in component names. :smiley:

Also, you don’t have to believe me, you can give the engine sources a look yourself and you’ll see UE does this with its own classes. Also, this is literally the comment next to one of those names:
/** Name of the CharacterMovement component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */

So far I managed to get rid of Mesh and Arrow Components using:

.DoNotCreateDefaultSubobject(ACharacter::MeshComponentName)
.DoNotCreateDefaultSubobject(TEXT("Arrow")

Couldn’t get rid of rest because they were tagged as “required”.

I also looked into Output Log, and I think I found where the problem is.

LogUObjectGlobals: Error: Class /Script/Conduit.CndComp_Character_Capsule is not a legal override for component None because it does not derive from Class /Script/Conduit.CndComp_Character_Mesh. Will use Class /Script/Conduit.CndComp_Character_Mesh when constructing component.

LogUObjectGlobals: Error: Class /Script/Conduit.CndComp_Character_Mesh is not a legal override for component None because it does not derive from Class /Script/Conduit.CndComp_Character_Move. Will use Class /Script/Conduit.CndComp_Character_Move when constructing component.

CndCharacter_Master.h has CndComp_Character.h included.

CndCharacter_Master.h 

#include "Conduit/Components/Character/CndComp_Character.h"

Everything seems to be set right, but it still complains.

CndComp_Character.h 

UCLASS(Blueprintable, ClassGroup = (CndComponents_Character), meta = (BlueprintSpawnableComponent))
class CONDUIT_API UCndComp_Character_Capsule : public UCapsuleComponent
{
    GENERATED_BODY()

public:


};

UCLASS(Blueprintable, ClassGroup = (CndComponents_Character), meta = (BlueprintSpawnableComponent))
class CONDUIT_API UCndComp_Character_Mesh : public USkeletalMeshComponent
{
	GENERATED_BODY()

public:


};

UCLASS(Blueprintable, ClassGroup = (CndComponents_Character), meta = (BlueprintSpawnableComponent))
class CONDUIT_API UCndComp_Character_Move : public UCharacterMovementComponent
{
    GENERATED_BODY()

public:


};

May I see the .cpp code as well? (since that’s what the errors are referring to)

CndComp_Character.cpp? It’s empty. It’s just:

// Copyright stuff

#include "Conduit/Components/Character/CndComp_Character.h"

I mean the ACndCharacter_Master constructor code, wherever that is. :smiley:

CndCharacter_Master.cpp

#include "Conduit/Character/ch/CndCharacter_Master.h"

// - Initializer Constructor - Default
ACndCharacter_Master::ACndCharacter_Master()
{
	BPF_Init_PathAssets();
	BPF_Init_DefaultValues();
}

// - Initializer Constructor - Object
ACndCharacter_Master::ACndCharacter_Master(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer
	.SetDefaultSubobjectClass<UCndComp_Character_Capsule>(CndKN_CompNames.Character.Capsule)
	.SetDefaultSubobjectClass<UCndComp_Character_Mesh>(CndKN_CompNames.Character.Mesh)
	.SetDefaultSubobjectClass<UCndComp_Character_Move>(CndKN_CompNames.Character.Move)
	.DoNotCreateDefaultSubobject(ACharacter::MeshComponentName)
	.DoNotCreateDefaultSubobject(TEXT("Arrow"))
	
	
	)
{

	BPF_Init_SetupComponents();

}

void ACndCharacter_Master::BPF_Init_SetupComponents()
{
	
	CO_CndCharacter_Capsule = CreateDefaultSubobject<UCndComp_Character_Capsule>(CndKN_CompNames.Character.Capsule);
	
	if (CO_CndCharacter_Capsule)
	{
		CO_CndCharacter_Capsule->SetCapsuleHalfHeight(90.0f);
		CO_CndCharacter_Capsule->SetCapsuleRadius(40.0f);
		RootComponent = CO_CndCharacter_Capsule;

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Capsule - Created!"));
		}

	}
	
	
	CO_CndCharacter_Mesh = CreateDefaultSubobject<UCndComp_Character_Mesh>(CndKN_CompNames.Character.Mesh);
	
	if (CO_CndCharacter_Mesh)
	{

		CO_CndCharacter_Mesh->SetupAttachment(CO_CndCharacter_Capsule);

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Mesh - Created!"));
		}


	}

	CO_CndCharacter_Move = CreateDefaultSubobject<UCndComp_Character_Move>(CndKN_CompNames.Character.Move);	
	
	if (CO_CndCharacter_Move)
	{

		CO_CndCharacter_Move->UpdatedComponent = CO_CndCharacter_Capsule;

		if (DebugCharacter)
		{
			UE_LOG(LogTemp, Log, TEXT("CND_DEBUG - Character_Master: CO_CndCharacter_Move - Created!"));

		}

	}
}

The custom FNames you’re using seem to not be initialized at the point those error messages were outputted, so you’re essentially providing a bunch of overrides for the “None” component. The errors turn out a bit unclear because engine code doesn’t ensure the provided overrides are unique, just packs them in an array and processes them later.

To verify, you can add logging to the constructor, your FNames should all print None.

To repeat myself, even if you fix this (by ensuring the names are initialized), you’re still fighting against the engine for no reason and (as you’ve seen) some components you won’t easily be able to remove. Using the built-in names for overrides would fix all of this; and if there is an issue with that, we can debug that issue.

Okay, after quite of an fight I managed to get rid of the default class components, as well as the Arrow with this.

Couldn’t override, Capsule and Movement Components at first, because they were tagged as required, but now it works.

ACndCharacter_Master::ACndCharacter_Master(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer
	.SetDefaultSubobjectClass<UCndComp_Character_Capsule>(TEXT("CO_CollisionCylinder"))
	.SetDefaultSubobjectClass<UCndComp_Character_Mesh>(TEXT("CO_CharacterMesh"))
	.SetDefaultSubobjectClass<UCndComp_Character_Move>(TEXT("CO_CharMove"))
	.DoNotCreateDefaultSubobject(ACharacter::MeshComponentName)
	.DoNotCreateDefaultSubobject(TEXT("Arrow"))	
	)
		
{

	BPF_Init_SetupComponents();
}

void ACndCharacter_Master::BPF_Init_SetupComponents()
{

	GetCapsuleComponent()->DestroyComponent();

	CO_CndCharacter_Capsule = CreateDefaultSubobject<UCndComp_Character_Capsule>(CndKN_CompNames.Character.Capsule);
CO_CndCharacter_Mesh = CreateDefaultSubobject<UCndComp_Character_Mesh>(CndKN_CompNames.Character.Mesh);

	if (CO_CndCharacter_Mesh)
	{

		CO_CndCharacter_Mesh->SetupAttachment(CO_CndCharacter_Capsule);
	}

	GetCharacterMovement()->DestroyComponent();
	CO_CndCharacter_Move = CreateDefaultSubobject<UCndComp_Character_Move>(CndKN_CompNames.Character.Move);	
	
	if (CO_CndCharacter_Move)
	{

		CO_CndCharacter_Move->UpdatedComponent = CO_CndCharacter_Capsule;
	}

And suprisingly, it’s not crashing the engine.
And to make sure it stays that way for subclasses inheriting Master Class:

ACndCharacter_Player::ACndCharacter_Player(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
}