[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
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