목차

     

     


     

     

     
    Source
        Characters
        CAnimInstance.h .cpp
    CPlayer.h .cpp (DoAction 입력만 추가)
    ICharacter.h .cpp
        Components
        CMontagesComponent.h .cpp 
    CMovementComponent.h .cpp 
    CStateComponent.h .cpp 
    CWeaponComponent.h .cpp 
        Notifies
        CAnimNotify_EndState.h .cpp
    CAnimNotifyState_Equip.h .cpp 생성
        Utilities
        CHelper.h
    CLog.h .cpp
        Weapons
        CAttachment.h .cpp
    CDoAction.h .cpp 생성
    CEquipment.h .cpp
    CWeaponAsset.h .cpp
    CWeaponStructures.h .cpp
        Global.h
    CGameMode.h .cpp
    U2212_06.Build.cs
        U2212_06.uproject
     

     

     

     

     

    무기 장착 및 기본 공격하기

     

     


     

     

    프로그래밍 설계의 진화

     

    Top-Down 방식 

    • 초기(과거) 프로그래밍 설계
    • 기능함수, 데이터 변수 수정에 대한 비용이 너무 크다는걸 인지. 

     

    그 후에 이 문제를 해결하기 위해...

    객체(함수 + 변수)가 등장!!

     

    요즘은 객체를 사용하여 프로그래밍한다.

     

     

     

     


     

     

    Weapon 구조

     

           
       WeponComponent  WeaponAsset  Attachment
     Equipment
     - OnEquip Event
     - OnUnequp Event 
     DoAction

     

    같은 레벨의 클래스를 부르는 방법

    • Delegation 사용하는 방법
      • 부분적으로 사용할 때 가장 좋은 방법 
      • ex. WeaponComponent  →  WeaponAsset  →  Equipment(에서 Event 발생 시 Delegation과 엮여있는 Attachment 내의 함수를 호출)
    • Friend를 사용하는 방법
      • 클래스에서만 사용되는 부분은 private으로 막아준다.
      • 다른곳에서 사용되는 부분을 public으로 열어준다.

     


     

     

    CEquipment

     

    CEquipment.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "UObject/NoExportTypes.h"
    #include "Weapons/CWeaponStructures.h"
    #include "CEquipment.generated.h"
    
    //직렬화하여 BP에서 사용가능하도록 DYNAMIC, 여러함수를 받을 수 있도록 MULTICAST 사용.
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEquipmentBeginEquip);
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEquipmentUnequip);
    
    UCLASS()
    class U2212_06_API UCEquipment : public UObject
    {
    	GENERATED_BODY()
    
    public:
    	void BeginPlay(class ACharacter* InOwner, const FEquipmentData& InData);
    
    public:
    	UFUNCTION(BlueprintNativeEvent)//필요시 BP에서 재정의하기 위해 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();
    
    public:
    	FEquipmentBeginEquip OnEquipmentBeginEquip;//DECLARE_DYNAMIC_MULTICAST_DELEGATE 하였다.
    	FEquipmentUnequip OnEquipmentUnequip;//DECLARE_DYNAMIC_MULTICAST_DELEGATE 하였다.
    
    private:
    	class ACharacter* OwnerCharacter;
    	FEquipmentData Data;
    
    private:
    	class UCMovementComponent* Movement;
    	class UCStateComponent* State;
    
    private:
    	bool bBeginEquip;//Equip이 시작되었는가 확인
    	bool bEquipped;  //Equip이 완료되었는지 확인
    };

    델리게이트 생성

    • 직렬화하여 BP에서 사용가능하도록 DYNAMIC, 여러함수를 받을 수 있도록 MULTICAST 사용.
    • DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEquipmentBeginEquip);
    • DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEquipmentUnequip);

    델리게이트를 사용한 함수 선언

    • FEquipmentBeginEquip OnEquipmentBeginEquip;
    • FEquipmentUnequip OnEquipmentUnequip;

     

     

    CEquipment.cpp

    더보기
    #include "Weapons/CEquipment.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;
    
    	if (OnEquipmentBeginEquip.IsBound())
    		OnEquipmentBeginEquip.Broadcast();
    }
    
    void UCEquipment::End_Equip_Implementation()
    {
    	bBeginEquip = false;
    	bEquipped = true;
    
    	Movement->Move();
    	State->SetIdleMode();
    }
    
    void UCEquipment::Unequip_Implementation()
    {
    	bEquipped = false;
    	Movement->DisableControlRotation();
    
    	if (OnEquipmentUnequip.IsBound())
    		OnEquipmentUnequip.Broadcast();
    }

    Begin_Equip_Implementation()

    • OnEquipmentBeginEquip.Broadcast(); 해준다.

    Unequip_Implementation()

    • OnEquipmentUnequip.Broadcast(); 해준다.

     


     

    CAttachment   +  BP_CAttachment_Sword

     

    CAttachment.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "CAttachment.generated.h"
    
    UCLASS()
    class U2212_06_API ACAttachment : public AActor
    {
    	GENERATED_BODY()
    
    protected:
    	UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
    		class USceneComponent* Root;
    	
    public:	
    	ACAttachment();
    
    protected:
    	virtual void BeginPlay() override;
    
    public:
    	UFUNCTION(BlueprintImplementableEvent)
    		void OnBeginEquip();
    
    	UFUNCTION(BlueprintImplementableEvent)
    		void OnUnequip();
    
    protected:
    	UFUNCTION(BlueprintCallable, Category = "Attach")
    		void AttachTo(FName InSocketName);
    
    protected:
    	UPROPERTY(BlueprintReadOnly, Category = "Game")
    		class ACharacter* OwnerCharacter;
    };

    UFUNCTION(BlueprintImplementableEvent)
    void OnBeginEquip();

    UFUNCTION(BlueprintImplementableEvent)
    void OnUnequip();

     

     

    CAttachment.cpp

    더보기
    #include "Weapons/CAttachment.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    #include "Components/SceneComponent.h"
    
    ACAttachment::ACAttachment()
    {
    	CHelpers::CreateComponent(this, &Root, "Root");
    }
    
    void ACAttachment::BeginPlay()
    {
    	OwnerCharacter = Cast<ACharacter>(GetOwner());
    	//ACharacter를 먼저 Cast 한 후에 Super::BeginPlay() 호출.
    	Super::BeginPlay();
    }
    
    void ACAttachment::AttachTo(FName InSocketName)
    {
    	AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), InSocketName);
    }

    변경사항 없음.

     

     

     

    BP_CAttachment_Sword


     

     

    CAnimNotifyState_Equip 생성  +  Draw_Sword_Montage에 할당

     

    새 C++ 클래스 - AnimNotifyState - CAnimNotifyState_Equip 생성

     

     

    CAnimNotifyState_Equip.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Animation/AnimNotifies/AnimNotifyState.h"
    #include "CAnimNotifyState_Equip.generated.h"
    
    UCLASS()
    class U2212_06_API UCAnimNotifyState_Equip : public UAnimNotifyState
    {
    	GENERATED_BODY()
    
    public:
    	FString GetNotifyName_Implementation() const override;
    
    	virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
    	virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
    };

    NotifyBegin, NotifyEnd 오버라이드

    • virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
    • virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;

     

     

     

    CAnimNotifyState_Equip.cpp

    더보기
    #include "Notifies/CAnimNotifyState_Equip.h"
    #include "Global.h"
    #include "Components/CWeaponComponent.h"
    #include "Weapons/CEquipment.h"
    
    FString UCAnimNotifyState_Equip::GetNotifyName_Implementation() const
    {
    	return "Equip";
    }
    
    void UCAnimNotifyState_Equip::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
    {
    	Super::NotifyBegin(MeshComp, Animation, TotalDuration);
    	CheckNull(MeshComp);
    	CheckNull(MeshComp->GetOwner());
    
    	UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
    	CheckNull(weapon);
    	CheckNull(weapon->GetEquipment());
    
    	weapon->GetEquipment()->Begin_Equip();
    }
    
    void UCAnimNotifyState_Equip::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
    {
    	Super::NotifyEnd(MeshComp, Animation);
    	CheckNull(MeshComp);
    	CheckNull(MeshComp->GetOwner());
    
    	UCWeaponComponent* weapon = CHelpers::GetComponent<UCWeaponComponent>(MeshComp->GetOwner());
    	CheckNull(weapon);
    	CheckNull(weapon->GetEquipment());
    
    	weapon->GetEquipment()->End_Equip();
    }

     

     

     

    Draw_Sword_Montage

    AnimNotifyState_Equp 할당

     


     

     

     

    실행화면

     

     


     

     

     

     

     

     

    Sword Mode 애니메이션 

     


     

     

    CAnimInstance

     

    CAnimInstance.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Animation/AnimInstance.h"
    #include "Components/CWeaponComponent.h"
    #include "CAnimInstance.generated.h"
    
    UCLASS()
    class U2212_06_API UCAnimInstance : public UAnimInstance
    {
    	GENERATED_BODY()
    
    protected:
        UPROPERTY(BlueprintReadOnly, Category = "Animation")
            float Speed;
    
        UPROPERTY(BlueprintReadOnly, Category = "Animation")
            float Pitch;
    
        UPROPERTY(BlueprintReadOnly, Category = "Animation")
            float Direction;
    protected:
        UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
            EWeaponType WeaponType = EWeaponType::Max;
    
    public:
        void NativeBeginPlay() override;
        void NativeUpdateAnimation(float DeltaSeconds) override;
    
    private:
        UFUNCTION()
            void OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType);
    
    private:
        class ACharacter* OwnerCharacter;
        class UCWeaponComponent* Weapon;
    
    private:
        FRotator PrevRotation;
    };

    헤더 추가

    • #include "Components/CWeaponComponent.h"

    변수 추가

    • UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
      EWeaponType WeaponType = EWeaponType::Max;
    • class UCWeaponComponent* Weapon;

    함수 추가

    •   UFUNCTION()
        void OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType);

     

     

     

    CAnimInstance.cpp

    더보기
    #include "Characters/CAnimInstance.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    
    void UCAnimInstance::NativeBeginPlay()
    {
    	Super::NativeBeginPlay();
    
    	OwnerCharacter = Cast<ACharacter>(TryGetPawnOwner());
    	CheckNull(OwnerCharacter);
    
    	Weapon = CHelpers::GetComponent<UCWeaponComponent>(OwnerCharacter);
    	if (!!Weapon)
    		Weapon->OnWeaponTypeChange.AddDynamic(this, &UCAnimInstance::OnWeaponTypeChanged);
    }
    
    void UCAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
    {
    	Super::NativeUpdateAnimation(DeltaSeconds);
    	CheckNull(OwnerCharacter);
    
    	Speed = OwnerCharacter->GetVelocity().Size2D();
    
    	FRotator rotator = OwnerCharacter->GetVelocity().ToOrientationRotator();
    	FRotator rotator2 = OwnerCharacter->GetControlRotation();
    	FRotator delta = UKismetMathLibrary::NormalizedDeltaRotator(rotator, rotator2);
    	PrevRotation = UKismetMathLibrary::RInterpTo(PrevRotation, delta, DeltaSeconds, 25);
    	Direction = PrevRotation.Yaw;
    
    	Pitch = UKismetMathLibrary::FInterpTo(Pitch, OwnerCharacter->GetBaseAimRotation().Pitch, DeltaSeconds, 25);
    }
    
    void UCAnimInstance::OnWeaponTypeChanged(EWeaponType InPrevType, EWeaponType InNewType)
    {
    	WeaponType = InNewType;
    }

     


     

    ABP_Character

     

     

    AnimGraph

     


     

    실행화면

     

     

     


     

     

     

     

    기본공격 구현

     


     

     

    CWeaponStructures

     

    CWeaponStructures.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "UObject/NoExportTypes.h"
    #include "CWeaponStructures.generated.h"
    
    USTRUCT()
    struct FEquipmentData
    {
    	GENERATED_BODY()
    
    public:
    	UPROPERTY(EditAnywhere)
    		class UAnimMontage* Montage;
    
    	UPROPERTY(EditAnywhere)
    		float PlayRate = 1;
    
    	UPROPERTY(EditAnywhere)
    		bool bCanMove = true;
    
    	UPROPERTY(EditAnywhere)
    		bool bUseControlRotation = true;
    };
    
    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 UFXSystemAsset* Effect; //사용할 Effect 변수
    
    	UPROPERTY(EditAnywhere)
    		FVector EffectLocation = FVector::ZeroVector;//(Effect)지정 방향의 보정치.
    
    	UPROPERTY(EditAnywhere)
    		FVector EffectScale = FVector::OneVector;//Effect 크기 기본값 1 설정.
    
    public:
    	void DoAction(class ACharacter* InOwner);
    };
    
    UCLASS()
    class U2212_06_API UCWeaponStructures : public UObject
    {
    	GENERATED_BODY()
    };

    이펙트에 사용될 변수 추가

    • UPROPERTY(EditAnywhere)
      class UFXSystemAsset* Effect;                           //사용할 Effect 변수
      FVector EffectLocation = FVector::ZeroVector;   //(Effect)지정 방향의 보정치.
      FVector EffectScale = FVector::OneVector;        //Effect 크기 기본값 1 설정.

    기본 공격 구현을 함수 추가

    • void DoAction(class ACharacter* InOwner);

     

     

    CWeaponStructures.cpp

    더보기
    #include "Weapons/CWeaponStructures.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    #include "Components/CStateComponent.h"
    #include "Components/CMovementComponent.h"
    #include "Animation/AnimMontage.h"
    
    void FDoActionData::DoAction(ACharacter* InOwner)
    {
    	UCMovementComponent* movement = CHelpers::GetComponent<UCMovementComponent>(InOwner);
    
    	if (!!movement)
    	{
    		if (bFixedCamera)
    			movement->EnableFixedCamera();
    
    		if (bCanMove == false)
    			movement->Stop();
    	}
    
    	if (!!Montage)
    		InOwner->PlayAnimMontage(Montage, PlayRate);
    }

     


     

     

    CDoAction 생성

     

    새 C++ 클래스 - Object - CDoAction 생성

     

     

    CDoAction.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "UObject/NoExportTypes.h"
    #include "Weapons/CWeaponStructures.h"
    #include "CDoAction.generated.h"
    
    UCLASS(Abstract)//DoAction 그 자체로는 객체화되면 안 되기 때문에 Abstract을 붙여준다.
    class U2212_06_API UCDoAction : public UObject
    {
    	GENERATED_BODY()
    
    public:
    	UCDoAction();
    
    	virtual void BeginPlay //재정의 할 수 있도록 virtual로 만든다.
    	(
    		class ACAttachment* InAttachment,
    		class UCEquipment* InEquipment,
    		class ACharacter* InOwner,
    		const TArray<FDoActionData>& InDoActionDatas
    	);
    
    public:
    	//재정의 할 수 있도록 virtual로 만든다.
    	virtual void DoAction();
    	virtual void Begin_DoAction();
    	virtual void End_DoAction();
    	
    protected:
    	bool bBeginAction;
    
    	class ACharacter* OwnerCharacter;
    	class UWorld* World;
    
    	class UCMovementComponent* Movement;
    	class UCStateComponent* State;
    
    	TArray<FDoActionData> DoActionDatas;
    };

    DoAction에 대한 수행역할을 전부 구조체에 넣어준다.

    • USTRUCT()
      struct FDoActionData

     

     

    CDoAction.cpp

    더보기
    #include "Weapons/CDoAction.h"
    #include "Global.h"
    #include "CAttachment.h"
    #include "CEquipment.h"
    #include "GameFramework/Character.h"
    #include "Components/CStateComponent.h"
    #include "Components/CMovementComponent.h"
    
    UCDoAction::UCDoAction()
    {
    }
    
    void UCDoAction::BeginPlay(ACAttachment* InAttachment, UCEquipment* InEquipment, ACharacter* InOwner, const TArray<FDoActionData>& InDoActionDatas)
    {
    	OwnerCharacter = InOwner;
    	World = OwnerCharacter->GetWorld();
    
    	State = CHelpers::GetComponent<UCStateComponent>(OwnerCharacter);
    	Movement = CHelpers::GetComponent<UCMovementComponent>(OwnerCharacter);
    
    	DoActionDatas = InDoActionDatas;
    }
    
    void UCDoAction::DoAction()
    {
    	State->SetActionMode();
    }
    
    void UCDoAction::Begin_DoAction()
    {
    	bBeginAction = true;
    }
    
    void UCDoAction::End_DoAction()
    {
    	bBeginAction = false;
    
    	State->SetIdleMode();
    
    	Movement->Move();
    	Movement->DisableFixedCamera();
    }

     


     

     

     

    CDoAction_Combo 생성

     

    새 C++ 클래스 - CDoAction 기반 C++ - CDoAction_Combo 생성

     

    CDoAction_Combo.h

    더보기
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Weapons/CDoAction.h"
    #include "CDoAction_Combo.generated.h"
    
    UCLASS()
    class U2212_06_API UCDoAction_Combo : public UCDoAction
    {
    	GENERATED_BODY()
    
    public:
    	FORCEINLINE void EnableCombo() { bEnable = true; }
    	FORCEINLINE void DisableCombo() { bEnable = false; }
    
    public:
    	void DoAction() override;
    	void Begin_DoAction() override;
    	void End_DoAction() override;
    
    private:
    	int32 Index;
    
    	bool bEnable;
    	bool bExist;
    };

     

     

     

    CDoAction_Combo.cpp

    더보기
    #include "Weapons/DoActions/CDoAction_Combo.h"
    #include "Global.h"
    #include "GameFramework/Character.h"
    #include "Components/CStateComponent.h"
    
    void UCDoAction_Combo::DoAction()
    {
    	CheckTrue(DoActionDatas.Num() < 1);
    
    	if (bEnable) //bEnable이라면 Combo 구간
    	{
    		bEnable = false;
    		bExist = true;
    
    		return;
    	}
    
    	CheckFalse(State->IsIdleMode());
    
    	Super::DoAction(); //첫 타격이 들어간 후에 Combo의 bEnable이 호출되어야 한다. 그래서 맨 위가 아닌 여기에 위치한다.//첫 타격 시 IsIdleMode()를 체크 통과한 후 부모의 DoAction으로 들어가 State->ActionMode()로 변경한다.
    	DoActionDatas[Index].DoAction(OwnerCharacter);
    }
    
    void UCDoAction_Combo::Begin_DoAction()
    {
    	Super::Begin_DoAction();
    	CheckFalse(bExist);//다음 Combo가 없으면 바로 End_DoAction으로 이동.
    
    	bExist = false;
    	DoActionDatas[++Index].DoAction(OwnerCharacter);
    }
    
    void UCDoAction_Combo::End_DoAction()
    {
    	Super::End_DoAction();
    
    	Index = 0;
    }

     

     


     

     

     

    DA_Sword에 Do Action Class 할당

     

     


     

     

    실행화면