시작하기 전에
이전 섹션인 1인칭 어드벤처 게임 코딩하기에서 다음 목표를 완료했는지 확인합니다.
입력 액션으로 플레이어 캐릭터 생성하기에서 C++ 1인칭 플레이어 캐릭터를 빌드했습니다.
아이템 및 데이터 관리에서 아이템 데이터를 관리할 데이터 기반 게임플레이 엘리먼트를 구성했습니다.
리스폰 픽업 아이템 생성하기에서 픽업 아이템을 생성하고 레벨에 추가했습니다.
새로운 CreateItemCopy 함수로 레퍼런스 아이템 생성하기
장착할 수 있는 새 픽업 아이템을 생성하기 전에 먼저 ItemDefinition 및 PickupBase 클래스를 수정하여 더 다양한 아이템 타입에서 레퍼런스 아이템을 캡처할 수 있도록 지원해야 합니다.
PickupBase 클래스의 InitializePickup() 함수에서 UItemDefinition 타입의 ReferenceItem을 설정합니다. 이 방식은 지나치게 제한적입니다. 이런 식으로 레퍼런스 아이템을 설정하면 UItemDefinition에서 파생된 새로운 특수 아이템 클래스에 추가할 추가 프로퍼티가 포함되지 않을 것이기 때문입니다.
이 문제를 해결하려면 ItemDefinition에서 해당 아이템의 사본을 생성하고 반환하는 새로운 가상 함수를 생성합니다. 가상 함수이기 때문에 UItemDefinition에서 상속하는 모든 클래스에 오버라이드할 수 있습니다. PickupBase가 이 함수를 호출하면 컴파일러는 호출된 클래스에 따라 호출할 올바른 함수를 결정합니다.
이 함수를 부모 ItemDefinition 클래스에 추가하면 프로젝트를 계속 확장하여 UItemDefinition에서 상속하는 아이템 타입을 더 포함하기로 한 경우에도 이 함수를 계속 사용할 수 있습니다.
레퍼런스 아이템 생성에 사용할 새 CreateItemCopy() 함수를 정의하려면 다음 단계를 따릅니다.
ItemDefinition.h를 엽니다.public섹션에서UItemDefinition포인터를 반환하는CreateItemCopy()라는 이름의 새 가상 상수 함수를 선언합니다.C++// Creates and returns a copy of the item. virtual UItemDefinition* CreateItemCopy() const;ItemDefinition.cpp에서CreateItemCopy()함수를 구현합니다. 내부에서StaticClass()를 사용하여ItemCopy라는 이름의 새UItemDefinition오브젝트 포인터를 생성합니다.C++UItemDefinition* UItemDefinition::CreateItemCopy() const { UItemDefinition* ItemCopy = NewObject<UItemDefinition>(StaticClass()); }Visual Studio는
UItemDefinition::StaticClass()를StaticClass()로 명확하게 구분합니다.ItemCopy의 각 필드를 이 클래스의 필드에 할당한 다음,ItemCopy를 반환합니다.C++/** * Creates and returns a copy of this Item Definition. * @return a copy of the item. */ UItemDefinition* UItemDefinition::CreateItemCopy() const { UItemDefinition* ItemCopy = NewObject<UItemDefinition>(StaticClass()); ItemCopy->ID = this->ID; ItemCopy->ItemType = this->ItemType;
그런 다음, ReferenceItem을 수동으로 설정하는 코드를 제거하여 InitializePickup() 함수를 리팩터링하고 해당 코드를 CreateItemCopy() 호출로 대체합니다.
새 CreateItemCopy() 함수로 InitializePickup()을 업데이트하려면 다음 단계를 따릅니다.
PickupBase.cpp를 열고InitializePickup()으로 이동합니다.ReferenceItem을 정의하고 설정하는 다음 다섯 줄을 삭제합니다.C++ReferenceItem = NewObject<UItemDefinition>(this, UItemDefinition::StaticClass()); ReferenceItem->ID = ItemDataRow->ID; ReferenceItem->ItemType = ItemDataRow->ItemType; ReferenceItem->ItemText = ItemDataRow->ItemText; ReferenceItem->WorldMesh = ItemDataRow->ItemBase->WorldMesh;ReferenceItem에서 다음과 같이TempItemDefinition->CreateItemCopy()을 호출합니다.C++// Create a copy of the item with the class type ReferenceItem = TempItemDefinition->CreateItemCopy();
PickupBase.cpp를 저장합니다. 이제 InitializePickup() 함수는 다음과 같아야 합니다.
if (PickupDataTable && !PickupItemID.IsNone())
{
// Retrieve the item data associated with this pickup from the data table
const FItemData* ItemDataRow = PickupDataTable->FindRow<FItemData>(PickupItemID, PickupItemID.ToString());
UItemDefinition* TempItemDefinition = ItemDataRow->ItemBase.Get();
// Create a copy of the item with the class type
ReferenceItem = TempItemDefinition->CreateItemCopy();
}장착할 수 있는 툴 데이터 정의하기
이전 섹션에서는 레벨에서 테이블 데이터의 구체적인 표현, 즉, 상호작용할 수 있는 픽업 오브젝트를 생성하는 방법을 알아봤습니다. 이 섹션에서는 캐릭터가 장착할 툴을 빌드하는 방법을 살펴봅니다.
장착할 수 있는 새 툴을 구성하려면 다음 클래스를 생성해야 합니다.
EquippableToolDefinition: 툴의 데이터를 저장하는ItemDefinition에서 파생된 데이터 에셋 클래스입니다.EquippableToolBase: 게임 내 툴을 나타내는 액터 클래스입니다. 이 클래스는 캐릭터가 툴을 잡고 조작할 수 있도록 애니메이션, 입력 매핑, 메시를 제공합니다.
캐릭터가 툴을 집어 장착할 수 있도록 하려면 다음 내용을 추가해야 합니다.
아이템을 저장할 공간.
인벤토리에 있는 각 아이템 타입을 파악할 방법.
툴을 장착할 방법.
EquippableToolBase 액터는 캐릭터가 들고 사용 중인 툴을 나타내고, PickupBase 액터는 레벨 안의 픽업 아이템을 나타낸다는 점을 기억하세요. 캐릭터가 아이템을 장착하려면 먼저 픽업 아이템과 충돌해야 하므로 콜리전에 성공한 후 캐릭터에 아이템을 부여하도록 PickupBase도 수정합니다.
그런 다음, 새 툴 클래스와 이미 빌드한 픽업 및 데이터 테이블을 결합하여 커스텀 다트 발사기를 생성하고 캐릭터에 붙입니다!
먼저 새 ItemDefinition 클래스에서 툴의 데이터를 정의합니다.
새 EquippableToolDefinition 클래스를 생성하려면 다음 단계를 따릅니다.
언리얼 에디터에서 툴(Tools) > 새 C++ 클래스(New C++ Class)로 이동합니다. 모든 클래스(All Classes)로 이동한 다음, First ItemDefinition을 검색하여 부모 클래스로 선택하고 다음(Next)을 클릭합니다.
그 클래스의 이름을
EquippableToolDefinition으로 지정한 다음, 클래스 생성(Create Class)을 클릭합니다.Visual Studio에서
EquippableToolDefinition.h의 상단에"ItemDefinition.h"에 대한 include를 추가한 다음, 아래의 포워드 선언을 추가합니다.class UInputMappingContext: 장착할 수 있는 각 툴은 그 툴을 사용하는 캐릭터에 적용할 입력 매핑 컨텍스트에 대한 레퍼런스를 보유해야 합니다.class AEquippableToolBase: 게임 내 툴을 나타내는 액터입니다. 이 클래스는 다음 단계에서 생성할 것입니다.C++#pragma once #include "CoreMinimal.h" #include "ItemDefinition.h" #include "EquippableToolDefinition.generated.h" class AEquippableToolBase; class UInputMappingContext; UCLASS(BlueprintType, Blueprintable)
public섹션에서ToolAsset이라는AEquippableToolBase타입의TSubclassOf프로퍼티를 추가합니다. 여기에EditDefaultsOnly를 사용하여UPROPERTY()매크로를 지정합니다.C++// The tool actor associated with this item UPROPERTY(EditDefaultsOnly) TSubclassOf<AEquippableToolBase> ToolAsset;TSubclassOf<AEquippableToolBase>는UClass를 감싸는 템플릿 래퍼로서, 타입 안전성을 보장하면서AEquippableToolBase의 블루프린트 서브클래스를 참조할 수 있게 해줍니다. 다양한 타입의 액터를 동적으로 스폰하려는 게임플레이 시나리오에 유용합니다.ToolAsset을 사용하면 캐릭터가 장착할 때 툴 액터를 동적으로 스폰할 수 있습니다.UItemDefinition에서 선언한CreateItemCopy()함수에 대한 오버라이드를 선언합니다. 이 오버라이드는UEquippableToolDefinition클래스 사본을 생성하고 반환합니다.완성된
EquippableToolDefinition.h파일은 다음과 같은 모습이어야 합니다.C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "ItemDefinition.h" #include "EquippableToolDefinition.generated.h" class AEquippableToolBase; class UInputMappingContext;EquippableToolDefinition.cpp에서CreateItemCopy()함수를 구현합니다. 이 함수는ItemDefinition.cpp의CreateItemCopy()함수와 비슷하게 보일 것입니다. 단, 이제ToolAsset도 복사할 수 있다는 점이 다릅니다.C++// Copyright Epic Games, Inc. All Rights Reserved. #include "EquippableToolDefinition.h" UEquippableToolDefinition* UEquippableToolDefinition::CreateItemCopy() const { UEquippableToolDefinition* ItemCopy = NewObject<UEquippableToolDefinition>(StaticClass()); ItemCopy->ID = this->ID; ItemCopy->ItemType = this->ItemType;
EquippableToolDefinition 클래스 파일을 모두 저장합니다.
장착할 수 있는 툴 액터 구성하기
다음으로, 장착 가능한 툴 액터를 빌드하기 시작합니다. 이 액터는 툴의 애니메이션, 컨트롤, 메시를 캐릭터에 추가하는 게임 내 표현입니다.
장착할 수 있는 새 베이스 툴 액터를 생성하고 구성하려면 다음 단계를 따릅니다.
언리얼 에디터에서 툴(Tools) > 새 C++ 클래스(New C++ Class)로 이동합니다. 액터를 부모 클래스로 선택하고 클래스 이름을 EquippableToolBase로 지정합니다.
클래스 생성(Create Class)을 클릭합니다. 언리얼 엔진이 자동으로 VS에서 새 클래스의 파일을 엽니다.
EquippableToolBase.h상단에서 classAAdventureCharacter및 classUInputAction을 선언합니다. 장착할 수 있는 툴은 자신이 어떤 캐릭터에 장착되어 있는지 알아야 툴별 입력 액션을 해당 캐릭터에 바인딩할 수 있습니다.클래스 선언의
UCLASS매크로에서 이 클래스를 블루프린트에 노출하기 위한BlueprintType지정자와Blueprintable지정자를 추가합니다.C++UCLASS(BlueprintType, Blueprintable) class ADVENTUREGAME_API AEquippableToolBase : public AActor
툴 애니메이션 선언하기
EquippableToolBase.h의 public 섹션에서 FirstPersonToolAnim 및 ThirdPersonToolAnim이라는 이름의 UAnimBlueprint 프로퍼티에 대한 TObjectPtr 두 개를 추가합니다. 이는 캐릭터가 이 툴을 장착했을 때 사용하는 1인칭 및 3인칭 애니메이션입니다.
이러한 프로퍼티에 EditAnywhere 및 BlueprintReadOnly를 사용하여 UPROPERTY() 매크로를 지정합니다.
// First Person animations
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UAnimBlueprint> FirstPersonToolAnim;
// Third Person animations
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UAnimBlueprint> ThirdPersonToolAnim;툴의 메시 생성하기
EquippableToolBase.h의 public 섹션에서 ToolMeshComponent라는 이름의 USkeletalMeshComponent에 대한 TObjectPtr을 추가합니다. 이는 캐릭터가 툴을 장착했을 때 보이는 툴의 스켈레탈 메시입니다. 여기에 EditAnywhere 및 BlueprintReadOnly를 사용하여 UPROPERTY() 매크로를 지정합니다.
// Tool Skeletal Mesh
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<USkeletalMeshComponent> ToolMeshComponent;EquippableToolBase.cpp에서 AEquippableToolBase() 생성자 함수를 수정하여 디폴트 USkeletalMeshComponent를 생성하고 이를 ToolMeshComponent에 할당합니다. 그런 다음, 툴이 로드될 때 ToolMeshComponent가 null이 아닌지 검사하여 툴에 모델이 있는지 확인합니다.
AEquippableToolBase::AEquippableToolBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Create this tool's mesh component
ToolMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("ToolMesh"));
check(ToolMeshComponent != nullptr);
}
툴의 오너 선언하기
EquippableToolBase.h의 public 섹션에서 OwningCharacter라는 이름의 캐릭터(Character) 클래스의 인스턴스에 대한 TObjectPtr을 생성합니다. 여기에 EditAnywhere 및 BlueprintReadOnly를 사용하여 UPROPERTY() 매크로를 지정합니다.
이는 현재 이 툴을 장착한 캐릭터입니다.
// The character holding this tool
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<AAdventureCharacter> OwningCharacter;입력 및 툴-사용 함수 선언하기
툴에는 캐릭터에 할당해야 할 입력 매핑 컨텍스트와 입력 액션이 포함되어 있습니다.
입력 매핑 컨텍스트를 추가하려면 public 섹션에서 ToolMappingContext라는 이름의 UInputMappingContext에 대한 TObjectPtr을 선언합니다. 여기에 EditAnywhere 및 BlueprintReadOnly를 사용하여 UPROPERTY() 매크로를 지정합니다.
// The input mapping context associated with this tool
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UInputMappingContext> ToolMappingContext;무브먼트 컨트롤을 구현할 때와 마찬가지로, 툴-사용 액션을 구현하는 함수와 입력 액션을 함수에 바인딩하는 새 함수를 추가합니다.
EquippableToolBase.h의 public 섹션에서 Use() 및 BindInputAction()이라는 이름의 두 가상 void 함수를 선언합니다.
이전에 캐릭터 무브먼트 컨트롤을 구현할 때는 타깃 함수의 정확한 이름을 전달해야 하는 InputComponent의 BindAction() 함수를 사용했습니다. 아직 함수의 전체 이름을 모르기 때문에 각 EquippableToolBase 서브클래스에 구현하여 [ToolChildClass]::Use를 전달하고 BindAction을 호출할 커스텀 BindInputAction() 함수가 필요합니다.
BindInputAction() 함수는 const UInputAction 포인터를 받아 주어진 입력 액션을 캐릭터의 Use() 함수에 바인딩합니다.
// Use the tool
UFUNCTION()
virtual void Use();
// Binds the Use function to the owning character
UFUNCTION()
virtual void BindInputAction(const UInputAction* ActionToBind);EquippableToolBase.cpp에서 Use() 및 BindInputAction() 함수를 구현합니다. 이 두 함수는 부모 클래스에서는 어떤 작업도 수행하지 않으므로 지금은 빈 채로 둬도 됩니다. EquippableToolBase 서브클래스를 생성할 때 여기에 로직을 추가할 것입니다. 예를 들어, Use() 함수에는 발사체 발사나 문 열기와 같은 툴별 액션이 포함되어야 합니다.
void AEquippableToolBase::Use()
{
}
void AEquippableToolBase::BindInputAction(const UInputAction* ActionToBind)
{
}
코드를 저장하고 VS에서 컴파일합니다.
이제 EquippableToolBase.h 파일은 다음과 같아야 합니다.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EquippableToolBase.generated.h"
class AAdventureCharacter;
class UInputAction;
이제 EquippableToolBase.cpp는 다음과 같아야 합니다.
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolBase.h"
#include "AdventureCharacter.h"
AEquippableToolBase::AEquippableToolBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
캐릭터에게 아이템 부여하기
캐릭터가 사용할 수 있는 도구를 정의했지만, 아직 장착할 수는 없습니다. 다음으로, 캐릭터가 아이템을 획득할 때 저장하고 장착할 수 있도록 인벤토리 시스템을 추가할 것입니다.
인벤토리 컴포넌트 빌드하기
캐릭터의 인벤토리는 캐릭터에 함수 기능을 추가하지만, 게임 월드에는 존재하지 않아야 합니다. 따라서 액터 컴포넌트(Actor Components) 클래스를 사용하여 캐릭터가 보유한 아이템을 파악하고, 툴을 교체할 수 있으며, 캐릭터가 같은 툴을 두 개 이상 획득하지 못하게 하는 인벤토리를 정의할 것입니다.
언리얼 에디터에서 툴(Tools) > 새 C++ 클래스(New C++ Class)로 이동합니다. 액터 컴포넌트를 부모 클래스로 선택하고 클래스 이름을 InventoryComponent로 지정합니다.
클래스 생성(Create Class)을 클릭합니다.
VS에서 InventoryComponent.h 파일 상단에 UEquippableToolDefinition을 포워드 선언합니다. 이는 인벤토리에 저장할 클래스입니다.
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ADVENTUREGAME_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public 섹션에서 ToolInventory라는 UEquippableToolDefinition 포인터의 TArray를 새로 선언합니다. 여기에 VisibleAnywhere, BlueprintReadOnly 및 Category = Tools을 사용하여 UPROPERTY() 매크로를 지정합니다.
public:
// Sets default values for this component's properties
UInventoryComponent();
// The array of tools stored in this inventory.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Tools)
TArray<UEquippableToolDefinition*> ToolInventory;이 인벤토리에는 툴만 저장되지만, 원하는 모든 타입의 아이템을 포함하도록 확장할 수 있습니다. 일반적으로 인벤토리를 구현할 경우, UI, 아이콘, 음향 효과, 비용 및 기타 아이템 프로퍼티를 포함하는 더 복잡한 인벤토리를 빌드하기 위해 UItemDefinition 또는 TSubclassOf<UItemDefinition> 값만 저장할 것입니다.
완성된 InventoryComponent.h 파일은 다음과 같아야 합니다.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
캐릭터에 툴 및 인벤토리 선언 추가하기
아이템을 보관할 공간을 확보했습니다. 이제 그 공간에 아이템을 부여하는 로직으로 캐릭터를 업그레이드할 준비가 되었습니다.
먼저 캐릭터의 .h 파일 상단에 AEquippableToolBase, UEquippableToolDefinition 및 UInventoryComponent 클래스를 포워드 선언합니다.
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "AdventureCharacter.generated.h"
class AEquippableToolBase;
class UAnimBlueprint;
protected 섹션에서 UseAction이라는 이름의 UInputAction에 대한 TObjectPtr을 선언합니다. 이는 툴의 Use() 함수에 바인딩할 '툴 사용' 입력 액션입니다.
// Use Input Actions
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
TObjectPtr<UInputAction> UseAction;캐릭터의 인벤토리 컴포넌트 생성하기
캐릭터의 .h 파일 내 public 섹션에서 InventoryComponent라는 이름의 UInventoryComponent에 대한 TObjectPtr을 선언합니다. 여기에 VisibleAnywhere 및 Category = Inventory를 사용하여 UPROPERTY() 매크로를 지정합니다.
// Inventory Component
UPROPERTY(VisibleAnywhere, Category = Inventory)
TObjectPtr<UInventoryComponent> InventoryComponent;캐릭터의 생성자 함수에서 메시 컴포넌트 서브오브젝트를 생성한 후, InventoryComponent라는 이름의 디폴트 UInventoryComponent 서브오브젝트를 생성합니다. 이렇게 하면 캐릭터가 스폰될 때 인벤토리가 제대로 구성됩니다.
// Create an inventory component for the owning player
InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("InventoryComponent"));기존 인벤토리 확인하기
툴을 붙이기 전에 플레이어가 이미 툴을 가지고 있는지 확인하여 여러 번 장착하지 않도록 합니다.
캐릭터의 .h 파일 내 public 섹션에서 UEquippableToolDefinition 포인터를 받아 해당 툴이 플레이어의 인벤토리에 존재하면 true를 반환하는 IsToolAlreadyOwned()라는 함수를 선언합니다.
// Returns whether or not the player already owns this tool
UFUNCTION()
bool IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition);캐릭터의 .cpp 파일인 AdventureCharacter.cpp에서 IsToolAlreadyOwned() 함수를 구현합니다. 내부의 for 루프에서 InventoryComponent->ToolInventory 배열에 액세스하여 캐릭터의 인벤토리에 있는 각 툴을 얻습니다.
bool AAdventureCharacter::IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition)
{
// Check that the character does not yet have this particular tool
for (UEquippableToolDefinition* InventoryItem : InventoryComponent->ToolInventory)
{
}
}
그런 다음, if 구문에서 이 함수에 전달된 툴의 ToolDefinition->ID가 InventoryItem->ID와 일치하는지 확인합니다. 일치한다면 캐릭터가 이미 이 툴을 소유하고 있으므로 true를 반환합니다. 일치하지 않으면 for 루프 뒤에 false를 반환합니다. ToolDefinition이 기존 인벤토리 아이템과 일치하지 않으므로 새 아이템이라는 뜻이기 때문입니다.
bool AAdventureCharacter::IsToolAlreadyOwned(UEquippableToolDefinition* ToolDefinition)
{
// Check that the character does not yet have this particular tool
for (UEquippableToolDefinition* InventoryItem : InventoryComponent->ToolInventory)
{
if (ToolDefinition->ID == InventoryItem->ID)
{
return true;
}
}
툴 붙이기
캐릭터의 .h 파일 내 public 섹션에서 UEquippableToolDefinition 포인터를 받는 AttachTool()이라는 이름의 함수를 선언합니다. 이 함수는 ToolDefinition 내에 있는 툴을 플레이어에 장착하려고 시도합니다.
// Attaches and equips a tool to the player
UFUNCTION()
void AttachTool(UEquippableToolDefinition* ToolDefinition);protected 섹션에서 EquippedTool이라는 이름의 AEquippableToolBase에 대한 TObjectPtr을 선언합니다. 여기에 VisibleAnywhere, BlueprintReadOnly 및 Category = Tools UPROPERTY() 지정자를 지정합니다.
// The currently-equipped tool
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Tools)
TObjectPtr<AEquippableToolBase> EquippedTool;캐릭터의 .cpp 파일에서 AttachTool()을 구현합니다. 먼저 if 구문에서 IsToolAlreadyOwned()를 호출하여 캐릭터에게 이미 툴이 있는지 확인합니다.
void AAdventureCharacter::AttachTool(UEquippableToolDefinition* ToolDefinition)
{
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
}
}
아이템 스폰하기
ToolDefinition 안에 저장된 AEquippableToolBase 툴은 액터이므로 AttachTool()이 호출될 때 로드되지 않을 수 있습니다. 이 문제를 처리하기 위해 SpawnActor() 함수를 사용하여 툴의 새 인스턴스를 스폰할 것입니다.
SpawnActor()는 맵과 맵 안의 액터를 나타내는 핵심 오브젝트인 UWorld 오브젝트의 일부입니다. 액터에서 GetWorld() 함수를 호출하여 액세스합니다.
if 구문에서 ToolToEquip이라는 이름의 새 AEquippableToolBase 포인터를 선언합니다. 이 값을 GetWorld()->SpawnActor()를 호출하여 스폰할 액터로 ToolDefinition->ToolAsset을, 스폰할 위치로 this->GetActorTransform()을 전달한 결과와 동일하게 설정합니다.
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
// Spawn a new instance of the tool to equip
AEquippableToolBase* ToolToEquip = GetWorld()->SpawnActor<AEquippableToolBase>(ToolDefinition->ToolAsset, this->GetActorTransform());
}ToolDefinition->ToolAsset을 SpawnActor에 전달하면, UE는 ToolAsset의 클래스 타입을 보고 해당 타입의 액터를 스폰해야 한다는 것을 파악합니다. (ToolAsset은 해당 ToolDefinition과 연관된 EquippableToolBase 액터입니다)
캐릭터에 아이템 붙이기
스폰된 툴을 캐릭터에 붙이려면 AttachementRules라는 이름의 새 FAttachementTransformRules를 선언합니다.
FAttachementTransformRules는 부착 시 위치와 회전, 스케일 조절 처리 방법을 정의하는 구조체입니다. 이 구조체는 피직스가 연관되었는지 여부를 UE에 알리기 위해 끝에서 EAttachmentRules 및 bool InWeldSimulatedBodies를 받습니다. true라면 UE는 두 바디를 서로 결합하여 움직일 때 하나처럼 상호작용할 수 있도록 합니다. 자주 사용되는 부착 규칙에는 KeepRelative(부모에 대한 상대적 트랜스폼 유지), KeepWorld(월드 트랜스폼 유지), SnapToTarget(부모의 트랜스폼에 스냅) 등이 있습니다.
AttachmentRoles 정의에서 EAttachmentRule::SnapToTarget 및 true를 추가합니다.
// Attach the tool to the First Person Character
FAttachmentTransformRules AttachmentRules(EAttachmentRule::SnapToTarget, true);그런 다음 툴을 캐릭터에 붙이기 위해 ToolToEquip->AttachToActor()를 호출하고, 이어서 ToolToEquip->AttachToComponent()를 호출하여 툴을 1인칭 메시 컴포넌트의 오른쪽 소켓에 연결합니다.
AttachToActor는 액터를 타깃 부모 액터에 붙이고, AttachToComponent는 액터의 루트 컴포넌트를 타깃 컴포넌트에 붙입니다. 구문은 다음과 같습니다.
MyActor->AttachToActor(ParentActor, AttachmentRules, OptionalSocketName)
// Attach the tool to this character, and then the right hand of their first-person mesh
ToolToEquip->AttachToActor(this, AttachmentRules);
ToolToEquip->AttachToComponent(FirstPersonMeshComponent, AttachmentRules, FName(TEXT("HandGrip_R")));캐릭터에 아이템 애니메이션 추가하기
툴의 1인칭 및 3인칭 애니메이션을 SetAnimInstanceClass()에 전달하여 1인칭 및 3인칭 메시의 애니메이션을 설정합니다.
// Set the animations on the character's meshes.
FirstPersonMeshComponent->SetAnimInstanceClass(ToolToEquip->FirstPersonToolAnim->GeneratedClass);
GetMesh()->SetAnimInstanceClass(ToolToEquip->ThirdPersonToolAnim->GeneratedClass);SetAnimInstanceClass는 런타임에 스켈레탈 메시의 애니메이션 블루프린트를 동적으로 변경하며, 아이템과 무기에 다양한 애니메이션 세트를 장착할 때 주로 사용됩니다. GeneratedClass는 블루프린트에서 생성된 실제 애님 인스턴스(AnimInstance) 클래스를 얻습니다.
인벤토리에 아이템 추가하기
ToolInventory.Add()를 사용하여 캐릭터의 인벤토리에 툴을 추가합니다.
// Add the tool to this character's inventory
InventoryComponent->ToolInventory.Add(ToolDefinition);
이제 툴이 붙었으므로 ToolToEquip->OwningCharacter를 이 캐릭터로 설정합니다.
ToolToEquip->OwningCharacter = this;새 툴을 캐릭터에 붙였으니 EquippedTool을 ToolToEquip으로 설정합니다.
EquippedTool = ToolToEquip;캐릭터에 아이템 컨트롤 추가하기
다음으로 도구의 입력 액션(Input Action)과 입력 매핑 컨텍스트(Input Mapping Context)를 캐릭터에 추가합니다.
이 부분은 캐릭터 무브먼트 환경설정의 입력 매핑을 캐릭터에 바인딩하기 섹션에서 AAdventureCharacter::BeginPlay()를 구성한 것과 비슷한 방식으로 구현할 것입니다. 즉 플레이어 컨트롤러(PlayerController)를 구한 다음 향상된 입력 로컬 서브시스템을 구하고, 진행하면서if 구문을 사용하여 null 포인터를 확인할 것입니다.
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(ToolToEquip->ToolMappingContext, 1);
}
}
이번에는 툴의 입력 매핑 컨텍스트를 플레이어 서브시스템에 추가할 때 그 우선순위를 1로 설정합니다. 플레이어의 메인 매핑 컨텍스트(FirstPersonContext)의 우선순위가 더 낮으므로(0), 두 매핑 컨텍스트의 키 바인딩이 같으면 ToolToEquip->ToolMappingContext의 입력 바인딩이 FirstPersonContext보다 우선권을 갖게 됩니다.
매핑 컨텍스트를 추가한 후, 캐릭터의 입력 액션을 툴의 Use() 함수에 바인딩하기 위해 UseAction을 전달하여 ToolToEquip->BindInputAction()을 호출합니다.
// Get the player controller for this character
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(ToolToEquip->ToolMappingContext, 1);
}
ToolToEquip->BindInputAction(UseAction);
}
완성된 AttachTool() 함수는 다음과 같은 모습이어야 합니다.
void AAdventureCharacter::AttachTool(UEquippableToolDefinition* ToolDefinition)
{
// Only equip this tool if it isn't already owned
if (not IsToolAlreadyOwned(ToolDefinition))
{
// Spawn a new instance of the tool to equip
AEquippableToolBase* ToolToEquip = GetWorld()->SpawnActor<AEquippableToolBase>(ToolDefinition->ToolAsset, this->GetActorTransform());
// Attach the tool to the First Person Character
GiveItem()으로 다양한 아이템 타입 지원하기
툴을 붙이는 방법은 있지만, 픽업과 픽업의 아이템 정의에는 단순한 툴 이상의 것이 포함될 수 있으므로 AttachTool()을 호출하기 전에 캐릭터가 어떤 종류의 아이템과 상호작용하는지 파악할 방법이 있어야 합니다.
전달된 ItemDefinition의 타입에 따라 다양한 액션을 수행하는 GiveItem() 함수를 생성합니다. ItemData.h에서 EItemType enum을 사용하여 다양한 타입의 아이템을 선언했는데, 이제 그 데이터를 사용하여 다양한 아이템 정의를 구분할 수 있습니다.
AdventureCharacter.h의 public 섹션에서 UItemDefinition() 포인터를 받는 GiveItem()이라는 이름의 함수를 선언합니다. 다른 클래스는 플레이어에게 아이템을 주려고 할 때 이 함수를 호출합니다.
// Public function that other classes can call to attempt to give an item to the player
UFUNCTION()
void GiveItem(UItemDefinition* ItemDefinition);AdventureCharacter.cpp에서 GiveItem()을 구현합니다. 먼저 이 함수에 전달된 아이템의 ItemType에 따라 케이스를 구분하는 switch 구문을 선언합니다.
void AAdventureCharacter::GiveItem(UItemDefinition* ItemDefinition)
{
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
}
}
switch 구문 내에서 EItemType::Tool, EItemType::Consumable에 대한 케이스와 디폴트 케이스를 선언합니다. 이 튜토리얼에서는 툴 타입 아이템만 구현하므로 소모품(Consumable) 및 디폴트(default) 케이스에서는 아이템 타입을 기록하고 스위치 케이스를 빠져나옵니다.
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
case EItemType::Tool:
{
}
case EItemType::Consumable:
{
// Not yet implemented
break;
툴(Tool) 케이스 안에서 ItemDefinition을 ToolDefinition이라는 이름의 UEquippableToolDefinition 포인터에 캐스트합니다.
그런 다음, ToolDefinition이 null인지 확인하여 캐스트가 성공했는지 확인합니다. null이 아닌 경우 AttachTool()을 호출하여 툴을 캐릭터에 붙입니다. 그렇지 않으면 오류를 print하고 break합니다.
case EItemType::Tool:
{
// If the item is a tool, attempt to cast and attach it to the character
UEquippableToolDefinition* ToolDefinition = Cast<UEquippableToolDefinition>(ItemDefinition);
if (ToolDefinition != nullptr)
{
AttachTool(ToolDefinition);
}
완성된 GiveItem() 함수는 다음과 같은 모습이어야 합니다.
void AAdventureCharacter::GiveItem(UItemDefinition* ItemDefinition)
{
// Case based on the type of the item
switch (ItemDefinition->ItemType)
{
case EItemType::Tool:
{
// If the item is a tool, attempt to cast and attach it to the character
마지막으로, 아이템 부여 로직을 작동시킬 게임 내 트리거가 필요합니다. 캐릭터가 픽업과 충돌하면 픽업은 캐릭터의 GiveItem() 함수를 호출하여 픽업의 ReferenceItem을 캐릭터에 부여해야 합니다.
이렇게 하려면 PickupBase.cpp를 엽니다.
OnSphereBeginOverlap()에서 캐릭터가 유효한지 확인한 후 Character->GiveItem(ReferenceItem)을 호출하여 캐릭터에 아이템을 부여합니다.
// Checking if it is a First Person Character overlapping
AAdventureCharacter* Character = Cast<AAdventureCharacter>(OtherActor);
if (Character != nullptr)
{
// Give the item to the character
Character->GiveItem(ReferenceItem);
// Unregister from the Overlap Event so it is no longer triggered
SphereComponent->OnComponentBeginOverlap.RemoveAll(this);
이제 툴 데이터와 액터를 구성했으니, 이를 사용하여 게임 내에서 캐릭터가 장착할 실제 툴을 빌드할 수 있습니다!
다트 발사기 구현하기
첫 번째 장착할 수 있는 툴로는 발사체를 발사할 수 있는 다트 발사기를 만들 것입니다. 이 섹션에서는 먼저 캐릭터가 들고 사용할 툴을 생성합니다. 이 튜토리얼의 다음 섹션에서는 발사체 로직을 구현합니다.
새 DartLauncher 클래스 구성하기
언리얼 에디터에서 툴(Tools) > 새 C++ 클래스(New C++ Class)를 선택합니다.
모든 클래스(All Classes)로 이동한 다음, EquippableToolBase를 검색하여 부모 클래스로 선택하고 클래스 이름을 DartLauncher로 지정합니다.툴의 코드를 저장할 Tools이라는 이름의 새 폴더를 생성합니다. 클래스 생성(Create Class)을 클릭합니다.
Visual Studio의 DartLauncher.h 상단에 다음 작업을 수행합니다.
"[프로젝트명]/EquippableToolBase.h"에 대한 include 구문을 추가합니다.BlueprintType및Blueprintable지정자를UCLASS()매크로에 추가합니다.public섹션에서AEquippableToolBase의Use()및BindInputAction()함수에 대한 오버라이드를 선언합니다.
완성된 DartLauncher.h 클래스는 다음과 같은 모습이어야 합니다.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AdventureGame/EquippableToolBase.h"
#include "DartLauncher.generated.h"
UCLASS(BlueprintType, Blueprintable)
class ADVENTUREGAME_API ADartLauncher : public AEquippableToolBase
DartLauncher.cpp 파일 상단에서 "[프로젝트명]/AdventureCharacter.h"에 대한 include 구문을 추가합니다. 이 부분은 BindInputAction() 함수에서 필요합니다.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"툴 컨트롤 구현하기
이제 특정 툴을 작업하고 있고, 어떤 함수를 바인딩할지 알고 있으므로 BindInputAction()을 구현할 수 있습니다.
먼저 Use() 함수를 구현합니다. Use() 안에 플레이어가 함수를 트리거할 때 알려주는 디버그 메시지를 추가합니다.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
다음으로 BindInputAction() 함수를 구현합니다. 함수 내부의 if 구문에서 GetController()를 사용하여 OwningCharacter를 위한 플레이어 컨트롤러를 얻은 다음, 이를 APlayerController로 캐스트합니다. 이는 AAdventureCharacter::BeginPlay() 함수에서 매핑 컨텍스트를 추가한 방식과 유사합니다.
void ADartLauncher::BindInputAction(const UInputAction* InputToBind)
{
// Set up action bindings
if (APlayerController* PlayerController = Cast<APlayerController>(OwningCharacter->GetController()))
{
}
}
캐릭터 무브먼트 환경설정의 무브먼트 액션 바인딩하기 섹션에서 무브먼트 액션을 바인딩하는 방법과 마찬가지로, 다른 if 구문에서 EnhancedInputComponent라는 이름의 새 UEnhancedInputComponent 포인터를 선언합니다. 이 값을 이 함수에 전달된 PlayerInputComponent에서 Cast()를 호출하여 UEnhancedInputComponent로 캐스트한 결과와 같게 설정합니다.
마지막으로, BindAction을 사용하여 BindAction()을 통해 이 함수에 전달된 InputToBind 액션에 ADartLauncher::Use 액션을 바인딩합니다. 이렇게 하면 InputAction을 Use()에 바인딩하여 지정된 액션이 발생하면 Use()가 호출되도록 합니다.
// Set up action bindings
if (APlayerController* PlayerController = Cast<APlayerController>(OwningCharacter->GetController()))
{
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerController->InputComponent))
{
// Fire
EnhancedInputComponent->BindAction(InputToBind, ETriggerEvent::Triggered, this, &ADartLauncher::Use);
}
}
캐릭터의 무브먼트를 구성할 때, 실패하면 게임이 크래시되는 CastChecked<>를 사용했습니다. 여기서 픽업 컨트롤이 제대로 초기화되지 않아도 게임이 중단되면 안 되므로 Cast<>를 사용하면 됩니다. 캐스트가 실패가 심각한 버그를 나타내는 경우에만 CastChecked<>를 사용합니다.
코드를 저장하고 컴파일합니다.
이제 BindInputAction() 함수와 완성된 DartLauncher.cpp 클래스는 다음과 같아야 합니다.
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
캐릭터에 맞춰 애니메이션 블루프린트 조정하기
1인칭 템플릿에는 무기 타입 아이템에 대한 샘플 애니메이션 블루프린트가 포함되어 있지만, 다트 발사기와 함께 작동하려면 이 블루프린트를 조금 변경해야 합니다.
캐릭터에 맞춰 기존 애니메이션 블루프린트를 조정하려면 다음 절차를 따릅니다.
콘텐츠 브라우저에서 Content > Variant_Shooter > Anim 폴더로 이동하여
ABP_FP_Pistol애니메이션 블루프린트를 우클릭하고 복제(Duplicate)를 선택합니다.이 사본의 이름을 ABP_FP_DartLauncher로 지정하고 Content > FirstPerson > Anims 폴더로 드래그한 다음 여기로 이동(Move Here)을 선택합니다.
ABP_TP_Pistol이 사용하는 것은BP_FPShooter캐릭터 전용 로직이 아니므로 캐릭터에 맞게 수정해야 합니다.이벤트 그래프 위쪽에서 Event Blueprint Begin Play 노드로 시작하는 노드 그룹을 확대합니다.
이 블루프린트는
BP_FPShooter에서 1인칭 메시(First Person Mesh) 및 1인칭 카메라(First Person Camera) 변수를 가져오므로, 캐릭터 블루프린트(이 튜토리얼에서는BP_AdventureCharacter)를 대신 사용하도록 변경합니다.다음 각 노드를 클릭하고 삭제(Delete)를 누릅니다.
Cast To BP_FPShooter
First Person Mesh
First Person Camera
이벤트 그래프에서 우클릭한 다음, Cast To BP_AdventureCharacter 노드를 검색해 선택합니다.
Event Blueprint Begin Play의 실행 핀을 새 노드에 연결한 다음, Set First Person Mesh 노드에 연결합니다.
Try Get Pawn Owner노드의 Return Value 핀을 Cast To 노드의 Object 핀에 연결합니다.
Cast To BP_AdventureCharacter에서 새 노드를 생성하려면 As BP My Adventure Character 핀을 클릭하고 그래프의 빈 곳으로 드래그합니다.
Get Mesh를 검색하여 Variables > Character 아래에서 선택합니다. 새 노드의 Mesh 핀을 Set First Person Mesh 노드에 연결합니다.
카메라의 경우 As BP My First Person Character 핀에서 다른 노드를 드래그하고 Get Component by Class를 검색하여 선택합니다.
'by'가 소문자로 된 Get Component by Class 노드를 생성했는지 확인하세요.
새 노드에서 컴포넌트 클래스(Component Class)를 카메라 컴포넌트(Camera Component)로 설정합니다. 그런 다음, Return Value 핀을 Set First Person Camera 노드에 연결합니다.
ABP_FP_DartLauncher 블루프린트를 저장하고 컴파일합니다.
다트 발사기 컨트롤 정의하기
캐릭터가 툴에서 발사체를 발사하려면 다트 발사기에 입력 액션과 입력 매핑 컨텍스트가 있어야 합니다.
다트 발사기 툴에 대한 플레이어 컨트롤을 생성하려면 다음 단계를 따릅니다.
콘텐츠 브라우저에서 Input > Actions 폴더로 이동합니다.
'툴 사용' 입력 액션을 생성하고 구성합니다.
추가(Add)를 클릭하고 입력(Input)으로 이동한 다음 입력 액션(Input Action)을 선택합니다. 액션 이름을 IA_UseTool로 지정합니다.
IA_UseTool을 더블클릭하여 엽니다. 디테일(Details) 패널에서 값 타입(Value Type)이 디지털(Digital) (bool)인지 확인합니다.
트리거(Triggers) 옆의 더하기 버튼(+)을 클릭한 다음, 트리거 목록에서 눌림(Pressed)을 선택합니다.
입력 액션을 저장하고 닫습니다.
콘텐츠 브라우저로 돌아가 Input 폴더로 이동합니다.
왼쪽 마우스 버튼과 게임패드 오른쪽 트리거를 다트 발사기의 사용 액션에 매핑하는 새 입력 매핑 컨텍스트를 생성하고 구성합니다.
IMC_DartLauncher라는 이름의 새 입력 매핑 컨텍스트를 생성합니다.
IMC_DartLauncher를 엽니다. 매핑(Mappings) 옆의 더하기 버튼 (+)을 클릭합니다.
드롭다운 목록에서
IA_UseTool을 선택합니다.화살표를 클릭하여 매핑을 펼칩니다. 키보드 버튼을 클릭한 다음, 왼쪽 마우스 버튼을 눌러 해당 버튼을
IA_UseTool에 바인딩합니다.IA_UseTool 옆의 더하기 버튼(+)을 클릭하여 다른 바인딩을 추가합니다. 드롭다운 목록에서 게임패드(Gamepad)를 펼쳐 게임패드 오른쪽 트리거 축(Gamepad Right Trigger Axis)을 선택합니다.
입력 매핑 컨텍스트를 저장하고 닫습니다.
DartLauncher 블루프린트 생성하기
메인 에디터의 콘텐츠 브라우저 에셋 트리에서 C++ Classes 폴더로 이동하여 DartLauncher 클래스를 우클릭하고 새 블루프린트 클래스를 생성합니다.
클래스 이름을 BP_DartLauncher로 지정합니다. FirstPerson > Blueprints 폴더에서 장착 가능한 아이템을 저장할 Tools이라는 이름의 새 폴더를 생성한 다음, 블루프린트 생성을 완료합니다.
블루프린트의 디테일(Details) 패널에서 다음 디폴트 프로퍼티를 설정합니다.
1인칭 툴 애님(First Person Tool Anim)을
ABP_FP_DartLauncher로 설정합니다.3인칭 툴 애님(Third Person Tool Anim)을
ABP_TP_Pistol로 설정합니다.툴 매핑 컨텍스트(Tool Mapping Context)를
IMC_DartLauncher로 설정합니다.
컴포넌트(Components) 탭에서 툴 메시 컴포넌트(Tool Mesh Component)를 선택하고 스켈레탈 메시 에셋(Skeletal Mesh Asset)을 SKM_Pistol로 설정합니다.
다트 발사기 데이터 에셋 생성하기
이 블루프린트의 데이터를 저장할 데이터 에셋을 생성하려면 다음 단계를 따릅니다.
콘텐츠 브라우저에서 FirstPerson > Data 폴더로 이동하여 새로운 데이터 에셋(Data Asset)을 생성하고 장착 가능한 툴 정의(Equippable Tool Definition)를 데이터 에셋 인스턴스로 선택합니다. 이 에셋의 이름을
DA_DartLauncher로 지정합니다.DA_DartLauncher내의 디테일 패널에서 다음 프로퍼티를 설정합니다.툴 에셋(Tool Asset)을
BP_DartLauncher로 설정합니다.ID를 tool_001로 설정합니다.
아이템 타입(Item Type)을 Tool로 설정합니다.
월드 메시(World Mesh)를
SM_Pistol로 설정합니다.
이름(Name)과 설명(Description)을 입력합니다.
데이터 에셋을 저장합니다.
툴용 데이터 테이블 생성하기
이 툴이 DT_PickupData 테이블에 들어갈 수도 있지만, 특정 항목을 트래킹하도록 테이블을 정리하는 것이 유용합니다. 예를 들면 특정 클래스가 장착할 수 있는 아이템에 쓸 별도의 테이블을 생성해도 되고, 다른 적을 쓰러뜨렸을 때 드롭하는 아이템에 쓸 테이블을 생성해도 됩니다. 이 튜토리얼에서는 소모품 테이블과 툴 테이블을 생성할 것입니다.
툴 아이템을 트래킹할 새 데이터 테이블을 생성하려면 다음 단계를 따릅니다.
콘텐츠 브라우저에서 FirstPerson > Data 폴더로 이동하여 새 데이터 테이블(Data Table)을 생성합니다.
ItemData를 행 구조체로 선택합니다.
테이블의 이름을 DT_ToolData로 지정한 다음 더블클릭하여 엽니다.
DT_ToolData 내에서 추가(Add)를 클릭하여 다트 발사기에 쓸 새 행을 생성합니다.
새 행을 선택한 상태에서 다음 필드를 설정합니다.
행 이름(Row Name) 및 ID를 tool_001로 설정합니다.
아이템 타입(Item Type)을 Tool로 설정합니다.
아이템 베이스(Item Base)를
DA_DartLauncher로 설정합니다.
데이터 테이블을 저장하고 닫습니다.
게임에서 다트 발사기 픽업 테스트하기
사용자에게 아이템을 부여하도록 픽업 클래스를 수정하고, 플레이어에게 새로운 메시, 애니메이션 및 컨트롤을 제공하는 장착 가능한 아이템 클래스를 생성하고, 다트 발사기 툴을 구성했습니다. 이제 모든 것을 한데 모아 이 튜토리얼에서 구성한, 장착 가능한 아이템의 모든 로직을 트리거하는 게임 내 픽업을 생성할 시간입니다.
콘텐츠 브라우저에서 Content > FirstPerson > Blueprints로 이동하여 새 BP_PickupBase를 레벨로 드래그합니다. 픽업 아이템 ID(Pickup Item ID)를 tool_001로 설정하고 픽업 데이터 테이블(Pickup Data Table)을 DT_ToolData로 설정합니다.
플레이(Play)를 클릭하여 게임을 테스트합니다. 게임이 시작되면 픽업이 다트 발사기로 초기화될 것입니다. 픽업 위로 달려가면 캐릭터가 툴을 들기 시작할 것입니다!
다음 순서
마지막 섹션에서는 다트 발사기에 발사체 피직스를 구현하여 폼 다트를 발사해 보겠습니다!
발사체 구현하기
C++를 사용하여 발사체를 구현하고 게임플레이 중에 스폰하는 방법을 알아봅니다.
완성된 코드
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ItemData.h"
#include "ItemDefinition.generated.h"
/**
* Defines a basic item with a static mesh that can be built from the editor.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "ItemDefinition.h"
#include "EquippableToolDefinition.generated.h"
class AEquippableToolBase;
class UInputMappingContext;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolDefinition.h"
UEquippableToolDefinition* UEquippableToolDefinition::CreateItemCopy() const
{
UEquippableToolDefinition* ItemCopy = NewObject<UEquippableToolDefinition>(StaticClass());
ItemCopy->ID = this->ID;
ItemCopy->ItemType = this->ItemType;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UEquippableToolDefinition;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AdventureGame/EquippableToolBase.h"
#include "DartLauncher.generated.h"
UCLASS(BlueprintType, Blueprintable)
class ADVENTUREGAME_API ADartLauncher : public AEquippableToolBase
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DartLauncher.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
}
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EquippableToolBase.generated.h"
class AAdventureCharacter;
class UInputAction;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EquippableToolBase.h"
#include "AdventureCharacter.h"
AEquippableToolBase::AEquippableToolBase()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdventureCharacter.h"
#include "EquippableToolBase.h"
#include "EquippableToolDefinition.h"
#include "ItemDefinition.h"
#include "InventoryComponent.h"
// Sets default values
AAdventureCharacter::AAdventureCharacter()