How to do it...

  1. Create an ActorComponent subclass using the engine called InventoryComponent:

  1. Inside of the InventoryComponent.h file, add the following code:
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class CHAPTER_04_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()

public:
// Sets default values for this component's properties
UInventoryComponent();

UPROPERTY()
TArray<AInventoryActor*> CurrentInventory;

UFUNCTION()
int32 AddToInventory(AInventoryActor* ActorToAdd);

UFUNCTION()
void RemoveFromInventory(AInventoryActor* ActorToRemove);

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

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


};
  1. Add the following function implementation to the source file:
int32 UInventoryComponent::AddToInventory(AInventoryActor* ActorToAdd)
{
return CurrentInventory.Add(ActorToAdd);
}

void UInventoryComponent::RemoveFromInventory(AInventoryActor* ActorToRemove)
{
CurrentInventory.Remove(ActorToRemove);
}

  1. Next, create a new StaticMeshActor subclass called InventoryActor. Remember to check Show All Classes to see the StaticMeshActor class:

  1. Now that we have the file, go to the InventoryComponent.h file and add the following includes:
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryActor.h"
#include "InventoryComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class CHAPTER_04_API UInventoryComponent : public UActorComponent

  1. Return to the InventoryActor.h file and add the following to its declaration:
UCLASS()
class CHAPTER_04_API AInventoryActor : public AStaticMeshActor
{
GENERATED_BODY()

public:
virtual void PickUp();
virtual void PutDown(FTransform TargetLocation);

};
  1. Implement the new functions in the implementation file:
void AInventoryActor::PickUp() 
{ 
  SetActorTickEnabled(false); 
  SetActorHiddenInGame(true); 
  SetActorEnableCollision(false); 
} 
 
void AInventoryActor::PutDown(FTransform TargetLocation) 
{ 
  SetActorTickEnabled(true); 
  SetActorHiddenInGame(false); 
  SetActorEnableCollision(true); 
  SetActorLocation(TargetLocation.GetLocation()); 
} 
  1. Also, change the constructor to look like the following:
AInventoryActor::AInventoryActor()
:Super()
{
PrimaryActorTick.bCanEverTick = true;
auto MeshAsset = ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'"));

if (MeshAsset.Object != nullptr)
{
GetStaticMeshComponent()->SetStaticMesh(MeshAsset.Object);
GetStaticMeshComponent()->SetCollisionProfileName( UCollisionProfile::Pawn_ProfileName);
}

GetStaticMeshComponent()->SetMobility(EComponentMobility::Movable);

SetActorEnableCollision(true);
}
  1. Afterwards, we need to add the following #includes for InventoryActor.cpp:
#include "InventoryActor.h"
#include "ConstructorHelpers.h"
#include "Engine/CollisionProfile.h"
  1. We need to add an InventoryComponent to our character so that we have an inventory that we can store items in. Create a class that's derived from the Character class called InventoryCharacter:

  1. Add the following to the #includes:
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InventoryComponent.h"
#include "InventoryActor.h"
#include "InventoryCharacter.generated.h"

UCLASS()
class CHAPTER_04_API AInventoryCharacter : public ACharacter
  1. Then, add the following to the InventoryCharacter class's declaration:
UCLASS()
class CHAPTER_04_API AInventoryCharacter : public ACharacter
{
GENERATED_BODY()

public:
// Sets default values for this character's properties
AInventoryCharacter();

UPROPERTY()
UInventoryComponent* MyInventory;

UFUNCTION()
void DropItem();

UFUNCTION()
void TakeItem(AInventoryActor* InventoryItem);

UFUNCTION()
virtual void NotifyHit(class UPrimitiveComponent* MyComp,
AActor* Other, class UPrimitiveComponent* OtherComp,
bool
bSelfMoved, FVector HitLocation, FVector
HitNormal, FVector
NormalImpulse, const FHitResult&
Hit) override;


UFUNCTION()
void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void PitchCamera(float AxisValue);
void YawCamera(float AxisValue);

protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;

public:
// Called every frame
virtual void Tick(float DeltaTime) override;

// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

private:
FVector MovementInput;
FVector CameraInput;

};
  1. Add the following line to the character's constructor implementation:
AInventoryCharacter::AInventoryCharacter()
{
// Set this character to call Tick() every frame. You can turn this
// off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;

MyInventory = CreateDefaultSubobject<UInventoryComponent>("MyInventory");
}
  1. Add the following code to the overridden SetupPlayerInputComponent:
