구조체 만들어주고 처리 까지

CAim_Third.h

#pragma once

#include "CoreMinimal.h"
#include "05_TPS/CAim.h"
#include "Components/TimelineComponent.h"
#include "CAim_Third.generated.h"

USTRUCT()
struct FAimData
{
	GENERATED_BODY()
		
public:
	UPROPERTY(EditDefaultsOnly)
		float TargetArmLength = 100;

	UPROPERTY(EditDefaultsOnly)
		FVector SocketOffset = FVector(0, 30, 10);

	UPROPERTY(EditDefaultsOnly)
		bool bEnableCameraLag = false;
	 

};

UCLASS(Blueprintable)
class U2110_03_API UCAim_Third : public UCAim
{
	GENERATED_BODY()
	
private:
	UPROPERTY(EditDefaultsOnly, Category = "Aim")
		FAimData AimData;

	UPROPERTY(EditDefaultsOnly, Category = "Aim")
		class UCurveFloat* Curve;

	UPROPERTY(EditDefaultsOnly, Category = "Aim")
		float CurveSpeed = 200;

public:	
	UCAim_Third();

	void BeginPlay(class ACharacter* InCharacter, class ACRifle* InRifle) override;
	void Tick(float DeltaTime) override;

	bool IsAvaliableZoom() override;

	void Begin_Aim() override;
	void End_Aim() override;

private:
	UFUNCTION()
		void Zooming(float Output) override;

private:
	class USpringArmComponent* SpringArm;
	class UCameraComponent* Camera;

	FAimData OriginAimData;

	FTimeline Timeline;
};

CAim_Third.cpp

#include "CAim_Third.h"
#include "Global.h"
#include "GameFramework/Character.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"

UCAim_Third::UCAim_Third()
{
//기본값이니 생성자에서 실행 
	CHelpers::GetAsset<UCurveFloat>(&Curve, "CurveFloat'/Game/05_TPS/Curve_Aim.Curve_Aim'");

}

void UCAim_Third::BeginPlay( ACharacter* InCharacter,  ACRifle* InRifle)
{
	Super::BeginPlay(InCharacter, InRifle);
	
	SpringArm = CHelpers::GetComponent<USpringArmComponent>(OwnerCharacter);
	Camera = CHelpers::GetComponent<UCameraComponent>(OwnerCharacter);

	FOnTimelineFloat zoomDelegate;
	zoomDelegate.BindUFunction(this, "Zooming"); //델리게이트 Float하나만 가진 델리게이트 

	Timeline.AddInterpFloat(Curve, zoomDelegate);
	Timeline.SetPlayRate(CurveSpeed);
}

void UCAim_Third::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	Timeline.TickTimeline(DeltaTime); //액터단위라서 라이플에서 콜해줘야 한다. (자동실행 안됨) 

}

bool UCAim_Third::IsAvaliableZoom()
{

	Super::IsAvaliableZoom();

	return !!SpringArm && !!Camera;
}

void UCAim_Third::Begin_Aim()
{
	Super::Begin_Aim();

	CheckFalse(IsAvaliableZoom());
	CheckTrue(bAiming); // Aiming상태에서 들어오면 안된다. 

	bAiming = true;

	OriginAimData.TargetArmLength = SpringArm->TargetArmLength;
	OriginAimData.SocketOffset = SpringArm->SocketOffset;
	OriginAimData.bEnableCameraLag = SpringArm->bEnableCameraLag;

	SpringArm->TargetArmLength = AimData.TargetArmLength;
	SpringArm->SocketOffset = AimData.SocketOffset;
	SpringArm->bEnableCameraLag = AimData.bEnableCameraLag;
	//Camera->FieldOfView = 40;

	Timeline.PlayFromStart();

}

