Unreal Engine C++ has its own set of abstractions, so you have to learn a lot of new patterns and systems in addition to vanilla C++. I put in commonly used Unreal C++ phrases that are used in common game development so that I can index them whenever I encounter them again.
1. Setting Timer
ex) you want a timer for your text block, so that it hides after a given time.
in this case, we use GetWorld->GetTimerManager().SetTimer(time, delegate function)
GetWorld()->GetTimerManager().SetTimer(MessageTimer, [this]()
{
MessageHide();
bIsMessageActive = false;
}, MessageLifetime, false);
2. FastArray
For arrays that need to be broadcasted to multiple players efficiently, we use FastArraySerializer
- Setting Entry and Array as friend class is common practice
TStructOpsTypeTraits - WithNetDeltaSerializer = True must be set to add this type to delta serializer in your reflection system.
- MarkItemDirty on add, MarkArrayDirty on remove.
- FastArray.h
USTRUCT(BlueprintType)
struct FInv_InventoryEntry: public FFastArraySerializerItem
{
GENERATED_BODY()
FInv_InventoryEntry() {}
private:
friend UInv_InventoryComponent;
friend FInv_InventoryFastArray;
UPROPERTY()
TObjectPtr<UInv_InventoryItem> Item = nullptr;
};
/**
* List of Inventory Items
*/
USTRUCT(BlueprintType)
struct FInv_InventoryFastArray: public FFastArraySerializer
{
GENERATED_BODY()
FInv_InventoryFastArray(): OwnerComponent(nullptr) {}
FInv_InventoryFastArray(UActorComponent* InOwnerComponent): OwnerComponent(InOwnerComponent) {}
TArray<UInv_InventoryItem*> GetAllItems() const;
// FFastArraySerializer contract
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
// End of FFastArraySerializer contract
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParams)
{
return FastArrayDeltaSerialize<FInv_InventoryEntry, FInv_InventoryFastArray>(Entries, DeltaParams, *this);
}
UInv_InventoryItem* AddEntry(UInv_ItemComponent* ItemComponent);
UInv_InventoryItem* AddEntry(UInv_InventoryItem* Item);
void RemoveEntry(UInv_InventoryItem* Item);
private:
friend UInv_InventoryComponent;
UPROPERTY()
TArray<FInv_InventoryEntry> Entries;
UPROPERTY(NotReplicated)
TObjectPtr<UActorComponent> OwnerComponent;
};
template<>
struct TStructOpsTypeTraits<FInv_InventoryFastArray> : TStructOpsTypeTraitsBase2<FInv_InventoryFastArray>
{
// Sets flag that makes reflection system know that this particular type should be delta serialized.
enum { WithNetDeltaSerializer = true };
};
- FastArray.cpp
UInv_InventoryItem* FInv_InventoryFastArray::AddEntry(UInv_InventoryItem* Item)
{
check(OwnerComponent);
AActor* OwningActor = OwnerComponent->GetOwner();
// why check? - only should do on server - in client crashes
check(OwningActor->HasAuthority());
FInv_InventoryEntry& NewEntry = Entries.AddDefaulted_GetRef();
NewEntry.Item = Item;
MarkItemDirty(NewEntry);
return Item;
}
void FInv_InventoryFastArray::RemoveEntry(UInv_InventoryItem* Item)
{
for (auto EntryIt = Entries.CreateIterator(); EntryIt; ++EntryIt)
{
if (EntryIt->Item == Item)
{
EntryIt.RemoveCurrent();
MarkArrayDirty();
}
}
}
3. FindComponentByClass
Very useful, but also costly -> if the class must have the component, declare as a member function instead of doing this dynamic search.
UInv_InventoryComponent* UInv_InventoryStatics::GetInventoryComponent(const APlayerController* PlayerController)
{
if (IsValid(PlayerController))
{
return nullptr;
}
UInv_InventoryComponent* InventoryComponent = PlayerController->FindComponentByClass<UInv_InventoryComponent>();
return InventoryComponent;
}
4. Multicast Delegates
To create callback functions for Multicast, we
- use
DECLARE_DYNAMIC_MULTICAST_DELEGATE_{params if there is}(delegate_type_name, param types, param names)
- Add the
delegate_type_names
members to the class - call
AddDynamic
on the member and bind the wanted callback. - call
.Broadcast
function on the member.
// Header File
// 1. Declare Multicast Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FInventoryItemChange, UInv_InventoryItem*, Item);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FNoRoomInInventory);
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent), Blueprintable)
class INVENTORY_API UInv_InventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
UInv_InventoryComponent();
void ToggleInventoryMenu();
// 2. Add Delegate members
FInventoryItemChange OnItemAdded;
FInventoryItemChange OnItemRemoved;
FNoRoomInInventory NoRoomInInventory;
// .cpp file
// 3. AddDynamic to bind wanted callback
if (IsValid(InventoryComponent))
{
InventoryComponent->NoRoomInInventory.AddDynamic(this, &ThisClass::OnNoRoom);
}
// 4. Broadcast
void UInv_InventoryComponent::TryAddItem(UInv_ItemComponent* ItemComponent)
{
NoRoomInInventory.Broadcast();
}
5. InstancedStruct
- Instanced along with its owner
- Can be used polymorphically - have childs derived while being exposed to Blueprint
// Header
#pragma once
#include "CoreMinimal.h"
#include "StructUtils/InstancedStruct.h"
#include "UObject/Object.h"
#include "Inv_InventoryItem.generated.h"
UCLASS()
class INVENTORY_API UInv_InventoryItem : public UObject
{
GENERATED_BODY()
public:
// Need to override this to set lifetime
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
private:
// Can only be FInv_ItemManifest and childs of that class
UPROPERTY(VisibleAnywhere, meta=(BaseStruct="/Script/Inventory.Inv_ItemManifest"), Replicated)
FInstancedStruct ItemManifest;
};
// cpp file
#include "Net/UnrealNetwork.h"
#include "Items/Inv_InventoryItem.h"
void UInv_InventoryItem::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
UObject::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, ItemManifest);
}
6. Gameplay Tags
How can I give unique types for every game item?
- If we do it with Enum we have to add every time new item is made
- We use Gameplay Tags
- NativeGameplayTags.h
- Add “GameplayTags” in build.cs
- Define namespace and add types to namespaces using
UE_DECLARE_GAMEPLAY_TAG_EXTERN
# Tags.h
namespace GameItems
{
namespace Equipment
{
namespace Weapons
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Axe)
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Sword)
}
namespace Cloaks
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(RedCloak)
}
namespace Masks
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(SteelMask)
}
}
namespace Consumables
{
namespace Potions
{
namespace Red
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Small)
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Large)
}
namespace Blue
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Small)
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Large)
}
}
}
namespace Craftables
{
UE_DECLARE_GAMEPLAY_TAG_EXTERN(FireFernFruit)
UE_DECLARE_GAMEPLAY_TAG_EXTERN(LuminDaisy)
UE_DECLARE_GAMEPLAY_TAG_EXTERN(ScorchPetalBlossom)
}
}
- In source file, define in the same namespace using
UE_DEFINE_GAMEPLAY_TAG
#include "Items/Inv_ItemTags.h"
namespace GameItems
{
namespace Equipment
{
namespace Weapons
{
UE_DEFINE_GAMEPLAY_TAG(Axe, "GameItems.Equipment.Weapons.Axe")
UE_DEFINE_GAMEPLAY_TAG(Sword, "GameItems.Equipment.Weapons.Sword")
}
namespace Cloaks
{
UE_DEFINE_GAMEPLAY_TAG(RedCloak, "GameItems.Equipment.Cloaks.RedCloak")
}
namespace Masks
{
UE_DEFINE_GAMEPLAY_TAG(SteelMask, "GameItems.Equipment.Masks.SteelMask")
}
}
namespace Consumables
{
namespace Potions
{
namespace Red
{
UE_DEFINE_GAMEPLAY_TAG(Small, "GameItems.Consumables.Potions.Red.Small")
UE_DEFINE_GAMEPLAY_TAG(Large, "GameItems.Consumables.Potions.Red.Large")
}
namespace Blue
{
UE_DEFINE_GAMEPLAY_TAG(Small, "GameItems.Consumables.Potions.Blue.Small")
UE_DEFINE_GAMEPLAY_TAG(Large, "GameItems.Consumables.Potions.Blue.Large")
}
}
}
namespace Craftables
{
UE_DEFINE_GAMEPLAY_TAG(FireFernFruit, "GameItems.Craftables.FireFernFruit")
UE_DEFINE_GAMEPLAY_TAG(LuminDaisy, "GameItems.Craftables.LuminDaisy")
UE_DEFINE_GAMEPLAY_TAG(ScorchPetalBlossom, "GameItems.Craftables.ScorchPetalBlossom")
}
}
7. Move Semantics in Unreal - “MoveTemp”
FInv_SlotAvailabilityResult UInv_InventoryGrid::HasRoomForItem(const FInv_ItemManifest& Manifest)
{
FInv_SlotAvailabilityResult Result;
Result.TotalRoomToFill = 1;
FInv_SlotAvailability SlotAvailability;
SlotAvailability.AmountToFill = 1;
SlotAvailability.Index = 0;
SlotAvailability.bItemAtIndex =false;
Result.SlotAvailabilities.Add(MoveTemp(SlotAvailability));
return Result;
}
8. requires to Give Trait Bounds
template<typename T>
requires std::derived_from<T, FInv_ItemFragment>
const T* FInv_ItemManifest::GetFragmentByTag(const FGameplayTag& Tag) const;