[UE] TPS Weapon - Weapon Framework 짜기

목차
Weapon - Framework 짜기
Characters | |
CAnimInstance.h .cpp CPlayer.h .cpp |
|
Utilities | |
CHelpers.h CLog.h .cpp |
|
Weapons | |
CWeapon.h .cpp 생성 CWeapon_AR4.h .cpp 생성 CWeaponComponent.h .cpp 생성 |
|
CGameMode.h .cpp Global.h |


https://designerd.tistory.com/entry/Unreal-20230111-10%EA%B0%95-%EB%AC%B4%EA%B8%B0%EA%B4%80%EB%A6%AC
[Unreal] (2023.01.11) 10강 - 무기관리
목차 무기 관리 BP: Blueprint Class BS: Blend Space ABP: Animation Blueprint AN: Blueprint Class AnimNotify ANS: Blueprint Class - AnimNotifyState E: Enum 열거형 DT: Data Table UE2212_01 01_Blueprints Level - LEVEL BP_01_Variable 02_Player 02_Playe
designerd.tistory.com
Service Locator
Weapon Component (이 패턴을 Service Locator. 응집도를 높이는 패턴)
Weapon
SOLID
- 단일책임 원칙
- 개방페쇄 원칙:
- 추가에는 열려있고 수정에는 닫혀있어야 한다는 원칙. 기능을 추가하는 것만 열려있다.
- 리스콥프 치환 원칙:
- 부모에 있는 것을 자식으로 내렸을때 정상적으로 작동해야한다.
- 부모는 공통적인 것만 들어가야 한다.
- df
- 의존역전 원칙:
응집도는 높이고 결합도는 낮추는 방향으로 프로그래밍해야 한다.
조건 체크 방식 - bool CanEquip()
class A { virtual bool CanEquip() { } //조건 체크를 여기서 한다. virtual void Equip() { if(CanEquip()) //조건체크 { } } } class A : B { bool CanEquip() { } //부모꺼 체크 void Equip() //자기꺼 체크 { Super::Equip(); if(CanEquip()) { } } }
부모 클래스에서 virtual bool CanEquip()으로 조건 체크를 한다.
- 자식 클래스에서 부모의 CanEquip()을 체크한다.
- 자식 클래스에서 필요한 내용을 재정의한다.
- 자식은 '부모 조건 체크 + 자기꺼 조건 체크'
CWeapon
새 C++ 클래스 생성 - Actor - CWeapon 생성
CWeapon.h
#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "CWeapon.generated.h" //abstract를 쓰면 Unreal Editor에서 추상 베이스 클래스로 취급하겠다는 의미. 추상이되면 배치시킬 수 없다. UCLASS(abstract) class U2212_04_API ACWeapon : public AActor { GENERATED_BODY() protected: UPROPERTY(EditDefaultsOnly, Category = "Equip") FName HolsterSocketName; private: UPROPERTY(VisibleAnywhere) class USceneComponent* Root; protected: UPROPERTY(VisibleAnywhere) class USkeletalMeshComponent* Mesh; public: ACWeapon(); protected: virtual void BeginPlay() override; public: virtual void Tick(float DeltaTime) override; public: bool CanEquip(); void Equip(); void Begin_Equip(); void End_Equip(); bool CanUnequip(); void Unequip(); private: class ACPlayer* Owner; };
모든 Weapon의 부모 클래스가 될 CWeapon에
- bool CanEquip();
- void Equip();
- void Begin_Equip();
- void End_Equip();
- bool CanUnequip();
- void Unequip();
을 정의한다.
CWeapon.cpp
#include "Weapons/CWeapon.h" #include "Global.h" #include "Character/CPlayer.h" #include "Components/SkeletalMeshComponent.h" ACWeapon::ACWeapon() { PrimaryActorTick.bCanEverTick = true; CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root"); CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Mesh, "Mesh", Root); } void ACWeapon::BeginPlay() { Super::BeginPlay(); Owner = Cast<ACPlayer>(GetOwner()); if (HolsterSocketName.IsValid()) AttachToComponent(Owner->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HolsterSocketName); } void ACWeapon::Tick(float DeltaTime) { Super::Tick(DeltaTime); } bool ACWeapon::CanEquip() { return true; } void ACWeapon::Equip() { } void ACWeapon::Begin_Equip() { } void ACWeapon::End_Equip() { } bool ACWeapon::CanUnequip() { return true; } void ACWeapon::Unequip() { }
추후에 코드 추가 예정.

- 위의 BP 노드를 Unreal C++ 코드로 작성하면 다음과 같다.
- AttachToComponent(Owner->GetMesh(), FAttachmentTransformRules EAttachmentRule::KeepRelative, true), HolsterSocketName);
CWeapon_AR4
Weapon의 자식 클래스 생성 - Actor - CWeapon_AR4 생성
CWeapon_AR4.h
#pragma once #include "CoreMinimal.h" #include "Weapons/CWeapon.h" #include "CWeapon_AR4.generated.h" UCLASS() class U2212_04_API ACWeapon_AR4 : public ACWeapon { GENERATED_BODY() public: ACWeapon_AR4(); };
CWeapon_AR4.cpp
#include "Weapons/CWeapon_AR4.h" #include "Global.h" #include "Components/SkeletalMeshComponent.h" ACWeapon_AR4::ACWeapon_AR4() { USkeletalMesh* mesh; CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SK_AR4.SK_AR4'"); Mesh->SetSkeletalMesh(mesh); //Equip { HolsterSocketName = "Rifle_AR4_Holster"; } }
CWeaponComponent
새 C++ 클래스 생성 - ActorComponent - CWeaponComponent 생성
CWeaponComponent.h
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "CWeaponComponent.generated.h" //BlueprintType을 써서 BP에서 공개되게 한다. 추후에 Animation BP에서 다루어야 한다. 추후에 switch로 Weapon Blending을 할 것이다. UENUM(BlueprintType) enum class EWeaponType : uint8 { AR4, AK47, Pistol, Max, }; DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType); UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class U2212_04_API UCWeaponComponent : public UActorComponent { GENERATED_BODY() private: UPROPERTY(EditAnywhere, Category = "Settings") TArray<TSubclassOf<class ACWeapon>> WeaponClasses; //Spawn시킬 때 class 타입을 TSubclassOf로 명시해준다. TArray는 그것에 대한 배열이다. public: FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; }//Max는 아무것도 선택안한 상황. FORCEINLINE bool IsAR4Mode() { return Type == EWeaponType::AR4; } FORCEINLINE bool IsAK47Mode() { return Type == EWeaponType::AK47; } FORCEINLINE bool IsPistolMode() { return Type == EWeaponType::Pistol; } public: // Sets default values for this component's properties UCWeaponComponent(); protected: // Called when the game starts virtual void BeginPlay() override; public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; private: class ACWeapon* GetCurrWeapon();//현재 무기를 리턴 public: void SetUnarmedMode(); void SetAR4Mode(); //void SetAK47Mode(); //void SetPistolMode(); private: void SetMode(EWeaponType InType); void ChangeType(EWeaponType InType); public: FWeaponTypeChanged OnWeaponTypeChanged; private: EWeaponType Type = EWeaponType::Max; //Max는 아무것도 선택안한 상황. private: class ACPlayer* Owner; //플레이어 변수 TArray<class ACWeapon*> Weapons; //무기 배열 변수 };
CWeaponComponent.cpp
#include "Weapons/CWeaponComponent.h" #include "Global.h" #include "CWeapon.h" #include "Character/CPlayer.h" // Sets default values for this component's properties UCWeaponComponent::UCWeaponComponent() { // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; } void UCWeaponComponent::BeginPlay() { Super::BeginPlay(); Owner = Cast<ACPlayer>(GetOwner()); CheckNull(Owner); FActorSpawnParameters params; params.Owner = Owner; params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; //받은 타입을 Spawn 시킨다. for(TSubclassOf<ACWeapon> weaponClass : WeaponClasses) { if(!!weaponClass) { ACWeapon* weapon = Owner->GetWorld()->SpawnActor<ACWeapon>(weaponClass, params);//weaponClass 타입으로 Spawn시킨다. Weapons.Add(weapon); } } } // Called every frame void UCWeaponComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); } ACWeapon* UCWeaponComponent::GetCurrWeapon() { CheckTrueResult(IsUnarmedMode(), nullptr);//IsUnarmedMode가 true면 무기 리턴해줄수 없으므로 nullptr로 리턴 return Weapons[(int32)Type]; //BP의 to int //현재 선택된 Weapon을 리턴해준다. } void UCWeaponComponent::SetUnarmedMode() { CheckFalse(GetCurrWeapon()->CanUnequip());//현재 무기가 해제될 수 있는지 체크 GetCurrWeapon()->Unequip(); //무기 해제 ChangeType(EWeaponType::Max);//현재 무기를 Max로 바꾸어줌(Max는 아무것도 선택안한 상태) } void UCWeaponComponent::SetAR4Mode() { SetMode(EWeaponType::AR4); } void UCWeaponComponent::SetMode(EWeaponType InType) { if(Type == InType) //현재 무기와 장착하려는 무기가 같은 경우 { SetUnarmedMode(); //무기 장착 해제 return; //무기 장착 해제 후 리턴 } else if (IsUnarmedMode() == false) { CheckFalse(GetCurrWeapon()->CanUnequip());//현재 무기가 해제될 수 있는지 체크 GetCurrWeapon()->Unequip(); //무기 해제 } CheckNull(Weapons[(int32)InType]); //장착될 무기가 있는지 체크 CheckFalse(Weapons[(int32)InType]->CanEquip());//장착될 무기가 장착될 수 있는 상황인지 체크 Weapons[(int32)InType]->Equip(); // 무기 장착 ChangeType(InType); //무기 변경 알려줌 } void UCWeaponComponent::ChangeType(EWeaponType InType) { EWeaponType type = Type; Type = InType; //기존의 무기 타입에서 바뀐 무기 타입을 넣어줌. if (OnWeaponTypeChanged.IsBound()) OnWeaponTypeChanged.Broadcast(type, InType);//기존 무기 타입, 바뀐 무기 타입 }
Player 부분 추가
Skel_Mannequin

