공부/Unreal

24.08.01

월러비 2024. 8. 6. 14:39

무기 시스템 예제

  • 무기 스폰 관리 : Attachment
  • 무기 장착 관리 : Equipment
  • 무기 공격 관리 : DoAction
    • DoAction을 C에서 상속해서 기능구현하고, 필요하면 블프로 만들어서 사용할 것이다.

헤머 추가 - (이전것 이어서 시작)무기 장착 이동 추가

  • BS_Hammer
    • 헤머 장착 이동 추가
  • 애니메이션 블루프린트
    • 스테이트 머신 추가 - Entry에 BS_Hammer 연결 - BS_Hammer 들어가기 - ‘Get Direction’ 검색 후 연결 - ‘Get Speed’ 검색 후 연결 - 생성한 Hammer 스테이트 머신 핀 끌고 ‘Save Pose’ 검색 후 이름변경 - 생성한 ‘Hammer Pose’ 검색 후 ‘EWeaponType 블렌드 포즈’의 Hammer 핀에 연결

공격 추가

  • CWeaponStructures - 여기부터 시작
    • 자기 자신의 데이터를 이용하기 위해 ‘구조체’ 안에 함수를 넣을 수 있다.
      • 즉, 구조체에 선언한 변수를 직접적으로 이용할 수 있는 함수를 구조체 안에 선언할 수 있다.
    • 헤더 파일 - FEquipmentData 요소 전체 복사 후 이름 변경 : FDoActionData - ‘카메라 사용’을 ‘카메라 고정’ 변수로 이름 변경 - ParticleSystem 클래스의 ‘이펙트’ 변수 선언 - FVector 자료형의 ‘이펙트 위치’ 변수를 선언하고 ‘벡터 제로 : (0, 0, 0)’으로 초기화한다. - FVector 자료형의 ‘이펙트 크기’ 변수를 선언하고 ‘벡터 1 : (1, 1, 1)’로 초기화한다. - 구조체 안에 선언된 변수들을 이용한 함수 : ‘DoAction’ 함수를 캐릭터 클래스의 ‘오너 캐릭터’를 넣어 선언한다.
    //hewader
    USTRUCT()
    struct FDoActionData
    {
    	GENERATED_BODY()
    
    public:
    	UPROPERTY(EditAnywhere)
    		class UAnimMontage* Montage; //공격 모션
    
    	UPROPERTY(EditAnywhere)
    		float PlayRate = 1;
    
    	UPROPERTY(EditAnywhere)
    		bool bCanMove = true;
    
    	UPROPERTY(EditAnywhere)
    		bool bFixedCamera;
    
    	UPROPERTY(EditAnywhere)
    		class UParticleSystem* Effect;
    
    	UPROPERTY(EditAnywhere)
    		FVector EffectLocation = FVector::ZeroVector;
    
    	UPROPERTY(EditAnywhere)
    		FVector EffectScale = FVector::OneVector;
    
    public:
    	void DoAction(class ACharacter* InOwner);
    };
    
    • CPP 파일 - ‘FDoActionData’ 구조체의 ‘DoAction’ 함수 정의 - 무브먼트 컴포넌트 ‘컴포넌트 생성’ 함수 호출해서 변수 선언하고 저장 - ‘무브먼트 컴포넌트’가 있다면 : ‘움직임 가능 확인’ 변수가 False 라면 : ‘무브먼트 컴포넌트’의 ‘정지’ 함수를 호출한다. - ‘몽타주’가 있다면 : ‘오너 캐릭터’의 ‘애님 몽타주 실행’ 함수를 ‘몽타주, 플레이 속도’를 넣어 호출한다.
    //CPP
    void FDoActionData::DoAction(ACharacter* InOwner)
    {
    	UCMovementComponent* movement = CHelpers::GetComponent<UCMovementComponent>(InOwner);
    
    	if (!!movement)
    	{
    		if (bCanMove == false)
    			movement->Stop();
    	}
    
    	if (!!Montage)
    		InOwner->PlayAnimMontage(Montage, PlayRate);
    }
    
  • CWeaponAsset
    • 콤보 공격이라면 공격 데이터가 여러개 필요하다.
      • FDoActionData 구조체 자료형의 변수를 ‘배열’로 선언해야한다.
      • 콤보공격의 콤보수를 정확히 모르니 ‘TArray’로 선언한다.
    • 헤더 파일 - FDoActionData 구조체 자료형의 ‘DoActionDatas’ 배열 변수 선언
    //header
    UPROPERTY(EditAnywhere)
    		TArray<FDoActionData> DoActionDatas;
    
  • CDoAction
    • AActor로부터 상속받지 않는 이상, 항상 ‘생성자’가 있다고 생각해라
      • Actor 상속 이외에는 그냥 거의 습관처럼 ‘생성자’를 만들어라
      • DoAction : Object로부터 상속받는다.
    • FDoActionData 구조체 자료형의 ‘InDoActionDatas’ 배열 변수를 단순하게 그냥 넣으면 호출될때마다 배열의 요소가 복사되어 들어가게 된다.
      • 해결 방법 : 매개 변수를 ‘레퍼런스 &’로 받는다.
    • 배열 변수의 내부요소가 복사되지 않는것도 좋지만 내부의 요소값이 수정되어서도 안된다.
      • 해결 방법 : ‘const 상수’로 선언한다.
    • 원래는 구조체 자료형의 배열 변수를 사용할때는 ‘주소 복사’로 클론을 만들어 사용했어야했다.
    • 헤더 파일 - 생성자 추가 - ‘임의의 BeginPlay’ 함수 선언 : Character ‘오너 캐릭터’ 변수, FDoActionData 구조체 자료형의 ‘InDoActionDatas’ const 레퍼런스 배열 변수 를 넣어 선언 - 받아온 DoActionData들을 받아서 사용할 수 있도록 FDoActionData 구조체 자료형의 배열 변수를 선언한다. - 매개변수로 받은 오너 캐릭터를 정장해서 사용하기 위해 ‘오너 캐릭터’ 변수를 선언한다. - 공격에는 소환이 많아 월드도 많이 싸용하기 때문에 ‘월드’ 변수도 선언 - ‘무브먼트 컴포넌트’ 변수와 ‘스테이트 컴포넌트’ 변수도 생성한다. - ‘공격 수행’, ‘공격 수행 시작’, ‘공격 수행 종료’ 함수를 선언하고 재정의가 가능하도록 virtual로 선언한다.
    //header
    public:
    	UCDoAction();
    
    	void BeginPlay
    	(
    		class ACharacter* InOwner,
    		const TArray<FDoActionData>& InDoActionDatas
    	);
    	
    public:
    	virtual void DoAction();
    	virtual void Begin_DoAction();
    	virtual void End_DoAction();
    	
    protected:
    	class ACharacter* OwnerCharacter;
    	class UWorld* World;
    
    	class UCMovementComponent* Movement;
    	class UCStateComponent* State;
    
    	TArray<FDoActionData> DoActionDatas;
    
    • CPP 파일 - ‘임의의 BeginPlay’ 함수 정의 - 매개변수로 받은 오너 캐릭터를 ‘오너 캐릭터’에 저장 - ‘오너 캐릭터’의 ‘월드 가져오기’ 함수를 호출해서 ‘월드’ 변수에 저장 - ‘무브먼트 컴포넌트’와 ‘스테이트 컴포넌트’ 변수에 ‘콤포넌트 가져오기’ 함수를 호출해서 저장한다. - 매개변수로 들어온 DoActionDatas를 ‘DoActionDatas’에 저장한다. - ‘DoAction’ 함수 정의 - ‘스테이트 컴포넌트’의 ‘공격 모드 설정’ 함수 호출 - ‘End_DoAction’ 함수 정의 - ‘스테이트 컴포넌트’의 ‘기본 모드 설정’ 함수 호출 - ‘무브먼트 컴포넌트’의 ‘이동’ 함수 호출
    //CPP
    void UCDoAction::BeginPlay(ACharacter* InOwner, const TArray<FDoActionData>& InDoActionDatas, const TArray<FHitData>& InHitDatas)
    {
    	OwnerCharacter = InOwner;
    	World = OwnerCharacter->GetWorld();
    
    	State = CHelpers::GetComponent<UCStateComponent>(OwnerCharacter);
    	Movement = CHelpers::GetComponent<UCMovementComponent>(OwnerCharacter);
    
    	DoActionDatas = InDoActionDatas;
    }
    
    void UCDoAction::DoAction()
    {
    	State->SetActionMode();
    }
    
    void UCDoAction::End_DoAction()
    {
    	State->SetIdleMode();
    
    	Movement->Move();
    }
    
  • CStateComponent
    • 헤더 파일 - ‘공격 모드 설정’ 함수 선언
    //header
    void SetActionMode();
    
    • CPP 파일 - ‘공격 모드 설정’ 함수 정의 - ‘타입 변경’ 함수에 ‘상태 타입 : 공격’을 넣고 호출한다.
    //CPP
    void UCStateComponent::SetActionMode()
    {
    	ChangeType(EStateType::Action);
    }
    
  • CDoAction_Combo
    • C++ 클래스를 상속받는 C++ 클래스를 생성할때는 상속받는 클래스의 이름을 앞에 남겨두고 뒤에 어떤 기능을 추가하는지 써놓는것이 좋다.
    • 공격 기능 중 ‘콤보’ 만을 관리하는 클래스다.
    • 필요한 요소는 부모인 CDoAction 에서 저장했으므로 정의할 함수들만 정의한다.
    • DoActionDatas의 요소 수가 1보다 작다는것은 : 공격할 데이터가 하나도 없다는 것이다.
      • 공격할 수 없다.
    • 헤더 파일 - ‘콤보 수’, ‘콤보 허용 확인’, ‘콤보 존재 확인’ 변수 선언 - ‘콤보 허용’ 함수를 선언하고 ‘콤보 허용 확인’ 변수에 True를 저장하는것으로 정의한다. - ‘콤보 비허용’ 함수를 선언하고 ‘콤보 허용 확인’ 변수에 False를 저장하는것으로 정의한다. - ‘공격 수행’, ‘공격 수행 시작’, ‘공격 수행 종료’ 함수를 선언하고 override로 선언한다.
    //header
    private:
    	int32 Index;
    
    	bool bEnable;
    	bool bExist;
    
    public:
    	FORCEINLINE void EnableCombo() { bEnable = true; }
    	FORCEINLINE void DisableCombo() { bEnable = false; }
    
    public:
    	void DoAction() override;
    	void Begin_DoAction() override;
    	void End_DoAction() override;
    
    • CPP 파일 - ‘DoAction’ 함수 정의 - 부모에 있는 ‘DoActionDatas’의 ‘수 반환’ 함수를 호출하고 1보다 작은지 확인한다. - ‘콤보 허용 확인’이 True 라면 : ‘콤보 허용확인’은 False로, ‘콤보 존재 확인’은 True로 저장한다. - 콤보 허용 구간이 아니라면 : ‘스테이트 컴포넌트’의 ‘기본 모드 확인’ 함수를 호출해서 False인지 확인한다. (최초공격을 할 수있는 상태인지 알아봐야한다.) - 부모의 ‘DoAction’을 호출한다. (부모의 DoAction에는 SetActionMode 가 있다.) - ‘DoActionDatas’의 ‘콤보 수’ 순번에서 ‘DoAction’에 ‘오너 캐릭터’를 넣고 호출한다. - Begin_DoAction’ 함수 정의 - 부모의 ‘Begin_DoAction’ 함수 호출 - ‘콤보 존재 확인’ 변수가 False인지 확인한다. - 존재한다면 ‘콤보 존재 확인’ 변수에 False를 저장한다. - ‘DoActionDatas’에서 ‘1 증가된 콤보수’의 순번의 ‘DoAction’ 함수에 ‘오너 캐릭터’를 넣어 호출한다. - ‘End_DoAction’ 함수 정의 - 부모 함수 호출 - ‘콤보 수’ 0으로 초기화
    //CPP
    void UCDoAction_Combo::DoAction()
    {
    	CheckTrue(DoActionDatas.Num() < 1); //
    
        if (bEnable)
        {
            bEnable = false;
            bExist = true;
    
            return;
        }
    
        //아이들 모드 아니면 최초 공격 안나가게 한다.
        CheckFalse(State->IsIdleMode());
    
        Super::DoAction();
        DoActionDatas[Index].DoAction(OwnerCharacter);
    
    }
    
    void UCDoAction_Combo::Begin_DoAction()
    {
        Super::Begin_DoAction();
    
        CheckFalse(bExist);
    
        bExist = false;
        DoActionDatas[++Index].DoAction(OwnerCharacter);
    
    }
    
    void UCDoAction_Combo::End_DoAction()
    {
        Super::End_DoAction();
    
        Index = 0;
    }
    
    • 즉, ‘DoActionDatas’의 ‘콤보 수’ 순번에서 ‘DoAction’에 ‘오너 캐릭터’를 넣고 호출하면 → WeaponStructures의 ‘DoAction’ 함수를 실행하고 결과적으로는, 움직임이 정지하고 공격 몽타주가 실행된다.
  • CWeaponAsset
    • 헤더 파일 - DoAction 클래스의 변수를 전언하고 ‘클래스 제한’으로 선언한다.
    //header
    UPROPERTY(EditAnywhere)
    		TSubclassOf<class UCDoAction> DoActionClass;
    
  • CPlayer
    • 공격 액션 연결
    • CPP 파일 - 인풋 컴포넌트의 ‘액션 연결’ 함수에 ‘액션 맵핑 이름, 액션 상태, 액션 연결 함수가 있는 클래스, 연결 함수’를 넣고 호출한다.
    //CPP
    PlayerInputComponent->BindAction("Action", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::DoAction);
    
  • CWeaponComponent
    • 헤더 파일 - DoAction 클래스의 ‘DoAction 가져오기’ 함수 선언
    //header
    class UCDoAction* GetDoAction();
    
    • CPP 파일 - ‘Equipment 가져오기’ 함수를 복붙하고 이름을 DoAction으로 바꾼다.
    //CPP
    UCDoAction* UCWeaponComponent::GetDoAction()
    {
    	CheckTrueResult(ISUnarmedMode(), nullptr);
    	CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
    
    	return DataAssets[(int32)Type]->GetDoAction();
    }
    
  • CWeaponAsset
    • 헤더 파일 - ‘Equipment 가져오기’ 함수를 복붙해서 DoAction으로 바꾼다. - DoAction 클래스 자료형의 변수를 선언한다.
    //header
    FORCEINLINE class UCDoAction* GetDoAction() { return DoAction; }
    
    class UCDoAction* DoAction; //장착 관리 담당
    
    • CPP 파일 - BeginPlay 함수 확인 - ‘DoAction 클래스’ 변수가 비어있지 않다면 : ‘DoAction’ 변수에 해당 자료형으로 ‘오브젝트 생성’ 함수를 호출하고 저장한다. - ‘DoAction’ 변수의 ‘BeginPlay’ 함수에 ‘오너 캐릭터, DoActionDatas’를 넣고 호출한다.
    //CPP
    if (!!DoActionClass)
    	{
    		DoAction = NewObject<UCDoAction>(this, DoActionClass);
    		DoAction->BeginPlay(InOwner, DoActionDatas, HitDatas);
    	}
    
  • CWeaponComponent
    • 헤더 파일 - ‘DoAction’ 함수 선언
    //header
    public:
    	void DoAction();
    
    • CPP 파일 - ‘DoAction’ 함수 정의 - ‘DoAction 가져오기’ 함수를 호출하고 비어있지 않다면 : ‘DoAction 가져오기’ 함수 호출의 ‘DoAction’ 함수를 호출한다.
    //CPP
    void UCWeaponComponent::DoAction()
    {
    	if (!!GetDoAction())
    	{
    		GetDoAction()->DoAction();
    	}
    }
    
  • DA_Sword
    • Do Action Datas 배열에 콤보 공격 데이터 넣기

