CRifle 생성
CRifle.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CRifle.generated.h"
UCLASS()
class U2110_03_API ACRifle : public AActor
{
GENERATED_BODY()
//카테고리를 잘 나눠나야 한다
private:
UPROPERTY(EditDefaultsOnly, Category = "Socket") //
FName HolsterSocket = "Holster_Rifle"; //socket
UPROPERTY(EditDefaultsOnly, Category = "Socket")
FName RightHandSocket = "Right_Hand_Rifle"; //소켓 네임
//장착
UPROPERTY(EditDefaultsOnly, Category = "Montage")
class UAnimMontage* GrabMontage;
UPROPERTY(EditDefaultsOnly, Category = "Montage")
float GrabMontageSpeed = 3;
//비장착
UPROPERTY(EditDefaultsOnly, Category = "Montage")
class UAnimMontage* UngrabMontage;
UPROPERTY(EditDefaultsOnly, Category = "Montage")
float UngrabMontageSpeed = 3;
private:
UPROPERTY(VisibleDefaultsOnly)
class USkeletalMeshComponent* Mesh;
public:
//자기자신 이것은 멤버함수 니까 객체가 있어야 사용 할 수 있다. 누가 만들지 모르니까 static 붙여준다.
//Rifle 클래스가 블프를 사용하는데 다른 총을 사용할 수 도 있기 때문에 ,TSubclassOf이 타입을 받는다.
static ACRifle* Spawn(TSubclassOf<ACRifle> RifleClass, class ACharacter* InOwner);
public:
FORCEINLINE bool GetEquipped() { return bEquipped; }
public:
ACRifle();
void Equip();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
private:
class ACharacter* OwnerCharacter; //캐릭터에
private:
bool bEquipped;
bool bEquipping;
};
CRifle.cpp
#include "CRifle.h"
#include "Global.h"
#include "Animation/AnimMontage.h" //몽타쥬 헤더
#include "Components/SkeletalMeshComponent.h" //스켈레탈메쉬
#include "GameFramework/Character.h"
**ACRifle::ACRifle()**
{
PrimaryActorTick.bCanEverTick = true;
CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Mesh, "Mesh"); //root가 될 것
//기본 값 총 경로 가져옴
USkeletalMesh* mesh; //총이 스켈레탈 메쉬형이니까.
CHelpers::GetAsset<USkeletalMesh>(&mesh, "SkeletalMesh'/Game/Weapons/Meshes/SK_AR4.SK_AR4'");
Mesh->SetSkeletalMesh(mesh); //스켈레탈 메쉬에 할당
// 기본 몽타쥬 할당
CHelpers::GetAsset<UAnimMontage>(&GrabMontage, "AnimMontage'/Game/Character/Montages/Rifle/Rifle_Grab_Montage.Rifle_Grab_Montage'");
CHelpers::GetAsset<UAnimMontage>(&UngrabMontage, "AnimMontage'/Game/Character/Montages/Rifle/Rifle_UnGrab_Montage.Rifle_UnGrab_Montage'");
}
ACRifle* ACRifle::Spawn(TSubclassOf<ACRifle> RifleClass, class ACharacter* InOwner)
{
FActorSpawnParameters params;
params.Owner = InOwner;
return InOwner->GetWorld()->SpawnActor<ACRifle>(RifleClass, params);
}
void ACRifle::BeginPlay()
{
Super::BeginPlay();
//character의 Owner를 가져온다.
OwnerCharacter = Cast<ACharacter>(GetOwner());
AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HolsterSocket);
}
void ACRifle::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ACRifle::Equip()
{
CheckTrue(bEquipping);
bEquipping = true;
if (bEquipped)
{
return;
}
OwnerCharacter->PlayAnimMontage(GrabMontage, GrabMontageSpeed);
}
애니메이션은 Rifle_Grab애니메이션 2개를 불러와서 하나는 Ungrab 으로 사용(grab 똑같은 애니메이션)
장착을 할 때 플레이어 입장에서 Spawn을 시켜야하는데. spawn은 Rifle이 해주는 것이다. spawn될 자기 자신이 해당 character에 되어 주는 것이다. (character가 spawn될 것을 생성시키는게 아니다.) 즉 무기들이 스스로 자기자신을 생성해서 리턴해 주는 개념이다.
팩토리(Factory) : 자기 스스로가 생성해서 리턴해주는 것(자기를 찍어서 주겠다) = 외부에서 자기자신을 콜 해야한다.
public:
//자기자신 이것은 멤버함수 니까 객체가 있어야 사용 할 수 있다. 누가 만들지 모르니까 static 붙여준다.
//Rifle 클래스가 블프를 사용하는데 다른 총을 사용할 수 도 있기 때문에 ,TSubclassOf이 타입을 받는다.
static ACRifle* Spawn(TSubclassOf<ACRifle> RifleClass, class ACharacter* InOwner);
beginplay이전에 생성
//CRifle.cpp
ACRifle* ACRifle::Spawn(TSubclassOf<ACRifle> RifleClass, class ACharacter* InOwner)
{
FActorSpawnParameters params;
params.Owner = InOwner; //해당 Owner
//onwer가 있었으면 그 해당 Owner의 world에 어딘가에 Spawn을 시켜준다.
return InOwner->GetWorld()->SpawnActor<ACRifle>(RifleClass, params);
}
//spawnactor가 ACRifle 타입을 생성 시켜서 InOwner에 준다. 이거 자체를 Return 시켜준것
//자기 자신을 생성시켜서 준 것
Player 기본으로 생성하게 할 것 이다.
CPlayer.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "CPlayer.generated.h"
UCLASS()
class U2110_03_API ACPlayer : public ACharacter
{
GENERATED_BODY()
//플레이어가 어떤 클래스를 생성 시킬지
private:
UPROPERTY(EditDefaultsOnly, Category = "Rifle")
TSubclassOf<class ACRifle> RifleClass;
private:
UPROPERTY(VisibleDefaultsOnly)
class USpringArmComponent* SpringArm;
UPROPERTY(VisibleDefaultsOnly)
class UCameraComponent* Camera;
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 OnVerticalLook(float InAxisValue);
void OnHorizontalLook(float InAxisValue);
void OnRun();
void OffRun();
void OnRifle();
public:
UFUNCTION(BlueprintCallable, Category = "Color")
void ChangeColor(FLinearColor InColor);
private:
class UMaterialInstanceDynamic* Materials[2];
//이 타입으로 만들어지고, 어떤 클래스 타입 부모 생성
private:
class ACRifle* Rifle;
CPlayer.cpp
#include "CPlayer.h"
#include "Global.h"
#include "05_TPS/CRifle.h"//헤더 삽입
#include "CAnimInstance.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.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);
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->MaxWalkSpeed = 400;
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/ABP_Character.ABP_Character_C'");
GetMesh()->SetAnimClass(animInstance);
SpringArm->SetRelativeLocation(FVector(0, 0, 60));
SpringArm->TargetArmLength = 200;
SpringArm->bDoCollisionTest = false;
SpringArm->bUsePawnControlRotation = true;
SpringArm->SocketOffset = FVector(0, 60, 0);
SpringArm->bEnableCameraLag = true;
CHelpers::GetClass<ACRifle>(&RifleClass, "Blueprint'/Game/05_TPS/BPCRifle.BPCRifle_C'");
}
void ACPlayer::BeginPlay()
{
Super::BeginPlay();
TArray<UMaterialInterface*> materials = GetMesh()->GetMaterials();
for (int32 i = 0; i < materials.Num(); i++)
{
Materials[i] = UMaterialInstanceDynamic::Create(materials[i], this);
GetMesh()->SetMaterial(i, Materials[i]);
}
Rifle = ACRifle::Spawn(RifleClass, this);
}
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("VerticalLook", this, &ACPlayer::OnVerticalLook);
PlayerInputComponent->BindAxis("HorizontalLook", this, &ACPlayer::OnHorizontalLook);
PlayerInputComponent->BindAction("Run", EInputEvent::IE_Pressed, this, &ACPlayer::OnRun);
PlayerInputComponent->BindAction("Run", EInputEvent::IE_Released, this, &ACPlayer::OffRun);
PlayerInputComponent->BindAction("Rifle", EInputEvent::IE_Pressed, this, &ACPlayer::OnRifle);
}
void ACPlayer::OnMoveForward(float InAxisValue)
{
FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0);
FVector direction = FQuat(rotator).GetForwardVector().GetSafeNormal2D();
AddMovementInput(direction, InAxisValue);
}
void ACPlayer::OnMoveRight(float InAxisValue)
{
FRotator rotator = FRotator(0, GetControlRotation().Yaw, 0);
FVector direction = FQuat(rotator).GetRightVector().GetSafeNormal2D();
AddMovementInput(direction, InAxisValue);
}
void ACPlayer::OnVerticalLook(float InAxisValue)
{
AddControllerPitchInput(InAxisValue);
}
void ACPlayer::OnHorizontalLook(float InAxisValue)
{
AddControllerYawInput(InAxisValue);
}
void ACPlayer::OnRun()
{
GetCharacterMovement()->MaxWalkSpeed = 600;
}
void ACPlayer::OffRun()
{
GetCharacterMovement()->MaxWalkSpeed = 400;
}
void ACPlayer::OnRifle()
{
Rifle->Equip();
}
void ACPlayer::ChangeColor(FLinearColor InColor)
{
for (UMaterialInstanceDynamic* material : Materials)
material->SetVectorParameterValue("BodyColor", InColor);
}
헤더 넣어주기
블프 먼저 생성
붙일려면 소켓 필요
Holster_Rifle → Spine03→ Holster_Rifle
Hand_R 에 소켓 생성 → Right_Hand_Rifle
Attach 시키는것은 누구의 역할 인가?
객체지향 속성
상속에 관련된
외부에 노출 (캡슐화를 정보은닉성과 동일한 개념으로 설명하지만 다른 개념 )
생각해보자 : player가 RIfle에 명령을 내렸고 Rifle을 생성했다. RIfle을 장착 할 것이다. 그럼 Holster에 장착을 할 것이다. 그럼 이것의 주체는 누가 되는 것인가?
Player의 입장에서는 Rifle에게 장착해라 라고 명령을 내리는 것이다. 그럼 장착 처리는 Rifle에 있는 것이다. (캡슐화)
[클린코드 : 객체지향 설계의 5가지 원칙 : 모든 원칙은 이 5가지로 부터 시작한다?](https://ppatabox.notion.site/5-5-aaba4c11b5c540acb7b8ea074a4f9fef)
CRifle
//CRilfe.cpp
void ACRifle::BeginPlay()
{
Super::BeginPlay();
OwnerCharacter = Cast<ACharacter>(GetOwner());
AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HolsterSocket);
}
장착 명령
keyboard 1번 Rifle 장착
CPlayer
CPlyer.cpp Player에서 OnRifle이 눌렸다면. CRifle에서 처리해주어야 한다.
PlayerInputComponent->BindAction("Rifle", EInputEvent::IE_Pressed, this, &ACPlayer::OnRifle);
CPlayer.h
void OnRifle();
장착 처리 는 CRifle에서
//CRifle.h
public:
ACRifle();
void Equip();
//==
//원래 장착되어 있던 것
private:
bool bEquipped;
bool bEquipping; //장착 중인지.
CRifle.cpp
void ACRifle::Equip()
{
CheckTrue(bEquipping); //true일때 리턴
bEquipping = true;
if (bEquipped) //장착 되었다면. 해제로
{
return;
}
//장착되어있는게 아니면 장착 모션에 들어가야한다.
OwnerCharacter->PlayAnimMontage(GrabMontage, GrabMontageSpeed);
}
Rifle 콜
//CPlayer.cpp
void ACPlayer::OnRifle()
{
Rifle->Equip(); //장착 명령
}
ABP_Character 애니메이션 블루프린트 수정
BS_Rifle 만약 애니메이션이 없을 경우? 애니메이션이 없을 경우 사용하는 방법?(다음에)
블프에선느 Private 뭐 Public 등 사용할 수 있게 열어놨었는데. C에서는 그렇게 할 수 없다.
CRifle.h
Equipped를 리턴해 줄 것 만들어준다.
선언과 동시에 정의 된 것이 실제로어떻게 정의 되는 가 정의 위치로 가면
public:
FORCEINLINE bool GetEquipped() { return bEquipped; }
예시)
inline bool ACRifle::GetEquipped()
{
return bEquipped;
}
이런식으로 선언된다. inline 함수를 거기로 점프하지 않고. 컴파일에서 그 해당 함수의 내용을 inline 을 호출 하는 곳에 코드를 붙여 버린다.
템플릿도 inline 처럼 동작을 한다.
일반적인 함수
void Add()
{
// 2) 이안에서 처리 하고
} // 3) 처리 되고
//어딘가
Add(); // 1) 이게 위 void로 가서. //4) 다시 돌아온다.
//이러한 스택 구조로 이루어진다. 일반적으로
Inline
inline void Add()
{
// 1) 이 안에 있는 코드의 내용을 컴파일에서 콜된 Add();를 지우고 복사 붙여넣기 해버린다.
}
//어딘가
Add();
컴파일 에서 Add를 콜하면
Template
//예시
Template<Typename T>
void Add(T a, T b)
{
a += b;
T c=a; //템플릿
}
//어딘가 0
Add();
a += b;
//어딘가 1
Add<int>(10, 20); // Int에 T의 자료형을 보고
a += b; -> int c = a //컴파일러가 자동으로 바꿔준다
add를 호출 할 때 이 해당 코드가 Inline화 처럼 바뀐다.
Add<int>(10, 20) // 이런식으로 호출 한다 하면 Inline화 처럼 바뀐다.
그러니까 Inline 함수는 Template함수는 점프를 하지 않는다. 컴파일러가 그 함수내용으로 복사해서 줄의 내용을 바꿔 버린다.
정리)
Inline 함수나 Template함수는 점프안한다. 컴파일러가 그내용을 복사해서 바꿔 둔다.
Inline 함수가 일어날 수 있는 조건 2가지
그런데 언리얼은 다양한 플랫폼이 다 있다. 그곳에 다 빌드가 이루어져야 한다. 그런데 어떤 것을 써야할지 판단이 안된다. 언리얼이 그것을 다 만들어 두었다. (무조건 inline으로 강제하겠다)
FORCEINLINE bool GetEquipped() { return bEquipped; }
FORCEINLINE : 강제적으로 inline해라 라는 매크로를 붙여준다.
이러면 GetEquipped는 무조건 Inline이 나온다.
이런 부류들이 Inline화가 되어야 좋은가?
inline 템플릿의 단점.