[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