Unreal Engine, Item Fragment 코드 작성
Item 코드 살펴보기 전 cpp 코드들을 작성하도록 한다.
D1Define.h 전체 코드
해당 경로의 D1Defind.h 파일을 열고 편집하도록 한다.
Project
└─Source
└─LyraGame
└─D1Defind.h (*)
이전 시간에 작성한 D1Define.h 에서 사용한 스킨, 아머타입 이외의 코드를 이어서 작성해주도록 한다.
#pragma once
UENUM(BlueprintType)
enum class ESlotState : uint8
{
Default,
Invalid,
Valid
};
UENUM(BlueprintType)
enum class EEquipmentSlotType : uint8
{
Unarmed_LeftHand,
Unarmed_RightHand,
Primary_LeftHand,
Primary_RightHand,
Primary_TwoHand,
Secondary_LeftHand,
Secondary_RightHand,
Secondary_TwoHand,
Utility_Primary,
Utility_Secondary,
Utility_Tertiary,
Utility_Quaternary,
Helmet,
Chest,
Legs,
Hands,
Foot,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EEquipState : uint8
{
Unarmed,
Weapon_Primary,
Weapon_Secondary,
Utility_Primary,
Utility_Secondary,
Utility_Tertiary,
Utility_Quaternary,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EEquipmentType : uint8
{
Armor,
Weapon,
Utility,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EArmorType : uint8
{
Helmet,
Chest,
Legs,
Hands,
Foot,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
Unarmed,
OneHandSword,
TwoHandSword,
GreatSword,
Shield,
Staff,
Bow,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EWeaponSlotType : uint8
{
Primary,
Secondary,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EWeaponHandType : uint8
{
LeftHand,
RightHand,
TwoHand,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EUtilityType : uint8
{
Drink,
LightSource,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EUtilitySlotType : uint8
{
Primary,
Secondary,
Tertiary,
Quaternary,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EItemRarity : uint8
{
Poor,
Common,
Uncommon,
Rare,
Legendary,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class ESpellType : uint8
{
None,
Projectile,
AOE,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class EOverlayTargetType : uint8
{
None,
Weapon,
Character,
ALL,
};
UENUM(BlueprintType)
enum class ECharacterSkinType : uint8
{
Asian,
Black,
Count UMETA(Hidden)
};
UENUM(BlueprintType, meta=(Bitflags))
enum class ECharacterClassType : uint8
{
Fighter,
Swordmaster,
Barbarian,
Wizard,
Archer,
Count UMETA(Hidden)
};
UENUM(BlueprintType)
enum class ED1TeamID : uint8
{
NoTeam,
Monster = 1,
};
LyraAssetManager.h 파일 편집하기
해당 경로의 LyraAssetManager 파일을 수정하도록 한다.
Project
└─Source
└─LyraGames
└─System
├─LyraAssetManager.cpp (*)
└─LyraAssetManager.h (*)
.h 헤더 코드에서 글로벌 클래스와 class ULyraAssetManager : public UAssetManager
함수 아래의 UD1ItemInstance 를 추가하고, 환경 변수도 가르키도록 Config 를 코드도 추가한다.
LyraAssetManager.h
class UD1ItemData;
class ULyraAssetManager : public UAssetManager
{
public:
...
const UD1ItemData& GetItemData();
...
...
UPROPERTY(Config)
TSoftObjectPtr<UD1ItemData> ItemDataPath;
...
}
코드 .cpp 에서는 DD1ItemData 에셋 경로를 가르키도록하고 GetItemInstance 코드도 추가한다.
LyraAssetManager.cpp
#include "Data/D1ItemData.h"
...
...
const UD1ItemData& ULyraAssetManager::GetItemData()
{
return GetOrLoadTypedGameData<UD1ItemData>(ItemDataPath);
}
GamePlayTag 추가하기
기존 GamePlayTagStack 대신 사용할 D1GamePlayTagStack를 추가하도록 한다.
D1GameplayTagStack 경로
Project
└─Source
└─D1Games
└─System
├─D1GameplayTagStack.cpp (+)
└─D1GameplayTagStack.h (+)
D1GameplayTagStack.h
#pragma once
#include "GameplayTagContainer.h"
#include "Net/Serialization/FastArraySerializer.h"
#include "D1GameplayTagStack.generated.h"
struct FD1GameplayTagStackContainer;
struct FNetDeltaSerializeInfo;
USTRUCT(BlueprintType)
struct FD1GameplayTagStack : public FFastArraySerializerItem
{
GENERATED_BODY()
public:
FD1GameplayTagStack() {}
FD1GameplayTagStack(FGameplayTag InTag, int32 InStackCount)
: Tag(InTag)
, StackCount(InStackCount) { }
public:
const FGameplayTag& GetStackTag() const { return Tag; }
int32 GetStackCount() const { return StackCount; }
FString GetDebugString() const;
private:
friend FD1GameplayTagStackContainer;
UPROPERTY()
FGameplayTag Tag;
UPROPERTY()
int32 StackCount = 0;
};
USTRUCT(BlueprintType)
struct FD1GameplayTagStackContainer : public FFastArraySerializer
{
GENERATED_BODY()
public:
FD1GameplayTagStackContainer() { }
public:
void AddStack(FGameplayTag Tag, int32 StackCount);
void RemoveStack(FGameplayTag Tag);
public:
const TArray<FD1GameplayTagStack>& GetStacks() const { return Stacks; }
int32 GetStackCount(FGameplayTag Tag) const { return TagToCountMap.FindRef(Tag); }
bool ContainsTag(FGameplayTag Tag) const { return TagToCountMap.Contains(Tag); }
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParams)
{
return FFastArraySerializer::FastArrayDeltaSerialize<FD1GameplayTagStack, FD1GameplayTagStackContainer>(Stacks, DeltaParams, *this);
}
private:
UPROPERTY()
TArray<FD1GameplayTagStack> Stacks;
UPROPERTY(NotReplicated)
TMap<FGameplayTag, int32> TagToCountMap;
};
template<>
struct TStructOpsTypeTraits<FD1GameplayTagStackContainer> : public TStructOpsTypeTraitsBase2<FD1GameplayTagStackContainer>
{
enum
{
WithNetDeltaSerializer = true,
};
};
D1GameplayTagStack.cpp
#include "D1GameplayTagStack.h"
#include "UObject/Stack.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1GameplayTagStack)
FString FD1GameplayTagStack::GetDebugString() const
{
return FString::Printf(TEXT("%sx%d"), *Tag.ToString(), StackCount);
}
void FD1GameplayTagStackContainer::AddStack(FGameplayTag Tag, int32 StackCount)
{
if (!Tag.IsValid())
{
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to AddStack"), ELogVerbosity::Warning);
return;
}
for (FD1GameplayTagStack& Stack : Stacks)
{
if (Stack.Tag == Tag)
{
const int32 NewCount = Stack.StackCount + StackCount;
Stack.StackCount = NewCount;
TagToCountMap[Tag] = NewCount;
MarkItemDirty(Stack);
return;
}
}
FD1GameplayTagStack& NewStack = Stacks.Emplace_GetRef(Tag, StackCount);
MarkItemDirty(NewStack);
TagToCountMap.Add(Tag, StackCount);
}
void FD1GameplayTagStackContainer::RemoveStack(FGameplayTag Tag)
{
if (!Tag.IsValid())
{
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to RemoveStack"), ELogVerbosity::Warning);
return;
}
for (auto It = Stacks.CreateIterator(); It; ++It)
{
FD1GameplayTagStack& Stack = *It;
if (Stack.Tag == Tag)
{
It.RemoveCurrent();
TagToCountMap.Remove(Tag);
MarkArrayDirty();
return;
}
}
}
void FD1GameplayTagStackContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
{
for (int32 Index : RemovedIndices)
{
const FGameplayTag Tag = Stacks[Index].Tag;
TagToCountMap.Remove(Tag);
}
}
void FD1GameplayTagStackContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
{
for (int32 Index : AddedIndices)
{
const FD1GameplayTagStack& Stack = Stacks[Index];
TagToCountMap.Add(Stack.Tag, Stack.StackCount);
}
}
void FD1GameplayTagStackContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
{
for (int32 Index : ChangedIndices)
{
const FD1GameplayTagStack& Stack = Stacks[Index];
TagToCountMap[Stack.Tag] = Stack.StackCount;
}
}
Data 폴더 관련 코드 작성하기
다음 경로의 코드를 작성하도록 한다.
Project
└─Source
└─D1Game
└─Data
├─D1ItemData.cpp (+)
└─D1ItemData.h (+)
D1ItemData.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "D1ItemData.generated.h"
class UD1ItemTemplate;
UCLASS(BlueprintType, Const, meta = (DisplayName = "D1 Item Data"))
class UD1ItemData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
static const UD1ItemData& Get();
public:
#if WITH_EDITORONLY_DATA
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif // WITH_EDITORONLY_DATA
#if WITH_EDITOR
virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override;
#endif // WITH_EDITOR
public:
const UD1ItemTemplate& FindItemTemplateByID(int32 ItemTemplateID) const;
int32 FindItemTemplateIDByClass(TSubclassOf<UD1ItemTemplate> ItemTemplateClass) const;
void GetAllItemTemplateClasses(TArray<TSubclassOf<UD1ItemTemplate>>& OutItemTemplateClasses) const;
const TArray<TSubclassOf<UD1ItemTemplate>>& GetWeaponItemTemplateClasses() const { return WeaponItemTemplateClasses; }
const TArray<TSubclassOf<UD1ItemTemplate>>& GetArmorItemTemplateClasses() const { return ArmorItemTemplateClasses; }
private:
UPROPERTY(EditDefaultsOnly)
TMap<int32, TSubclassOf<UD1ItemTemplate>> ItemTemplateIDToClass;
UPROPERTY()
TMap<TSubclassOf<UD1ItemTemplate>, int32> ItemTemplateClassToID;
private:
UPROPERTY()
TArray<TSubclassOf<UD1ItemTemplate>> WeaponItemTemplateClasses;
UPROPERTY()
TArray<TSubclassOf<UD1ItemTemplate>> ArmorItemTemplateClasses;
};
D1ItemData.cpp
#include "D1ItemData.h"
#if WITH_EDITOR
#include "Misc/DataValidation.h"
#endif // WITH_EDITOR
#include "Item/D1ItemTemplate.h"
#include "Item/Fragments/D1ItemFragment_Equipable_Armor.h"
#include "Item/Fragments/D1ItemFragment_Equipable_Weapon.h"
#include "System/LyraAssetManager.h"
#include "UObject/ObjectSaveContext.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1ItemData)
const UD1ItemData& UD1ItemData::Get()
{
return ULyraAssetManager::Get().GetItemData();
}
#if WITH_EDITORONLY_DATA
void UD1ItemData::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
ItemTemplateIDToClass.KeySort([](const int32 A, const int32 B)
{
return A < B;
});
ItemTemplateClassToID.Empty();
WeaponItemTemplateClasses.Empty();
ArmorItemTemplateClasses.Empty();
for (const auto& Pair : ItemTemplateIDToClass)
{
ItemTemplateClassToID.Emplace(Pair.Value, Pair.Key);
const UD1ItemTemplate* ItemTemplate = Pair.Value.GetDefaultObject();
if (const UD1ItemFragment_Equipable_Weapon* WeaponFragment = ItemTemplate->FindFragmentByClass<UD1ItemFragment_Equipable_Weapon>())
{
if (WeaponFragment->WeaponType != EWeaponType::Unarmed)
{
WeaponItemTemplateClasses.Add(Pair.Value);
}
}
else if (ItemTemplate->FindFragmentByClass<UD1ItemFragment_Equipable_Armor>())
{
ArmorItemTemplateClasses.Add(Pair.Value);
}
}
}
#endif // WITH_EDITORONLY_DATA
#if WITH_EDITOR
EDataValidationResult UD1ItemData::IsDataValid(FDataValidationContext& Context) const
{
EDataValidationResult Result = Super::IsDataValid(Context);
TSet<int32> ItemTemplateIDSet;
TSet<TSubclassOf<UD1ItemTemplate>> ItemTemplateClassSet;
for (const auto& Pair : ItemTemplateIDToClass)
{
// ID Check
const int32 ItemTemplateID = Pair.Key;
if (ItemTemplateID <= 0)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Invalid ID : [ID : %d]\n"), ItemTemplateID)));
Result = EDataValidationResult::Invalid;
}
if (ItemTemplateIDSet.Contains(ItemTemplateID))
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Duplicated ID : [ID : %d]\n"), ItemTemplateID)));
Result = EDataValidationResult::Invalid;
}
ItemTemplateIDSet.Add(ItemTemplateID);
// Class Check
const TSubclassOf<UD1ItemTemplate> ItemTemplateClass = Pair.Value;
if (ItemTemplateClass == nullptr)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Invalid Class : [ID : %d]\n"), ItemTemplateID)));
Result = EDataValidationResult::Invalid;
}
if (ItemTemplateClassSet.Contains(ItemTemplateClass))
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Duplicated Class : [ID : %d]\n"), ItemTemplateID)));
Result = EDataValidationResult::Invalid;
}
ItemTemplateClassSet.Add(ItemTemplateClass);
}
return Result;
}
#endif // WITH_EDITOR
const UD1ItemTemplate& UD1ItemData::FindItemTemplateByID(int32 ItemTemplateID) const
{
const TSubclassOf<UD1ItemTemplate>* ItemTemplateClass = ItemTemplateIDToClass.Find(ItemTemplateID);
ensureAlwaysMsgf(ItemTemplateClass, TEXT("Can't find ItemTemplateClass from ID [%d]"), ItemTemplateID);
return *(ItemTemplateClass->GetDefaultObject());
}
int32 UD1ItemData::FindItemTemplateIDByClass(TSubclassOf<UD1ItemTemplate> ItemTemplateClass) const
{
const int32* ItemTemplateID = ItemTemplateClassToID.Find(ItemTemplateClass);
ensureAlwaysMsgf(ItemTemplateID, TEXT("Can't find ItemTemplateID from Class"));
return *ItemTemplateID;
}
void UD1ItemData::GetAllItemTemplateClasses(TArray<TSubclassOf<UD1ItemTemplate>>& OutItemTemplateClasses) const
{
OutItemTemplateClasses.Reset();
OutItemTemplateClasses.Reserve(ItemTemplateIDToClass.Num());
for (auto& Pair : ItemTemplateIDToClass)
{
OutItemTemplateClasses.Add(Pair.Value);
}
}
Item 폴더 관련 코드 작성하기
파일 분량이 많다. Managers 는 D1CosmeticManager 를 작성할 때 사용한 폴더이다.
Project
└─Source
└─LyraGame
└─Item
├─Fragments
│ ├─D1ItemFragment_Equipable.cpp
│ ├─D1ItemFragment_Equipable.h
│ ├─D1ItemFragment_Equipable_Armor.cpp
│ ├─D1ItemFragment_Equipable_Armor.h
│ ├─D1ItemFragment_Equipable_Attachment.cpp
│ ├─D1ItemFragment_Equipable_Attachment.h
│ ├─D1ItemFragment_Equipable_Utility.cpp
│ ├─D1ItemFragment_Equipable_Utility.h
│ ├─D1ItemFragment_Equipable_Weapon.cpp
│ └─D1ItemFragment_Equipable_Weapon.h
├─Managers
├─D1ItemInstance.cpp (+)
├─D1ItemInstance.h (+)
├─D1ItemTemplate.cpp (+)
└─D1ItemTemplate.h (+)
D1ItemTemplate.h
#pragma once
#include "D1ItemTemplate.generated.h"
class UD1ItemInstance;
UCLASS(DefaultToInstanced, EditInlineNew, Abstract)
class UD1ItemFragment : public UObject
{
GENERATED_BODY()
public:
virtual void OnInstanceCreated(UD1ItemInstance* Instance) const { }
};
UCLASS(Blueprintable, Const, Abstract)
class UD1ItemTemplate : public UObject
{
GENERATED_BODY()
public:
UD1ItemTemplate(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
protected:
#if WITH_EDITOR
virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override;
#endif // WITH_EDITOR
public:
UFUNCTION(BlueprintCallable, BlueprintPure="false", meta=(DeterminesOutputType="FragmentClass"))
const UD1ItemFragment* FindFragmentByClass(TSubclassOf<UD1ItemFragment> FragmentClass) const;
template <typename FragmentClass>
const FragmentClass* FindFragmentByClass() const
{
return (FragmentClass*)FindFragmentByClass(FragmentClass::StaticClass());
}
public:
UPROPERTY(EditDefaultsOnly)
FIntPoint SlotCount = FIntPoint::ZeroValue;
UPROPERTY(EditDefaultsOnly)
int32 MaxStackCount = 1;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FText DisplayName;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FText Description;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UTexture2D> IconTexture;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSoftObjectPtr<UStaticMesh> PickupableMesh;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Instanced)
TArray<TObjectPtr<UD1ItemFragment>> Fragments;
};
D1ItemTemplate.cpp
#include "D1ItemTemplate.h"
#if WITH_EDITOR
#include "Misc/DataValidation.h"
#endif // WITH_EDITOR
#include "Fragments/D1ItemFragment_Equipable.h"
#include "Fragments/D1ItemFragment_Equipable_Armor.h"
#include "Fragments/D1ItemFragment_Equipable_Utility.h"
#include "Fragments/D1ItemFragment_Equipable_Weapon.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1ItemTemplate)
UD1ItemTemplate::UD1ItemTemplate(const FObjectInitializer& ObjectInitializer)
:Super(ObjectInitializer)
{
}
#if WITH_EDITOR
EDataValidationResult UD1ItemTemplate::IsDataValid(FDataValidationContext& Context) const
{
EDataValidationResult Result = UObject::IsDataValid(Context);
if (SlotCount.X < 1 || SlotCount.Y < 1)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("SlotCount is less than 1"))));
Result = EDataValidationResult::Invalid;
}
if (MaxStackCount < 1)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("MaxStackCount is less than 1"))));
Result = EDataValidationResult::Invalid;
}
const UD1ItemFragment_Equipable* FoundEquipable = nullptr;
for (UD1ItemFragment* Fragment : Fragments)
{
if (UD1ItemFragment_Equipable* CurrentEquippable = Cast<UD1ItemFragment_Equipable>(Fragment))
{
if (FoundEquipable)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Deuplicated Equippable Fragment"))));
return EDataValidationResult::Invalid;
}
FoundEquipable = CurrentEquippable;
}
}
if (FoundEquipable)
{
if (FoundEquipable->EquipmentType == EEquipmentType::Count)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Eqipment Type is Invalid : [EEquipmentType::Count]"))));
return EDataValidationResult::Invalid;
}
if (FoundEquipable->EquipmentType == EEquipmentType::Armor)
{
const UD1ItemFragment_Equipable_Armor* ArmorFragment = Cast<UD1ItemFragment_Equipable_Armor>(FoundEquipable);
if (ArmorFragment->ArmorType == EArmorType::Count)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Armor Type is Invalid : [EArmorType::Count]"))));
Result = EDataValidationResult::Invalid;
}
}
else if (FoundEquipable->EquipmentType == EEquipmentType::Weapon)
{
const UD1ItemFragment_Equipable_Weapon* WeaponFragment = Cast<UD1ItemFragment_Equipable_Weapon>(FoundEquipable);
if (WeaponFragment->WeaponType == EWeaponType::Count)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Weapon Type is Invalid : [EWeaponType::Count]"))));
Result = EDataValidationResult::Invalid;
}
if (WeaponFragment->WeaponHandType == EWeaponHandType::Count)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Weapon Hand Type is Invalid : [EWeaponHandType::Count]"))));
Result = EDataValidationResult::Invalid;
}
}
else if (FoundEquipable->EquipmentType == EEquipmentType::Utility)
{
const UD1ItemFragment_Equipable_Utility* UtilityFragment = Cast<UD1ItemFragment_Equipable_Utility>(FoundEquipable);
if (UtilityFragment->UtilityType == EUtilityType::Count)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("UTILITY Type is Invalid : [EUtilityType::Count]"))));
Result = EDataValidationResult::Invalid;
}
}
if (FoundEquipable->EquipmentType == EEquipmentType::Armor || FoundEquipable->EquipmentType == EEquipmentType::Weapon)
{
if (MaxStackCount != 1)
{
Context.AddError(FText::FromString(FString::Printf(TEXT("Armor or Weapon Type must have MaxStackCount of 1: [MaxStackCount != 1]"))));
Result = EDataValidationResult::Invalid;
}
}
}
return Result;
}
#endif //WITH_EDITOR
const UD1ItemFragment* UD1ItemTemplate::FindFragmentByClass(TSubclassOf<UD1ItemFragment> FragmentClass) const
{
if (FragmentClass)
{
for (UD1ItemFragment* Fragment : Fragments)
{
if (Fragment && Fragment->IsA(FragmentClass))
{
return Fragment;
}
}
}
return nullptr;
}
D1ItemInstance.h
#pragma once
#include "D1Define.h"
#include "AbilitySystem/LyraAbilitySourceInterface.h"
#include "System/D1GameplayTagStack.h"
#include "D1ItemInstance.generated.h"
USTRUCT(BlueprintType)
struct FD1ItemRarityProbability
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere)
EItemRarity Rarity = EItemRarity::Poor;
UPROPERTY(EditAnyWhere)
float Probability = 0;
};
UCLASS(BlueprintType)
class UD1ItemInstance : public UObject, public ILyraAbilitySourceInterface
{
GENERATED_BODY()
public:
UD1ItemInstance(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual bool IsSupportedForNetworking() const override { return true; };
virtual float GetDistanceAttenuation(float Distance, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr) const override;
virtual float GetPhysicalMaterialAttenuation(const UPhysicalMaterial* PhysicalMaterial, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr) const override;
public:
void Init(int32 InItemTemplateID, EItemRarity InItemRarity);
public:
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly)
void AddStatTagStack(FGameplayTag StatTag, int32 StackCount);
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly)
void RemoveStatTagStack(FGameplayTag StatTag);
public:
static EItemRarity DetermineItemRarity(const TArray<FD1ItemRarityProbability>& ItemProbabilities);
public:
UFUNCTION(BlueprintCallable, BlueprintPure)
int32 GetItemTemplateID() const { return ItemTemplateID; }
UFUNCTION(BlueprintCallable, BlueprintPure)
EItemRarity GetItemRarity() const { return ItemRarity; }
UFUNCTION(BlueprintCallable, BlueprintPure)
bool HasStatTag(FGameplayTag StatTag) const;
UFUNCTION(BlueprintCallable, BlueprintPure)
int32 GetStackCountByTag(FGameplayTag StatTag) const;
UFUNCTION(BlueprintCallable, BlueprintPure)
const FD1GameplayTagStackContainer& GetStatContainer() const { return StatContainer; }
public:
UFUNCTION(BlueprintCallable, BlueprintPure="false", meta=(DeterminesOutputType="FragmentClass"))
const UD1ItemFragment* FindFragmentByClass(TSubclassOf<UD1ItemFragment> FragmentClass) const;
template <typename FragmentClass>
const FragmentClass* FindFragmentByClass() const
{
return (FragmentClass*)FindFragmentByClass(FragmentClass::StaticClass());
}
private:
#if UE_WITH_IRIS
virtual void RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags) override;
#endif // UE_WITH_IRIS
private:
UPROPERTY(Replicated)
int32 ItemTemplateID = INDEX_NONE;
UPROPERTY(Replicated)
EItemRarity ItemRarity = EItemRarity::Poor;
UPROPERTY(Replicated)
FD1GameplayTagStackContainer StatContainer;
};
D1ItemInstance.cpp
#include "D1ItemInstance.h"
#if UE_WITH_IRIS
#include "iris/ReplicationSystem/ReplicationFragmentUtil.h"
#endif // UE_WITH_IRIS
#include "D1ItemTemplate.h"
#include "LyraLogChannels.h"
#include "Data/D1ItemData.h"
#include "Net/UnrealNetwork.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1ItemInstance)
UD1ItemInstance::UD1ItemInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UD1ItemInstance::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, ItemTemplateID);
DOREPLIFETIME(ThisClass, ItemRarity);
DOREPLIFETIME(ThisClass, StatContainer);
}
float UD1ItemInstance::GetDistanceAttenuation(float Distance, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags) const
{
return 0;
}
float UD1ItemInstance::GetPhysicalMaterialAttenuation(const UPhysicalMaterial* PhysicalMaterial, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags) const
{
return 0;
}
void UD1ItemInstance::Init(int32 InItemTemplateID, EItemRarity InItemRarity)
{
if (InItemTemplateID <= INDEX_NONE || InItemRarity == EItemRarity::Count)
{
return;
}
ItemTemplateID = InItemTemplateID;
ItemRarity = InItemRarity;
const UD1ItemTemplate& ItemTemplate = UD1ItemData::Get().FindItemTemplateByID(ItemTemplateID);
for (const UD1ItemFragment* Fragment : ItemTemplate.Fragments)
{
if (Fragment)
{
Fragment->OnInstanceCreated(this);
}
}
}
#if UE_WITH_IRIS
void UD1ItemInstance::RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags)
{
using namespace UE::Net;
FReplicationFragmentUtil::CreateAndRegisterFragmentsForObject(this, Context, RegistrationFlags);
}
#endif // UE_WITH_IRIS
void UD1ItemInstance::AddStatTagStack(FGameplayTag StatTag, int32 StackCount)
{
StatContainer.AddStack(StatTag, StackCount);
}
void UD1ItemInstance::RemoveStatTagStack(FGameplayTag StatTag)
{
StatContainer.RemoveStack(StatTag);
}
EItemRarity UD1ItemInstance::DetermineItemRarity(const TArray<FD1ItemRarityProbability>& ItemProbabilities)
{
float TotalProbability = 0.f;
for (const FD1ItemRarityProbability& ItemProbability : ItemProbabilities)
{
TotalProbability += ItemProbability.Probability;
}
if (TotalProbability > 100.f)
{
return EItemRarity::Count;
}
float SumProbability = 0.f;
float RandomValue = FMath::RandRange(0.f, 100.f);
for (const FD1ItemRarityProbability& ItemProbability : ItemProbabilities)
{
SumProbability += ItemProbability.Probability;
if (RandomValue < SumProbability)
{
return ItemProbability.Rarity;
}
}
return EItemRarity::Count;
}
bool UD1ItemInstance::HasStatTag(FGameplayTag StatTag) const
{
return StatContainer.ContainsTag(StatTag);
}
int32 UD1ItemInstance::GetStackCountByTag(FGameplayTag StatTag) const
{
return StatContainer.GetStackCount(StatTag);
}
const UD1ItemFragment* UD1ItemInstance::FindFragmentByClass(TSubclassOf<UD1ItemFragment> FragmentClass) const
{
if (ItemTemplateID > INDEX_NONE && FragmentClass)
{
const UD1ItemTemplate& ItemTemplate = UD1ItemData::Get().FindItemTemplateByID(ItemTemplateID);
return ItemTemplate.FindFragmentByClass(FragmentClass);
}
return nullptr;
}
Fragment 폴더 편집하기
Project
└─Source
└─LyraGame
└─Item
├─Fragments (+)
│ ├─D1ItemFragment_Equipable.cpp (+)
│ ├─D1ItemFragment_Equipable.h (+)
│ ├─D1ItemFragment_Equipable_Armor.cpp (+)
│ ├─D1ItemFragment_Equipable_Armor.h (+)
│ ├─D1ItemFragment_Equipable_Attachment.cpp (+)
│ ├─D1ItemFragment_Equipable_Attachment.h (+)
│ ├─D1ItemFragment_Equipable_Utility.cpp (+)
│ ├─D1ItemFragment_Equipable_Utility.h (+)
│ ├─D1ItemFragment_Equipable_Weapon.cpp (+)
│ └─D1ItemFragment_Equipable_Weapon.h (+)
├─Managers
├─D1ItemInstance.cpp
├─D1ItemInstance.h
├─D1ItemTemplate.cpp
└─D1ItemTemplate.h
D1ItemFragment_Equipable.h
#pragma once
#include "D1Define.h"
#include "GameplayTags.h"
#include "Item/D1ItemTemplate.h"
#include "D1ItemFragment_Equipable.generated.h"
class ULyraAbilitySet;
class UD1ItemInstance;
USTRUCT(BlueprintType)
struct FRarityStat
{
GENERATED_BODY()
public:
UPROPERTY(VisibleDefaultsOnly)
EItemRarity Rarity = EItemRarity::Poor;
UPROPERTY(EditDefaultsOnly)
int32 Value = 0;
};
USTRUCT(BlueprintType)
struct FRarityStatSet
{
GENERATED_BODY()
public:
FRarityStatSet();
public:
UPROPERTY(EditDefaultsOnly, meta=(Categories="SetByCaller"))
FGameplayTag StatTag;
UPROPERTY(EditDefaultsOnly, EditFixedSize)
TArray<FRarityStat> RarityStats;
};
USTRUCT(BlueprintType)
struct FRarityStatRange
{
GENERATED_BODY()
public:
UPROPERTY(VisibleDefaultsOnly)
EItemRarity Rarity = EItemRarity::Poor;
UPROPERTY(EditDefaultsOnly)
int32 MinValue = 0;
UPROPERTY(EditDefaultsOnly)
int32 MaxValue = 0;
};
USTRUCT(BlueprintType)
struct FRarityStatRangeSet
{
GENERATED_BODY()
public:
FRarityStatRangeSet();
public:
UPROPERTY(EditDefaultsOnly, meta=(Categories="SetByCaller"))
FGameplayTag StatTag;
UPROPERTY(EditDefaultsOnly, EditFixedSize)
TArray<FRarityStatRange> RarityStatRanges;
};
UCLASS(Abstract, Const)
class UD1ItemFragment_Equipable : public UD1ItemFragment
{
GENERATED_BODY()
public:
UD1ItemFragment_Equipable(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
protected:
void AddStatTagStack(UD1ItemInstance* ItemInstance, const TArray<FRarityStatSet>& RarityStatSets) const;
void AddStatTagStack(UD1ItemInstance* ItemInstance, const TArray<FRarityStatRangeSet>& RarityStatRangeSets) const;
public:
bool IsEquipableClassType(ECharacterClassType ClassType) const;
public:
EEquipmentType EquipmentType = EEquipmentType::Count;
UPROPERTY(EditDefaultsOnly, meta=(Bitmask, BitmaskEnum="ECharacterClassType"))
uint32 EquipableClassFlags = ((1 << (uint32)ECharacterClassType::Count) - 1);
UPROPERTY(EditDefaultsOnly)
TObjectPtr<const ULyraAbilitySet> BaseAbilitySet;
};
D1ItemFragment_Equipable.cpp
#include "D1ItemFragment_Equipable.h"
#include "Item/D1ItemInstance.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1ItemFragment_Equipable)
FRarityStatSet::FRarityStatSet()
{
RarityStats.SetNum((int32)EItemRarity::Count);
for (int32 i = 0; i < RarityStats.Num(); i++)
{
RarityStats[i].Rarity = (EItemRarity)i;
}
}
FRarityStatRangeSet::FRarityStatRangeSet()
{
RarityStatRanges.SetNum((int32)EItemRarity::Count);
for (int32 i = 0; i < RarityStatRanges.Num(); i++)
{
RarityStatRanges[i].Rarity = (EItemRarity)i;
}
}
UD1ItemFragment_Equipable::UD1ItemFragment_Equipable(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UD1ItemFragment_Equipable::AddStatTagStack(UD1ItemInstance* ItemInstance, const TArray<FRarityStatSet>& RarityStatSets) const
{
if (ItemInstance == nullptr) {
return;
}
for (const FRarityStatSet& RarityStatSet : RarityStatSets)
{
const FGameplayTag& StatTag = RarityStatSet.StatTag;
const FRarityStat& Stat = RarityStatSet.RarityStats[(int32)ItemInstance->GetItemRarity()];
ItemInstance->AddStatTagStack(StatTag, Stat.Value);
}
}
void UD1ItemFragment_Equipable::AddStatTagStack(UD1ItemInstance* ItemInstance, const TArray<FRarityStatRangeSet>& RarityStatRangeSets) const
{
if (ItemInstance == nullptr) {
return;
}
for (const FRarityStatRangeSet& RarityStatRangeSet : RarityStatRangeSets)
{
const FGameplayTag& StatTag = RarityStatRangeSet.StatTag;
const FRarityStatRange& StatRange = RarityStatRangeSet.RarityStatRanges[(int32)ItemInstance->GetItemRarity()];
const int32 StatValue = FMath::RandRange(StatRange.MinValue, StatRange.MaxValue);
ItemInstance->AddStatTagStack(StatTag, StatValue);
}
}
bool UD1ItemFragment_Equipable::IsEquipableClassType(ECharacterClassType ClassType) const
{
return (EquipableClassFlags & (1 << (uint32)ClassType)) != 0;
}
D1ItemFragment_Equipable_Armor.h
#pragma once
#include "D1Define.h"
#include "D1ItemFragment_Equipable.h"
#include "D1ItemFragment_Equipable_Armor.generated.h"
UCLASS()
class UD1ItemFragment_Equipable_Armor : public UD1ItemFragment_Equipable
{
GENERATED_BODY()
public:
UD1ItemFragment_Equipable_Armor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
protected:
#if WITH_EDITORONLY_DATA
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif //WITH_EDITORONLY_DATA
public:
virtual void OnInstanceCreated(UD1ItemInstance* ItemInstance) const override;
public:
UPROPERTY(EditDefaultsOnly)
EArmorType ArmorType = EArmorType::Count;
UPROPERTY(EditDefaultsOnly, meta=(EditCondition="ArmorType == EArmorType::Chest", EditConditionHides))
bool bIsFullBody = false;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<USkeletalMesh> ArmorMesh;
UPROPERTY(EditDefaultsOnly, meta=(ForceInlineRow))
TArray<FRarityStatRangeSet> RarityStatRangeSets;
};
D1ItemFragment_Equipable_Armor.cpp
#include "D1ItemFragment_Equipable_Armor.h"
#include "Item/D1ItemInstance.h"
#include "UObject/ObjectSaveContext.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1ItemFragment_Equipable_Armor)
UD1ItemFragment_Equipable_Armor::UD1ItemFragment_Equipable_Armor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
EquipmentType = EEquipmentType::Armor;
}
#if WITH_EDITORONLY_DATA
void UD1ItemFragment_Equipable_Armor::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
if (ArmorType != EArmorType::Chest)
{
bIsFullBody = false;
}
for (int i = 0; i < RarityStatRangeSets.Num(); i++)
{
TArray<FRarityStatRange>& RarityStatRanges = RarityStatRangeSets[i].RarityStatRanges;
RarityStatRanges.SetNum((int32)EItemRarity::Count);
for (int32 j = 0; j < RarityStatRanges.Num(); j++)
{
RarityStatRanges[j].Rarity = (EItemRarity)j;
}
}
}
#endif // WITH_EDITORONLY_DATA
void UD1ItemFragment_Equipable_Armor::OnInstanceCreated(UD1ItemInstance* ItemInstance) const
{
Super::OnInstanceCreated(ItemInstance);
AddStatTagStack(ItemInstance, RarityStatRangeSets);
}
D1ItemFragment_Equipable_Attachment.h
#pragma once
#include "D1ItemFragment_Equipable.h"
#include "D1ItemFragment_Equipable_Attachment.generated.h"
class AD1EquipmentBase;
USTRUCT(BlueprintType)
struct FD1WeaponAttachInfo
{
GENERATED_BODY()
public:
//UPROPERTY(EditDefaultsOnly)
//TSubclassOf<AD1EquipmentBase> SpawnWeaponClass;
UPROPERTY(EditDefaultsOnly)
FName AttachSocket;
UPROPERTY(EditDefaultsOnly)
FTransform AttachTransform;
};
UCLASS(Abstract, Const)
class UD1ItemFragment_Equipable_Attachment : public UD1ItemFragment_Equipable
{
GENERATED_BODY()
public:
UD1ItemFragment_Equipable_Attachment(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
public:
UPROPERTY(EditDefaultsOnly)
EWeaponHandType WeaponHandType = EWeaponHandType::Count;
UPROPERTY(EditDefaultsOnly)
FD1WeaponAttachInfo WeaponAttachInfo;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UAnimMontage> EquipMontage;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UAnimMontage> FrontHitMontage;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UAnimMontage> BackHitMontage;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UAnimMontage> LeftHitMontage;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UAnimMontage> RightHitMontage;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UAnimMontage> BlockHitMontage;
UPROPERTY(EditDefaultsOnly)
TSoftObjectPtr<UAnimMontage> PocketWorldIdleMontage;
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAnimInstance> AnimInstanceClass;
};
D1ItemFragment_Equipable_Attachment.cpp
#include "D1ItemFragment_Equipable_Attachment.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1ItemFragment_Equipable_Attachment)
UD1ItemFragment_Equipable_Attachment::UD1ItemFragment_Equipable_Attachment(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
D1ItemFragment_Equipable_Utility.h
#pragma once
#include "D1ItemFragment_Equipable_Attachment.h"
#include "D1ItemFragment_Equipable_Utility.generated.h"
class UGameplayEffect;
UCLASS()
class UD1ItemFragment_Equipable_Utility : public UD1ItemFragment_Equipable_Attachment
{
GENERATED_BODY()
public:
UD1ItemFragment_Equipable_Utility(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
protected:
#if WITH_EDITORONLY_DATA
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif // WITH_EDITORONLY_DATA
public:
virtual void OnInstanceCreated(UD1ItemInstance* ItemInstance) const override;
public:
UPROPERTY(EditDefaultsOnly)
EUtilityType UtilityType = EUtilityType::Count;
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UGameplayEffect> UtilityEffectClass;
UPROPERTY(EditDefaultsOnly, meta=(ForceInlineRow))
TArray<FRarityStatSet> RarityStatSets;
};
D1ItemFragment_Equipable_Utility.cpp
#include "D1ItemFragment_Equipable_Utility.h"
#include "UObject/ObjectSaveContext.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1ItemFragment_Equipable_Utility)
UD1ItemFragment_Equipable_Utility::UD1ItemFragment_Equipable_Utility(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
EquipmentType = EEquipmentType::Utility;
}
#if WITH_EDITORONLY_DATA
void UD1ItemFragment_Equipable_Utility::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
for (int i = 0; i < RarityStatSets.Num(); i++)
{
TArray<FRarityStat>& RarityStats = RarityStatSets[i].RarityStats;
RarityStats.SetNum((int32)EItemRarity::Count);
for (int32 j = 0; j < RarityStats.Num(); j++)
{
RarityStats[j].Rarity = (EItemRarity)j;
}
}
}
#endif // WITH_EDITORONLY_DATA
void UD1ItemFragment_Equipable_Utility::OnInstanceCreated(UD1ItemInstance* ItemInstance) const
{
Super::OnInstanceCreated(ItemInstance);
AddStatTagStack(ItemInstance, RarityStatSets);
}
D1ItemFragment_Equipable_Weapon.h
#pragma once
#include "D1Define.h"
#include "D1ItemFragment_Equipable_Attachment.h"
#include "D1ItemFragment_Equipable_Weapon.generated.h"
UCLASS()
class UD1ItemFragment_Equipable_Weapon : public UD1ItemFragment_Equipable_Attachment
{
GENERATED_BODY()
public:
UD1ItemFragment_Equipable_Weapon(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
protected:
#if WITH_EDITORONLY_DATA
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif // WITH_EDITORONLY_DATA
public:
virtual void OnInstanceCreated(UD1ItemInstance* ItemInstance) const override;
public:
UPROPERTY(EditDefaultsOnly)
EWeaponType WeaponType = EWeaponType::Count;
UPROPERTY(EditDefaultsOnly)
TObjectPtr<USoundBase> AttackSwingSound;
UPROPERTY(EditDefaultsOnly)
TArray<TObjectPtr<const ULyraAbilitySet>> SkillAbilitySets;
UPROPERTY(EditDefaultsOnly)
TArray<FRarityStatRangeSet> RarityStatRangeSets;
};
D1ItemFragment_Equipable_Weapon.cpp
#include "D1ItemFragment_Equipable_Weapon.h"
#include "Item/D1ItemInstance.h"
#include "UObject/ObjectSaveContext.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(D1ItemFragment_Equipable_Weapon)
UD1ItemFragment_Equipable_Weapon::UD1ItemFragment_Equipable_Weapon(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
EquipmentType = EEquipmentType::Weapon;
}
#if WITH_EDITORONLY_DATA
void UD1ItemFragment_Equipable_Weapon::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
for (int i = 0; i < RarityStatRangeSets.Num(); i++)
{
TArray<FRarityStatRange>& RarityStatRanges = RarityStatRangeSets[i].RarityStatRanges;
RarityStatRanges.SetNum((int32)EItemRarity::Count);
for (int32 j = 0; j < RarityStatRanges.Num(); j++)
{
RarityStatRanges[j].Rarity = (EItemRarity)j;
}
}
}
#endif // WITH_EDITORONLY_DATA
void UD1ItemFragment_Equipable_Weapon::OnInstanceCreated(UD1ItemInstance* ItemInstance) const
{
Super::OnInstanceCreated(ItemInstance);
AddStatTagStack(ItemInstance, RarityStatRangeSets);
}
ItemData 블루프린트 추가
Gladiator Content
└─Data (*)
작성한 CPP Item Data 를 불려올 수 있도록 다음 경로의 블루프린트 추가하도록 한다.
cpp 에서 정상적으로 작성에 성공하였다면 블루프린트 클래스 D1 Item Data 를 생성할 수 있게 될 것이다. 해당 클래스로 선택하고 블루프린트를 추가한 다음 ItemData_GladiatorGame 이름으로 지어주도록 하였다.
DefaultConfig.ini 파일 편집하기
아이템들도 에셋 매니저를 통해 로딩이 가능하게 해야 한다.
해당 경로의 DefaultGame.ini 파일을 열어 데이터 에셋 경로를 추가하도록 한다.
Sources
└─Config
└DefaultGame.ini
추가할 문구는 다음과 같이 ItemDataPath 경로와 함께 넣어주도록 한다.
[/Script/LyraGame.LyraAssetManager]
...
ItemDataPath=/GladiatorCore/Data/ItemData_GladiatorGame.ItemData_GladiatorGame
...