Download

TArray and storing data correctly

Hello Community!!

I’m having a huge issue with storing Weapon Data in my Arrays. I have a post on the Answerhub (https://answers.unrealengine.com/questions/142062/tarray-storing-ammo-data-help.html), but I’m getting no response.
In short, My ammo disappears when i store my information in the Array (The TSubclassOf array),

should I use



     //Inventory for the Weapons
     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Inventory)
     TArray<TSubclassOf<AWeapon>> WeapInventory;


or should I use a pointer?



     //Inventory for the Weapons
     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Inventory)
     TArray<class AWeapon*> WeapInventory;


I’m really lost on storing information in the arrays Dx

It depends on whether you want to store a pointer to an instance of the class, or the class itself. TSubclassOf should be better for an inventory. You can then use the stored class to spawn your item.

I would like to help you with the dissappearing ammo, but this is a lot of code to look through. Have you been able to track down the issue a little further so we know where to look?

One possibility could be though, that on the class the weapons derive from, you have set the ammo to 0 or didn’t even define it, and only changed it on the actual instances. When accessing the stored weapon’s defaultobject you will not get the ammo value of the instance you picked up, but of it’s base-class. Just an idea though.

This is an array of classes, not weapon instances. Since instance information (such as ammo, in your case) is stored on instances (surprise!), defining your inventory in this fashion will not retain ammo information. I’m going to venture that you get the weapon type from this inventory and then spawn the weapon whenever you “equip” it? If so, the ammo count will be pulled from the weapon’s defaults.

This, however, stores pointers to actual weapon instances. This would fix the problem you’re experiencing, though you will be using the array slightly differently as a result.

If you have a 1-to-1 correspondence between your ammo types and weapon types, then an array of instances would be a sufficient solution.

But if you intend to share ammo data across different weapons, then you should store the ammo separately from its weapons. If you drop/swap weapons out and you want to keep track of its ammo count until you swap the same weapon back in (a common occurrence in modern FPS games), then you also need to store ammo separately from the weapon.

I wanted to have the weapons to own the Ammo information. I’ll give this a try and post the changes of the code very soon if it works or not.

OK so here’s what I have

TesterCharacter.h



// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/Character.h"
#include "Weapon.h"
#include "Item.h"
#include "Knife.h"
#include "TesterCharacter.generated.h"

UCLASS(config=Game)
class ATesterCharacter : public ACharacter
{
	GENERATED_BODY()

	/** Pawn mesh: 1st person view (arms; seen only by self) */
	UPROPERTY(VisibleDefaultsOnly, Category=Mesh)
	class USkeletalMeshComponent* Mesh1P;

	UPROPERTY(VisibleDefaultsOnly, Category = Collision)
	class UBoxComponent *CollisionComp;

	/** First person camera */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FirstPersonCameraComponent;
public:
	ATesterCharacter(const FObjectInitializer& ObjectInitializer);

	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseTurnRate;

	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseLookUpRate;

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

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

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

	/** AnimMontage to play each time we fire */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
	class UAnimMontage* FireAnimation;

	//Stores Currently Equiped Weapon Information(to access functions, variables, etc)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Weapon)
	AWeapon *CurrentWeapon;

	//Inventory for the Weapons
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Inventory)
	TArray<TSubclassOf<AWeapon>> WeapInventory;

	UPROPERTY(VisibleAnyWhere, BlueprintReadOnly, Category = Inventory)
	TArray<class AWeapon*> Inventory;

	//Inventory for Key Items
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Inventory)
	TArray<TSubclassOf<AItem>> ItemInventory;

	TSubclassOf<AKnife> Knife_D;

	UPROPERTY(EditDefaultsOnly, Category = Inventory)
	TArray<TSubclassOf<AWeapon>> DefaultInventory;

	void GiveDefaultWeapons();

	//Tells Inventory if that weapon is already in inventory. If it is, the function will then add ammo. If ammo is full, it will then not process the pickup
	void ProcessWeaponPickup(AWeapon *Weapon);

	AWeapon* GetPreviousWeapon();

	AWeapon* GetNextWeapon();

	void NextWeapon();

	void PrevWeapon();

	void ProcessItemPickup(AItem *Item);

	void EquipWeapon(AWeapon *Weapon);

	virtual void BeginPlay() override;