- 소켓 추가
CHelper
CHelper.h
#pragma once #include "CoreMinimal.h" #define CheckTrue(x) { if(x == true) return; } #define CheckTrueResult(x, y) { if(x == true) return y; } #define CheckFalse(x) { if(x == false) return;} #define CheckFalseResult(x, y) { if(x == false) return y;} #define CheckNull(x) { if(x == nullptr) return;} #define CheckNullResult(x, y) { if(x == nullptr) return y;} #define CreateTextRender()\ {\ CHelpers::CreateComponent<UTextRenderComponent>(this, &Text, "Tex", Root);\ Text->SetRelativeLocation(FVector(0, 0, 100));\ Text->SetRelativeRotation(FRotator(0, 180, 0));\ Text->SetRelativeScale3D(FVector(2));\ Text->TextRenderColor = FColor::Red;\ Text->HorizontalAlignment = EHorizTextAligment::EHTA_Center;\ Text->Text = FText::FromString(GetName().Replace(L"Default__", L""));\ } class U2212_04_API CHelpers { public: template<typename T> static void CreateComponent(AActor* InActor, T** OutComponent, FName InName, USceneComponent* InParent = nullptr) { *OutComponent = InActor->CreateDefaultSubobject<T>(InName); if (!!InParent) { (*OutComponent)->SetupAttachment(InParent); return; } InActor->SetRootComponent(*OutComponent); } //CreateActorComponent 추가 template<typename T> static void CreateActorComponent(AActor* InActor, T** OutComponent, FName InName) { *OutComponent = InActor->CreateDefaultSubobject<T>(InName); } template<typename T> static void GetAsset(T** OutObject, FString InPath) { ConstructorHelpers::FObjectFinder<T> asset(*InPath); *OutObject = asset.Object; } template<typename T> static void GetAssetDynamic(T** OutObject, FString InPath) { *OutObject = Cast<T>(StaticLoadObject(T::StaticClass(), nullptr, *InPath)); } template<typename T> static void GetClass(TSubclassOf<T>* OutClass, FString InPath) { ConstructorHelpers::FClassFinder<T> asset(*InPath); *OutClass = asset.Class; } template<typename T> static T* FindActor(UWorld* InWorld) { for (AActor* actor : InWorld->GetCurrentLevel()->Actors) { if (!!actor && actor->IsA<T>()) return Cast<T>(actor); } return nullptr; } template<typename T> static void FindActors(UWorld* InWorld, TArray<T*>& OutActors) { for (AActor* actor : InWorld->GetCurrentLevel()->Actors) { if (!!actor && actor->IsA<T>()) OutActors.Add(Cast<T>(actor)); } } template<typename T> static T* GetComponent(AActor* InActor) { return Cast<T>(InActor->GetComponentByClass(T::StaticClass())); } template<typename T> static T* GetComponent(AActor* InActor, const FString& InName) { TArray<T*> components; InActor->GetComponents<T>(components); for (T* component : components) { if (component->GetName() == InName) return component; } return nullptr; } };
추가된 부분
template<typename T> static void CreateActorComponent(AActor* InActor, T** OutComponent, FName InName) { *OutComponent = InActor->CreateDefaultSubobject<T>(InName); } template<typename T> static T* GetComponent(AActor* InActor, const FString& InName) { TArray<T*> components; InActor->GetComponents<T>(components); for (T* component : components) { if (component->GetName() == InName) return component; } return nullptr; }
CPlayer
CPlayer.h
#pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "CPlayer.generated.h" UCLASS() class U2212_04_API ACPlayer : public ACharacter { GENERATED_BODY() private: UPROPERTY(VisibleAnywhere) class USpringArmComponent* SpringArm; UPROPERTY(VisibleAnywhere) class UCameraComponent* Camera; private: UPROPERTY(VisibleAnywhere) class UCWeaponComponent* Weapon; public: ACPlayer(); protected: virtual void BeginPlay() override; public: virtual void Tick(float DeltaTime) override; virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; private: void OnMoveForward(float InAxisValue); void OnMoveRight(float InAxisValue); void OnHorizontalLook(float InAxisValue); void OnVerticalLook(float InAxisValue); private: void OnRun(); void OffRun(); };
CPlayer에 WeaponComponent를 넣어준다.
CPlayer.cpp
#include "CPlayer.h" #include "Global.h" #include "CAnimInstance.h" #include "Weapons/CWeaponComponent.h" #include "GameFramework/SpringArmComponent.h" #include "GameFramework/CharacterMovementComponent.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "Components/InputComponent.h" #include "Materials/MaterialInstanceDynamic.h" ACPlayer::ACPlayer() { PrimaryActorTick.bCanEverTick = true; CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetCapsuleComponent()); CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm); CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon"); USkeletalMesh* mesh; CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'"); GetMesh()->SetSkeletalMesh(mesh); GetMesh()->SetRelativeLocation(FVector(0, 0, -90)); GetMesh()->SetRelativeRotation(FRotator(0, -90, 0)); TSubclassOf<UCAnimInstance> animInstance; CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/Player/ABP_Player.ABP_Player_C'"); GetMesh()->SetAnimClass(animInstance); bUseControllerRotationYaw = false; GetCharacterMovement()->bOrientRotationToMovement = true; GetCharacterMovement()->MaxWalkSpeed = 400; SpringArm->SetRelativeLocation(FVector(0, 0, 60)); SpringArm->TargetArmLength = 200; SpringArm->bUsePawnControlRotation = true; SpringArm->bEnableCameraLag = true; } void ACPlayer::BeginPlay() { Super::BeginPlay(); //Super가 BP의 BeginPlay를 콜한다. } void ACPlayer::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis("MoveForward", this, &ACPlayer::OnMoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ACPlayer::OnMoveRight); PlayerInputComponent->BindAxis("HorizontalLook", this, &ACPlayer::OnHorizontalLook); PlayerInputComponent->BindAxis("VerticalLook", this, &ACPlayer::OnVerticalLook); PlayerInputComponent->BindAction("Run", EInputEvent::IE_Pressed, this, &ACPlayer::OnRun); PlayerInputComponent->BindAction("Run", EInputEvent::IE_Released, this, &ACPlayer::OffRun); PlayerInputComponent->BindAction("AR4", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetAR4Mode);//Object를 Weapon으로 받는다. } void ACPlayer::OnMoveForward(float InAxisValue) { FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0); FVector direction = FQuat(rotator).GetForwardVector(); AddMovementInput(direction, InAxisValue); } void ACPlayer::OnMoveRight(float InAxisValue) { FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0); FVector direction = FQuat(rotator).GetRightVector(); AddMovementInput(direction, InAxisValue); } void ACPlayer::OnHorizontalLook(float InAxisValue) { AddControllerYawInput(InAxisValue); } void ACPlayer::OnVerticalLook(float InAxisValue) { AddControllerPitchInput(InAxisValue); } void ACPlayer::OnRun() { GetCharacterMovement()->MaxWalkSpeed = 600; } void ACPlayer::OffRun() { GetCharacterMovement()->MaxWalkSpeed = 400; }
BP_CPlayer