void UCAim_Third::End_Aim()
{
	Super::End_Aim();

	CheckFalse(IsAvaliableZoom());
	CheckFalse(bAiming); 

	bAiming = false;

	SpringArm->TargetArmLength = OriginAimData.TargetArmLength;
	SpringArm->SocketOffset = OriginAimData.SocketOffset;
	SpringArm->bEnableCameraLag = OriginAimData.bEnableCameraLag;

	//Camera->FieldOfView = 90;

	Timeline.ReverseFromEnd();
}

void UCAim_Third::Zooming(float Output)
{
	Super::Zooming(Output);

	Camera->FieldOfView = Output;
}

동적할당?

CreateDefaultSubobject

NewObject

New

오브젝트 소멸 _경로

참고로 약 포인터 는 오브젝트 가비지 컬렉션 여부에 영향을 주지 않습니다.

오브젝트 소멸 처리는 오브젝트가 엔진에 더이상 레퍼런스되지 않을 때 가비지 컬렉션 시스템에 의해 자동으로 이루어집니다. 즉 어떤 UPROPERTY 포인터나, 엔진 컨테이너나, (TArray<UObject*> 또는 TWeakPtr<UObject*> 와 같은) 스마트 포인터 클래스도 강 레퍼런스를 갖지 않는 액터에 대해 일어난다는 뜻입니다. 가비지 컬렉터가 실행되면, 검색된 것 중 레퍼런스가 없는 오브젝트는 삭제될 것입니다. 추가로, MarkPendingKill() 함수를 오브젝트에서 바로 호출할 수 있는데, 그러면 해당 오브젝트로의 모든 포인터는 NULL 로 설정되고 글로벌 검색에서도 제거됩니다. 마찬가지로 이 오브젝트는 다음 가비지 컬렉션 패스에서 완전히 삭제될 것입니다. - 이것은 UObject의 라이프 싸이클

만들어 졌고, 그 주소를 A* 에 돌려준 것 = 감시 콜렉터의 역할

A* a = NewObject<A>();

	 A ~~~ 무엇이라고 쓰여질 것이다. 

//그런데 더이상 안쓴다.
	A = nullptr;  //그럼 기존에 있던 공간은 쓰여지지 않게 되는 것 언리얼에서는 그 공간이 끊어지면 자동으로 삭제한다. 

MarkPendingKill() 함수를 오브젝트에서 바로 호출할 수 있는데, 그러면 해당 오브젝트로의 모든 포인터는 NULL 로 설정되고 글로벌 검색에서도 제거됩니다

ex) 이곳에서 MarkPendingKill하면 3개 모두 Null로 바뀐다.

A* a = NewObject<A>(); // 이공간은 끊어지면서 삭제 된다. 

A* b = ?; //어딘가에 A주소를 넣었다

--------------

A* ? = b; //어딘가에 b의 주소를 넣었다, (결국 같은 말) 

이전에 C#은 가비지 컬렉터를 제어하지 못한다. 그러다 보니 메모리제거가 일어나야하는데. 메모리 제거는 속도를 저하시킨다. 그래서 언리얼 같은 경우는 가비지 컬렉터를 별도의 쓰레드로 관리한다. 인터럽트를 발생시키기 때문에. 그래서 문제가 되지 않지만. 유니티의 경우는 가비지 컬렉터를 기본으로 돌리는데. 이 컬렉터가 게임쓰레드 내에서 같이 돈다. 그래서 게임이 끊기는 현상이 생기는데 요즘엔 멀티쓰레드를 지원 한다고 한다.

ex)이렇게 하면 전역에서 쓰이고 헤더만 가져다 쓰면 되니까. 이름이 꼬일 상황이 생길 수 있다.

//전역에서 쓰이는 방식  헤더만 추가하면 가져다 쓸 수 있는 
enum Type
{
	Type_A, Type_B,...
};

그래서 전통적인 방식은 namespace방식을 사용한다.

namespace Collision {
	enum Type
	{
		Type_A, Type_B,...
	};
}

또다른 방식은 클래스로 해주는것

enum class Type
{
	A, B, C, ...
};

CRifle.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/TimelineComponent.h"
#include "CRifle.generated.h"