protected:

	/** Handler for a touch input beginning. */
	void TouchStarted(const ETouchIndex::Type FingerIndex, const FVector Location);

	/** Fires a projectile. */
	void OnFire();

	/** Handles moving forward/backward */
	void MoveForward(float Val);

	/** Handles stafing movement, left and right */
	void MoveRight(float Val);

	/**
	 * Called via input to turn at a given rate.
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void TurnAtRate(float Rate);

	/**
	 * Called via input to turn look up/down at a given rate.
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void LookUpAtRate(float Rate);

protected:
	// APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
	// End of APawn interface

	UFUNCTION()
	void OnCollision(AActor *OtherActor, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult);

public:
	/** Returns Mesh1P subobject **/
	FORCEINLINE class USkeletalMeshComponent* GetMesh1P() const { return Mesh1P; }
	/** Returns FirstPersonCameraComponent subobject **/
	FORCEINLINE class UCameraComponent* GetFirstPersonCameraComponent() const { return FirstPersonCameraComponent; }
};




TesterCharacter.cpp



// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.

#include "Tester.h"
#include "TesterCharacter.h"
#include "TesterProjectile.h"
#include "Animation/AnimInstance.h"


//////////////////////////////////////////////////////////////////////////
// ATesterCharacter

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

	static ConstructorHelpers::FObjectFinder<UBlueprint>BP_Knife(TEXT("Blueprint'/Game/Blueprints/Weapon/Knife/Knife_BP.Knife_BP'"));
	if (BP_Knife.Succeeded())
	{
		Knife_D = (UClass*)BP_Knife.Object->GeneratedClass;
	}
	
	// Set size for collision capsule
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	// set our turn rates for input
	BaseTurnRate = 45.f;
	BaseLookUpRate = 45.f;

	WeapInventory.SetNum(10, false);
	Inventory.SetNum(10, false);
	CurrentWeapon = NULL;

	// Create a CameraComponent	
	FirstPersonCameraComponent = ObjectInitializer.CreateDefaultSubobject<UCameraComponent>(this, TEXT("FirstPersonCamera"));
	FirstPersonCameraComponent->AttachParent = GetCapsuleComponent();
	FirstPersonCameraComponent->RelativeLocation = FVector(0, 0, 64.f); // Position the camera
	FirstPersonCameraComponent->bUsePawnControlRotation = true;

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

	// Create a mesh component that will be used when being viewed from a '1st person' view (when controlling this pawn)
	Mesh1P = ObjectInitializer.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("CharacterMesh1P"));
	Mesh1P->SetOnlyOwnerSee(true);			// only the owning player will see this mesh
	Mesh1P->AttachParent = FirstPersonCameraComponent;
	Mesh1P->RelativeLocation = FVector(0.f, 0.f, -150.f);
	Mesh1P->bCastDynamicShadow = false;
	Mesh1P->CastShadow = false;

	CollisionComp = ObjectInitializer.CreateDefaultSubobject<UBoxComponent>(this, TEXT("CollisionComp"));
	CollisionComp->AttachParent = RootComponent;
	CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &ATesterCharacter::OnCollision);

	// Note: The ProjectileClass and the skeletal mesh/anim blueprints for Mesh1P are set in the
	// derived blueprint asset named MyCharacter (to avoid direct content references in C++)
}

void ATesterCharacter::BeginPlay()
{
	GiveDefaultWeapons();
}

