본문 바로가기

언리얼C++게임개발/10. 아이템 상자와 무기 제작

아이템상자와 무기제작

캐릭터 소켓 설정

캐릭터 소켓 설정

캐릭터 무기 추가를 위해 마켓 플레이스에서 InfinityBlade: Weapons 를 프로젝트에 추가하자. 무기는 캐릭터에 트랜스폼으로 배치하는 것이 아니라 메시에 착용해야 캐릭터 애니메이션에 따라 무기가 같이 움직인다. 언리얼 엔진은 아이템을 캐릭터에 부착하기 위해 소켓 시스템을 제공한다. hand_rSocket 소켓에 검을 부착해보자.

그리고 위치값을 조정해서 자연스럽게 보이도록 하자.

코드를 통해무기 스켈레탈 메시 컴포넌트를 캐릭터 메시에 부착해보자. StaticMeshComponent* Weapon 변수를 선언하고 CreateDefaultSubObject()로 만들어서 원하는 Sword를 찾아 SK_WEAPON에 넣어서 Weapon->SetAttachment로 붙여주자. 이때 위에서 정한 소켓을 넣어주면 잘 붙는다.

ABCharacter.h

public:
    UPROPERTY(VisibleAnywhere, Category = Weapon)
	USkeletonMeshComponent* Weapon;

ABChracter.cpp

AABCharacter::AABCharacter()
{
	...
    FName WeaponSocket(TEXT("hand_rSocket"));
	if (GetMesh()->DoesSocketExist(WeaponSocket))
	{
		Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WEAPON"));
		static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_WEAPON(TEXT("/Game/InfinityBladeWeapons/Weapons/Blade/Swords/Blade_BlackKnight/SK_Blade_BlackKnight.SK_Blade_BlackKnight"));
		if (SK_WEAPON.Succeeded())
		{
			Weapon->SetSkeletalMesh(SK_WEAPON.Object);
		}
		Weapon->SetupAttachment(GetMesh(), WeaponSocket);
	}

실행해보면 손에 무기를 들고 있다.

 

무기 액터의 제작

필요에 따라 무기를 바꿀수 있게 하려면 무기를 액터로 분리해 만드는 것이 좋다. ABWeapon 액터 클래스를 생성하자.

MyWeapon.h

UCLASS()
class HUNT_PROTOTYPE_API AMyWeapon : public AActor
{
	GENERATED_BODY()
	
public:	
	AMyWeapon();

protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

public:
	UPROPERTY(VisibleAnywhere, Category = Weapon)
		USkeletalMeshComponent* Weapon;							// 설정해줄 Skeletal Mesh
};

MyWeapon.cpp

AMyWeapon::AMyWeapon()
{
 	PrimaryActorTick.bCanEverTick = false;						// Tick이 호출될 필요 없음

	Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WEAPON"));
	RootComponent = Weapon;

	static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_WEAPON(TEXT("SkeletalMesh'/Game/InfinityBladeWeapons/Weapons/Blade/Swords/Blade_BlackKnight/SK_Blade_BlackKnight.SK_Blade_BlackKnight'"));

	if (SK_WEAPON.Succeeded()) {
		Weapon->SetSkeletalMesh(SK_WEAPON.Object);
	}
	
	Weapon->SetCollisionProfileName(TEXT("NoCollision"));		// 충돌처리 안해줄거임
}

컴파일후 weapon을 레벨에 끌어다 놔서 확인후 지워주자 

ABCharacter의 생성자의 무기컴포넌트생성부분을 지워주고  BeginPlay()에서 Weapon을 쥐어보자  이번에는 Actor를 컴포넌트에 붙이기때문에 코드가 약간 다르다

ABCharacter.cpp 

void AABCharacter::BeginPlay()
{
	Super::BeginPlay();
	FName WeaponSocket(TEXT("hand_rSocket"));
	auto CurWeapon = GetWorld()->SpawnActor<AABWeapon>(FVector::ZeroVector, FRotator::ZeroRotator);
	if (CurWeapon)
	{
		CurWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponSocket);
	}

컴파일후 플레이를 해보면 캐릭터손에 무기가 쥐어진 것을 확인할 수 있다.

결과 화면은 같지만 캐릭터의 무기를 액터로 분리했다는 점이 다르다.

아이템 상자의 제작

플레이어에게 무기를 공급해줄 아이템 상자를 제작해보자. Actor클래스를 부모로하는 ABItemBox 클래스를 생성하였다.

아이템의 습득

이제 아이템 상자를 통과하면 맨손의 플레이어에게 아이템을 쥐어쥐도록 구현해보자. ABItemBox의 OnCharacterOverlap 함수에서 캐릭터에게 Weapon을 설정해주는 코드를 책의 예제 코드를 따라 작성하였다. 기존에 있던 캐릭터의 BeginPlay 함수에서의 Weapon 설정은 삭제하였고, 컴파일후 테스트 해보았다.

#include "ABWeapon.h"
void AABCharacter::AABCharacter()
{
   ...
   // 1.1. 무기 생성/장착 코드 삭제
}


void AABCharacter::BeginPlay()
{
	Super::BeginPlay();

	FName WeaponSocket(TEXT("hand_rSocket"));
	auto CurWeapon = GetWorld()->SpawnActor<AABWeapon>(FVector::ZeroVector, FRotator::ZeroRotator);
	if (nullptr != CurWeapon)
	{
		CurWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponSocket);
	}
}