- Weapon - 세팅 - Weapon Classes - 추가
- 0번에 BP_CWeapon_AR4 할당
ABP_Player - 동작 할당
AnimGraph

- WeaponType에 따른 블랜드 포즈 할당.
실행화면

'⭐ Unreal Engine > UE FPS TPS' 카테고리의 다른 글
[UE] Hand IK, AnimInstance, Fire (0) | 2023.03.21 |
---|---|
[UE] TPS Weapon, AnimNotify, Aim (0) | 2023.03.17 |
[UE] Line Trace, Multi Trace, TPS 기본 세팅 (0) | 2023.03.15 |
[UE] Collision(Override), BP와 C++ 실행순서 (0) | 2023.03.14 |
[UE] Collsion(trigger, MultiTrigger, Explosion) (0) | 2023.03.13 |
댓글
이 글 공유하기
다른 글
-
[UE] Hand IK, AnimInstance, Fire
[UE] Hand IK, AnimInstance, Fire
2023.03.21 -
[UE] TPS Weapon, AnimNotify, Aim
[UE] TPS Weapon, AnimNotify, Aim
2023.03.17 -
[UE] Line Trace, Multi Trace, TPS 기본 세팅
[UE] Line Trace, Multi Trace, TPS 기본 세팅
2023.03.15 -
[UE] Collision(Override), BP와 C++ 실행순서
[UE] Collision(Override), BP와 C++ 실행순서
2023.03.14
댓글을 사용할 수 없습니다.