// Called to bind functionality to input
void AInventoryCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);

PlayerInputComponent->BindAction("DropItem",
EInputEvent::IE_Pressed, this,
&AInventoryCharacter::DropItem);

// Movement
PlayerInputComponent->BindAxis("MoveForward", this,
&AInventoryCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this,
&AInventoryCharacter::MoveRight);
PlayerInputComponent->BindAxis("CameraPitch", this,
&AInventoryCharacter::PitchCamera);
PlayerInputComponent->BindAxis("CameraYaw", this,
&AInventoryCharacter::YawCamera);
}

  1. Next, add in the MoveForward, MoveRight, CameraPitch, and CameraYaw axes and DropItem action to the Input menu. If you do not recall how to do this, read Chapter 6, Input and Collision, where we go into detail on this. Here are the settings that I used for this particular example:

  1. Finally, add the following function implementations:
void AInventoryCharacter::DropItem()
{
if (MyInventory->CurrentInventory.Num() == 0)
{
return;
}
AInventoryActor* Item = MyInventory->CurrentInventory.Last();
MyInventory->RemoveFromInventory(Item);


FVector ItemOrigin;
FVector ItemBounds;
Item->GetActorBounds(false, ItemOrigin, ItemBounds);

FTransform PutDownLocation = GetTransform() + FTransform(RootComponent->GetForwardVector() * ItemBounds.GetMax());

Item->PutDown(PutDownLocation);
}


void AInventoryCharacter::NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
AInventoryActor* InventoryItem = Cast<AInventoryActor>(Other);
if (InventoryItem != nullptr)
{
TakeItem(InventoryItem);
}

}

void AInventoryCharacter::TakeItem(AInventoryActor* InventoryItem)
{
InventoryItem->PickUp();
MyInventory->AddToInventory(InventoryItem);
}

//Movement
void AInventoryCharacter::MoveForward(float AxisValue)
{
MovementInput.X = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}

void AInventoryCharacter::MoveRight(float AxisValue)
{
MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}

void AInventoryCharacter::PitchCamera(float AxisValue)
{
CameraInput.Y = AxisValue;
}

void AInventoryCharacter::YawCamera(float AxisValue)
{
CameraInput.X = AxisValue;
}
  1. To handle the movement functions, update the Tick function to the following:
// Called every frame
void AInventoryCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);

if (!MovementInput.IsZero())
{
MovementInput *= 100;

//Scale our movement input axis values by 100 units
// per second
FVector InputVector = FVector(0, 0, 0);
InputVector += GetActorForwardVector()* MovementInput.X *
DeltaTime;
InputVector += GetActorRightVector()* MovementInput.Y *
DeltaTime;
/* GEngine->AddOnScreenDebugMessage(-1, 1,
FColor::Red,
FString::Printf(TEXT("x- %f, y - %f, z - %f"),
InputVector.X, InputVector.Y, InputVector.Z)); */
}

if (!CameraInput.IsNearlyZero())
{
FRotator NewRotation = GetActorRotation();
NewRotation.Pitch += CameraInput.Y;
NewRotation.Yaw += CameraInput.X;

APlayerController* MyPlayerController =
Cast<APlayerController>(GetController());
if (MyPlayerController != nullptr)
{
MyPlayerController->AddYawInput(CameraInput.X);
MyPlayerController->AddPitchInput(CameraInput.Y);
}
SetActorRotation(NewRotation);
}
}

  1. Then, add the following #include:
#include "InventoryCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
  1. Compile your code and test it in the Editor. Create a new level and drag a few instances of InventoryActor out into your scene.
  2. Refer to the Instantiating an Actor using SpawnActor recipe if you need a reminder of how to override the current game mode. Add the following line to the constructor of your Game Mode from that recipe, and then set your level's GameMode to the one you created in that recipe:
#include "Chapter_04GameModeBase.h"
#include "InventoryCharacter.h"

AChapter_04GameModeBase::AChapter_04GameModeBase()
{
DefaultPawnClass = AInventoryCharacter::StaticClass();
}
  1. Of course, we will also need to update the GameMode's .h file:
UCLASS()
class CHAPTER_04_API AChapter_04GameModeBase : public AGameModeBase
{
GENERATED_BODY()
AChapter_04GameModeBase();
};

  1. Compile and launch your project. If all went well, you should be able to pick up objects by walking on them:

  1. Then, you can drop the items whenever you hit the key that's assigned for DropItem in front of you: