필요한 개념
전체적인 구조 : IRifle을 CPlayer가 상속받아 Rifle을 다룰 수 있도록 하고, 즉, IRifle을 상속받는 애들은 Rifle을 다룰 수 있게 되는 애들이다. CRifle에서는 몽타주 재생효과 및 무기탈착에 대한 전반적인 정보를 가진다. OwnerCharacter를 구해서 처리한다. CAnimInstance에서는 bool 타입으로 탈착여부를 가지고 있고, 이것으로 어떤 포즈를 가질지 결정한다. 그리고 Montage를 재생하다보면 우리가 NotifyState를 세팅한 부분이 호출될 것이다. CAnimNotifyState_Equip, CAnimNotifyState_UnEquip에서는 각각 장착, 탈착에 관한 것을 CRifle에서 호출해준다.
* 액션 매핑 추가
* 소켓 추가
SK_Mannequin 스켈레톤 트리에 총기를 붙일 소켓 추가(소켓에 프리뷰 메시 추가하고 무기에 맞게 Transform 세팅)
원래 Spawn시키는 함수를 Factory라 해서 만드는 함수를 내부에 심어 놓음(그냥 Spawn시키는 식으로 Factory처럼 내부에 Static으로 놔서 어떻게 처리하는지 알려줌)
public:
// 1. 스폰시킬 위치 2 : 소유할 캐릭터
static ACRifle* Spawn(class UWorld* InWorld, class ACharacter* InOwner);
// 뭘 스폰이나 생성할때 내부적으로 생성해서 리턴시켜주는 Factory구조라고 함
// 총을 스폰 시키고 싶으면 이 함수를 사용해 총 객체를 리턴받으면 된다.
ACRifle* ACRifle::Spawn(UWorld* InWorld, ACharacter* InOwner)
{
FActorSpawnParameters params;
params.Owner = InOwner;
// spawn 시키면서 ACRifle 형태로 Spawn됨
return InWorld->SpawnActor<ACRifle>(params);
}
// 해당 클래스 내부에 정의해서 해당 클래스 내부에서 해당 객체를 생성하여 리턴하는 방식을 팩토리라고 부름
// Q) Spawn을 외부에서 만들어서 콜 하면 안되나?
// 외부에서 만들면 Spawn되는 코드를 다 찾아서 수정해야하기 때문에, 이렇게 내부에 모아 놓으면 공통으로 사용하니까 여기만 수정하면됨(관리 편하도록)
// factory라는게 찍어내는 것(모두 다 동일하게 찍어내는 것)
총에 대한 추가적인 설정이 있다면 BP로 만들고 그걸로 스폰시키면 됨
Spawn, Attach는 BeginPlay에서 하면됨
BlendSpace로 Aim, Grab해서 움직이는 것도 가지고 있어야함
Grab 애니메이션으로 Grab, UnGrab -> 몽타주 생성
FORCEINLINE 이란?
public:
FORCEINLINE bool GetEquipped() { return bEquipped; }
사실
public:
bool GetEquipped() { return bEquipped; } -> 이렇게만 해도 inline이 이뤄진다.(클래스 선언부에서 함수의 선언과 정의를 동시에 한다면 인라인으로 취급됨)
인라인 함수 : 함수 호출부로 점프하지 않고 컴파일 시에 해당 함수의 정의 부분을 호출부분으로 복사되는 함수(간단한거는 점프하지 않아서 빠름, 복잡하는거는 inline이 문제가 될 소지가 있음)
언리얼에는 FORCEINLINE을 붙여서 항상 인라인 할것을 권고함(!정의와 선언부가 같이 있다면 FORCEINLINE을 붙여라)
* 인라인 함수인지 확인하는 방법은
public:
bool GetEquipped() { return bEquipped; } // Ctrl + . 해서 정의 위치 이동하면 밑에 처럼 결과가 바뀜
=======================================
.h
=======================================
public:
bool GetEquipped();
=======================================
.cpp
=======================================
inline bool ACRifle::GetEquipped() { return bEquipped; }
인라인화된 함수여서 그럼
이거는 Visual C의 기준인거고
게임기마다 규칙은 지키는데 인라인 규칙은 다다름(XBox다르고 플스 다르고)
FORCEINLINE : 어떤 플랫폼에 상관없이 이 키워드가 붙은 함수는 무조건 인라인 함수로 사용되도록 강제하는 키워드
*Tip) 함수 내에서 if, else 보단 if에서 return 빠져나오고 else 안쓰고 코드를 넣는 것이 보기가 편함
void ACPlayer::OnRifle()
{
if (Rifle->GetEquipped())
{
Rifle->UnEquip();
return;
}
Rifle->Equip();
}
// 어떤 함수에서 몽타주를 호출해서 Begin과 End를 호출한다면 이름 관리를
// 편하게 하기 위해 해당 함수의 이름앞에 Begin_, End_를 붙여 함수이름관리를
// 용이하게 함
// 장착명령
void Equip();
void Begin_Equip();
void End_Equip();
// 장착해제
void UnEquip();
void Begin_UnEquip();
void End_UnEquip();
* 몽타주 속도 세팅
몽타주 -> 에셋 디테일 -> RateScale을 수정하면 속도가 달라짐
* 몽타주 디폴트 그룹 3가지
몽타주->슬롯 ->슬롯이름 -> 3가지 나옴(DefaultGroup.DefaultSlot, DefaultGroup.UpperBody, DefaultGroup.FullBody)
DefaultGroup.UpperBody로 세팅
애니메이션 섞음
애니메이션 블루프린트에서 Rifle Grab Idle의 상체와 기본 동작의 하체를 섞어서 상체에는 총을 쥐고 있는 동작을 플레이시킴
(섞으면 발은 뛰고 상체는 무기를 든 Idle이 됨)
(섞을 때(본마다 레이어로 블랜딩) Spine01, 02, 03중 제일 그럴싸한 것을 선택해서 섞으면 된다.) -> 우리는 spine_01 기준으로 섞음
디테일을 보면
LayerSetup 추가해서
BoneName 입력해주고, BlendDepth : 1, MeshSpaceRotationBlend(메쉬공간 회전) : true
Runtime -> BlendWeight도 꺼줌(완전히 섞을 꺼라)
* 총을 장착하는지 여부로 애니메이션 판단
bool로 포즈를 블렌딩(True면 위에 포즈, false면 밑에 포즈)
결국 bool값이 필요해서 AnimInstance C에서 작업하는 것에서 bool 타입을 넣어줌
(총은 플레이어(적, 플레이어) 사용) -> 인터페이스 사용해야함(C에서 만듬)
* 새 C++만들기 맨 밑에 UnrealInterface만들기가 있음
(UObject 인터페이스 클래스로, 다른 UObject 기반 클래스에서 구현됨)이라고 설명 나오는데
다른 UObject 클래스에서 구현해달라는 의미임
// UINTERFACE가 붙은 위의 클래스는 이 부분은 BP와 통신을 위해
// 자동으로 정의되어 있는 클래스이며 따로 수정하지 않습니다.
UINTERFACE(MinimalAPI)
class UIRifle : public UInterface
{
GENERATED_BODY()
};
* AnimNotifyState로 노티파이 클래스 생성해준다.(무기 장착위해)
TIP)
4.25에서는 접근지정자를 정의하지 않으면 public이 아니라 private이다.
* NotifyState 추가
노티파이 클래스 컴파일 하면
몽타주->노티파이 우클릭 -> 노티파이 스테이트 추가에 해당 클래스 명이 뜨게 된다.
추가 해주면 된다.
노티파이를 쭉 늘여뜨려 주면 시작하면 함수가 호출되고 끝날때 함수가 호출됨
손이 붙었다 떨어질쯤에... 노티파이 넣어주면됨(무기 탈착이니깐 자연스럽게)
IRifle.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "IRifle.generated.h"
// UINTERFACE가 붙은 위의 클래스는 이 부분은 BP와 통신을 위해
// 자동으로 정의되어 있는 클래스이며 따로 수정하지 않습니다.
UINTERFACE(MinimalAPI)
class UIRifle : public UInterface
{
GENERATED_BODY()
};
class UONLINE_03_CPP_API IIRifle
{
GENERATED_BODY()
public:
// 순수 가상함수로 상속 받은 클래스는 반드시 재정의해줘야 상속 받은 클래스는 순수가상함수가 되지 않는다.
virtual class ACRifle* GetRifle() = 0;
};
CPlayer.h 추가된 내용(IIRifle을 상속 받음)
...
UCLASS()
class UONLINE_03_CPP_API ACPlayer : public ACharacter, public IIRifle
{
GENERATED_BODY()
public:
// 선언과 동시에니까 FORCEINLINE
FORCEINLINE class ACRifle* GetRifle() override { return Rifle; }
private:
class ACRifle* Rifle;
};
CPlayer.cpp
...
void ACPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
PlayerInputComponent->BindAction("Rifle", EInputEvent::IE_Pressed, this, &ACPlayer::OnRifle);
}
void ACPlayer::OnRifle()
{
if (Rifle->GetEquipped())
{
Rifle->UnEquip();
return;
}
Rifle->Equip();
}
CRifle.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CRifle.generated.h"
UCLASS()
class UONLINE_03_CPP_API ACRifle : public AActor
{
GENERATED_BODY()
private:
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class USkeletalMeshComponent* Mesh;
// 총기가 장착될 소켓 지정
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
FName HandSocket = "Hand_Rifle";
// 총기가 장착될 소켓 지정
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
FName HolsterSocket = "Holster_Rifle";
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class UAnimMontage* GrabMontage;
UPROPERTY(VisibleDefaultsOnly, Category = "Rifle")
class UAnimMontage* UngrabMontage;
public:
// 1. 스폰시킬 위치 2 : 소유할 캐릭터
static ACRifle* Spawn(class UWorld* InWorld, class ACharacter* InOwner);
public:
bool GetEquipped() { return bEquipped; };
bool GetEquipping() { return bEquipping; };
bool GetAiming() { return bAiming; };
public:
ACRifle();
// 어떤 함수에서 몽타주를 호출해서 Begin과 End를 호출한다면 이름 관리를
// 편하게 하기 위해 해당 함수의 이름앞에 Begin_, End_를 붙여 함수이름관리를
// 용이하게 함
// 장착명령
void Equip();
void Begin_Equip();
void End_Equip();
// 장착해제
void UnEquip();
void Begin_UnEquip();
void End_UnEquip();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
private:
// 적에도 붙을 수 있어서, 공통의 변수이름 짓기
class ACharacter* OwnerCharacter;
// 장착했는지
bool bEquipped;
// 장착 중인지
bool bEquipping;
};
CRifle.cpp
#include "CRifle.h"
#include "Global.h"
#include "IRifle.h"
#include "Animation/AnimMontage.h"
#include "GameFramework/Character.h"
#include "Engine/StaticMeshActor.h" // 과녁들을 우리가 staticMesh로 배치해놨다.
#include "Components/SkeletalMeshComponent.h"
// 뭘 스폰이나 생성할때 내부적으로 생성해서 리턴시켜주는 Factory구조라고 함
// 총을 스폰 시키고 싶으면 이 함수를 사용해 총 객체를 리턴받으면 된다.
ACRifle* ACRifle::Spawn(UWorld* InWorld, ACharacter* InOwner)
{
FActorSpawnParameters params;
params.Owner = InOwner;
// spawn 시키면서 ACRifle 형태로 Spawn됨
return InWorld->SpawnActor<ACRifle>(params);
}
ACRifle::ACRifle()
{
PrimaryActorTick.bCanEverTick = true;
// 이렇게 뒤에 parent 안넣으면 루트가 됨
CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Mesh, "Mesh");
USkeletalMesh* mesh;
// SkeletalMesh 불러옴
CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Weapons/Meshes/AR4/SK_AR4.SK_AR4'");
Mesh->SetSkeletalMesh(mesh);
CHelpers::GetAsset<UAnimMontage>(&GrabMontage, "AnimMontage'/Game/Character/Montages/Rifle_Grab_Montage.Rifle_Grab_Montage'");
CHelpers::GetAsset<UAnimMontage>(&UngrabMontage, "AnimMontage'/Game/Character/Montages/Rifle_UnGrab_Montage.Rifle_UnGrab_Montage'");
}
void ACRifle::Equip()
{
// 장착되어 있다면 하지마라
CheckTrue(bEquipped);
// 장착 중이라면 하지 마라
CheckTrue(bEquipping);
bEquipping = true;
OwnerCharacter->PlayAnimMontage(GrabMontage);
}
void ACRifle::Begin_Equip()
{
bEquipped = true;
// Hand 소켓에 붙음
AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HandSocket);
}
void ACRifle::End_Equip()
{
bEquipping = false;
}
void ACRifle::UnEquip()
{
CheckFalse(bEquipped);
CheckTrue(bEquipping);
bEquipping = true;
OwnerCharacter->PlayAnimMontage(UngrabMontage);
}
void ACRifle::Begin_UnEquip()
{
bEquipped = false;
// HolsterSocket으로 다시 위치 돌려놈
AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HolsterSocket);
}
void ACRifle::End_UnEquip()
{
bEquipping = false;
}
void ACRifle::BeginPlay()
{
Super::BeginPlay();
OwnerCharacter = Cast<ACharacter>(GetOwner());
// BP와 다르게 C에서는 Rule을 하나로 묶어서 정의함
// 물론 C++에서도 BP처럼 하나씩 정의는 가능
// BP와 같은 함수를 콜
// weld simulated bodies : 시뮬레이티드 피직스 바디를 결합
AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HolsterSocket);
}
void ACRifle::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
CAnimInstance.h 추가된 내용
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "CAnimInstance.generated.h"
/**
*
*/
UCLASS()
class UONLINE_03_CPP_API UCAnimInstance : public UAnimInstance
{
GENERATED_BODY()
...
protected:
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
bool bEquipped;
};
CAnimInstance.cpp 추가된 내용
...
void UCAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
...
IIRifle* rifle = Cast<IIRifle>(OwnerCharacter);
if (!!rifle) // NotNull
{
bEquipped = rifle->GetRifle()->GetEquipped();
}
}
CAnimNotifyState_Equip.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "CAnimNotifyState_Equip.generated.h"
UCLASS()
class UONLINE_03_CPP_API UCAnimNotifyState_Equip : public UAnimNotifyState
{
GENERATED_BODY()
public:
// BlueprintNativeEvent : 필요시 재정의해서 사용하는 키워드, 자식 클래스에서 재정의할 때는
// Implementation을 붙여서 사용한다.
FString GetNotifyName_Implementation() const;
public:
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration);
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation);
};
CAnimNotifyState_Equip.cpp
#include "CAnimNotifyState_Equip.h"
#include "Global.h"
#include "IRifle.h"
#include "CRifle.h"
FString UCAnimNotifyState_Equip::GetNotifyName_Implementation() const
{
return "Equip";
}
void UCAnimNotifyState_Equip::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
Super::NotifyBegin(MeshComp, Animation, TotalDuration);
// 게임모드가 아니라면 MeshComp가 NULL일 수 있음
CheckNull(MeshComp);
// Character가 IIRifle로 casting됨
IIRifle* rifle = Cast<IIRifle>(MeshComp->GetOwner());
CheckNull(rifle);
rifle->GetRifle()->Begin_Equip();
}
void UCAnimNotifyState_Equip::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::NotifyEnd(MeshComp, Animation);
CheckNull(MeshComp);
IIRifle* rifle = Cast<IIRifle>(MeshComp->GetOwner());
CheckNull(rifle);
rifle->GetRifle()->End_Equip();
}
CAnimNotifyState_UnEquip.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "CAnimNotifyState_UnEquip.generated.h"
UCLASS()
class UONLINE_03_CPP_API UCAnimNotifyState_UnEquip : public UAnimNotifyState
{
GENERATED_BODY()
public:
FString GetNotifyName_Implementation() const;
public:
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration);
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation);
};
CAnimNotifyState_UnEquip.cpp
#include "CAnimNotifyState_UnEquip.h"
#include "Global.h"
#include "IRifle.h"
#include "CRifle.h"
FString UCAnimNotifyState_UnEquip::GetNotifyName_Implementation() const
{
return "UnEquip";
}
void UCAnimNotifyState_UnEquip::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
Super::NotifyBegin(MeshComp, Animation, TotalDuration);
// 게임모드가 아니라면 MeshComp가 NULL일 수 있음
CheckNull(MeshComp);
// Character가 IIRifle로 casting됨
IIRifle* rifle = Cast<IIRifle>(MeshComp->GetOwner());
CheckNull(rifle);
rifle->GetRifle()->Begin_UnEquip();
}
void UCAnimNotifyState_UnEquip::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::NotifyEnd(MeshComp, Animation);
CheckNull(MeshComp);
IIRifle* rifle = Cast<IIRifle>(MeshComp->GetOwner());
CheckNull(rifle);
rifle->GetRifle()->End_UnEquip();
}
결과
'Unreal Engine 4 > C++' 카테고리의 다른 글
<Unreal C++> 31 - Gun Shooting Mode (Fire Effect) (0) | 2022.05.02 |
---|---|
<Unreal C++> 27 - Gun Shooting Mode(Aiming), Fire & CrossHair (0) | 2022.04.25 |
<Unreal C++> 22 - SweepTrace (0) | 2022.04.24 |
<Unreal C++> 20 - Line Trace / Dynamic Delegate (0) | 2022.04.15 |
<Unreal C++> 19 - Function Override (0) | 2022.04.15 |