void ATesterCharacter::GiveDefaultWeapons()
{
	for (int32 i = 0; i < 1; i++)
	{
		if (DefaultInventory*)
		{
			AWeapon *Spawner = GetWorld()->SpawnActor<AWeapon>(DefaultInventory*);
			if (Spawner)
			{
				Inventory* = Spawner;
				CurrentWeapon = Inventory*;
				Inventory*->CollisionComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
				Inventory*->AttachRootComponentTo(GetMesh1P(), "WeapSocket", EAttachLocation::SnapToTarget);
			}
		}
	} 

}

//////////////////////////////////////////////////////////////////////////
// Input

void ATesterCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	// set up gameplay key bindings
	check(InputComponent);

	InputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
	InputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
	InputComponent->BindAction("PreviousWeapon", IE_Pressed, this, &ATesterCharacter::PrevWeapon);
	InputComponent->BindAction("NextWeapon", IE_Pressed, this, &ATesterCharacter::NextWeapon);
	
	InputComponent->BindAction("Fire", IE_Pressed, this, &ATesterCharacter::OnFire);
	InputComponent->BindTouch(EInputEvent::IE_Pressed, this, &ATesterCharacter::TouchStarted);

	InputComponent->BindAxis("MoveForward", this, &ATesterCharacter::MoveForward);
	InputComponent->BindAxis("MoveRight", this, &ATesterCharacter::MoveRight);
	
	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
	// "turn" handles devices that provide an absolute delta, such as a mouse.
	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
	InputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
	InputComponent->BindAxis("TurnRate", this, &ATesterCharacter::TurnAtRate);
	InputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
	InputComponent->BindAxis("LookUpRate", this, &ATesterCharacter::LookUpAtRate);
}

void ATesterCharacter::OnFire()
{
	if (CurrentWeapon != NULL)
	{
		CurrentWeapon->Fire();
	}
}

void ATesterCharacter::OnCollision(AActor *OtherActor, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
{
	AWeapon *Weapon = Cast<AWeapon>(OtherActor);
	if (Weapon)
	{
		ProcessWeaponPickup(Weapon);
	}
}

void ATesterCharacter::ProcessWeaponPickup(AWeapon *Weapon)
{
	if (Weapon != NULL)
	{
		//Check to see if Weapon is not currently in the priority slot in Array (basically checks to see if weapon is in the array)
		if (Inventory[Weapon->WeapConfig.Priority] != Weapon)
		{
			//Insert Weapon in correct slot by priority
			WeapInventory.Insert(Weapon->GetClass(), Weapon->WeapConfig.Priority); 
			Inventory.Insert(Weapon, Weapon->WeapConfig.Priority);
			GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Black, "You Just picked up a " + Inventory[Weapon->WeapConfig.Priority]->WeapConfig.Name);

			//Checks to see if the weapon is a higher priority number
			if (Weapon->WeapConfig.Priority > Inventory[CurrentWeapon->WeapConfig.Priority]->WeapConfig.Priority)
			{
				//Equip weapon with the higher priority
				EquipWeapon(Inventory[Weapon->WeapConfig.Priority]);
			}
			//Destroy the picked up weapon on the scene
			Weapon->Destroy();
		}
		
		else
		{
			if (Inventory[Weapon->WeapConfig.Priority]->CurrentAmmo >= 0 && Weapon->CurrentAmmo <= (Inventory[Weapon->WeapConfig.Priority]->WeapConfig.MaxAmmo - Inventory[Weapon->WeapConfig.Priority]->CurrentAmmo))
			{
				Inventory[Weapon->WeapConfig.Priority]->CurrentAmmo += Weapon->CurrentAmmo;
				GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Black, "Added " + Weapon->CurrentAmmo);
				Weapon->Destroy();
			}
			else
			{
				GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Black, "Full Ammo on " + WeapInventory[Weapon->WeapConfig.Priority]->GetDefaultObject<AWeapon>()->WeapConfig.Name);
			}
			
		}
	}
}

void ATesterCharacter::ProcessItemPickup(AItem *Item)
{

}

