Notice
Recent Posts
Recent Comments
Link
«   2025/11   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Tags
more
Archives
Today
Total
관리 메뉴

공부용 이모저모

UE4 - 아이템 상자와 무기 제작 본문

UE4 - C++

UE4 - 아이템 상자와 무기 제작

불타는버스 2021. 10. 28. 17:48

이득우의 언리얼 C++ 챕터 10

마켓 플레이스에 Infinity Blade : Weapon을 무료로 받을 수 있다.

받는것과 별개로 프로젝트에 추가는 따로 들어가서 해주어야한다.

이번 예제에 쓰일 무기

언리얼 엔진에는 무기나 엑서서리를 부착할 소켓 이라는 시스템을 제공함.

해당 스켈레톤을 누르고 소켓을 추가하면 된다.이름은 hands_rSocket으로
프리뷰 에셋에 추가로 knight를 입혀본다.
소켓 위치에 검이 생성된다.프리뷰용이라 실제 인게임에선 표시되지 않는다.
프리뷰 애니메이션에서 애니메이션 클릭
손모양이  무기와 어색하므로 소켓을 클릭하고 디테일을 바꾼다.
딱 달라붙어있음을 확인 할 수 있다.
소켓에서 프리뷰 에셋 떼어내는 방법

다음은 실제로 붙혀본다

ABCharacter.h

	UPROPERTY(VisibleAnywhere, Category = Weapon)
		USkeletalMeshComponent* Weapon;

ABCharacter.cpp

// Sets default values
AABCharacter::AABCharacter()
{
	...
    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);
	}
}

무기가 부착된 모습

이번에는 무기 액터를 따로 분류해본다.

파일 - C++ 클래스 생성 - 액터 생성. ABWeapon으로 만든다.

ABWeapon.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ABWeapon.h"
#include "GameFramework/Actor.h"
#include "ABWeapon.generated.h"

UCLASS()
class ARENABATTLE_API AABWeapon : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AABWeapon();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
public:
	UPROPERTY(VisibleAnywhere, Category = Weapon)
		USkeletalMeshComponent* Weapon;
};

ABWeapon.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABWeapon.h"

// Sets default values
AABWeapon::AABWeapon()
{
	// 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;

	Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WEAPON"));
	RootComponent = 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->SetCollisionProfileName(TEXT("NoCollision"));
}

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

// Called every frame
void AABWeapon::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

ABWeapon이 추가되었다.이제 기존 ABCharacter의 Socket에 붙인걸 제거한다.

#include "ABCharacter.h"
#include "ABAnimInstance.h"
#include "ABGhostTrail.h"
#include "ABWeapon.h"
#include "DrawDebugHelpers.h"

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);
	//}
}

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);
	}
    ...
    }

ABCharacter이 ABWeapon을 들고있는걸 확인 할 수 있다.

아이템 상자 제작

 

이번에는 ABItemBox를 만든다
아이템 박스용으로 쓰일 에셋

ABItemBox.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ArenaBattle.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AABItemBox();

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
public:
	UPROPERTY(VisibleAnywhere, Category = Box)
		UBoxComponent* Trigger;

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

ABItemBox.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABItemBox.h"

// Sets default values
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));

}

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

// Called every frame
void AABItemBox::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

ABItemBox가 생성됨을 확인 할 수 있다.

이번에는 폰이 아이템을 획득하도록 아이템상자에 오브젝트 채널을 추가해본다

새 오브젝트 채널 - ItemBox를 Ignore 옵션으로 추가한다.
새 프로파일 - ItemBox를 만들고 Query Only로 수정. ABCharacter을 제외하고 전부 무시로 취급한다.

아이템이므로 충돌로서 막히면 안되며, 주인공은 그걸 감지해야 하므로

주인공을 제외한 모든 개체를 무시로 설정.

 

ABCharacter에서 ItemBox는 겹침으로 변경

박스 컴포넌트에 Overlap 이벤트를 처리하도록 OnComponentBeginOverlap이란 딜리게이트가 존재한다.

멀티 캐스트 다이나믹 딜리게이트이다. 박스를 만들어 콜리전정보를 붙혀서 딜리게이트를 발동시키기로 한다.

 

ABItemBox.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ArenaBattle.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AABItemBox();

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

public:
	UPROPERTY(VisibleAnywhere, Category = Box)
		UBoxComponent* Trigger;

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

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

ABItemBox.cpp

// Sets default values
AABItemBox::AABItemBox()
{
 	...
	Trigger->SetCollisionProfileName(TEXT("ItemBox"));
	//박스에는 NoCollision이라는 콜리전 정보를 세팅한다
	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)
{
	UE_LOG(LogTemp, Warning, TEXT("Warning"));
}

닿을때마다 딱 한번만 Warning이 표기된다.

아이템을 습득하는 코드를 추가한다.

아이템 안에 무기정보를 넣어주는 변수를 추가한다.

 

ABItemBox.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "ArenaBattle.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

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

 

ABItemBox.Cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "ABItemBox.h"
#include "ABWeapon.h"

// Sets default values
AABItemBox::AABItemBox()
{
 	...
	WeaponItemClass = AABWeapon::StaticClass();
}

WeaponItemClass라는 메뉴가 추가된다.

이제 캐릭터에게 아이템을 장착시킬 준비를 한다.

 

ABCharacter.h

