[UE] 무기 시스템 설계하기
목차
Source | ||
Characters | ||
CAnimInstance.h .cpp CPlayer.h .cpp ICharacter.h .cpp |
||
Components | ||
CMontagesComponent.h .cpp CMovementComponent.h .cpp CStateComponent.h .cpp CWeaponComponent.h .cpp |
||
Notifies | ||
CAnimNotify_EndState.h .cpp | ||
Utilities | ||
CHelper.h CLog.h .cpp |
||
Weapons | ||
CAttachment.h .cpp CEquipment.h .cpp 생성 CWeaponAsset.h .cpp CWeaponStructures.h .cpp 생성 |
||
Global.h CGameMode.h .cpp U2212_06.Build.cs |
||
U2212_06.uproject | ||
무기 시스템 만들기
Weapon System 구조 개요
WeponComponent | WeaponAsset | Attachment Equipment DoAction |
CPlayer + BP_CPlayer
CPlayer.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/CStateComponent.h"
#include "Characters/ICharacter.h"
#include "CPlayer.generated.h"
UCLASS()
class U2212_06_API ACPlayer
: public ACharacter, public IICharacter //다중상속
{
GENERATED_BODY()
private:
UPROPERTY(VisibleAnywhere)
class USpringArmComponent* SpringArm;
UPROPERTY(VisibleAnywhere)
class UCameraComponent* Camera;
private:
UPROPERTY(VisibleAnywhere)
class UCWeaponComponent* Weapon;
UPROPERTY(VisibleAnywhere)
class UCMontagesComponent* Montages;
UPROPERTY(VisibleAnywhere)
class UCMovementComponent* Movement;
UPROPERTY(VisibleAnywhere)
class UCStateComponent* State;
public:
ACPlayer();
protected:
virtual void BeginPlay() override;
public:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
UFUNCTION()
void OnStateTypeChanged(EStateType InPrevType, EStateType InNewType);
private:
void OnAvoid();
private:
void BackStep();
public:
void End_BackStep() override;//ICharacter의 함수 오버라이드
};
Player 내에 WeaponComponent 추가
- UPROPERTY(VisibleAnywhere)
class UCWeaponComponent* Weapon;
CPlayer.cpp
#include "Characters/CPlayer.h"
#include "Global.h"
#include "CAnimInstance.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/InputComponent.h"
#include "Components/CMontagesComponent.h"
#include "Components/CMovementComponent.h"
#include "Components/CWeaponComponent.h"
ACPlayer::ACPlayer()
{
CHelpers::CreateComponent<USpringArmComponent>(this, &SpringArm, "SpringArm", GetMesh());
CHelpers::CreateComponent<UCameraComponent>(this, &Camera, "Camera", SpringArm);
CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon");
CHelpers::CreateActorComponent<UCMontagesComponent>(this, &Montages, "Montage");
CHelpers::CreateActorComponent<UCMovementComponent>(this, &Movement, "Movement");
CHelpers::CreateActorComponent<UCStateComponent>(this, &State, "State");
GetMesh()->SetRelativeLocation(FVector(0, 0, -90));
GetMesh()->SetRelativeRotation(FRotator(0, -90, 0));
USkeletalMesh* mesh;
CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Character/Mesh/SK_Mannequin.SK_Mannequin'");
GetMesh()->SetSkeletalMesh(mesh);
TSubclassOf<UCAnimInstance> animInstance;
CHelpers::GetClass<UCAnimInstance>(&animInstance, "AnimBlueprint'/Game/ABP_Character.ABP_Character_C'");
GetMesh()->SetAnimClass(animInstance);
SpringArm->SetRelativeLocation(FVector(0, 0, 140));
SpringArm->SetRelativeRotation(FRotator(0, 90, 0));
SpringArm->TargetArmLength = 200;
SpringArm->bDoCollisionTest = false;
SpringArm->bUsePawnControlRotation = true;
SpringArm->bEnableCameraLag = true;
GetCharacterMovement()->RotationRate = FRotator(0, 720, 0);
}
void ACPlayer::BeginPlay()
{
Super::BeginPlay();
Movement->OnRun(); //Movement의 기본을 Run으로 설정
Movement->DisableControlRotation();//Movement의 기본을 DisableControlRotation으로 설정
State->OnStateTypeChanged.AddDynamic(this, &ACPlayer::OnStateTypeChanged);
}
void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis("MoveForward", Movement, &UCMovementComponent::OnMoveForward);
PlayerInputComponent->BindAxis("MoveRight", Movement, &UCMovementComponent::OnMoveRight);
PlayerInputComponent->BindAxis("VerticalLook", Movement, &UCMovementComponent::OnVerticalLook);
PlayerInputComponent->BindAxis("HorizontalLook", Movement, &UCMovementComponent::OnHorizontalLook);
PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Pressed, Movement, &UCMovementComponent::OnSprint);
PlayerInputComponent->BindAction("Sprint", EInputEvent::IE_Released, Movement, &UCMovementComponent::OnRun);
PlayerInputComponent->BindAction("Avoid", EInputEvent::IE_Pressed, this, &ACPlayer::OnAvoid);
PlayerInputComponent->BindAction("Sword", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetSwordMode);
}
void ACPlayer::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
{
switch (InNewType)
{
case EStateType::BackStep: BackStep(); break;
}
}
void ACPlayer::OnAvoid()
{
CheckFalse(State->IsIdleMode());
CheckFalse(Movement->CanMove());
CheckTrue(InputComponent->GetAxisValue("MoveForward") >= 0.0f);//뒷방향을 입력했다면
State->SetBackStepMode();//State을 BackStepMode로 변경한다.
}
void ACPlayer::BackStep()
{
Movement->EnableControlRotation();//정면을 바라본 상태로 뒤로 뛰어야하기 때문에 EnableControlRotation으로 만들어준다.
Montages->PlayBackStepMode();//PlayBackStepMode()를 통해 몽타주 재생.
}
void ACPlayer::End_BackStep()
{
Movement->DisableControlRotation();//Backstep이 끝나면 원래대로 돌려준다.
State->SetIdleMode();//Idle상태로 돌려줌.
}
Player 내에 WeaponComponent 추가
- CHelpers::CreateActorComponent<UCWeaponComponent>(this, &Weapon, "Weapon");
Sword 장착/해제 키 입력 추가
- PlayerInputComponent->BindAction("Sword", EInputEvent::IE_Pressed, Weapon, &UCWeaponComponent::SetSwordMode);
BP_CPlayer
Player내에 추가한 WeaponComponet에 Data Asset 할당
- Weapon - Data Asset - Data Assets - Sword - DA_Sword 할당
CWeaponComponent.h .cpp
CWeaponComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CWeaponComponent.generated.h"
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
Fist, Sword, Hammer, Warp, Around, Bow, Max,
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWeaponTypeChanged, EWeaponType, InPrevType, EWeaponType, InNewType);
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class U2212_06_API UCWeaponComponent : public UActorComponent
{
GENERATED_BODY()
private://DataAsset을 받아온다.
UPROPERTY(EditAnywhere, Category = "DataAsset")
class UCWeaponAsset* DataAssets[(int32)EWeaponType::Max];
public: //무기 Type이 맞는지 확인해주는 함수들
FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; }
FORCEINLINE bool IsFistMode() { return Type == EWeaponType::Fist; }
FORCEINLINE bool IsSwordMode() { return Type == EWeaponType::Sword; }
FORCEINLINE bool IsHammerMode() { return Type == EWeaponType::Hammer; }
FORCEINLINE bool IsWarpMode() { return Type == EWeaponType::Warp; }
FORCEINLINE bool IsAroundMode() { return Type == EWeaponType::Around; }
FORCEINLINE bool IsBowMode() { return Type == EWeaponType::Bow; }
public:
UCWeaponComponent();
protected:
virtual void BeginPlay() override;
private:
bool IsIdleMode();//StateComponent, WeaponComponent 둘 다 같은 레벨이다. 서로 소유할 수 없는 관계이기 때문에 참조만해서 리턴받기 위해 IsIdleMode를 사용한다.
public:
class ACAttachment* GetAttachment();
class UCEquipment* GetEquipment();
public: //무기 세팅
void SetUnarmedMode();
void SetFistMode();
void SetSwordMode();
void SetHammerMode();
void SetWarpMode();
void SetAroundMode();
void SetBowMode();
private:
void SetMode(EWeaponType InType);
void ChangeType(EWeaponType InType);
public: //무기가 바뀌었을때 통보해줄 delegate
FWeaponTypeChanged OnWeaponTypeChange;
private:
class ACharacter* OwnerCharacter;
EWeaponType Type = EWeaponType::Max;
};
WeaponAsset의 데이터를 가져온다.
- UPROPERTY(EditAnywhere, Category = "DataAsset")
class UCWeaponAsset* DataAssets[(int32)EWeaponType::Max];
무기 Type이 맞는지 확인해주는 함수들 추가
- FORCEINLINE bool IsUnarmedMode() { return Type == EWeaponType::Max; }
- FORCEINLINE bool IsFistMode() { return Type == EWeaponType::Fist; }
- FORCEINLINE bool IsSwordMode() { return Type == EWeaponType::Sword; }
- FORCEINLINE bool IsHammerMode() { return Type == EWeaponType::Hammer; }
- FORCEINLINE bool IsWarpMode() { return Type == EWeaponType::Warp; }
- FORCEINLINE bool IsAroundMode() { return Type == EWeaponType::Around; }
- FORCEINLINE bool IsBowMode() { return Type == EWeaponType::Bow; }
IsIdleMode() 상태
- bool IsIdleMode();
- StateComponent, WeaponComponent 둘 다 같은 레벨이다. 서로 소유할 수 없는 관계이기 때문에 참조만해서 리턴받기 위해 IsIdleMode를 사용한다.
CWeaponComponent.cpp
#include "Components/CWeaponComponent.h"
#include "Global.h"
#include "CStateComponent.h"
#include "GameFramework/Character.h"
#include "Weapons/CWeaponAsset.h"
#include "Weapons/CAttachment.h"
#include "Weapons/CEquipment.h"
UCWeaponComponent::UCWeaponComponent()
{
}
void UCWeaponComponent::BeginPlay()
{
Super::BeginPlay();
OwnerCharacter = Cast<ACharacter>(GetOwner());
for (int32 i=0; i < (int32)EWeaponType::Max; i++)
{
if (!!DataAssets[i]) //DataAssets[i]이 있다면(=무기가 할당되어 있다면)
DataAssets[i]->BeginPlay(OwnerCharacter);//BeginPla y 시 OwnerCharacter에 Spawn시켜준다.
}
}
bool UCWeaponComponent::IsIdleMode()
{
return CHelpers::GetComponent<UCStateComponent>(OwnerCharacter)->IsIdleMode();
}
ACAttachment* UCWeaponComponent::GetAttachment()
{
CheckTrueResult(IsUnarmedMode(), nullptr);
CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
return DataAssets[(int32)Type]->GetAttachment();
}
UCEquipment* UCWeaponComponent::GetEquipment()
{ CheckTrueResult(IsUnarmedMode(), nullptr);
CheckFalseResult(!!DataAssets[(int32)Type], nullptr);
return DataAssets[(int32)Type]->GetEquipment();
}
void UCWeaponComponent::SetUnarmedMode()
{
GetEquipment()->Unequip();
ChangeType(EWeaponType::Max);
}
void UCWeaponComponent::SetFistMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Fist);
}
void UCWeaponComponent::SetSwordMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Sword);
}
void UCWeaponComponent::SetHammerMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Hammer);
}
void UCWeaponComponent::SetWarpMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Warp);
}
void UCWeaponComponent::SetAroundMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Around);
}
void UCWeaponComponent::SetBowMode()
{
CheckFalse(IsIdleMode());
SetMode(EWeaponType::Bow);
}
void UCWeaponComponent::SetMode(EWeaponType InType)
{
if (Type == InType)
{
SetUnarmedMode();
return;
}
else if (IsUnarmedMode() == false)
{
GetEquipment()->Unequip();
}
if (!!DataAssets[(int32)InType])
{
DataAssets[(int32)InType]->GetEquipment()->Equip();
ChangeType(InType);
}
}
void UCWeaponComponent::ChangeType(EWeaponType InType)
{
EWeaponType prevType = Type;
Type = InType;
if (OnWeaponTypeChange.IsBound())
OnWeaponTypeChange.Broadcast(prevType, InType);
}
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;
protected:
UFUNCTION(BlueprintCallable, Category = "Attach")
void AttachTo(FName InSocketName);
protected:
UPROPERTY(BlueprintReadOnly, Category = "Game")
class ACharacter* OwnerCharacter;
};
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);
}
왼쪽은 Blueprint에서 사용하는 버전인 Attach Actor To Component.
Blueprint와는 달리 C++에서는 AttachToComponent라는 함수를 사용한다.
BP_CAttachment_Sword
실행화면
게임을 플레이하면 Holster_Sword 위치에 Sword가 생성된다.
무기 장착 및 해제 시스템 만들기
CEquipment 생성
새 C++ 클래스 - Object - CEquipment 생성
CEquipment.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Weapons/CWeaponStructures.h"
#include "CEquipment.generated.h"
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();
private:
class ACharacter* OwnerCharacter;
FEquipmentData Data;
private:
class UCMovementComponent* Movement;
class UCStateComponent* State;
private:
bool bBeginEquip;//Equip이 시작되었는가 확인
bool bEquipped; //Equip이 완료되었는지 확인
};
CEquipment.cpp
#include "Weapons/CEquipment.h"
CWeaponStructures 생성
새 C++ 클래스 - Object - 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;
};
UCLASS()
class U2212_06_API UCWeaponStructures : public UObject
{
GENERATED_BODY()
};
CWeaponStructures.cpp
#include "Weapons/CWeaponStructures.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;
}
void UCEquipment::End_Equip_Implementation()
{
bBeginEquip = false;
bEquipped = true;
Movement->Move();
State->SetIdleMode();
}
void UCEquipment::Unequip_Implementation()
{
bEquipped = false;
Movement->DisableControlRotation();
}
CWeaponAsset + DA_Sword
CWeaponAsset.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Weapons/CWeaponStructures.h"
#include "CWeaponAsset.generated.h"
UCLASS()
class U2212_06_API UCWeaponAsset : public UDataAsset
{
GENERATED_BODY()
private:
UPROPERTY(EditAnywhere)
TSubclassOf<class ACAttachment> AttachmentClass;
UPROPERTY(EditAnywhere)
FEquipmentData EquipmentData;
UPROPERTY(EditAnywhere)
TSubclassOf<class UCEquipment> EquipmentClass;
public:
FORCEINLINE class ACAttachment* GetAttachment() { return Attachment; }//외부에 생성된 것을 리턴해줌.
FORCEINLINE class UCEquipment* GetEquipment() { return Equipment; }//외부에 생성된 것을 리턴해줌.
public:
UCWeaponAsset();
void BeginPlay(class ACharacter* InOwner);
private:
//UPROPERTY를 붙여 가비지 콜렉터가 제거하기 전까지 물고 있게 만든다.
//UWeaponAsset은 UObject로부터 상속받아 Actor의 생성주기에 영향을 받지 않아 가비지 콜렉터에 영향을 받는다.
UPROPERTY()
class ACAttachment* Attachment;
UPROPERTY()
class UCEquipment* Equipment;
};
CWeaponAsset.cpp
#include "Weapons/CWeaponAsset.h"
#include "Global.h"
#include "CAttachment.h"
#include "CEquipment.h"
#include "GameFramework/Character.h"
UCWeaponAsset::UCWeaponAsset()
{
AttachmentClass = ACAttachment::StaticClass();//기본값
EquipmentClass = UCEquipment::StaticClass();//기본값
}
void UCWeaponAsset::BeginPlay(ACharacter* InOwner)
{
if (!!AttachmentClass)//AttachmentClass가 선택되어 있다면
{
FActorSpawnParameters params;
params.Owner = InOwner;
Attachment = InOwner->GetWorld()->SpawnActor<ACAttachment>(AttachmentClass, params);
}
if (!!EquipmentClass)
{
Equipment = NewObject<UCEquipment>(this, EquipmentClass);
Equipment->BeginPlay(InOwner, EquipmentData);
}
}
DA_Sword
실행화면
Sword 키 입력을 하면 Equip_Sword 몽타주가 실행된다.
'⭐ Unreal Engine > UE RPG Weapon System' 카테고리의 다른 글
[UE] AnimNotify, DoAction (0) | 2023.05.08 |
---|---|
[UE] 무기 장착 및 기본 공격하기 (0) | 2023.05.02 |
[UE] Interface, AnimNotify, Backstep 구현하기 (0) | 2023.04.28 |
[UE] Component 컴포넌트, Player 이동 (0) | 2023.04.27 |
[UE] Player 초기 세팅하기 (0) | 2023.04.26 |
댓글
이 글 공유하기
다른 글
-
[UE] AnimNotify, DoAction
[UE] AnimNotify, DoAction
2023.05.08 -
[UE] 무기 장착 및 기본 공격하기
[UE] 무기 장착 및 기본 공격하기
2023.05.02 -
[UE] Interface, AnimNotify, Backstep 구현하기
[UE] Interface, AnimNotify, Backstep 구현하기
2023.04.28 -
[UE] Component 컴포넌트, Player 이동
[UE] Component 컴포넌트, Player 이동
2023.04.27