초보 코린이의 성장 일지

UE4 Weapon Attach, Sword Equip 본문

언리얼

UE4 Weapon Attach, Sword Equip

코오린이 2023. 5. 1. 17:30

무기를 캐릭터에 붙이는 작업을 진행해 보겠다.

더보기
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CWeaponComponent.generated.h"

// 무기 타입
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
	Fist, Sword, Hammer, Warp, Around, Bow, Max,
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType);

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class U2212_06_API UCWeaponComponent : public UActorComponent
{
	GENERATED_BODY()

private:
	UPROPERTY(EditAnywhere, Category = "DataAsset")
		class UCWeaponAsset* DataAssets[(int32)EWeaponType::Max];

public:
	// 각 타입이 맞는지 확인하기 위한 returnn 함수.
	FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; }
	FORCEINLINE bool IsFistMode() { return Type == EWeaponType::Fist; }
	FORCEINLINE bool IsSwordMode() { return Type == EWeaponType::Sword; }
	FORCEINLINE bool IsHammerMode() { return Type == EWeaponType::Hammer; }
	FORCEINLINE bool IsWarpMode() { return Type == EWeaponType::Warp; }
	FORCEINLINE bool IsAroundMode() { return Type == EWeaponType::Around; }
	FORCEINLINE bool IsBowMode() { return Type == EWeaponType::Bow; }

public:	
	UCWeaponComponent();

protected:
	virtual void BeginPlay() override;

private:
	bool IsIdleMode(); // 컴포넌트끼리 같은것을 소유하면 안되므로, 참조만해서 return 받기위해 사용.

public:
	class ACAttachment* GetAttachment(); // 내부, 외부 둘다 사용하기 위해 생성.
	class UCEquipment* GetEquipment();

public:
	// 각 무기 세팅
	void SetUnarmedMode();
	void SetFistMode();
	void SetSwordMode();
	void SetHammerMode();
	void SetWarpMode();
	void SetAroundMode();
	void SetBowMode();

private:
	void SetMode(EWeaponType InType); // 모드 확인
	void ChangeType(EWeaponType InType); // 바꾸기

public:
	FWeaponTypeChanged OnWeaponTypeChange; // 바뀌었을때 이벤트 통보해줄 델리게이트

private:
	class ACharacter* OwnerCharacter;

	EWeaponType Type = EWeaponType::Max; 

};
#include "Components/CWeaponComponent.h"
#include "Global.h"
#include "CStateComponent.h"
#include "GameFramework/Character.h"
#include "Weapons/CWeaponAsset.h"
#include "Weapons/CAttachment.h"
#include "Weapons/CEquipment.h"

UCWeaponComponent::UCWeaponComponent()
{

}

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

	OwnerCharacter = Cast<ACharacter>(GetOwner());
	for (int32 i = 0; i < (int32)EWeaponType::Max; i++)
	{
		// CWeaponAsset에 있는 Begin_Play 불러오기
		if (!!DataAssets[i])
			DataAssets[i]->BeginPlay(OwnerCharacter); // 콜해줌으로써 Sword가 생성된다.

	}
}

bool UCWeaponComponent::IsIdleMode()
{
	// StateComponent 참조 return 받는 곳
	return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode();
}

ACAttachment* UCWeaponComponent::GetAttachment()
{
	CheckTrueResult(IsUnarmedMode(), nullptr); // 무기가 장착이 아닌 상태 체크
	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);

	return DataAssets[(int32)Type]->GetAttachment();
}

UCEquipment* UCWeaponComponent::GetEquipment()
{
	CheckTrueResult(IsUnarmedMode(), nullptr);
	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);

	return DataAssets[(int32)Type]->GetEquipment();
}

void UCWeaponComponent::SetUnarmedMode()
{
	//GetEquipment()->Unequip();

	ChangeType(EWeaponType::Max);
}

void UCWeaponComponent::SetFistMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Fist);
}

void UCWeaponComponent::SetSwordMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Sword);
}

void UCWeaponComponent::SetHammerMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Hammer);
}

void UCWeaponComponent::SetWarpMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Warp);
}

void UCWeaponComponent::SetAroundMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Around);
}

void UCWeaponComponent::SetBowMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Bow);
}