class ARENABATTLE_API AABCharacter : public ACharacter
{
	...
public:
	bool CanSetWeapon();							//웨폰 장착이 가능한가?
	void SetWeapon(class AABWeapon* NewWeapon);		//웨폰 장착 함수
    
	UPROPERTY(VisibleAnyWhere, Category=Weapon)
	class AABWeapon* CurrentWeapon;					// 웨폰 변수
private:
	...    
};

ABCharacter.cpp

...

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);
	//}
}

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::SnapToTargetIncludingScale, WeaponSocket);
		NewWeapon->SetOwner(this);
		CurrentWeapon = NewWeapon;
	}
}

ABItemBox.cpp

#include "ABCharacter.h"

...
void AABItemBox::OnCharacterOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	//UE_LOG(LogTemp, Warning, TEXT("Warning"));

	auto ABCharacter = Cast<AABCharacter>(OtherActor);
	
	//잡힌 액터가 캐릭터이며, 현재 무기를 끼지 않아야한다.
	if (nullptr != ABCharacter && nullptr != WeaponItemClass)
	{
		if (ABCharacter->CanSetWeapon())
		{
			//아이템 박스가 갖고있던 웨폰정보를 넘겨준다.
			auto NewWeapon = GetWorld()->SpawnActor<AABWeapon>(WeaponItemClass, FVector::ZeroVector, FRotator::ZeroRotator);
			ABCharacter->SetWeapon(NewWeapon);
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Weapon Equiped"));
		}
	}
}

박스에 닿으면 무기가 생기고,한번 더  들어가면 경고가 뜬다/

 

이번엔 이펙트가 나오게 하면서 박스를 소멸 시켜보기로 한다

파티클 시스템을 필터링 하면, InfinityGrassLands에 있는 파티클들이 표기된다.여기서 P_TreasureChest_Open_Mesh를 쓰기로 한다

ABItemBox.h

{
	...

public:
	UPROPERTY(VisibleAnywhere, Category = Box)
		UBoxComponent* Trigger;

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

	//신규추가
	UPROPERTY(VisibleAnywhere, Category = Effect)
		UParticleSystemComponent* Effect;

	UPROPERTY(EditInstanceOnly, Category = Box)
		TSubclassOf<class AABWeapon>	WeaponItemClass;
private:
	UFUNCTION()
		void OnCharacterOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	
    //신규 추가
	UFUNCTION()
		void OnEffectFinished(class UParticleSystemComponent* PSystem);
};

ABItemBox.cpp

//Effect 관련 신규 생성
AABItemBox::AABItemBox()
{
 	// 틱을 쓸일이 없으면 꺼두는것이 부하가 적다
	PrimaryActorTick.bCanEverTick = false;

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

	RootComponent = Trigger;
	Box->SetupAttachment(RootComponent);
	Effect->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);
	}

	static ConstructorHelpers::FObjectFinder<UParticleSystem> P_CHESTOPEN(TEXT("/Game/InfinityBladeGrassLands/Effects/FX_Treasure/Chest/P_TreasureChest_Open_Mesh.P_TreasureChest_Open_Mesh"));
	if (P_CHESTOPEN.Succeeded())
	{
		Effect->SetTemplate(P_CHESTOPEN.Object);
		Effect->bAutoActivate = false;
	}
	Box->SetRelativeLocation(FVector(0.0f, -3.5f, -30.0f));

	//아이템박스라는 콜리전 정보를 세팅한다
	Trigger->SetCollisionProfileName(TEXT("ItemBox"));
	//박스에는 NoCollision이라는 콜리전 정보를 세팅한다
	Box->SetCollisionProfileName(TEXT("NoCollision"));
	WeaponItemClass = AABWeapon::StaticClass();
}

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

	auto ABCharacter = Cast<AABCharacter>(OtherActor);
	
	//잡힌 액터가 캐릭터이며, 현재 무기를 끼지 않아야한다.
	if (nullptr != ABCharacter && nullptr != WeaponItemClass)
	{
		if (ABCharacter->CanSetWeapon())
		{
			auto NewWeapon = GetWorld()->SpawnActor<AABWeapon>(WeaponItemClass, FVector::ZeroVector, FRotator::ZeroRotator);
			ABCharacter->SetWeapon(NewWeapon);
			//신규 추가
			Effect->Activate(true);
			//박스를 화면 표기에서 안보이게
			Box->SetHiddenInGame(true, true);
			//더이상 충돌이 일어나지 않게
			SetActorEnableCollision(false);
			//이펙트 끝을 알리는 딜리게이트
			Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished);
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Weapon Equiped"));
		}
	}
}


void AABItemBox::OnEffectFinished(class UParticleSystemComponent* PSystem)
{
	//이펙트가 끝나면 박스 삭제
	Destroy();
}

아이템을 먹으면 박스가 화면에서 사라지고, 이펙트가 끝나면 삭제된다.

다음엔 무기의 종류를 바꾸기 위해 블루프린트로 상속시키기로 한다

Book - Blueprint 폴더 안에 블루프린트 클래스 새로 생성
부모를 ABWeapon으로. 이름은 BlackKnight로 짓겠다.
Weapon을 선택하고 메시부분을 GreatBlade로 바꾼다.도끼로 모양이 바뀐다.이후 컴파일 - 저장버튼을 누른다.
상자의 웨폰 클래스를 BlackKnight로 바꾼다.
무기가 도끼로 변경되어있다.