이제 상자에 이펙트를 추가해보자 예제에 있는 이펙트파일을 활용하여 책의 예제코드를 따라 작성하였다. 그리고 컴파일후 플레이 테스트를 통해 상자와 겹침시 이펙트와함께 상자가 사라지게 할것이다

아이템 박스 크기를 참고해 아이템박스 액터에 콜리전 컴포넌트의 크기와 스태틱메시 컴포넌트의 애셋을 지정한다.

 

ABItemBox.h

class ARENABATTLE_API AABItemBox : public AActor
{
...
	
public:
	UPROPERTY(VisibleAnywhere, Category = Box)
	UBoxComponent Trigger;

	UPROPERTY(VisibleAnywhere, Category = Box)
	UBoxCompoenet* Box;
};

ABItemBox.cpp

AABItemBox::AABItemBox()
{
    PrimaryActorTick.bCanEverTick = false;

    Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("TRIGGER"));
    Box = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BOX"));

    RootComponent = Trigger;
    Box->SetupAttachment(RootComponent);

    Trigger->SetBoxExtent(FVector(40.0f, 42.0f, 30.0f));
    static ConstructorHelpers::FObjectFinder<UStaticMesh> SM_BOX(TEXT("/Game/InfinityBladeGrassLands/Environments/Breakables/StaticMesh/Box/SM_Env_Breakables_Box1.SM_Env_Breakables_Box1"));
    
    if (SM_BOX.Succeeded())
    {
        Box->SetStaticMesh(SM_BOX.Object);
    }

    Box->SetRelativeLocation(FVector(0.0f, -3.5f, -30.0f));
}

박스 설치와 초기 설정을 완료했으면 폰이 아이템을 획득하도록 아이템 상자에 오브젝트 채널을 추가해보자. 프로젝트 설정에서 ItemBox의 콜리전과 프리셋을 설정한다. 이때, 캐릭터에게 아이템 이벤트를 실행시키기 위해 ItemBox의 콜리전과 프리셋은 모두 무시로 하되, ABCharacter에 대한 충돌 속성만 겹침으로 변경해야 한다.

 

ItemBox라는 프리셋도 추가하여 모두 무시하되 ABCharacter만 겹침으로 설정하고 ABCharacter의 프리셋에도 ItemBox 오브젝트를 겹침으로 설정한다.