void UCWeaponComponent::SetMode(EWeaponType InType)
{
	if (Type == InType)
	{
		SetUnarmedMode(); // 타입이 같다면 해제이므로 UnarmedMode

		return;
	}
	else if (IsUnarmedMode() == false)
	{
		//GetEquipment()->Unequip(); // UnarmedMode가 아니라면 그걸 해제하고 다른걸 장착
	}

	if (!!DataAssets[(int32)InType])
	{
		//DataAssets[(int32)InType]->GetEquipment()->Equip();

		ChangeType(InType);
	}
}

void UCWeaponComponent::ChangeType(EWeaponType InType)
{
	EWeaponType prevType = Type;
	Type = InType;

	if (OnWeaponTypeChange.IsBound())
		OnWeaponTypeChange.Broadcast(prevType, InType);
}

1. 각 무기 모드를 Enum Type로 만들어 판단.

2. 각 타입을 확인하고, 맞다면 모드가 변경되도록 설정.

3. 무기 장착 유무로 다른 무기를 장착하거나 장착을 해야하는지 판단.

4. Equip 관련된 부분은 아래에 코드를 작성 후 진행할 것이다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Characters/ICharacter.h"
#include "CPlayer.generated.h"

UCLASS()
class U2212_06_API ACPlayer
	: public ACharacter
	, public IICharacter
{
	GENERATED_BODY()
    
private:
	UPROPERTY(VisibleAnywhere)
		class UCWeaponComponent* Weapon;
#include "Characters/CPlayer.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/InputComponent.h"
#include "Components/CWeaponComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"

ACPlayer::ACPlayer()
{
	// 구조체 내용들
	CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon");
}

void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	PlayerInputComponent->BindAction("Sword", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetSwordMode);
}

1. WeaponComponent에서 넘겨준 무기를 Player에게 장착시켜준다.

 


더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CAttachment.generated.h"

UCLASS()
class U2212_06_API ACAttachment : public AActor
{
	GENERATED_BODY()
	
protected:
	// BP안에 붙이는 함수도 있지만, 필요한것만 사용하는게 좋으므로, BP에서 사용할 수 있도록 만들어준다.
	UFUNCTION(BlueprintCallable, Category = "Attach")
		void AttachTo(FName InSocketName);
        
};
#include "Weapons/CAttachment.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/SceneComponent.h"

void ACAttachment::AttachTo(FName InSocketName)
{
	// 플레이어 Mesh, 소켓 생성된 부위 지정, Sword 장착
	AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), InSocketName);
}

 

1. Attach 시켜주기 위해, BP에서 사용할 수 있는 함수를 생성

2. 플레이어 소켓에 장착되도록 만들어 준다.

기존에 함수.
만들어 준 함수

1. 기존에는 같은 Attach 함수에 여러요소가 있어 누군가와 작업을 할때 혹여나 수정될 일이 발생할 수 있다.

2. 필요한 부분만 생성해 줌으로써 오류발생을 최소화 시키고 캐릭터 소켓에 붙여서 생성해준다.

1. 바닥에 Spawn된 Sword가 캐릭터 왼쪽 허리에 붙여진걸 확인할 수 있다.


1. 기능만을 제공할 것이므로, UObject로 생성해준다. 즉 BP에서 설정할게 있냐를 나타낸다.

2. 클래스 생성을 해준다.

더보기
#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Weapons/CWeaponStructures.h"
#include "CEquipment.generated.h"

UCLASS()
class U2212_06_API UCEquipment : public UObject
{
	GENERATED_BODY()

public:
	
	void BeginPlay(class ACharacter* InOwner, const FEquipmentData& InData);

public:
	// BP에서 필요하면 재정의 할 수 있도록 만들어준다.
	// 기본도 만들어 놓는다 재정의 안하면 그대로 사용.
	UFUNCTION(BlueprintNativeEvent) 
		void Equip();
	void Equip_Implementation();

	UFUNCTION(BlueprintNativeEvent)
		void Begin_Equip();
	void Begin_Equip_Implementation();

	UFUNCTION(BlueprintNativeEvent)
		void End_Equip();
	void End_Equip_Implementation();