UENUM()
enum class EAimType : uint8
{
	Third = 0, First, Max,//, 뒤로 

};

UCLASS()
class U2110_03_API ACRifle : public AActor
{
	GENERATED_BODY()
	
private:
	UPROPERTY(EditDefaultsOnly, Category = "Socket")
		FName HolsterSocket = "Holster_Rifle";

	UPROPERTY(EditDefaultsOnly, Category = "Socket")
		FName HandSocket = "Hand_Rifle";

	UPROPERTY(EditDefaultsOnly, Category = "Montage")
		class UAnimMontage* GrabMontage;

	UPROPERTY(EditDefaultsOnly, Category = "Montage")
		class UAnimMontage* UngrabMontage;

	UPROPERTY(EditDefaultsOnly, Category = "Montage_Play")
		float GrabMontageSpeed = 3;

	UPROPERTY(EditDefaultsOnly, Category = "Montage_Play")
		float UngrabMontageSpeed = 3;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		TSubclassOf<class ACBullet> BulletClass;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		float PitchAngle = 0.25f;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		float LimitPitchAngle = 0.25f;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		TSubclassOf<class UMatineeCameraShake> CameraShakeClass;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		class USoundWave* Sound;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		class UParticleSystem* FlashParticle;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		class UParticleSystem* EjectParticle;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		TSubclassOf<class UCUserWidget_AutoFire> AutoFireWidgetClass;

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		float AutoFireInterval = 0.1f;

	UPROPERTY(EditDefaultsOnly, Category = "Trace")
		float AimDistance = 3000.0f;

	UPROPERTY(EditDefaultsOnly, Category = "Aim")
		TSubclassOf<class UCAim> AimClasses[(int32)EAimType::Max];

protected:
	UPROPERTY(BlueprintReadOnly, VisibleDefaultsOnly)
		class USkeletalMeshComponent* Mesh;

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

public:
	static ACRifle* Spawn(TSubclassOf<ACRifle> RifleClass, class ACharacter* InOwner);

public:	
	ACRifle();

public:
	void Equip();
	void Begin_Equip();
	void End_Equip();

private:
	void Unequip();
public:
	void Begin_Unequip();
	void End_Unequip();

private:
	UFUNCTION()
		void Fire();

public:
	void Begin_Fire();
	void End_Fire();

	void Toggle_AutoFire();

	void Begin_Aim();
	void End_Aim();

	bool IsAiming();

protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

private:
	class ACharacter* OwnerCharacter;

private:
	bool bEquipping;
	bool bEquipped;
	bool bFiring;

	float CurrPitchAngle;

private:
	class UCUserWidget_AutoFire* AutoFireWidget;

private:
	FTimerHandle AutoFireHandle;

private:
	UPROPERTY()
		class UCAim* Aims[(int32)EAimType::Max];

};

CRifle.cpp

#include "CRifle.h"
#include "Global.h"
#include "CBullet.h"
#include "CUserWidget_AutoFire.h"
#include "CAim.h"
#include "Animation/AnimMontage.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h"
#include "Camera/CameraComponent.h"
#include "Sound/SoundWave.h"
#include "Particles/ParticleSystem.h"

