Creating components provides encapsulation of weapon and sheath specific code and functionality. I was thinking you should create multiple scene components for each weapon type, which allows you to easily ‘scale’ how many sheathes your actor has without cluttering your code with all kinds of extra pointers and conditionals. This will be exceptionally useful for your scenario since your character is going to be an “arsenal.” As an example, lets suppose you wanted one sheath on your character’s left thigh at the start of the game, then at some point later you wanted them to have an additional sheath on their right thigh. Your code begins to become something like:
const USkeletalMeshSocket* KnifeSheathLeft = GetMesh()->GetSocketByName("KnifeSheathLeft");
AKnife* KnifeLeft = nullptr;
const USkeletalMeshSocket* KnifeSheathRight = GetMesh()->GetSocketByName("KnifeSheathRight");
AKnife* KnifeRight = nullptr;
const USkeletalMeshSocket* PistolSheath = GetMesh()->GetSocketByName("PistolSheath");
APistol* Pistol = nullptr;
...
switch (Weapon->WeaponType) {
case EWeaponType::EWT_Knife:
if (KnifeSheathLeft) {
if(!KnifeLeft){
...]
} else {
//This logic changes since you need to account for the right sheath as well
if(HasUnlockedRightKnifeSheath && KnifeSheathRight){
if(!KnifeRight){
//Assign to right knife
} else {
//What ever you do when both are occupied
}
}
}
} else if(HasUnlockedRightKnifeSheath && KnifeSheathRight){
...] //See above
}
As you can see, your conditionals start to become a mess. Some of it can be cleaned up by checking socket validity in the actor’s constructor, but you’ll still have quite a bit of branching. Now consider what happens if you add a third knife or you decide that you want two pistols on your character’s hip and one on each ankle. The same applies for the rest of your weapons. Also keep in mind, your character shouldn’t need to have references to all these weapons. At any given time, they only need to know about whatever is actually equiped and being used, one maybe two weapons. So most of those pointers are only being used to check whether or not the character can pick up a new weapon. What I had in mind was a setup more like:
//I'll leave you to actually define the classes, but here's the hierarchy
class USheath : USceneComponent;
class UKnifeSheath : USheath;
bool USheath::AddWeapon(AWeapon NewWeapon){
if(IsValidSocket() && IsUnlocked() && IsAvailable()){
//Attach NewWeapon to owning actor's mesh at SocketName socket
Weapon = NewWeapon;
return true;
}
return false;
}
void USheath::SetSocketName(FName NewSocketName){
SocketName = NewSocketName;
Socket = GetOwner()->GetMesh()->GetSocketByName(SocketName)
//Attach this component to socket, not required but helps visualize sheath location
}
UPROPERTY()
UKnifeSheath* LeftSheath;
UPROPERTY()
UKnifeSheath* RightSheath;
AYourCharacter::OnConstruct(){
...]
KnifeSheathLeft = NewObject<UKnifeSheath>();
//Attach to mesh
KnifeSheathLeft->SetSocketName("KnifeSheathLeft");
if(!KnifeSheathLeft->IsValidSocket()){
//Check that Socket member is a valid pointer
//Log that socket was invalid
}
KnifeSheathRight = NewObject<UKnifeSheath>();
//Attach to mesh
KnifeSheathRight->SetSocketName("KnifeSheathRight");
if(!KnifeSheathRight->IsValidSocket()){
//Check that Socket member is a valid pointer
//Log that socket was invalid
}
}
...]
switch (Weapon->WeaponType) {
case EWeaponType::EWT_Knife:
TArray<UKnifeSheath*> KnifeSheathes;
GetComponents<UKnifeSheath>(KnifeSheathes);
UKnifeSheath* WorstKnife = nullptr;
bool KnifeAdded = false;
for(UKnifeSheath* KnifeSheath : KniveSheathes){
KnifeAdded = KnifeSheath->AddWeapon(Weapon);
if(KnifeAdded){
break;
} else if(KnifeSheath->ShouldReplaceWeaponWith(Weapon)
//Check if the sheathed weapon is 'worse' than new weapon
&& (!WorstKnife || KnifeSheath->ShouldReplaceWeaponWith(WorstKnife->GetWeapon()))){
WorstKnife = KnifeSheath;
}
}
if(!KnifeAdded){
//What ever you do when all are unavailable
if(WorstKnife)
WorstKnife->ReplaceWeapon(Weapon);
}
}
Each sheath references one weapon and vice versa. One of the nice things about this approach is that once a derived Sheath component is created and the socket name has been set, it’ll manage getting ‘filled’ by itself for the most part. Another plus is that most of the functions can be defined in the base class since they are
the same logic regardless of the weapon. Then you can simply create or override weapon specific functions like ShouldReplaceWeaponWith, for comparing weapons, in the derived sheath classes.
Alternatively, you could create a component that manages all weapons instead of inheriting a different component for each weapon type. My main issue with going that route is that you still have to manage the socket names and their corresponding states, which would still be best handled by defining new classes and/or structs anyway. You also can’t fall back on the GetComponents function template to find the relevant objects for you. In any case, it’s up to you figure out what will work best for your game, but hopefully this post has given you some different ideas on how to manage your character’s weapons.