무기 시스템 예제
무기 장착 해제
- CEquipment : 장착 관리
- 헤더 파일 - ‘장착 해제’ Native 함수와 Implementation 함수 선언 - CPP 파일 - ‘장착 해제’ Implementation 함수 정의 - ‘장착 데이터’의 ‘컨트롤 회전 사용’이 true 라면 : ‘움직임’ 변수의 ‘컨트롤 회전 사용 안함’ 함수 호출
//header UFUNCTION(BlueprintNativeEvent) void Unequip(); void Unequip_Implementation(); //CPP if (Data.bUseControlRotation) { Movement->DisableControlRotation(); } - CWeaponAsset : 장착, 무기 소환 등 관리
- 헤더 파일 - ‘Attachment 가져오기’ 함수를 선언하고 ‘Attachment’ 변수를 반환하여 정의한다.
//header FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; } - CWeaponComponent : 무기 관련 명령 내리기
- 헤더 파일 - ‘무기 장착 안함 모드’ 함수 선언 - ‘Attachment 가져오기’ 함수와 ‘Equipment 가져오기’ 함수 선언 - ‘무기 타입 변경’ 델리게이트를 ‘이전 무기 타입, 새로운 무기 타입’ 을 파라미터로 넣고 선언한다. - 델리게이션 자료형으로 ‘무기 타입 변경 실행’ 변수 선언
//header public: void SetUnarmedMode(); public: class ACAttachment* GetAttachment(); class UCEquipment* GetEquipment(); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType); public: FWeaponTypeChanged OnWeaponTypeChanged;- CPP 파일 - ‘Attachment 가져오기’ 함수 정의 - ‘무기 장착 안함 오드 확인’ 함수를 호출하고 true라면 nullptr을 반환한다. - ‘데이터 에셋’에서 ‘현재 무기 타입’의 순서가 비어있다면 nullptr을 반환한다. - 조건이 다 통과하면 ‘데이터 에셋’에서 ‘현재 무기 타입’의 순서에서 ‘Attachment 가져오기’ 함수를 호출한다. - ‘Equipment 가져오기’ 함수 정의 - ‘무기 장착 안함 오드 확인’ 함수를 호출하고 true라면 nullptr을 반환한다. - ‘데이터 에셋’에서 ‘현재 무기 타입’의 순서가 비어있다면 nullptr을 반환한다. - 조건이 다 통과하면 ‘데이터 에셋’에서 ‘현재 무기 타입’의 순서에서 ‘Equipment 가져오기’ 함수를 호출한다. - ‘무기 장착 안함 모드 설정’ 함수 정의 - ‘Equipment 가져오기’ 함수를 호출하고 ‘장착 해제’ 함수를 호출한다. - ‘타입 변경’ 함수를 ‘무기 타입 : Max’를 넣고 호출한다. - ‘모드 설정’ 함수 확인 - 현재 무기타입과 들어온 무기타입이 같을때 ‘무기 장착 안함 모드 설정’ 함수 호출하고 반환한다. - ‘무기 장착 안함 모드 확인’ 함수를 호출했는데 true 가 아닐 경우 : ‘Equipment 가져오기’ 함수의 ‘장착 해제’ 함수를 호출한다. - ‘데이터 에셋’에서 현재 무기 타입의 순서가 비어있지 않다면 : ‘데이터 에셋’에서 현재 무기 타입의 순서의 ‘Equipment 가져오기’함수를 호출하고 ‘장착’ 함수를 호출한다. - ‘타입 변경 함수를 매개변수로 들어온 ‘무기 타입’을 넣고 호출한다. - ‘타입 변경’ 함수 정의 - ‘현재 무기 타입’ 변수를 저장할 ‘이전 타입’ 변수 선언 - ‘현재 무기 타입’ 변수에 매개변수로 들어온 ‘무기 타입’을 저장한다. - ‘무기 타입 변경 실행’의 ‘연결 확인’ 함수를 호출해서 연결된 함수가 있다면 : ‘무기 타입 변경 실행’의 ‘연결 함수 실행’을 ‘이전 무기 타입, 새로운 무기 타입’을 넣어 호출한다.
//CPP ACAttachment* UCWeaponComponent::GetAttachment() { CheckTrueResult(ISUnarmedMode(), nullptr); CheckFalseResult(!!DataAssets[(int32)Type], nullptr); return DataAssets[(int32)Type]->GetAttachment(); } void UCWeaponComponent::SetUnarmedMode() { GetEquipment()->Unequip(); ChangeType(EWeaponType::Max); } void UCWeaponComponent::SetMode(EWeaponType InType) { //현재 들고 있는 무기와 같다면 : 무기 해제 if (Type == InType) { SetUnarmedMode(); //타입을 Max로 바꾼다. return; } else if (ISUnarmedMode() == false) //이미 장착 되어있다는 뜻이다. { //현재 장착 무기를 해제한다. => 현재무기를 다른 무기로 바꾼다. GetEquipment()->Unequip(); } if (!!DataAssets[(int32)InType]) { //무기 장착 DataAssets[(int32)InType]->GetEquipment()->Equip(); //타입 변경 //현재 장착된 무기 타입을 전달해서 다른걸로 바꾼다. ChangeType(InType); } } void UCWeaponComponent::ChangeType(EWeaponType InType) { EWeaponType prevType = Type; //현재 타입 저장 Type = InType; if (OnWeaponTypeChanged.IsBound()) { OnWeaponTypeChanged.Broadcast(prevType, InType); } } - 데이터 에셋 : 검 파일 - Equipment Data - ‘검 장착’ 몽타주 할당
검 손에 붙이기
- CEquipment
- 헤더 파일 - ‘장착 시작’ Native 함수와 Implementation 함수 선언 - ‘장착 종료’ Native 함수와 Implementation 함수 선언
//header UFUNCTION(BlueprintNativeEvent) void Begin_Equip(); void Begin_Equip_Implementation(); UFUNCTION(BlueprintNativeEvent) void End_Equip(); void End_Equip_Implementation();- CPP 파일 - ‘장착 종료’ Implementation 함수 정의 - ‘장착 데이터’의 ‘움직임 가능 확인’ 변수가 false 라면 : ‘움직임 컴포넌트’ 변수의 ‘움직임’ 함수를 호출한다. - ‘상태 컴포넌트’의 ‘아이들 모드 설정’ 함수를 호출한다.
//CPP if (Data.bCanMove == false) { Movement->Move(); } State->SetIdleMode();- 장착 상태 노티파이 CPP 파일 - 애니메이션을 사용하는 주체가 가진 웨폰 컴포넌트에서 이벤트 함수를 호출한다. - ‘컴포넌트 가져오기’ 함수의 자료형을 ‘웨폰 컴포넌트’로 지정하고 ‘매개변수의 메쉬 컴포넌트’의 ‘오저 가져오기’ 함수를 호출하여 넣고 웨폰 컴포넌트 클래스의 ‘웨폰’ 변수를 선언해서 저장한다. - ‘웨폰’이 비었는지 체크한다. - ‘웨폰’의 ‘Equipment 가져오기’가 비어있는지 확인한다. - ‘웨폰’의 ‘Equipment 가져오기’ 함수를 호출하고 ‘장착 시작’ 함수를 호출한다. ⇒ ‘장착 종료’ 이벤트도 똑같다.
//CPP //애니메이션 플레이 주체에서 웨폰 컴포넌트 가져오기 UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner()); CheckNull(weapon); CheckNull(weapon->GetEquipment()); weapon->GetEquipment()->Begin_Equip();
Equipment에서 Attachment에 명령을 전달 - 델리게이션
- CEquipment
- Attachment에 장착과 장착 시작을 알리는 역할이다.
- 장착 시작을 알려 검을 붙여야하지만, 장착 종료는 하는일이 없기에 알리지 않는다.
- 장착 해제는 알려서 무기를 없애야한다.
- 헤더 파일 -‘Equipment 장착 시작’ 델리게이션을 Dynamic과 MULTICAST 메크로로 선언한다. - ‘Equipment 장착 해제’ 델리게이션을 Dynamic과 MULTICAST 메크로로 선언한다. - ‘Equipment 장착 시작’ 델리게이션 자료형의 ‘Equipment 장착 시작 활성화’ 변수를 선언한다. - ‘Equipment 장착 해제’ 델리게이션 자료형의 ‘Equipment 장착 해제 활성화’ 변수를 선언한다.
//header DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEquipmentBeginEquip); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEquipmentUnequip); public: FEquipmentBeginEquip OnEquipmentBeginEquip; FEquipmentUnequip OnEquipmentUnequip;- CPP 파일 - ‘장착 시작 Implementation’ 함수 확인 - ‘Equipment 장착 시작 활성화’ 변수에 ‘바인딩 확인’ 함수를 호출해서 연결된 함수가 있다면 : ‘Equipment 장착 시작 활성화’변수의 ‘연결된 함수 모드 실행’ 함수를 호출한다. - ‘장착 해제 Implementation’ 함수 확인 - ‘Equipment 장착 해제 활성화’ 변수에 ‘바인딩 확인’ 함수를 호출해서 연결된 함수가 있다면 : ‘Equipment 장착 해제 활성화’변수의 ‘연결된 함수 모드 실행’ 함수를 호출한다.
//CPP void UCEquipment::Begin_Equip_Implementation() { if (OnEquipmentBeginEquip.IsBound()) { OnEquipmentBeginEquip.Broadcast(); } } void UCEquipment::Unequip_Implementation() { if (OnEquipmentUnequip.IsBound()) { OnEquipmentUnequip.Broadcast(); } }
실제로 손에 부착시키기
- CAttachment
- 헤더 파일 - ‘장착 시작 활성화’ 함수를 NativeEvent로 작성하고 Implementation 함수도 선언한다. - ‘장착 해제 활성화’ 함수를 NativeEvent로 작성하고 Implementation 함수도 선언한다.
//header public: UFUNCTION(BlueprintNativeEvent) void OnBeginEquip(); void OnBeginEquip_Implementation() {}; UFUNCTION(BlueprintNativeEvent) void OnUnequip(); void OnUnequip_Implementation() {}; - BP_CAttachment_Sword
- On Begin Equip 이벤트 검색 - Attach To 검색 후 연결 - On Unequip 이벤트 검색 - BeginPlay와 연결되어있는 Attach To 노드에 연결
- CWeaponAsset
- OnBeginEquip과 OnUnequip 이벤트를 호출하여 무기를 붙일때 서로간에 관여가 안되도록 상위 클래스(WeaponAsset)에서 작성한다.
- CPP 파일 - BeginPlay 함수의 EquipmentClass 확인 코드 - ‘Attachment’ 변수가 비어있지 않다면 : ‘Equipment’ 변수의 ‘Equipment 장착 시작 활성화’ 델리게이션 변수의 ‘함수 연결’ 함수에 ‘Attachment, Attachment의 ‘장착 시작 활성화’’ 넣어서 호출한다. - ‘Equipment’ 변수의 ‘Equipment 장착 종료 활성화’ 델리게이션 변수의 ‘함수 연결’ 함수에 ‘Attachment, Attachment의 ‘장착 종료 활성화’’ 넣어서 호출한다.
//CPP //위에서 어테치먼트가 생성 되었다면 if (!!Attachment) { Equipment->OnEquipmentBeginEquip.AddDynamic(Attachment, &ACAttachment::OnBeginEquip); Equipment->OnEquipmentUnequip.AddDynamic(Attachment, &ACAttachment::OnUnequip); } - 정리 : Equipment의 ‘Begin Equip_Implementation’이 호출되면 - WeaponAsset에서 Attachment에 연결된 함수(OnBeginEquip)를 호출한다.
무기 장착 상태에서 파쿠르 금지
- CPlayer
- CPP 파일 - ‘보조 액션 활성화’ 확인 - ‘상태 컴포넌트’의 ‘기본 모드 확인’을 호출해서 False인지 확인한다. - ‘웨폰 컴포넌트’의 ‘무기 장착 안함 모드 확인’ 함수를 호출해서 False인지 확인한다. - ‘땅인지 확인’ 함수 확인 - ‘상태 컴포넌트’의 ‘기본 모드 확인’을 호출해서 False인지 확인한다.
//CPP CheckFalse(State->IsIdleMode()); //아이들 모드 아니면 안한다. CheckFalse(Weapon->ISUnarmedMode()); //맨손만 한다. void ACPlayer::Landed(const FHitResult& Hit) { CheckFalse(State->IsIdleMode()); //아이들 모드 아니면 안한다. }
칼 장착 동작 사용
- CWeaponComponent
- EWeaponType을 블루프린트에서도 사용할 수 있도록해서 애니메이션 블렌드의 조건으로 사용한다.
- 헤더 파일 - EWeaponTYpe 확인 - UEnum에 Alt + W - BlueprintTYpe 체크 - 웨폰 타입 ‘타입 가져오기’ 함수를 선언하고 ‘현재 웨폰 타입’을 반환하는것으로 정의한다. (반환은 Enum으로 줘도 상관 없다. ,세팅만 SetUnarmedMode 같은 함수를 통해서 진행한다. , 리턴은 값이 바뀔리가 없다. ⇒ 값 확인용이다.)
//header UENUM(BlueprintType) public: FORCEINLINE EWeaponType GetType() { return Type; } - 플레이어 애니메이션 블루프린트 파일
- WeaponType에 생성하고 저장한 캐시 포즈를 연결하고 Enum Value를 받아서 지정한 포즈를 플레이 시키는 방식이다.
- 이전에 생성한 ‘Equipped’ 노드 삭제 - ‘Sword Pose 캐시 포즈 사용’과 ‘UnaramedPose 캐시 포즈 사용’가 연결된 ‘부울로 포즈 블렌딩’ 노드 삭제 - ‘블렌드 포즈(EWeaponType)’ 검색 - Active Enum Value 핀을 끌고 ‘Get Weapon Type’ 검색 - ‘디폰트 포즈’ 핀에 ‘UnaramedPose 캐시 포즈 사용’ 노드 연결 - 디폴드 포즈 핀 우클릭 : EWeaponType에 선언한 타입들이 목록에 나온다. - Sword 클릭하고 ‘Sword Pose 캐시 포즈 사용’ 노드 연결
- CAnimationInstance
- 삭제 이유 : 블루프린트에서 열거형을 직접 받아서 상태를 장착 상태를 변화하기 떄문이다.
- 변수 초기화 이유 : 프리뷰때문이다.
- 실행시 터지는 문제가 발생하는 생성도 있으니 변수를 생성하면 그냥 초기화를 하자
- 헤더 파일 - ‘장착됨’ 변수 삭제 - 웨폰 타입의 ‘웨폰 타입’ 변수를 생성하고 Max로 초기화한다. - 웨폰 타입을 사용하니 ‘웨폰 컴포넌트’ 헤더 파일을 include 한다. - 웨폰 컴포넌트 변수 선언
//header protected: UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation") EWeaponType WeaponType = EWeaponType::Max; class UCWeaponComponent* Weapon;- CPP 파일 - ‘웨폰’ 변수에 ‘컴포넌트 가져오기’ 함수 호출 후 저장 - ‘웨폰’ 변수가 비어있지 않다면 : ‘웨폰 타입’변수에 ‘웨폰’의 ‘타입 가져오기’ 함수를 호출해서 저장해라
//CPP Weapon = CHelpers::GetComponent<UCWeaponComponent>(OwnerCharacter); if (!!Weapon) { WeaponType = Weapon->GetType(); }
헤머 추가
- 메테리얼 파일이 깨져있다면 연결된 노드에 직접 텍스쳐를 넣어주면 된다.
- CAttachment를 상속하는 BP_CAttachment_Hammer 생성 - ‘스켈레탈 메시 컴포넌트’ 추가 - 헤머 스켈레탈 메시 할당 - ‘Capsule Collision’ 추가(원래는 봉 하나, 망치 하나로 2개를 준다.) - 콘텐츠 브라우저 - 우클릭 - 기타 - ‘데이터 에셋’ 클릭 - 생성한 CWeaponAsset 클릭 - Attachment Class에 생성한 BP_CAttachment_Hammer 할당 - 프로젝트 세팅 - 입력 - 액션 매핑 : 해머 추가 - 캐릭터 스켈레탈 마네킹 파일 - 헤머 소켓 추가
- CWeaponCOmponent
- 헤더 파일 - ‘헤머 모드 설정’ 함수 선언
//header void SetHammerMode(); //헤머 모드 설정- CPP 파일 - ‘헤머 모드 설장’ 함수 정의 - ‘기본 모드 확인’ 함수를 호출하고 False인지 확인한다. - ‘모드 설정’ 함수에 ‘웨폰 타입 : 헤머’를 넣고 호출한다.
//CPP void UCWeaponComponent::SetHammerMode() { //아이들 모드여야 실행한다. CheckFalse(IsIdleMode()); SetMode(EWeaponType::Hammer); } - CPlayer
- CPP 파일 - ‘인풋 컴포넌트’의 ‘액션 연결’ 함수를 (액션 매핑 이름, 액션 상태, 연결된 함수가 있는 클래스, 연결한 함수’를 넣고 호출한다.
//CPP PlayerInputComponent->BindAction("Hammer", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetHammerMode); - CEquipment
- Implementation이 아닌 ‘Begin_Equip’함수와 ‘End_Equip’으로 호출하는 이유 : Implementation은 C에서 정의한것이기 때문에 블루프린트에서는 호출이 안된다.
- CPP 파일 - ‘Equip_Implementation’ 함수 확인 - 몽타주가 있다면 몽타주를 플레이하지만, 몽타주가 없다면 : Equipment에 있는 ‘Begin_Equip’함수와 ‘End_Equip’ 함수를 호출한다.
//CPP if (!!Data.Montage) { OwnerCharacter->PlayAnimMontage(Data.Montage, Data.PlayRate); } else { Begin_Equip(); End_Equip(); } - 플레이어 블루프린트 파일
- 웨폰 컴포넌트 - 헤머 데이터 에셋 할당
- BP_CAttachment_Hammer
- 헤머는 뽑는 동작 없이 바로 무기가 장착된 동작으로 실행한다.
- 무기가 장착된 기본 동작 실행과 동시에 무기의 Visibility를 활성화해서 보이도록 한다.
- ‘BeginPlay’ 이벤트 검색 - ‘Attach To’ 검색 후 연결 - ‘Set Visibility’를 검색하고 연결한 뒤 ‘스켈레탈 메쉬’를 끌어서 ‘타깃’ 핀에 연결한다. - ‘On Begin Equip’ 검색 - ‘Set Visibility’ 검색 후 연결하고 ‘스켈레탈 메쉬’를 끌어서 ‘타깃’ 핀에 연결 - Unequip은 다시 끄는것이 첫 실행의 순서와 같으므로 ‘OnUnequip’ 이벤트를 검색해서 ‘BeginPlay’ 노드 순서에서 ‘Set Visibility’ 노드에 같이 연결한다.
- 헤머는 뽑는 동작 없이 바로 무기가 장착된 동작으로 실행한다.
- 테스트로 헤머 기본동작 연결 - 진짜 간단하게
- 애니메이션 블루프린트 파일 - 블렌드 포즈(EWeaponType) - Sword 포즈 우클릭 - Hammer 클릭 - 애니메이션 끌어서 연결

- 정리 : 헤머 무기 장착 실행 (장착 몽타주가 없다.) - 장착 몽타주가 없으니 Equipment의 ‘Begin_Equip’ 호출 - ‘OnEquipmentBeginEquip’에 연결된 함수가 있다면 실행 - WeaponAsset에서 ‘OnEquipmentBeginEquip’과 Attachment의 ‘OnBeginEquip’이 연결 되어있다. - Attachment의 ‘OnBeginEquip_Implementation’ 확인 : 정의가 없으므로 블루프린트 정의를 따른다. - ‘On Begin Equip’ 블루프린트 노드 확인 - 순서대로 실행
- Unequip도 같은 순서를 따른다.
지금까지의 무기시스템 정리
- ACharacter → CPlayer → BP_CPlayer
- UCWeaponComponent
- UCWeaponAsset
- ACAttachment → BP_CAttachment
- UCEquipment
- BeginPlay 실행 클래스 순서 (역순으로 실행된다.)
- BP_Player
- UCWeaponComponent : DataAssets에 있는 DA_Sword가 저장되어있다.
- UCWeaponAsset
- BP_Attachment_Sword : 무기를 홀스터에 스폰시킨다.
- 장착 실행 순서
- 이렇게 복잡하게 하는 이유 : 무기가 여러개 있으면 붙이는 위치도 다르고, 애니메이션도 다르기 때문이다.
- Equipment : 장착 수행, 장착 동작을 담당한다.
- Attachment : 무기가 어디에 붙는지, 무기의 Visibility를 활성화하는지를 담당한다.
- CPlayer에 있는 InputComponent의 2번에 Sword를 등록했다.
- 2번을 누르면 연결되어있는 WeaponComponent에 있는 SetSwordMode()를 호출한다.
- SetSowrdMode는 DataAssets에 있는 Equipment를 가져온다.
- WeaponAsset에 있는 GetEquipment가 호출된다.
- Equipment에 있는 Equip() 함수를 호출한다.
- Equip()에서 칼 빼는 동작을 PlayAnimMontage()를 호출해서 플레이시킨다.
- 플레이 되는 애니메이션에서 Notify 발생
- Notify는 WeaponComponent에서 GetEquipment()를 호출한다.
- Equipment의 Begin_Equip()을 호출한다.
- Equipment에서 장착이 시작될때 OnBeginEquip을 호출하라고 작성했다.
- Attachment에 Begin_Equip 명령을 OnBeginEquip과 연결(델리게이션)하고 호출한다.
- 2번을 눌러 명령이 OnBeginEquip으로 들어오면, 연결된 Attachment의 Begin_Equip이 호출된다.
- 이것을 Implementable해서 블프에서 재정의해서 사용한다.
- 블프에서 장착하는 Attach To를 연결해서 붙인다.
- 이렇게 복잡하게 하는 이유 : 무기가 여러개 있으면 붙이는 위치도 다르고, 애니메이션도 다르기 때문이다.