[UE] 무기 장착 및 기본 공격하기

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

실행화면

'⭐ Unreal Engine > UE RPG Weapon System' 카테고리의 다른 글
[UE] Hit Data, Effect, Object Pooling(오브젝트 풀링) (0) | 2023.05.09 |
---|---|
[UE] AnimNotify, DoAction (0) | 2023.05.08 |
[UE] 무기 시스템 설계하기 (0) | 2023.05.01 |
[UE] Interface, AnimNotify, Backstep 구현하기 (0) | 2023.04.28 |
[UE] Component 컴포넌트, Player 이동 (0) | 2023.04.27 |
댓글
이 글 공유하기
다른 글
-
[UE] Hit Data, Effect, Object Pooling(오브젝트 풀링)
[UE] Hit Data, Effect, Object Pooling(오브젝트 풀링)
2023.05.09 -
[UE] AnimNotify, DoAction
[UE] AnimNotify, DoAction
2023.05.08 -
[UE] 무기 시스템 설계하기
[UE] 무기 시스템 설계하기
2023.05.01 -
[UE] Interface, AnimNotify, Backstep 구현하기
[UE] Interface, AnimNotify, Backstep 구현하기
2023.04.28
댓글을 사용할 수 없습니다.