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 할당

 

 


 

 

실행화면