새로운 프리셋을 박스 컴포넌트에 설정하고, 박스 컴포넌트에서 캐릭터를 감지할 때 관련 행동을 구현한다.

박스 컴포넌트는 Overlap 이벤트를 처리할 수 있도록 OnComponentBeginOverlap 델리게이트가 선언되어 있다!

그리고 OnComponentBeginOverlap 델리게이트를 활용하여 겹침 이벤트를 구현해 보자

ABItemBox.h

 
UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	virtual void PostInitializeComponents() override;

private:
	UFUNCTION()
		void OnCharacterOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
		...
}

ABItemBox.cpp

 
AABItemBox::AABItemBox()
{
	...
	
	Trigger->SetCollisionProfileName(TEXT("ItemBox"));
    Box->SetCollisionProfileName(TEXT("NoCollision"));
}

void AABItemBox::PostInitializeComponents()
{
    Super::PostInitializeComponents();
    Trigger->OnComponentBeginOverlap.AddDynamic(this, &AABItemBox::OnCharacterOverlap);
}

void AABItemBox::OnCharacterOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    ABLOG_S(Warning);
}

컴파일 후 아이템을 배치하여 캐릭터를 아이템에 통과할때 로그가 발생함을 확인할 수 있다.

2.2. 아이템 습득

아이템 상자를 통과하면 플레이어에게 아이템을 쥐어주는 기능을 구현해보자.

  1. 아이템 상자에 클래스 정보를 저장할 속성 추가 * TSubclassof : 특정 클래스와 상속받은 클래스들로 목록 한정 * 구현부 생성자 코드에 무기아이템 속성에 대한 기본 클래스값 지정
  2. 플레이어가 아이템 상자 영역에 들어왔을 때 아이템 생성
    • 캐릭터에 무기를 장착시키는 멤버 함수 선언
      • 현재 캐릭터에 무기가 없으면 hand_rSocket에 무기 장착
      • 무기 액터의 소유자를 캐릭터로 변경
  3. BeginPlay에서 시작할 때 무지 액터를 장착시킨 로직 삭제
  4. 상자에 Overlap 이벤트가 발생하면 아이템 상자에 설정된 클래스 정보로부터 무기 생성

ABItemBox.h

class ARENABATTLE_API AABItemBox : public AActor
{
public:
	UPROPERTY(EditInstanceOnly, Category = Box)
	TSubclassOf<class AABWeapon> WeaponItemClass;
	
	...
}

ABItemBox.cpp

 

#include "ABWeapon.h"
#include "ABCharacter.h"

AABItemBox::AABItemBox()
{
    WeaponItemClass = AABWeapon::StaticClass();
    
    ...
}

void AABItemBox::OnCharacterOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    ABLOG_S(Warning);

    auto ABCharacter = Cast<AABCharacter>(OtherActor);
    ABCHECK(nullptr != ABCharacter);
    if (nullptr != ABCharacter && nullptr != WeaponItemClass)
    {
        if (ABCharacter->CanSetWeapon())
        {
            auto NEwWeapon = GetWorld()->SpawnActor<AABWeapon>(WeaponItemClass, FVector::ZeroVector, FRotator::ZeroRotator);
            ABCharacter->SetWeapon(NEwWeapon);
        }
        else
        {
            ABLOG(Warning, TEXT("%s can't equip weapon currently."), *ABCharacter->GetName());
        }
    }
}

ABCharacter.h

class ARENABATTLE_API AABCharacter : public ACharacter
{
public:
	bool CanSetWeapon();
	void SetWeapon(class AABWeapon* NewWeapon);
    UPROPERTY(VisibleAnywhere, Category = Weapon)
	class AABWeapon* CurrentWeapon;
	
	...
}

ABCharacter.cpp

bool AABCharacter::CanSetWeapon()
{
	return (nullptr == CurrentWeapon);
}

