Create modular Blueprint based on FPS Character


I’m trying to separate the weapon of the FPSCharacter (the basic one) in an other component. The goal is to do multiple weapon with several different projectile.
But i’m quite confused with the unreal organisation, I’ve tried to do an SceneComponent named WeaponPlayer which add a weapon (a skeletalMesh for FP_Gun, a sceneComponent for the muzzle etc etc)
Do you think it’s the good way to organized this? (As i’m a beginner in Unreal)

And an other question is that even if my class is correct (when i drag and drop my c++ class in the editor and configure it) I Can’t manage to create a blueprint with it. The SkeletalMesh on the WeaponPlayer (So the new Component used to be the weapon) disappear when i create a blueprint.

This is the WeaponPlayer:

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class RAPIERE_API UWeaponPlayer : public UActorComponent

	/** Location on gun mesh where projectiles should spawn. */
	UPROPERTY(EditDefaultsOnly, Category = WeaponMesh)
		class USceneComponent* FP_MuzzleLocation;

	/** Location on VR gun mesh where projectiles should spawn. */
	UPROPERTY(EditAnywhere, Category = WeaponMesh)
		class USceneComponent* VR_MuzzleLocation;

	UPROPERTY(EditAnywhere, Category = WeaponMesh)
		uint32 bUsingMotionControllers;


	/** Gun mesh: 1st person view (seen only by self) */
	UPROPERTY(EditAnywhere, Category = WeaponMesh)
		class USkeletalMeshComponent* FP_Gun;

	/** Gun mesh: VR view (attached to the VR controller directly, no arm, just the actual gun) */
	UPROPERTY(EditAnywhere, Category = WeaponMesh)
		class USkeletalMeshComponent* VR_Gun;

	/** Gun muzzle's offset from the characters location */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
		FVector GunOffset;

	/** Projectile class to spawn */
	UPROPERTY(EditAnywhere, Category = Projectile)
		TSubclassOf<class ARapiereProjectile> ProjectileClass;

	/** Sound to play each time we fire */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
		class USoundBase* FireSound;


	// Sets default values for this component's properties
	void Init(uint32 motionController);

	// Called when the game starts
	virtual void BeginPlay() override;

	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	void OnFire();

And this is how it is configured:


	// Create a gun mesh component
	FP_Gun = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FP_Gun"));
	FP_Gun->SetOnlyOwnerSee(true);			// only the owning player will see this mesh
	FP_Gun->bCastDynamicShadow = false;
	FP_Gun->CastShadow = false;

	FP_MuzzleLocation = CreateDefaultSubobject<USceneComponent>(TEXT("MuzzleLocation"));
	FP_MuzzleLocation->SetRelativeLocation(FVector(0.2f, 48.4f, -10.6f));

	// Default offset from the character location for projectiles to spawn
	GunOffset = FVector(100.0f, 0.0f, 10.0f);

	// Create a gun and attach it to the right-hand VR controller.
	// Create a gun mesh component
	VR_Gun = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("VR_Gun"));
	VR_Gun->SetOnlyOwnerSee(true);			// only the owning player will see this mesh
	VR_Gun->bCastDynamicShadow = false;
	VR_Gun->CastShadow = false;
	VR_Gun->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));

	VR_MuzzleLocation = CreateDefaultSubobject<USceneComponent>(TEXT("VR_MuzzleLocation"));
	VR_MuzzleLocation->SetRelativeLocation(FVector(0.000004, 53.999992, 10.000000));
	VR_MuzzleLocation->SetRelativeRotation(FRotator(0.0f, 90.0f, 0.0f));		// Counteract the rotation of the VR gun model.

	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;

	// ...

And in the character:

Weapon = CreateDefaultSubobject<UWeaponPlayer>(TEXT("WeaponPlayer"));
	Weapon->FP_Gun->SetupAttachment(Mesh1P, TEXT("GripPoint"));

While you can create sub-components inside actor components, it’s not a good idea - as lot’s of properties won’t be able to be set properly.

I use component-based weapons too (since I need a lot of them in the world and don’t want all of AActors overhead), and to get around this I create components at runtime (when the weapon enters an actors inventory), rather than in the constructor - I create the components as children of the actual actor, but keep references to them in the weapon so I can destroy them later. Example:

void UBZGame_Weapon::CreateWeaponComponents()
	// TODO: Skip this if we're on a dedicated server
	ASSERTV(HasValidOwner(), TEXT("CreateWeaponComponents: Invalid Owner"));
	ASSERTV(WeaponMesh == nullptr && WeaponMesh1P == nullptr, TEXT("CreateWeaponComponents has already been called for this weapon!"));

	if (!WeaponConfig.ThirdPersonMesh.IsNull())
		WeaponMesh = NewObject<USkeletalMeshComponent>(OwningPawn);
		ASSERTV(WeaponMesh != nullptr, TEXT("Couldn't Create WeaponMesh"));

		// TODO: Setup Animation
		WeaponMesh->SetSkeletalMesh(WeaponConfig.ThirdPersonMesh.LoadSynchronous(), true);
		WeaponMesh->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered;
		WeaponMesh->bReceivesDecals = false;
		WeaponMesh->bCastDynamicShadow = true;
		WeaponMesh->bCastStaticShadow = false;
		WeaponMesh->bCastHiddenShadow = true;
		WeaponMesh->SetAbsolute(false, false, true);
	if (!WeaponConfig.FirstPersonMesh.IsNull())
		WeaponMesh1P = NewObject<USkeletalMeshComponent>(OwningPawn);
		ASSERTV(WeaponMesh1P != nullptr, TEXT("Couldn't Create WeaponMesh 1P"));

		WeaponMesh->SetSkeletalMesh(WeaponConfig.FirstPersonMesh.LoadSynchronous(), true);
		WeaponMesh1P->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered;
		WeaponMesh1P->bReceivesDecals = false;
		WeaponMesh1P->bCastDynamicShadow = true;
		WeaponMesh1P->bCastStaticShadow = false;
		WeaponMesh1P->bCastHiddenShadow = false;
		WeaponMesh1P->bSelfShadowOnly = true;
		WeaponMesh1P->SetAbsolute(false, false, true);

	// Attach the weapons to the third / first person meshes, if we have them.
	if (WeaponMesh || WeaponMesh1P)
		const FName MeshAttachPoint = OwningUnit->GetHardpoints()[WeaponIndex].AttachSocketName;
		const ABZGame_PlayerController* OwnerPC = Cast<ABZGame_PlayerController>(OwningPawn->GetController());
		const IBZGame_GameObjectInterface* OwningGO = Cast<IBZGame_GameObjectInterface>(OwningPawn);
		USkeletalMeshComponent* MeshAttachComponent = OwningGO->GetRootMesh();
		if (WeaponMesh && WeaponMesh->SkeletalMesh && MeshAttachComponent)
			WeaponMesh->AttachToComponent(MeshAttachComponent, FAttachmentTransformRules(EAttachmentRule::SnapToTarget, false), MeshAttachPoint);

		if (WeaponMesh1P && WeaponMesh1P->SkeletalMesh && OwnerPC && OwnerPC->GetFirstPersonMesh() && OwnerPC->GetFirstPersonMesh()->SkeletalMesh)
			WeaponMesh1P->AttachToComponent(OwnerPC->GetFirstPersonMesh(), FAttachmentTransformRules(EAttachmentRule::SnapToTarget, false), MeshAttachPoint);

There’s a bit of my game-specific code there but you can work out the basics from that :slight_smile:

The weapon still works with multiplayer / replication, since it uses the actor replication channel.