공격 후 다시 공격하기

  • 노티파이 - 공격 시작, 공격 종료
    • CPP 파일 - DoAction 헤더 include - ‘웨폰 컴포넌트’를 ‘컴포넌트 가져오기’ 함수를 호출해서 저장한다. - ‘웨폰’이 비어있는지 확인 - ‘웨폰’의 ‘Equipment 가져오기’ 함수를 호출해서 비었는지 확인하기 - ‘웨폰’의 ‘Equipment 가져오기’ 함수를 호출하서 ‘Begin_Equip’ 함수를 호출한다.
    • 공격 종료는 공격 시작에서 이름만 변경한다.
    //CPP
    //애니메이션 플레이 주체에서 웨폰 컴포넌트 가져오기
    UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
    CheckNull(weapon);
    CheckNull(weapon->GetDoAction());
    
    weapon->GetDoAction()->Begin_DoAction();
    
  • 노티파이 스테이트 - 콤보
    • 콤보 관리는 CDoAction_Combo에서 담당하고 있다.
      • 즉, WeaponCOmponent에서 GetDoAction을 호출해서 DoAction_Combo로 다운 캐스팅 될 수 있는지 확인하고, DoAction_Combo의 ‘EnableCombo’ 함수와 ‘DisableCombo’를 호출해야한다.
    • CPP 파일 - ‘웨폰 컴포넌트’ 가져와서 ‘웨폰’ 변수와 ‘GetDoAction’ 이 가져와젔는지는 똑같다. - ‘웨폰’ 변수의 ‘GetDoAction’을 호출을 ‘DoAction_Combo’ 자료형으로 ‘Cast’ 함수를 호출해서 ‘DoAction_Combo’ 클래스 포인터 변수로 선언하고 저장한다. - ‘콤보’가 비어있는지 확인한다. - 콤보의 ‘콤보 허용’ 함수를 호출한다.
    • 콤보 노티파이 스테이트 종료는 DisableCombo만 바꿔주면 된다.
    //CPP
    //애니메이션 플레이 주체에서 웨폰 컴포넌트 가져오기
    UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
    CheckNull(weapon);
    CheckNull(weapon->GetDoAction());
    
    UCDoAction_Combo* combo = Cast<UCDoAction_Combo>(weapon->GetDoAction());
    CheckNull(combo);
    
    combo->EnableCombo();
    

