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

Projectile쓸려면 맨위에가 충돌체가 나와야한다.

CBullet.cpp

#include "CBullet.h"
#include "Global.h"
#include "Components/CapsuleComponent.h" 
#include "Components/StaticMeshComponent.h" //충돌체 같이 들어감
#include "GameFramework/ProjectileMovementComponent.h"
#include "Materials/MaterialInstanceConstant.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;
}

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

	Capsule->OnComponentHit.AddDynamic(this, &ACBullet::OnHit); // 불러들인 자기 충돌에 대해 
}

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

//자기 충돌 제거 
void ACBullet::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	Destroy();
}

Untitled

CHelpers.h

#pragma once

#include "CoreMinimal.h"
#include "Components/SceneComponent.h"

class U2110_03_API CHelpers
{
public:
	template<typename T>
	static void CreateComponent(AActor* InActor, T** OutComponent, FName InName, USceneComponent* InParent = nullptr, FName InSocketName = NAME_None)
	{
		*OutComponent = InActor->CreateDefaultSubobject<T>(InName);

		if (!!InParent)
		{
			(*OutComponent)->SetupAttachment(InParent, InSocketName);

			return;
		}

		InActor->SetRootComponent(*OutComponent);
	}

//실행만 시켜주기 부모 자식 관계 없음
	template<typename T>
	static void CreateActorComponent(AActor* InActor, T** OutComponent, FName InName)
	{
		*OutComponent = InActor->CreateDefaultSubobject<T>(InName);
	}

	template<typename T>
	static void GetAsset(T** OutObject, FString InPath)
	{
		ConstructorHelpers::FObjectFinder<T> asset(*InPath);
		*OutObject = asset.Object;
	}

	template<typename T>
	static void GetAssetDynamic(T** OutObject, FString InPath)
	{
		*OutObject = Cast<T>(StaticLoadObject(T::StaticClass(), nullptr, *InPath));
	}

	template<typename T>
	static void GetClass(TSubclassOf<T>* OutClass, FString InPath)
	{
		ConstructorHelpers::FClassFinder<T> asset(*InPath);
		*OutClass = asset.Class;
	}

	template<typename T>
	static T* FindActor(UWorld* World)
	{
		for (AActor* actor : World->GetCurrentLevel()->Actors)
		{
			if (!!actor && actor->IsA<T>())
				return Cast<T>(actor);
		}

		return nullptr;
	}

	template<typename T>
	static void FindActors(UWorld* World, TArray<T *>& OutArray)
	{
		OutArray.Empty();

		for (AActor* actor : World->GetCurrentLevel()->Actors)
		{
			if (!!actor && actor->IsA<T>())
				OutArray.Add(Cast<T>(actor));
		}
	}

	template<typename T>
	static T* GetComponent(AActor* InActor)
	{
		//return Cast<USkeletalMeshComponent>(GetComponentByClass(USkeletalMeshComponent::StaticClass()));
		return Cast<T>(InActor->GetComponentByClass(T::StaticClass()));
	}

	template<typename T>
	static T* GetComponent(AActor* InActor, const FString& InName)
	{
		TArray<T *> components;
		InActor->GetComponents<T>(components);

		for (T* component : components)
		{
			if (component->GetName() == InName)
				return component;
		}

		return nullptr;
	}
};

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

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

public:
	void Fire();
	void Begin_Fire();

protected:
	virtual void BeginPlay() override;

public:	
	virtual void Tick(float DeltaTime) override;

private:
	class ACharacter* OwnerCharacter;

private:
	bool bEquipping;
	bool bEquipped;
	bool bAiming;
};

CRifle.cpp

#include "CRifle.h"
#include "Global.h"
#include "CBullet.h"
#include "Animation/AnimMontage.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.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'");

//클래스 타입 _C 
	CHelpers::GetClass<ACBullet>(&BulletClass, "Blueprint'/Game/05_TPS/BP_CBullet.BP_CBullet_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);
}

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

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

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

//fire 위치 및 생성 
void ACRifle::Fire()
{
	CheckTrue(bEquipping);
	CheckFalse(bEquipped);

//카메라 섞어서 가져오는 
	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); //탄착군 형성 보기위한 draw

	FVector muzzleLocation = Mesh->GetSocketLocation("MuzzleFlash"); //소켓 가져다가 

	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()
{
	
}

알고만 있자) 쿼터니온 하고 Rotator는 1:1 매칭 된다.

FRotator == FQuat == FRotateMatrix (1 : 1 매칭 되는 것들 )

ACBullet* bullet = GetWorld()->SpawnActor<ACBullet>(BulletClass, 생성방향, direction.Rotation());

소켓 위치에 잘 못 주면 콜리전과 걸려가지고, 발사가 안될 수 있다. 소켓을 좀더 앞으로 배치하거나. (이문제는 또 해당 위치에서 전방으로 나가지 않고 아래로 붙어서 날라가는 경우가 발생할 수 있다. )

애니메이션 문제로 인해 아래로 날라가는 현상이 생길 수 있다. 주의하자.