UI 에 따른 연발 모드 실행 및 발사

CRifle.h

#pragma once

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

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")
		class UAnimMontage* FireMontage;

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

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

	UPROPERTY(EditDefaultsOnly, Category = "Fire")
		float LimitPitchAngle = 0.25f; //0.25 가 45도 

	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;

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

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

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

protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

private:
	class ACharacter* OwnerCharacter;

private:
	bool bEquipping;
	bool bEquipped;
	bool bAiming;
	bool bFiring;

	float CurrPitchAngle; //발사시 반동 재현

private:
	class UCUserWidget_AutoFire* AutoFireWidget;

private:
	FTimerHandle AutoFirehandle;
};

CRifle.cpp

#include "CRifle.h"
#include "Global.h"
#include "CBullet.h"
#include "CUserWidget_AutoFire.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::GetAsset<UAnimMontage>(&FireMontage, "AnimMontage'/Game/Character/Montages/Rifle/Rifle_Equip_Fire_Montage.Rifle_Equip_Fire_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'");
}

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

void ACRifle::Tick(float DeltaTime)
{
	Super::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");
	
	//pitchAngle
	{
		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 (AutoFireWidget->GetOn())
	{
		if (GetWorld()->GetTimerManager().IsTimerActive(AutoFirehandle))
			GetWorld()->GetTimerManager().ClearTimer(AutoFirehandle);
		
// 어디에 들어오든 해지가 되는
	}

	bFiring = false;
}

//타이머가 끝나고 나서. 없어지는
void ACRifle::Toggle_AutoFire()
{
	AutoFireWidget->Toggle();

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

CPlayer.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "05_TPS/IRifle.h"
#include "CPlayer.generated.h"

UCLASS()
class U2110_03_API ACPlayer 
	: public ACharacter
	, public IIRifle
{
	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;

public:
	virtual bool IsEquipped_Rifle() override;
	virtual bool IsAiming_Rifle() override;

	virtual void Begin_Equip_Rifle() override;
	virtual void End_Equip_Rifle() override;
	virtual void Begin_Unequip_Rifle() override;
	virtual void End_Unequip_Rifle() override;

	//virtual void Fire_Rifle() override;

private:
	void OnMoveForward(float InAxisValue);
	void OnMoveRight(float InAxisValue);
	void OnVerticalLook(float InAxisValue);
	void OnHorizontalLook(float InAxisValue);

public:
	void OnRun();
	void OffRun();

	void OnRifle();

	void OnSubAction();
	void OffSubAction();

	void OnFire();
	void OffFire();

	void OnAutoFire();

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;

	
	RifleClass = ACRifle::StaticClass();
}

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

	PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Pressed, this, &ACPlayer::OnSubAction);
	PlayerInputComponent->BindAction("SubAction", EInputEvent::IE_Released, this, &ACPlayer::OffSubAction);

	PlayerInputComponent->BindAction("Fire", EInputEvent::IE_Pressed, this, &ACPlayer::OnFire);
	PlayerInputComponent->BindAction("Fire", EInputEvent::IE_Released, this, &ACPlayer::OffFire);

	PlayerInputComponent->BindAction("AutoFire", EInputEvent::IE_Pressed, this, &ACPlayer::OnAutoFire);
}

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

void ACPlayer::OnSubAction()
{
	
}

void ACPlayer::OffSubAction()
{
	
}

void ACPlayer::Begin_Equip_Rifle()
{
	Rifle->Begin_Equip();
}

void ACPlayer::End_Equip_Rifle()
{
	Rifle->End_Equip();
}

void ACPlayer::Begin_Unequip_Rifle()
{
	Rifle->Begin_Unequip();
}

void ACPlayer::End_Unequip_Rifle()
{
	Rifle->End_Unequip();
}

bool ACPlayer::IsEquipped_Rifle()
{
	return Rifle->IsEquipped();
}

bool ACPlayer::IsAiming_Rifle()
{
	return Rifle->IsAiming();
}