void ATesterCharacter::NextWeapon()
{
	if (GetNextWeapon() != NULL && GetNextWeapon() != CurrentWeapon)
	{
		EquipWeapon(GetNextWeapon());
	}
}

void ATesterCharacter::PrevWeapon()
{
	if (GetPreviousWeapon() != NULL && CurrentWeapon != GetPreviousWeapon())
	{
		EquipWeapon(GetPreviousWeapon());
	}
}

AWeapon* ATesterCharacter::GetPreviousWeapon()
{
	if (Inventory[CurrentWeapon->WeapConfig.Priority - 1] < 0)
	{
		return Inventory[CurrentWeapon->WeapConfig.Priority];
	}
	else
	{
		return Inventory[CurrentWeapon->WeapConfig.Priority - 1];
	}
}

AWeapon* ATesterCharacter::GetNextWeapon()
{
	if (Inventory[CurrentWeapon->WeapConfig.Priority + 1] == NULL)
	{
		return Inventory[CurrentWeapon->WeapConfig.Priority];
	}
	else
	{
		return Inventory[CurrentWeapon->WeapConfig.Priority + 1];
	}
}

void ATesterCharacter::EquipWeapon(AWeapon *Weapon)
{
	if (CurrentWeapon != NULL)
	
		//store the current weapon in it's correct inventory slot
		CurrentWeapon = Inventory[CurrentWeapon->WeapConfig.Priority];
		//destroy the current weapon on the scene
		CurrentWeapon->Destroy();
		Inventory[Weapon->WeapConfig.Priority]->WeapConfig.bIsEquipped = true;
		AWeapon *Spawner = GetWorld()->SpawnActor<AWeapon>(Weapon->GetClass());
		if (Spawner)
		{
			Inventory[Weapon->WeapConfig.Priority] = Spawner;
			Spawner->CollisionComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
			GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Black, "Spawned");
			CurrentWeapon = Inventory[Weapon->WeapConfig.Priority];
			Inventory[Weapon->WeapConfig.Priority]->AttachRootComponentTo(GetMesh1P(), "WeapSocket", EAttachLocation::SnapToTarget);
		}
	}
}


now the issue is the check on the Inventory if I already have the weapon. It keeps thinking I dont have the weapon in that slot. Need to do this so I know it’s checking the information correctly when trying to pick up ammo.

On another side of it besides that

I switched weapons back and forth, it still wont keep the information correctly :/.

You are still spawning a fresh new weapon when equipping it. This will have the same behaviour as storing it as a class, since you are spawning a fresh weapon with no information of prior state. Instead of spawning on equip, spawn on PostInitializeComponents() and store spawned weapons in your array. Call SetVisibility( false ) on their roots so they don’t show up in-game. Then, instead of equipping weapons by destroying/recreating them (which is very wasteful in the first place!), just attach them and call SetVisibility( true ) to equip, do the inverse to unequip.

Edit: Since you’re picking up weapons as well, you wouldn’t want to spawn them in PostInitializeComponents as you’d have all the weapons then. You can spawn the weapon on pickup instead, and store it in the array. Then, to determine if you already have the weapon, just check the array at that Priority index, if there’s something there then you already have the weapon. Don’t compare against the weapon you’re picking up as it is not the same instance and the check will fail. You could always compare the class instead if the Priority index is not always the same for each weapon.

I agree with cmartel, except that I think spawning the weapons fresh makes more sense. Just working with already spawned instances is simpler, but it comes with a few limitations. Of course it always depends on how dynamic your inventory needs to be. If all you ever need to do is picking up weapons in the level, you don’t need to worry. But for example if you need to add a weapon to the inventory prior to the gamestart without hardcoding it every time (For example if you have lots of NPCs, that share the inventory-functionality. You wouldn’t want to go through all of them and hardcode it). You would need to place a weapon in the level and add this instance to the inventory, which can very quickly become extremely messy. What I do is using a different derived blueprint for all of my items, this doesn’t allow me to edit the spawned instances and have those changes be reflected in the inventory, but on the other hand, it is much easier to manage. If you need to look up weapon stats you can just go directly in your library instead of searching through all of your placed items in the level. And I’m sure you could even work around this, by using a struct to store the different weapon-stats manually when an item is picked up and recreating it when it needs to be spawned.