타격 히트 시키기

  • 노티파이 스테이트 - 충돌
    • CPP 파일 - 기존 스테이트 코드에서 ‘충돌 활성화’와 ‘충돌 비활성화’로 바꿔준다.
  • CAttachment
    • 충돌체가 몇개인지, 어떤 충돌체 모양인지도 모르니 ‘ShapeComponent’ 클래스의 배열로 지정한다.
      • 메쉬 컴포넌트 같이 캐스트가 안되는 요소는 Null로 반환되고 이외의 충돌체들은 객체가 저장된다.
    • 헤더 파일 - ShapeComponent 클래스의 배열 변수 ‘충돌체들’을 선언한다. - ‘충돌 활성화’ 함수와 ‘충돌 비활성화’ 함수를 선언한다. - ‘오버랩 시작 활성’ 함수와 ‘오버랩 종료 활성’ 함수를 선언한다.
    //header
    protected:
    	TArray<class UShapeComponent*> Collisions;
    	
    public:
    	void OnCollisions();
    	void OffCollisions();
    
    • CPP 파일 - BeginPlay 확인 - 블프 시작 전 확인 - SceneComponent 클래스 배열 변수 ‘아이들’ 선언 - ‘루트’의 ‘아이들 가져오기’ 함수에 ‘자식의 자식까지 전부 찾을건지 여부, 찾은 결과를 저장할 ‘아이들’ 변수’를 넣고 호출한다. - ‘아이들’의 요소를 하나씩 꺼내 ‘아이’ 변수에 넣고 반복을 시작한다. - ‘아이’를 ShapeComponent 자료형을 넣고 ‘Cast’ 함수를 호출해서 ShapeComponent 변수를 선언하고 저장한다. - ‘shape’가 비어있지 않다면 : ‘shape’의 ‘오버랩 시작’ 함수의 ‘함수 연결’에 ‘this, 연결할 함수’를 넣고 호출한다. - 오버랩 종료 함수도 같다. - ‘충돌체들’의 ‘추가’ 함수에 ‘shape’를 넣고 호출한다. - 반복이 끝나면 ‘충돌 비활성화’ 함수를 호출한다. - ‘충돌 활성화’ 함수 정의 - ‘충돌체들’의 요소들을 하나씩 ‘shape’ 변수에 넣고 반복한다 - ‘shape’의 ‘충돌 허용 설정’ 함수에 ‘충돌 설정 : QueryAndPhysics’를 넣고 호출한다. - ‘충돌 비활성화’ 함수 정의 - ‘충돌체들’의 요소들을 하나씩 ‘shape’ 변수에 넣고 반복한다 - ‘shape’의 ‘충돌 허용 설정’ 함수에 ‘충돌 설정 : NoCollision’를 넣고 호출한다. - ‘오버랩 시작 활성’ 함수 정의 - ‘오너 캐릭터’가 ‘충돌된 액터’와 같은지 확인한다. - ‘오버랩 종료 활성’ 함수 정의 - ‘오너 캐릭터’가 ‘충돌된 액터’와 같은지 확인한다.
    //CPP
    TArray<USceneComponent*> children;
    	Root->GetChildrenComponents(true, children);
    
    	for (USceneComponent* child : children)
    	{
    		UShapeComponent* shape = Cast<UShapeComponent>(child);
    
    		if (!!shape)
    		{
    			shape->OnComponentBeginOverlap.AddDynamic(this, &ACAttachment::OnComponentBeginOverlap);
    			shape->OnComponentEndOverlap.AddDynamic(this, &ACAttachment::OnComponentEndOverlap);
    
    			Collisions.Add(shape);
    		}
    	}
    
    	OffCollisions();
    	
    	void ACAttachment::OnCollisions()
    {
    	for (UShapeComponent* shape : Collisions)
    	{
    		shape->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    	}
    }
    
    void ACAttachment::OffCollisions()
    {
    	for (UShapeComponent* shape : Collisions)
    	{
    		shape->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    	}
    }
    
    void ACAttachment::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
    {
    	CheckTrue(OwnerCharacter == OtherActor);
    
    	CLog::Print("Begin : " + OwnerCharacter->GetName(), 9998);
    }
    
    void ACAttachment::OnComponentEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
    {
    	CheckTrue(OwnerCharacter == OtherActor);
    
    	CLog::Print("End : " + OwnerCharacter->GetName(), 9999);
    }
    

기반 블루프린트 클래스를 생성할 수 없는 클래스

  • UCLASS(BluprintType)에서 BluprintType 이라는 요소가 블루프린트에서 해당 클래스를 ‘타입’으로 다룰 수 있게하는 기능이다.
  • Blueprintable : 블루프린트 화가 가능하다는 기능이다.
  • UCLASS() 의 ()괄호 안에 Blueprintable이 없다면 기본값은 ‘Not Bluprintable’ 이다.
  • abstract : 블루프린트 클래스의 배치를 막는 기능이다. (나중에 다시 배운다.)

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

24.08.05  (0) 2024.08.07
24.08.02  (0) 2024.08.07
24.07.31  (0) 2024.08.04
24.07.30  (0) 2024.08.01
24.07.29  (0) 2024.07.31