We need to define how a pickup item looks in code. Each pickup item will derive from a common base class. Let's construct the base class for a PickupItem
class now.
The PickupItem
base class should inherit from the AActor
class. Similar to how we created multiple NPC blueprints from the base NPC class, we can create multiple PickupItem
blueprints from a single PickupItem
base class, as shown in the following screenshot:
Once you have created the PickupItem
class, open its code in Visual Studio.
The APickupItem
class will need quite a few members, as follows:
FString
variable for the name of the item being picked upint32
variable for the quantity of the item being picked upUSphereComponent
variable for the sphere that you will collide with for the item to be picked upUStaticMeshComponent
variable to hold the actual Mesh
UTexture2D
variable for the icon that represents the itemThis is how the code in PickupItem.h
looks:
UCLASS() class GOLDENEGG_API APickupItem : public AActor { GENERATED_UCLASS_BODY() // The name of the item you are getting UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Item) FString Name; // How much you are getting UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Item) int32 Quantity; // the sphere you collide with to pick item up UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Item) TSubobjectPtr<USphereComponent> ProxSphere; // The mesh of the item UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = Item) TSubobjectPtr<UStaticMeshComponent> Mesh; // The icon that represents the object in UI/canvas UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Item) UTexture2D* Icon; // When something comes inside ProxSphere, this function runs UFUNCTION(BlueprintNativeEvent, Category = Collision) void Prox( AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult ); };
The point of all these UPROPERTY()
declarations is to make APickupItem
completely configurable by blueprints. For example, the items in the Pickup category will be displayed as follows in the blueprints editor:
In the PickupItem.cpp
file, we complete the constructor for the APickupItem
class, as shown in the following code:
APickupItem::APickupItem(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP) { Name = "UNKNOWN ITEM"; Quantity = 0; // initialize the unreal objects ProxSphere = PCIP.CreateDefaultSubobject<USphereComponent>(this, TEXT("ProxSphere")); Mesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("Mesh")); // make the root object the Mesh RootComponent = Mesh; Mesh->SetSimulatePhysics(true); // Code to make APickupItem::Prox() run when this // object's proximity sphere overlaps another actor. ProxSphere->OnComponentBeginOverlap.AddDynamic(this, &APickupItem::Prox); ProxSphere->AttachTo( Mesh ); // very important! }
In the first two lines, we perform an initialization of Name
and Quantity
to values that should stand out to the game designer as being uninitialized. I used block capitals so that the designer can clearly see that the variable has never been initialized before.
We then initialize the ProxSphere
and Mesh
components using PCIP.CreateDefaultSubobject
. The freshly initialized objects might have some of their default values initialized, but Mesh
will start out empty. You will have to load the actual mesh later, inside blueprints.
For the mesh, we set it to simulate realistic physics so that pickup items will bounce and roll around if they are dropped or moved. Pay special attention to the line ProxSphere->AttachTo( Mesh )
. This line tells you to make sure the pickup item's ProxSphere
component is attached to the Mesh
root component. This means that when the mesh moves in the level, ProxSphere
follows. If you forget this step (or if you did it the other way around), then ProxSphere
will not follow the mesh when it bounces.
In the preceding code, we assigned RootComponent
of APickupItem
to the Mesh
object. The RootComponent
member is a part of the AActor
base class, so every AActor
and its derivatives has a root component. The root component is basically meant to be the core of the object, and also defines how you collide with the object. The RootComponent
object is defined in the Actor.h
file, as shown in the following code:
/** * Collision primitive that defines the transform (location, rotation, scale) of this Actor. */ UPROPERTY() class USceneComponent* RootComponent;
So the UE4 creators intended RootComponent
to always be a reference to the collision primitive. Sometimes the collision primitive can be capsule shaped, other times it can be spherical or even box shaped, or it can be arbitrarily shaped, as in our case, with the mesh. It's rare that a character should have a box-shaped root component, however, because the corners of the box can get caught on walls. Round shapes are usually preferred. The RootComponent
property shows up in the blueprints, where you can see and manipulate it.
Finally, the Prox_Implementation
function gets implemented, as follows:
void APickupItem::Prox_Implementation( AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult ) { // if the overlapped actor is NOT the player, // you simply should return if( Cast<AAvatar>( OtherActor ) == nullptr ) { return; } // Get a reference to the player avatar, to give him // the item AAvatar *avatar = Cast<AAvatar>( UGameplayStatics::GetPlayerPawn( GetWorld(), 0 ) ); // Let the player pick up item // Notice use of keyword this! // That is how _this_ Pickup can refer to itself. avatar->Pickup( this ); // Get a reference to the controller APlayerController* PController = GetWorld()- >GetFirstPlayerController(); // Get a reference to the HUD from the controller AMyHUD* hud = Cast<AMyHUD>( PController->GetHUD() ); hud->addMessage( Message( Icon, FString("Picked up ") + FString::FromInt(Quantity) + FString(" ") + Name, 5.f, FColor::White, FColor::Black ) ); Destroy(); }
A couple of tips here that are pretty important: first, we have to access a couple of globals to get the objects we need. There are three main objects we'll be accessing through these functions that manipulate the HUD: the controller (APlayerController
), the HUD (AMyHUD
), and the player himself (AAvatar
). There is only one of each of these three types of objects in the game instance. UE4 has made finding them easy.
The player
class object can be found at any time from any place in the code by simply calling the following code:
AAvatar *avatar = Cast<AAvatar>( UGameplayStatics::GetPlayerPawn( GetWorld(), 0 ) );
We then pass him the item by calling the AAvatar::Pickup()
function defined earlier.
Because the PlayerPawn
object is really an AAvatar
instance, we cast the result to the AAvatar
class, using the Cast<AAvatar>
command. The UGameplayStatics
family of functions are accessible anywhere in your code—they are global functions.
Retrieving the player controller is from a superglobal as well:
APlayerController* PController = GetWorld()->GetFirstPlayerController();
The GetWorld()
function is actually defined in the UObject
base class. Since all UE4 objects derive from UObject
, any object in the game actually has access to the world
object.
Although this organization might seem strange at first, the HUD is actually attached to the player's controller. You can retrieve the HUD as follows:
AMyHUD* hud = Cast<AMyHUD>( PController->GetHUD() );
We cast the HUD object since we previously set the HUD to being an AMyHUD
instance in blueprints. Since we will be using the HUD often, we can actually store a permanent pointer to the HUD inside our APickupItem
class. We will discuss this point later.
Next, we implement AAvatar::Pickup
, which adds an object of the type APickupItem
to Avatar's backpack:
void AAvatar::Pickup( APickupItem *item ) { if( Backpack.Find( item->Name ) ) { // the item was already in the pack.. increase qty of it Backpack[ item->Name ] += item->Quantity; } else { // the item wasn't in the pack before, add it in now Backpack.Add(item->Name, item->Quantity); // record ref to the tex the first time it is picked up Icons.Add(item->Name, item->Icon); } }
In the preceding code, we check whether the pickup item that the player just got is already in his pack. If it is, we increase its quantity. If it is not in his pack, we add it to both his pack and the Icons
mapping.
To add the pickup items to the pack, use the following line of code:
avatar->Pickup( this );
The APickupItem::Prox_Implementation
is the way this member function will get called.
Now, we need to display the contents of our backpack in the HUD when the player presses I.
3.22.181.47