	UFUNCTION(BlueprintNativeEvent)
		void Unequip();
	void Unequip_Implementation();

private:
	class ACharacter* OwnerCharacter;
	FEquipmentData Data;

private:
	class UCMovementComponent* Movement;
	class UCStateComponent* State;

private:
	bool bBeginEquip; // Equip 시작했는지
	bool bEquipped; // Equip가 완료되었는지
};
#include "Weapons/CEquipment.h"

1. 동작에 대해 BP안에서도 재정의 할 수 있도록 만들어 준다.

2. 재정의 하지 않는다면 있는 그대로를 사용할 것이다.

3. cpp는 필요가 없는 이유는 단지 동작 유무를 판단하기 위함.


1. Equip에서 어떠한 동작으로 장착될지를 결정해주기 위해 구조체로 관리해줄 것이다.

2. 클래스 생성.

더보기
#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CWeaponStructures.generated.h"

// 구조체 직렬화가 되야하므로, class 위에서 작업.
USTRUCT()
struct FEquipmentData
{
	GENERATED_BODY()

public:
	// 에디터에서 세팅은 해야하므로, EditAnywhere.
	UPROPERTY(EditAnywhere)
		class UAnimMontage* Montage;

	UPROPERTY(EditAnywhere)
		float PlayRate = 1;

	UPROPERTY(EditAnywhere)
		bool bCanMove = true;

	UPROPERTY(EditAnywhere)
		bool bUseControlRotation = true;
};

UCLASS()
class U2212_06_API UCWeaponStructures : public UObject
{
	GENERATED_BODY()

};
#include "Weapons/CWeaponStructures.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "Components/CMovementComponent.h"
#include "Components/CStateComponent.h"
#include "CEquipment.h"

void UCEquipment::BeginPlay(ACharacter* InOwner, const FEquipmentData& InData)
{
	// 데이터들 받아주기.
	OwnerCharacter = InOwner;
	Data = InData;

	Movement = CHelpers::GetComponent<UCMovementComponent>(InOwner);
	State = CHelpers::GetComponent<UCStateComponent>(InOwner);
}

void UCEquipment::Equip_Implementation()
{
	State->SetEquipMode();

	if (Data.bCanMove == false)
		Movement->Stop();

	if (Data.bUseControlRotation)
		Movement->EnableControlRotation();

	if (!!Data.Montage)
	{
		// 데이터안에 몽타주 콜해주기.
		OwnerCharacter->PlayAnimMontage(Data.Montage, Data.PlayRate);
	}
	else
	{
		// 몽타주가 없다면 콜해주기.
		Begin_Equip();
		End_Equip();
	}
}

void UCEquipment::Begin_Equip_Implementation()
{
	bBeginEquip = true;
}

void UCEquipment::End_Equip_Implementation()
{
	bBeginEquip = false;
	bEquipped = true;

	Movement->Move();
	State->SetIdleMode();
}

void UCEquipment::Unequip_Implementation()
{
	bEquipped = false;
	Movement->DisableControlRotation();
}

1. 구조체 안에있는 데이터 값들을 받아와서 콜해준다.

2. 입력된 값에 따라 동작이 나올것이다.


1. BP로 가서 만들어 놓은 구조체 가져와준다.

2. BP에서 구조체 카테고리가 생기고, 안에 값들을 설정해 줄 수 있다.


더보기
#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Weapons/CWeaponStructures.h"
#include "CWeaponAsset.generated.h"

UCLASS()
class U2212_06_API UCWeaponAsset : public UDataAsset
{
	GENERATED_BODY()
    
private:
    UPROPERTY(EditAnywhere)
		FEquipmentData EquipmentData; // 구조체 가져오기

	UPROPERTY(EditAnywhere)
		TSubclassOf<class UCEquipment> EquipmentClass;
        
public:
	// 생성된걸 return 해주기 위해
    FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }
	FORCEINLINE class UCEquipment* GetEquipment() { return Equipment; }

private:
	UPROPERTY()
		class ACAttachment* Attachment;

	UPROPERTY()
		class UCEquipment* Equipment;
  
};

 

#include "Weapons/CWeaponAsset.h"
#include "Global.h"
#include "CAttachment.h"
#include "CEquipment.h"
#include "GameFramework/Character.h"

UCWeaponAsset::UCWeaponAsset()
{
	// 기본값으로 StaticClass를 넣어준다.
	AttachmentClass = ACAttachment::StaticClass();
	EquipmentClass = UCEquipment::StaticClass();
}

void UCWeaponAsset::BeginPlay(ACharacter* InOwner)
{
	if (!!AttachmentClass)
	{
		FActorSpawnParameters params;
		params.Owner = InOwner;

		// 선택이 되어있다면 붙여주기.
		Attachment = InOwner->GetWorld()->SpawnActor<ACAttachment>(AttachmentClass, params);
	}

	if (!!EquipmentClass)
	{
		// Object 동적할당, 실제로 만들어질 EquipmentClass
		Equipment = NewObject<UCEquipment>(this, EquipmentClass);
		Equipment->BeginPlay(InOwner, EquipmentData); // 정의해 놓은거 대입
	}
}

1. 구조체에 담긴 값으로 생성을 시켜주기 위함.

2. 만들어질 EquipmentClass를 넣어주고, 정의해 놓은 값 대입.


더보기
#include "Components/CWeaponComponent.h"
#include "Global.h"
#include "CStateComponent.h"
#include "GameFramework/Character.h"
#include "Weapons/CWeaponAsset.h"
#include "Weapons/CAttachment.h"
#include "Weapons/CEquipment.h"

UCWeaponComponent::UCWeaponComponent()
{

}

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

	OwnerCharacter = Cast<ACharacter>(GetOwner());
	for (int32 i = 0; i < (int32)EWeaponType::Max; i++)
	{
		// CWeaponAsset에 있는 Begin_Play 불러오기
		if (!!DataAssets[i])
			DataAssets[i]->BeginPlay(OwnerCharacter); // 콜해줌으로써 Sword가 생성된다.

	}
}

bool UCWeaponComponent::IsIdleMode()
{
	// StateComponent 참조 return 받는 곳
	return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode();
}

ACAttachment* UCWeaponComponent::GetAttachment()
{
	CheckTrueResult(IsUnarmedMode(), nullptr); // 무기가 장착이 아닌 상태 체크
	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);

	return DataAssets[(int32)Type]->GetAttachment();
}

UCEquipment* UCWeaponComponent::GetEquipment()
{
	CheckTrueResult(IsUnarmedMode(), nullptr);
	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);

	return DataAssets[(int32)Type]->GetEquipment();
}

void UCWeaponComponent::SetUnarmedMode()
{
	GetEquipment()->Unequip();

	ChangeType(EWeaponType::Max);
}

void UCWeaponComponent::SetFistMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Fist);
}

void UCWeaponComponent::SetSwordMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Sword);
}

void UCWeaponComponent::SetHammerMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Hammer);
}

void UCWeaponComponent::SetWarpMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Warp);
}

void UCWeaponComponent::SetAroundMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Around);
}

void UCWeaponComponent::SetBowMode()
{
	CheckFalse(IsIdleMode());

	SetMode(EWeaponType::Bow);
}

void UCWeaponComponent::SetMode(EWeaponType InType)
{
	if (Type == InType)
	{
		SetUnarmedMode(); // 타입이 같다면 해제이므로 UnarmedMode

		return;
	}
	else if (IsUnarmedMode() == false)
	{
		GetEquipment()->Unequip(); // UnarmedMode가 아니라면 그걸 해제하고 다른걸 장착
	}

	if (!!DataAssets[(int32)InType])
	{
		DataAssets[(int32)InType]->GetEquipment()->Equip();

		ChangeType(InType);
	}
}

void UCWeaponComponent::ChangeType(EWeaponType InType)
{
	EWeaponType prevType = Type;
	Type = InType;

	if (OnWeaponTypeChange.IsBound())
		OnWeaponTypeChange.Broadcast(prevType, InType);
}

1. 초반에 주석처리 해놓은 Equip 주석제거 후 동작 나오도록 만들어준다.

 

https://www.youtube.com/watch?v=_86QQJRXaLc 

 

'언리얼' 카테고리의 다른 글

UE4 AnimNotify, ComboState, TakeDamage  (0) 2023.05.08
UE4 Equip, DoAction  (2) 2023.05.04
UE4 Notify, Attach, Weapon  (0) 2023.04.28
UE4 Component, BackStep  (0) 2023.04.27
UE4 Tool Bar, Player  (0) 2023.04.26
Comments