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 똑같은 애니메이션)

Untitled

Untitled

장착을 할 때 플레이어 입장에서 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;

Untitled

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);
}

헤더 넣어주기

Untitled

블프 먼저 생성

Untitled

붙일려면 소켓 필요

Holster_Rifle → Spine03→ Holster_Rifle

Untitled

Hand_R 에 소켓 생성 → Right_Hand_Rifle

Untitled

Attach 시키는것은 누구의 역할 인가?

객체지향 속성

상속에 관련된

  1. 상속성
  2. 추상성
  3. 다형성

외부에 노출 (캡슐화를 정보은닉성과 동일한 개념으로 설명하지만 다른 개념 )

  1. 정보은닉성 : private | public | protected 외부에 공개할 것만 공개 하자
  2. 캡슐화 : (블랙박스화?) 입력 줄 수 있고, 그것으로 부터 결과를 리턴 받을 수 있다. 하지만 내부는 볼 수 없는 것
    1. ex) 윈도우 클릭할 때도 보면 프로그램이 어떻게 열리는 지는 모르겠지만. 우리 자신 한테는 그 프로그램이 보여지는 것 그 내부는 알 수 없는 것

생각해보자 : player가 RIfle에 명령을 내렸고 Rifle을 생성했다. RIfle을 장착 할 것이다. 그럼 Holster에 장착을 할 것이다. 그럼 이것의 주체는 누가 되는 것인가?

Player의 입장에서는 Rifle에게 장착해라 라고 명령을 내리는 것이다. 그럼 장착 처리는 Rifle에 있는 것이다. (캡슐화)

  1. spawn도 마찬가지로. player가 Rifle에게 spawn 시켜라 라고 명령을 내렸고.플레이어 입장이 Rifle 안에서는 어떻게 처리 되고 있는지 모른다. 그런데 spawn 해서 Return 받은 것이다. 그러니까 장착하라는 것도 Rifle에 있다는 것이 된다.
  2. Holster는 시작하자 마자 장착이 될 것이다(등에) beginplay에서 하면 된다.

[클린코드 : 객체지향 설계의 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);

}

Untitled

Untitled

장착 명령

keyboard 1번 Rifle 장착

CPlayer

CPlyer.cpp Player에서 OnRifle이 눌렸다면. CRifle에서 처리해주어야 한다.

PlayerInputComponent->BindAction("Rifle", EInputEvent::IE_Pressed, this, &ACPlayer::OnRifle);

Untitled

CPlayer.h

void OnRifle();

Untitled

장착 처리 는 CRifle에서

//CRifle.h
public:	
	ACRifle();

	void Equip();

//==
//원래 장착되어 있던 것 
private:
	bool bEquipped;
	bool bEquipping; //장착 중인지. 

Untitled

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 애니메이션 블루프린트 수정

Untitled

Untitled

BS_Rifle 만약 애니메이션이 없을 경우? 애니메이션이 없을 경우 사용하는 방법?(다음에)

블프에선느 Private 뭐 Public 등 사용할 수 있게 열어놨었는데. C에서는 그렇게 할 수 없다.

CRifle.h

Equipped를 리턴해 줄 것  만들어준다.

Equipped를 리턴해 줄 것 만들어준다.

선언과 동시에 정의 된 것이 실제로어떻게 정의 되는 가 정의 위치로 가면

public:
	FORCEINLINE bool GetEquipped() { return bEquipped; }

Untitled

예시)

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함수는 점프를 하지 않는다. 컴파일러가 그 함수내용으로 복사해서 줄의 내용을 바꿔 버린다.

정리)

  1. Inline 함수나 Template함수는 점프안한다. 컴파일러가 그내용을 복사해서 바꿔 둔다.

  2. Inline 함수가 일어날 수 있는 조건 2가지

    1. 클래스 헤더에 선언과 동시에 정의하면 그걸 무조건 Inline으로 간주 한다.
    2. inline이라는 키워드가 함수 앞에 붙으면 (C마다 컴파일러가 다 틀리다)
      1. inline : 플랫폼이 inline화 할 수 있는 상황이면 하고 아니면 하지 안는 것
      2. _inline : 프로그래머가 강제적으로 무조건 inline만 하라고 명령 하는 것
      3. __inline : 컴파일러 판단
  3. 그런데 언리얼은 다양한 플랫폼이 다 있다. 그곳에 다 빌드가 이루어져야 한다. 그런데 어떤 것을 써야할지 판단이 안된다. 언리얼이 그것을 다 만들어 두었다. (무조건 inline으로 강제하겠다)

    FORCEINLINE bool GetEquipped() { return bEquipped; }
    

    FORCEINLINE : 강제적으로 inline해라 라는 매크로를 붙여준다.

    이러면 GetEquipped는 무조건 Inline이 나온다.

이런 부류들이 Inline화가 되어야 좋은가?

inline 템플릿의 단점.