ACRifle::ACRifle()
{
	PrimaryActorTick.bCanEverTick = true;

	CHelpers::CreateComponent<USkeletalMeshComponent>(this, &Mesh, "Mesh");

	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_Equip_Montage.Rifle_Equip_Montage'");
	CHelpers::GetAsset<UAnimMontage>(&UngrabMontage, "AnimMontage'/Game/Character/Montages/Rifle/Rifle_Unequip_Montage.Rifle_Unequip_Montage'");

	CHelpers::GetClass<ACBullet>(&BulletClass, "Blueprint'/Game/05_TPS/BP_CBullet.BP_CBullet_C'");
	CHelpers::GetClass<UMatineeCameraShake>(&CameraShakeClass, "Blueprint'/Game/05_TPS/BP_CameraShake.BP_CameraShake_C'");

	CHelpers::GetAsset<USoundWave>(&Sound, "SoundWave'/Game/Weapons/S_RifleShoot.S_RifleShoot'");

	CHelpers::GetAsset<UParticleSystem>(&FlashParticle, "ParticleSystem'/Game/Weapons/Particle/VFX_Muzzleflash.VFX_Muzzleflash'");
	CHelpers::GetAsset<UParticleSystem>(&EjectParticle, "ParticleSystem'/Game/Weapons/Particle/VFX_Eject_bullet.VFX_Eject_bullet'");

	CHelpers::GetClass<UCUserWidget_AutoFire>(&AutoFireWidgetClass, "WidgetBlueprint'/Game/05_TPS/WB_CAutoFire.WB_CAutoFire_C'");

	CHelpers::GetClass<UCAim>(&AimClasses[(int32)EAimType::Third], "Blueprint'/Game/05_TPS/BP_CAimSub.BP_CAimSub_C'");

}

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

	OwnerCharacter = Cast<ACharacter>(GetOwner());
	AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HolsterSocket);

	if (!!AutoFireWidgetClass)
	{
		AutoFireWidget = CreateWidget<UCUserWidget_AutoFire, APlayerController>
		(
			OwnerCharacter->GetController<APlayerController>(),
			AutoFireWidgetClass
		);
		AutoFireWidget->AddToViewport();
		AutoFireWidget->SetVisibility(ESlateVisibility::Hidden);
	}

	Aims[(int32)EAimType::Third] = NewObject<UCAim>(this, AimClasses[(int32)EAimType::Third]);
	Aims[(int32)EAimType::Third]->BeginPlay(OwnerCharacter, this); //위에 대한 beginplay를 콜해줘야한다. 
	
}

void ACRifle::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
//타임라인 콜 
	if (!!Aims[(int32)EAimType::Third])
		Aims[(int32)EAimType::Third]->Tick(DeltaTime);
}

void ACRifle::Equip()
{
	CheckTrue(bEquipping);

	bEquipping = true;
	if (bEquipped)
	{
		Unequip();

		return;
	}

	OwnerCharacter->PlayAnimMontage(GrabMontage, GrabMontageSpeed);
}

void ACRifle::Begin_Equip()
{
	AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HandSocket);
	bEquipped = true;

	OwnerCharacter->bUseControllerRotationYaw = true;
	OwnerCharacter->GetCharacterMovement()->bOrientRotationToMovement = false;

	AutoFireWidget->SetVisibility(ESlateVisibility::Visible);
}

void ACRifle::End_Equip()
{
	bEquipping = false;
}

void ACRifle::Unequip()
{
	OwnerCharacter->PlayAnimMontage(UngrabMontage, UngrabMontageSpeed);
}

void ACRifle::Begin_Unequip()
{
	AttachToComponent(OwnerCharacter->GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, true), HolsterSocket);
	bEquipped = false;

	OwnerCharacter->bUseControllerRotationYaw = false;
	OwnerCharacter->GetCharacterMovement()->bOrientRotationToMovement = true;

	AutoFireWidget->SetVisibility(ESlateVisibility::Hidden);
}

void ACRifle::End_Unequip()
{
	bEquipping = false;
}