//void ACPlayer::Fire_Rifle()
//{
//	Rifle->Begin_Fire();
//}

void ACPlayer::OnFire()
{
	Rifle->Begin_Fire();
}
//타임델리게이트 이용해서 지난 시간마다. 샷을 만든다?
void ACPlayer::OffFire()
{
	Rifle->End_Fire();
}

void ACPlayer::OnAutoFire()
{
	Rifle->Toggle_AutoFire();
}

연발 모드가 시간마다 콜해주는 ?

CBullet.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CBullet.generated.h"

UCLASS()
class U2110_03_API ACBullet : public AActor
{
	GENERATED_BODY()
	
//벽면이나. 어디 맞았을 때 메터리얼 효과 
private:
	UPROPERTY(EditDefaultsOnly, Category = "Impact")
		class UMaterialInstanceConstant* Decal;

	UPROPERTY(EditDefaultsOnly, Category = "Impact")
		class UParticleSystem* Particle;

private:
	UPROPERTY(VisibleDefaultsOnly)
		class UCapsuleComponent* Capsule;

	UPROPERTY(VisibleDefaultsOnly)
		class UStaticMeshComponent* Mesh;

	UPROPERTY(VisibleDefaultsOnly)
		class UProjectileMovementComponent* Projectile;

public:	
	ACBullet();

	void Shoot(const FVector& InDirection);

protected:
	virtual void BeginPlay() override;

private:
	UFUNCTION()
		void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
//맞았을 때 상황을 
};

CBullet.cpp

#include "CBullet.h"
#include "Global.h"
#include "Components/CapsuleComponent.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Components/DecalComponent.h"
#include "Particles/ParticleSystem.h"

ACBullet::ACBullet()
{
	CHelpers::CreateComponent<UCapsuleComponent>(this, &Capsule, "Capsule");
	CHelpers::CreateComponent<UStaticMeshComponent>(this, &Mesh, "Mesh", Capsule);
	CHelpers::CreateActorComponent<UProjectileMovementComponent>(this, &Projectile, "Projectile");

	Capsule->SetRelativeRotation(FRotator(90, 0, 0));
	Capsule->SetCapsuleHalfHeight(50);
	Capsule->SetCapsuleRadius(2);
	Capsule->SetCollisionProfileName("BlockAllDynamic");

	UStaticMesh* mesh;
	CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/Meshes/Sphere.Sphere'");
	Mesh->SetStaticMesh(mesh);
	Mesh->SetRelativeScale3D(FVector(1, 0.025f, 0.05f));
	Mesh->SetRelativeRotation(FRotator(90, 0, 0));

	Projectile->InitialSpeed = 2e+4f; //2 * 10 ^ 4
	Projectile->MaxSpeed = 2e+4f; //2 * 10 ^ 4
	Projectile->ProjectileGravityScale = 0;

	CHelpers::GetAsset<UMaterialInstanceConstant>(&Decal, "MaterialInstanceConstant'/Game/Weapons/M_Decal_Inst.M_Decal_Inst'");
	CHelpers::GetAsset<UParticleSystem>(&Particle, "ParticleSystem'/Game/Weapons/Particle/VFX_Impact_Default.VFX_Impact_Default'");
}

void ACBullet::BeginPlay()
{
	Super::BeginPlay();

	Capsule->OnComponentHit.AddDynamic(this, &ACBullet::OnHit);
}

void ACBullet::Shoot(const FVector& InDirection)
{
	Projectile->Velocity = InDirection * Projectile->InitialSpeed;
}

//히트가 되었을 때 decal 생성 
void ACBullet::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	FRotator rotation = Hit.ImpactNormal.Rotation(); //회전값을 구해주고

	if (!!Decal)
	{
		UDecalComponent* decal = UGameplayStatics::SpawnDecalAtLocation(GetWorld(), Decal, FVector(5), Hit.Location, rotation, 10);
		decal->SetFadeScreenSize(0);
	}

	if (!!Particle)
	{
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), Particle, Hit.Location, rotation);
	}

	Destroy();
}