목차

     

     

     


     

     

    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에 따른 블랜드 포즈 할당.

     

    실행화면