An InventoryComponent
enables its containing Actor
to store InventoryActors
in its inventory, and place them back into the game world.
Make sure you've followed the Axis Mappings – keyboard, mouse and gamepad directional input for an FPS character recipe in Chapter 6, Input and Collision, before continuing with this recipe, as it shows you how to create a simple character.
Also, the recipe Instantiating an Actor using SpawnActor in this chapter shows you how to create a custom GameMode
.
ActorComponent
subclass using the engine called InventoryComponent
, then add the following code to it:UPROPERTY() TArray<AInventoryActor*> CurrentInventory; UFUNCTION() int32 AddToInventory(AInventoryActor* ActorToAdd); UFUNCTION() void RemoveFromInventory(AInventoryActor* ActorToRemove);
int32 UInventoryComponent::AddToInventory(AInventoryActor* ActorToAdd) { return CurrentInventory.Add(ActorToAdd); } void UInventoryComponent::RemoveFromInventory(AInventoryActor* ActorToRemove) { CurrentInventory.Remove(ActorToRemove); }
StaticMeshActor
subclass called InventoryActor
. Add the following to its declaration:virtual void PickUp(); virtual void PutDown(FTransform TargetLocation);
void AInventoryActor::PickUp() { SetActorTickEnabled(false); SetActorHiddenInGame(true); SetActorEnableCollision(false); } void AInventoryActor::PutDown(FTransform TargetLocation) { SetActorTickEnabled(true); SetActorHiddenInGame(false); SetActorEnableCollision(true); SetActorLocation(TargetLocation.GetLocation()); }
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); }
InventoryComponent
to our character so that we have an inventory that we can store items in. Create a new SimpleCharacter
subclass using the editor, and add the following to its declaration:UPROPERTY() UInventoryComponent* MyInventory; UFUNCTION() virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override; 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;
MyInventory = CreateDefaultSubobject<UInventoryComponent>("MyInventory");
SetupPlayerInputComponent
:void AInventoryCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent) { Super::SetupPlayerInputComponent(InputComponent); InputComponent->BindAction("DropItem", EInputEvent::IE_Pressed, this, &AInventoryCharacter::DropItem); }
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); }
InventoryActor
out into your scene.GameMode
to the one you created in that recipe:DefaultPawnClass = AInventoryCharacter::StaticClass();
#pragma once #include "GameFramework/Character.h" #include "InventoryComponent.h" #include "InventoryCharacter.generated.h" UCLASS() class UE4COOKBOOK_API AInventoryCharacter : public ACharacter { GENERATED_BODY() public: AInventoryCharacter(); virtual void BeginPlay() override; virtual void Tick( float DeltaSeconds ) override; virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override; UPROPERTY() UInventoryComponent* MyInventory; UPROPERTY() UCameraComponent* MainCamera; UFUNCTION() void TakeItem(AInventoryActor* InventoryItem); UFUNCTION() void DropItem(); void MoveForward(float AxisValue); void MoveRight(float AxisValue); void PitchCamera(float AxisValue); void YawCamera(float AxisValue); UFUNCTION() virtual void NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) override; private: FVector MovementInput; FVector CameraInput; }; #include "UE4Cookbook.h" #include "InventoryCharacter.h" AInventoryCharacter::AInventoryCharacter() :Super() { PrimaryActorTick.bCanEverTick = true; MyInventory = CreateDefaultSubobject<UInventoryComponent>("MyInventory"); MainCamera = CreateDefaultSubobject<UCameraComponent>("MainCamera"); MainCamera->bUsePawnControlRotation = 0; } void AInventoryCharacter::BeginPlay() { Super::BeginPlay(); MainCamera->AttachTo(RootComponent); } void AInventoryCharacter::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); if (!MovementInput.IsZero()) { MovementInput *= 100; FVector InputVector = FVector(0,0,0); InputVector += GetActorForwardVector()* MovementInput.X * DeltaTime; InputVector += GetActorRightVector()* MovementInput.Y * DeltaTime; GetCharacterMovement()->AddInputVector(InputVector); 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); } } void AInventoryCharacter::SetupPlayerInputComponent(class UInputComponent* InputComponent) { Super::SetupPlayerInputComponent(InputComponent); InputComponent->BindAxis("MoveForward", this, &AInventoryCharacter::MoveForward); InputComponent->BindAxis("MoveRight", this, &AInventoryCharacter::MoveRight); InputComponent->BindAxis("CameraPitch", this, &AInventoryCharacter::PitchCamera); InputComponent->BindAxis("CameraYaw", this, &AInventoryCharacter::YawCamera); InputComponent->BindAction("DropItem", EInputEvent::IE_Pressed, this, &AInventoryCharacter::DropItem); } 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::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; } 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); } #pragma once #include "Components/ActorComponent.h" #include "InventoryActor.h" #include "InventoryComponent.generated.h" UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) class UE4COOKBOOK_API UInventoryComponent : public UActorComponent { GENERATED_BODY() public: UInventoryComponent(); virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override; UPROPERTY() TArray<AInventoryActor*> CurrentInventory; UFUNCTION() int32 AddToInventory(AInventoryActor* ActorToAdd); UFUNCTION() void RemoveFromInventory(AInventoryActor* ActorToRemove); }; #include "UE4Cookbook.h" #include "InventoryComponent.h" UInventoryComponent::UInventoryComponent() { bWantsBeginPlay = true; PrimaryComponentTick.bCanEverTick = true; } void UInventoryComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) { Super::TickComponent( DeltaTime, TickType, ThisTickFunction ); } int32 UInventoryComponent::AddToInventory(AInventoryActor* ActorToAdd) { return CurrentInventory.Add(ActorToAdd); } void UInventoryComponent::RemoveFromInventory(AInventoryActor* ActorToRemove) { CurrentInventory.Remove(ActorToRemove); } #pragma once #include "GameFramework/GameMode.h" #include "UE4CookbookGameMode.generated.h" UCLASS() class UE4COOKBOOK_API AUE4CookbookGameMode : public AGameMode { GENERATED_BODY() public: AUE4CookbookGameMode(); }; #include "UE4Cookbook.h" #include "MyGameState.h" #include "InventoryCharacter.h" #include "UE4CookbookGameMode.h" AUE4CookbookGameMode::AUE4CookbookGameMode() { DefaultPawnClass = AInventoryCharacter::StaticClass(); GameStateClass = AMyGameState::StaticClass(); }
InputAction
to the bindings in the editor. To do this, bring up the Project Settings... window by selecting Edit | Project Settings...:Then, select Input on the left-hand side. Select the plus symbol beside Action Mappings, and type DropItem
into the text box that appears. Underneath it is a list of all the potential keys you can bind to this action. Select the one labelled E
. Your settings should now look like the following:
TArray
add/remove functionality, but allow us to optionally do things such as checking if the array is within a specified size limit before going ahead with storing the item.InventoryActor
is a base class that can be used for all items that can be taken by a player.PickUp
function, we need to disable the actor when it is picked up. To do that, we have to do the following:SetActorTickEnabled
, SetActorHiddenInGame
, and SetActorEnableCollision
.PutDown
function is the reverse. We enable actor ticking, unhide the actor, and then turn its collision back on, and we transport the actor to the desired location.InventoryComponent
to our new character as well as a function to take items.InventoryComponent
.NotifyHit
override so that we are notified when the character hits other Actors.InventoryActor
. If the cast is successful, then we know our Actor
was an InventoryActor
, and so we can call the TakeItem
function to take it.TakeItem
function, we notify the Inventory item actor that we want to pick it up, then we add it to our inventory.InventoryCharacter
is the DropItem
function. This function checks if we have any items in our inventory. If it has any items, we remove it from our inventory, then we calculate a safe distance in front of our player character to drop the item using the Item Bounds to get its maximum bounding box dimension.SimpleCharacter
class mentioned in this recipe18.119.160.154