void AABCharacter::SetWeapon(AABWeapon* NewWeapon)
{
	ABCHECK(nullptr != NewWeapon && nullptr == CurrentWeapon);
	FName WeaponSocket(TEXT("hand_rSocket"));
	if (nullptr != NewWeapon)
	{
		NewWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponSocket);
		NewWeapon->SetOwner(this);
		CurrentWeapon = NewWeapon;
	}
}

2.3. 이펙트 구현

캐릭터가 아이템을 습득하면 상자가 이펙트를 재생하고 사라지는 기능을 구현한다.

  1. 상자 액터에 파티클 컴포넌트 추가
  2. 이펙트 애셋 레퍼런스를 파티클 컴포넌트 템플릿으로 지정
  3. 멤버 함수를 추가하고 파티클 컴포넌트 시스템에서 제공하는 OnSystemFinishied 델리게이트에 추가
    • 이펙트 생성이 종료되면 아이템 상자 제거
    • OnSystemFinishied 델리게이트는 다이나믹 형식이므로 바인딩할 대상 멤버 함수에 UFUNCTION 매크로 선언
  4. 캐릭터가 아이템을 두 번 습득하지 못하도록 함
    • 이펙트 재생 도중에는 액터 충돌 기능 제거
    • 액터가 제거될 때까지 박스 스태틱메시 숨김

 

ABItemBox.h

class ARENABATTLE_API AABItemBox : public AActor
{
...
	
public:
	UPROPERTY(VisibleAnywhere, Category = Box)
	UBoxComponent* Trigger;

	UPROPERTY(VisibleAnywhere, Category = Box)
	UStaticMeshComponent* Box;
};

ABItemBox.cpp

#include "ABItemBox.h"

// Sets default values
AABItemBox::AABItemBox()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = false;

    Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("TRIGGER"));
    Box = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BOX"));

    RootComponent = Trigger;
    Box->SetupAttachment(RootComponent);

    Trigger->SetBoxExtent(FVector(40.0f, 42.0f, 30.0f));
    static ConstructorHelpers::FObjectFinder<UStaticMesh> SM_BOX(TEXT("/Game/InfinityBladeGrassLands/Environments/Breakables/StaticMesh/Box/SM_Env_Breakables_Box1.SM_Env_Breakables_Box1"));

    if (SM_BOX.Succeeded())
    {
        Box->SetStaticMesh(SM_BOX.Object);
    }

    Box->SetRelativeLocation(FVector(0.0f, -3.5f, -30.0f));

}

// Called when the game starts or when spawned
void AABItemBox::BeginPlay()
{
	Super::BeginPlay();
	
}

부모 클래스를 ABWeapon으로 하는 새 블루프린트 클래스를 생성한다. 부모 클래스 선택 시 모든 클래스 탭을 눌러 사용자 지정 클래스를 선택할 수 있다.

ABWeapon이 부모 클래스이므로 사전에 지정해두었던 무기가 기본 블루프린트 형식으로 출력된다. 블루프린트를 열고 스켈레탈 메시에서 도끼로 무기를 변경해보자.

언리얼 에디터에서 상자의 WeaponClass 설정을 방금 설정한 블루프린트 이름으로 바꾸어준다.

 

결과 화면

캐릭터가 아이템 상자를 습득하면 도끼가 부착된다.

요약

  • 캐릭터 소켓에 무기 부착
  • 무기 액터를 통한 무기 장착
  • 아이템 상자 습득과 이펙트 연결

https://yj59.github.io/unreal%20tutorials/DW-Unreal-Tuto-10/

 

[이득우의 C++ 언리얼] #10 언리얼 소켓 시스템

🤍ref. 이득우의 언리얼 C++ 게임 개발의 정석, 이득우, 에이콘출판사, 2018 Unreal Engine 4.27 Documentation 유니티 개발자를 위한 언리얼 엔진4

yj59.github.io