공부/Unreal

24.07.31

월러비 2024. 8. 4. 15:32

무기 시스템 예제

무기 장착 해제

  • 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를 활성화하는지를 담당한다.
    1. CPlayer에 있는 InputComponent의 2번에 Sword를 등록했다.
    2. 2번을 누르면 연결되어있는 WeaponComponent에 있는 SetSwordMode()를 호출한다.
    3. SetSowrdMode는 DataAssets에 있는 Equipment를 가져온다.
    4. WeaponAsset에 있는 GetEquipment가 호출된다.
    5. Equipment에 있는 Equip() 함수를 호출한다.
    6. Equip()에서 칼 빼는 동작을 PlayAnimMontage()를 호출해서 플레이시킨다.
    7. 플레이 되는 애니메이션에서 Notify 발생
    8. Notify는 WeaponComponent에서 GetEquipment()를 호출한다.
    9. Equipment의 Begin_Equip()을 호출한다.
    10. Equipment에서 장착이 시작될때 OnBeginEquip을 호출하라고 작성했다.
    11. Attachment에 Begin_Equip 명령을 OnBeginEquip과 연결(델리게이션)하고 호출한다.
      1. 2번을 눌러 명령이 OnBeginEquip으로 들어오면, 연결된 Attachment의 Begin_Equip이 호출된다.
      2. 이것을 Implementable해서 블프에서 재정의해서 사용한다.
      3. 블프에서 장착하는 Attach To를 연결해서 붙인다.

'공부 > Unreal' 카테고리의 다른 글

24.08.02  (0) 2024.08.07
24.08.01  (1) 2024.08.06
24.07.30  (0) 2024.08.01
24.07.29  (0) 2024.07.31
24.07.26  (0) 2024.07.31