void ACRifle::Fire()
{
	UCameraComponent* camera = CHelpers::GetComponent<UCameraComponent>(OwnerCharacter);
	FVector direction = camera->GetForwardVector();
	direction = UKismetMathLibrary::RandomUnitVectorInConeInDegrees(direction, PitchAngle);

	FTransform transform = camera->GetComponentToWorld();
	FVector start = transform.GetLocation() + direction;
	FVector end = transform.GetLocation() + direction * AimDistance;

	//DrawDebugLine(GetWorld(), start, end, FColor::Green, true, 5);

	FVector muzzleLocation = Mesh->GetSocketLocation("MuzzleFlash");

	if(!!CameraShakeClass)
		OwnerCharacter->GetController<APlayerController>()->PlayerCameraManager->StartCameraShake(CameraShakeClass);

	if (!!Sound)
		UGameplayStatics::PlaySoundAtLocation(GetWorld(), Sound, muzzleLocation);

	if (!!FlashParticle)
		UGameplayStatics::SpawnEmitterAttached(FlashParticle, Mesh, "MuzzleFlash");

	if (!!EjectParticle)
		UGameplayStatics::SpawnEmitterAttached(EjectParticle, Mesh, "EjectBullet");
	
	//Pitch Angle
	{
		CurrPitchAngle -= PitchAngle * GetWorld()->GetDeltaSeconds();
		
		if (CurrPitchAngle > -LimitPitchAngle)
			OwnerCharacter->AddControllerPitchInput(CurrPitchAngle);
	}
	
	//Spawn Bullet
	{
		FVector spawnLocation = muzzleLocation + direction * 50;

		FActorSpawnParameters params;
		params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

		ACBullet* bullet = GetWorld()->SpawnActor<ACBullet>(BulletClass, spawnLocation, direction.Rotation());
		bullet->Shoot(direction);
	}
}

void ACRifle::Begin_Fire()
{
	CheckTrue(bEquipping);
	CheckFalse(bEquipped);
	CheckTrue(bFiring);

	bFiring = true;
	CurrPitchAngle = 0;

	if (AutoFireWidget->GetOn())
	{
		GetWorld()->GetTimerManager().SetTimer(AutoFireHandle, this, &ACRifle::Fire, AutoFireInterval, true);

		return;
	}

	Fire();
}

void ACRifle::End_Fire()
{
	if(GetWorld()->GetTimerManager().IsTimerActive(AutoFireHandle))
		GetWorld()->GetTimerManager().ClearTimer(AutoFireHandle);

	bFiring = false;
}

void ACRifle::Toggle_AutoFire()
{
	AutoFireWidget->Toggle();

	if (bFiring && AutoFireWidget->GetOn() == false)
		End_Fire();
}

void ACRifle::Begin_Aim()
{
	CheckFalse(bEquipped); //장착 되어있어야 한다.
	CheckTrue(bEquipping); //장착 중이 면 안된다. 

	CheckNull(Aims[(int32)EAimType::Third]); // 나중에 마우스 드래그에 의해 해당 변수노드에 따라? 변경 할거 

	Aims[(int32)EAimType::Third]->Begin_Aim();
}

void ACRifle::End_Aim()
{
	CheckFalse(bEquipped);
	CheckTrue(bEquipping);

	CheckNull(Aims[(int32)EAimType::Third]);

	Aims[(int32)EAimType::Third]->End_Aim();
}

bool ACRifle::IsAiming()
{
	bool b = false;
//나중에 First나오면 똑같이 해주고 둘 중의 하나 라도 조준 되어있으면 조준 된다. 
	if (!!Aims[(int32)EAimType::Third])
		b |= Aims[(int32)EAimType::Third]->IsAiming();

	return b;

}

Tip

해당 코드 줄에 F9를 눌러서 디버깅 해준다. F5누르면 디버깅 하면서 실행하게 되서 언리얼 창이 뜬다. 에러 없는 상태에서만 실행가능 - 언리얼창이 뜨면 에셋건드리면 안된다. 안들어오면 안걸린다.

Shift F5 하면 빌드 모드 꺼진다.

CHelpers에 만들어둔 LogLine(); 을 사용해준다. = 콜이되면 실행 된다. 해당 로그를 볼 수 있어서 실행되는지 확인 할 수 있다.

CAnimInstance.h

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "CAnimInstance.generated.h"

UCLASS()
class U2110_03_API UCAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	
protected:
	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
		float Speed;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
		float Direction;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Animation")
		float Pitch;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Rifle")
		bool bEquipped;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Rifle")
		bool bAiming;

public:
	virtual void NativeBeginPlay() override;
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

private:
	class ACharacter* OwnerCharacter;

	
};