But it really depends on what you are trying to achieve.

@cmartel
Ok, so basically what I should do is either two things: 1.) spawn them at the start of the game then store the instances in an array(which gives me all of the weapons basically) 2.) amongst collision, spawn the picked up weapon, store in the array according to my priority(Would Inventory.Insert still be a good idea to use or a for loop?). but having the weapon on the scene itself and not destroying it. alright that makes sense :). Haha Idk, just my logic was thinking to destroy the weapon and spawn the new one. little did i know destroying and spawning a new instance was the cause of the information disappearing Dx. BIG QUESION THOUGH. Amongst changing levels, how would i store the weapons I own in the array to be on the scene?

@HammelGammel

I’m trying to achieve an old school inventory system like Turok: Dinosaur Hunter(Just for basics really ^_^) so there will be many weapons to own. As for the weapon, I actually have base stats with the weapon class and whatever derives from that class has all of the properties and more if needed



// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Actor.h"
#include "Weapon.generated.h"

#define TRACE_WEAPON ECC_GameTraceChannel1

UENUM(BlueprintType)
namespace EProjectile
{
	enum Type
	{
		E_Bullet			UMETA(DisplayName = "Bullet"),
		E_Projectile		UMETA(DisplayName = "Projectile"),
		E_Melee				UMETA(DisplayName = "Melee"),
	};
}

USTRUCT(BlueprintType)
struct FWeaponConfig
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	int32 MaxAmmo;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	int32 MaxClip;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	float WeaponRange;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	float TimeBetweenShots;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Ammo)
	int32 ShotCost;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	float WeaponSpread;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	FString Name;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	UTexture2D* SplashArt;

	//Use of Priority Makes the picking up of weapon and equiping them automatically sort them in TesterCharacter
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	int32 Priority;

	//Checks to see if weapon is equipped
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	bool bIsEquipped;
};

/**
 * 
 */
UCLASS()
class TESTER_API AWeapon : public AActor
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Mesh)
	USkeletalMeshComponent *Mesh;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Collision)
	UBoxComponent *CollisionComp;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	FWeaponConfig WeapConfig;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	TEnumAsByte<EProjectile::Type> ProjType;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	int32 CurrentAmmo;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Config)
	int32 CurrentClip;

	void Fire();
	void ReloadAmmo();

	virtual void Instant_Fire();
	virtual void ProjectileFire();
	virtual void MeleeFire();

protected:
	FHitResult WeaponTrace(const FVector &TraceFrom, const FVector &TraceTo) const;

	void ProcessInstantHit(const FHitResult &Impact, const FVector &Origin, const FVector &ShootDir, int32 RandomSeed, float ReticleSpread);
	
};



but I understand where you are coming from with no way in hell wanting to hard code each one like that Dx.

both cmartek && HammelGammel

I really REALLY REALLY appreciate your help!! this makes a huge difference with understanding storing information in Arrays properly. I’ll keep an update on my changes here :slight_smile:

I want to say, I got this to work!!! Thank you all again for this help!!! I want to integrate this with the Weapon Essentials Series! Are you two alright with this if I mention you in the video and link to this thread?

Glad I could help. Sure, I’m fine with it.

Hi, sorry for the necro. I noticed in your code that you have a separate inventory for items and weapons. I am also making an FPS and I was curious why you chose to separate the inventories like that and how that’s worked out for you. Right now I’m making a single inventory with weapons being subclasses of the item base class stored in the inventory, but I would considering switching to your method if you feel like it was a good choice.