Feedback on weapon system solution

Hello,

I’m currently trying to implement a weapon system, in which the player throughout the game, will be able to pickup very specific weapons, that’ll be available to them for the rest of the game. I suppose this is a very Doom Eternal’ish implementation of a weapon system. I’d love some feedback on the system, and some guidance on how you would build it.

Implementation and idea so far
I’m thinking the the following implementation is a way to build the weapon system:

  1. Custom ACharacter child class that has the definition of the weapons in multiple struct properties
  2. Pickup actor to enable the weapon for the player
  3. USkeletalMeshComponent to switch weapon mesh when the player switches weapon
  4. Base weapon actor class, with function such as Fire, Reload, etc.

With this system

Code so far
So far, I’ve created the following code for the system:

EWeaponType and FWeaponDetails

UENUM()
enum EWeaponType
{
	SMG = 1					UMETA(DisplayName = "SMG"),
	Shotgun = 2				UMETA(DisplayName = "Shotgun"),
	RocketLauncher = 3		UMETA(DisplayName = "Rocket launcher")
};

USTRUCT(BlueprintType)
struct FWeaponDetails
{
	GENERATED_BODY()

	// Name of the given weapon.
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	FString Name;

	// Type of weapon.
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TEnumAsByte<EWeaponType> WeaponType;

	// Mesh to use on pickup as well as on equip.
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TObjectPtr<USkeletalMesh> Mesh;

	// Actor class reference.
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TSubclassOf<AWeapon> WeaponClass;

	// Whether the weapon is available for the player to use or not
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	bool bIsAvailable;
};

CustomPlayerCharacter.h

// SMG info.
UPROPERTY(/*...*/)
FUnnamedWeaponDetails SMG;

// Shotgun info.
UPROPERTY(/*...*/)
FUnnamedWeaponDetails Shotgun;

// Rocket Launcher info.
UPROPERTY(/*...*/)
FUnnamedWeaponDetails RocketLeauncher;

// Weapon details of the currently equipped weapon.
UPROPERTY()
FUnnamedWeaponDetails EquippedWeaponDetails;

// Currently equipped weapon, if any.
UPROPERTY()
ACustomWeapon* EquippedWeapon;

// Add weapon to the player's weapon arsenal and equip it.
// Returns true if the weapon could be added, false if the weapon is already in the player's arsenal.
bool EnableWeapon(EWeaponType WeaponType, bool bEquipIfPossible);

// Equip the given weapon if it's available to the player.
void EquipWeapon(FWeaponDetails& Weapon);

// Get weapon details struct by the given type.
bool GetWeaponDetailsByType(EWeaponType WeaponType, FWeaponDetails& Weapon) const;

CustomPlayerCharacter.cpp

bool AUnnamedCharacter::EnableWeapon(EWeaponType WeaponType, bool bEquipIfPossible)
{
	FUnnamedWeaponDetails WeaponDetails;
	if (!GetWeaponDetailsByType(WeaponType, WeaponDetails))
	{
		// Weapon type does not exist on the player.
		return false;
	}

	// Make this weapon available to the player.
	WeaponDetails.bIsAvailable = true;

	// Equip if requested.
	if(bEquipIfPossible)
		EquipWeapon(WeaponDetails);

	return true;	
}

void ACustomPlayerCharacter::EquipWeapon(FWeaponDetails& Weapon)
{
	// Weapon is not yet available to the player.
	if (!Weapon.bIsAvailable)
		return;

	// Weapon is already equipped.
	if (EquippedWeaponDetails == Weapon)
		return;

	EquippedWeaponDetails = Weapon;

    //... Spawn weapon and attach to player skeletal mesh bone, etc.
}

bool ACustomPlayerCharacter::GetWeaponDetailsByType(EWeaponType WeaponType, FWeaponDetails& Weapon) const
{
	switch (WeaponType)
	{
		case EWeaponType::SMG:
			Weapon = SMG;
			return true;
		case EWeaponType::Shotgun:
			Weapon = Shotgun;
			return true;
		case EWeaponType::RocketLauncher:
			Weapon = RocketLeauncher;
			return true;
	}

	return false;
}

I know that I may be asking for a lot, but I’m hoping that some of you may shed some light on the concept, and give me some feedback before i delve further down this rabbit hole :slight_smile:

Thanks in advance!

For the sake of debate:

I would build it on an actor component for the sake of modularity.


Assuming the weapon details will be fixed per type: Could all this be managed with the enum and a data table rather than hardcoding each weapon? Maybe use the enum to get details from a data table. Might be better for save too?


TArray<EWeaponType> InArsenal;
EWeaponType Equipped;

Perhaps a single weapon class that uses the enum to switch between different skeletal meshes, particles, rate of fire, sounds, etc? The Asset Manager could be used to keep these assets in memory.


Is this bool meant to indicate possession of the weapon or is it a condition for equipping it? If it’s the latter perhaps it should be managed by a quest or progression system instead?


My goal is to allow more weapons to be added in the future without modifying any of the core classes or save game.

Thank you for your feedback :slight_smile:

So an actor component handling the weapon arsenal, instead of directly within the character class?

Weapon details would be fixed per type, and never change throughout the game.
I suppose they could. Maybe that would make it more manageable. I’d have to read up on data tables though, but I see what you mean and actually like the idea.

So instead of creating multiple weapon blueprints inheriting from a weapon class, simply create a single weapon blueprint that automatically changes it’s information based on the currently equipped. Wouldn’t that potentially limit me somewhere down the road? E.g. if some weapon in the future would be very different from the others?

Condition for equipping it. Defines if the player has this weapon available in their arsenal. Considering that this information should persist through multiple levels, one could argue that the available weapon arsenal should perhaps be implemented in the player state instead of the character itself?

Yes.

Valid point. It would depend on what very different means.

Then perhaps a simple return MyArsenalArray.Contains(EWeaponType::MyWeapon) could be enough?

The array could help with this too:

bool AddWeapon(const EWeaponType WeaponType) {
    if (!MyArsenalArray.Contains(WeaponType)) 
    {
        MyArsenalArray.Add(WeaponType);      
        return true;
    }
    return false;
}

Oh I like this. Could simplify the code quite a bit.

I really appreciate your feedback. Thank you for your input :slight_smile: