🎮Unreal4/C++

[UE4] Equip/Unequip (WeaponAsset, WeaponComponent, Equipment, Attachment, AnimNotifyState_Equip)

공대 컴린이 2023. 4. 12. 19:29
728x90

Equip 기능을 구현하기 위해서 먼저, Equip과 관련된 객체 및 데이터를 WeaponAsset 클래스(Data Asset)에 초기화시켰다.

💻 WeaponAsset : DataAsset

이전에는 AttachmentClass와, EquipmentClass까지만 선언하여, DataAsset의 구조만 잡아놨었다.

이번에는 Attachment 객체와 Equipment 객체를 직렬화하여 선언하고, 객체를 반환해주는 Get함수까지 제작하였다.

 

이때 Attachment와 Equipment에는 UPROPERTY로 각각 직렬화를 수행하였는데, 이는 두 객체가 NULL이 되는 상황이 와도 가비지컬렉터에 의해 제거되지않도록 하기 위함이다.

 

생성자에서는 AttachmentClass와 EquipmentClass의 기본 선택사항으로 이전에 만들어두었던 CAttachment, CEquipment로 초기화 해 두었다.

 

BeginPlay에서는 AttachmentClass가 null 이 아니라면, Character를 Owner로 받고, Owner의 World에 Attachment를 배치하여 Attachment 객체를 초기화하였다. (Attachment가 무기 이다!)

 

또한 EquipmentClass가 null아 아니라면, NewObject<>() 함수Equipment 라는 동적 객체를 생성하였다.

평소 컴포넌트를 생성할 때 사용했던 CreateDefaultSubobject 함수는 사실 컴포넌트를 생성하는 함수가 아니라, 생성자의 동적할당 함수이다.

 

NewObject 함수는 게임중(Runtime)에서 사용할 수 있는 동적할당 함수이다.

첫번째 매개변수로 들어가는 UObject* Outer이 클래스에 귀속될 곳을 전달해줘야 한다. 귀속이 없다면 nullptr을 전달하면 된다. 나는 this를 전달하여 나중에 Owner를 구할 수 있도록 하였다.

두번째 매개변수인 UClass& Class어떤 Class타입으로 만들어질것인지를 전달하므로, EquipmentClass를 전달하였다.

 

이후 Equipment의 BeginPlay에 Equipment 데이터를 전달하여 각 무기별 WeaponAsset이 생성될 때, 무기별 EquipData가 설정되도록 하였다.

 

Attachment가 null이 아니라면, Equipment 객체에 생성해두었던 BeginEquip, Unequip 델리게이트에 Attachment의 OnBeginEquip, OnUnequip 함수를 등록해두었다.

(Attachment에 관한 설명은 현재 글의 아래에 설명해두었다.)

 

이후, 무기의 관리는 WeaponComponent에서 담당하도록 구현하였다.

📦 WeaponComponent

Weapon Component에서는

Weapon의 종류를 나타내는 Enum 클래스(EWeaponType)

무기가 바뀔때마다 호출될 다이나믹 멀티캐스트 델리게이트(FWeaponTypeChanged),

무기의 착용여부를 알려주는 함수들(Is~)과, 

무기의 착용을 명령하는 함수들(Set~), 

무기별 DataAsset 객체와, 무기와 조합되어 작동할 GetAttachment, GetEquipment 함수가 존재한다.

 

Weapon의 관리 컴포넌트를 제작 완료하였으니, 이제 실제 Weapon을 제작해보았다.

🗡️ OneHand - Sword 만들기

CAttachment 클래스의 블루프린트 기반 클래스를 생성하여 OneHand 무기 액터를 생성하였다.

 

Root 컴포넌트는 Cpp 클래스에서 미리 생성해두었으니, 스켈레탈 메시를 추가한 뒤, Sword 메시를 적용시켜주었다.

 

이후, OneHand의 DataAsset 클래스에 Attachment Class를 방금 만든 BP_OneHand 클래스로 지정하고, Equip 몽타주를 Sword의 Draw 몽타주로 설정해놓았다.

 

이렇게 무기별 DataAsset을 만들어놓으면, 프로그래머나 디자이너가 Attachment, Equipment를 조합하여 다양한 기능을 실행시킬 수 있다.

 

무기 제작까지 다 마무리 되었다면, Equip/Unequip 기능을 구현하기 위한 재료들이 모두 준비되어 있는것이다.

따라서 실질적인 Equip/Unequip을 수행시킬 Equipment 클래스에 기능을 구현하였다.

💻 Equipment

Equipment 클래스에선 Equip, Begin_Equip, Unequip 함수를 각각 선언하였다.

이때, BlueprintNativeEvent 속성으로 직렬화하여 블루프린트에서 재정의하여 사용할 수 있도록 구현하였다.

BlueprintNativeEvent 로 직렬화 한 함수는 꼭 _Implementation 함수를 선언하여 내용을 작성해야한다는것을 기억하자!

 

또한 BeginEquip과 Unequip 시에 무기별로 추가될 동작이 있다면, 실행시킬 수 있도록 델리게이트를 각각 선언해주었다.

 

Equip 함수에서는 FEquipmentData 데이터(Data)를 참고하여 Movement를 설정하고, 몽타주의 유무에 따라 코드를 작성하였다.

몽타주가 존재한다면 캐릭터의 PlayAnimMontage 함수로 몽타주를 실행시키고, 몽타주가 존재하지 않는다면 Begin_Equip 함수와 End_Equip 함수를 즉시 호출해준다.

몽타주에서는 노티파이로 Begin_Equip과 End_Equip을 호출하기때문에 따로 호출해주지 않는다.

 

Begin_Equip 함수에서는 선언해두었던 BeginEquip 델리게이트에 등록된 함수가 있다면 해당 함수를 Broadcast 하도록 호출하였다.

 

End_Equip 함수에서는 캐릭터의 상태를 다시 Idle 모드로 돌아가도록 설정하고, Unequip 함수에서도 델리게이트에 등록된 함수를 확인하여 Broadcast 시켜주었다.

💻 AnimNotifyState_Equip

Equip 몽타주가 실행될 때 호출할 Equip 노티파이 스테이트를 생성하였다.

 

노티파이의 Begin과 End에서 호출되는 기능은 매우 간단하다.

MeshComponent의 소유자인 GetOwner WeaponComponent가 존재하고, WeaponComponent의 Equipment가 존재한다면 해당 Equipment의 Begin_Equip/End_Equip 함수를 호출하도록 하였다.

 

이후, 애니메이션 블루프린트에 무기별 Equip 몽타주를 출력할 수 있도록 WeaponType의 블렌드포즈를 설정하였다.

 

Equip 몽타주가 잘 실행된다면, 이제 실제로 무기를 플레이어의 손에 쥘 수 있도록 부착하는 기능이 있어야 한다.

💻 Attachment

Player의 Mesh에 무기를 부착할 소켓을 미리 만들어두었으므로, Attachment 클래스에서 무기를 부착하는 함수를 만들어주었다.

 

Attachment.h

무기를 캐릭터에게 Attach 하는 기능은 거의 블루프린트에서 사용할 것이다.

Cpp 클래스에서 할수도있지만, 요소가 자주 바뀌어 블루프린트에서 사용하는것이 편리하다. 

 

따라서 OnBeginEquip 함수와 OnUnequip 함수는 UFUNCTION의 BlueprintImplementableEvent 속성을 부여하여 블루프린트에서 재정의하여 사용하도록 설정하였고, Cpp 클래스에서 함수를 정의하지 않았다.

 

OnBeginEquip 함수와 OnUnequip 함수의 호출은 WeaponAsset 클래스의 BeginPlay 함수에서 실행된다.

 

Attachment.cpp

Attachment의 BeginPlay함수에서 부모의 BeginPlay 함수가 호출되기 전에 OwnerCharacter를 먼저 할당해줘야 한다.

블루프린트의 BeginPlay가 호출될 때 Super::BeginPlay가 호출되므로, 그 이전에 OwnerCharacter를 할당해야 한다.

 

AttachTo 함수는 블루프린트의 AttachActorToComponent 함수가 너무 복잡해서 편리하게 사용하기 위해 간략하게 만든 함수이다.

 

컴파일 후 OneHand의 Attachment 블루프린트에 Cpp에서 선언만 해두었던 On Begin Equip, On UnEquip 함수를 호출하였고, Attach To 함수를 호출하도록 재정의하여 사용해주었다.

 

Player.cpp

마지막으로 PlayerInputComponent에 Sword Equip 입력(숫자 2)을 등록해주었다.

 

프로그램을 실행시킨 뒤, 2번을 입력하면 OneHand 무기인 Sword가 잘 장착되고 해제되는것을 확인할 수 있었다.

728x90