본문 바로가기
언리얼5 & C++

[Unreal, C++] 3인칭 슈팅게임 연습 일지 - 2 : 사격, 이펙트 구현

by 17번 일개미 2022. 6. 18.
728x90

저번에 했던 Shoot 함수 안에는 PullTrigger()함수가 있었다.

이번에는 이 함수에 대해 적어볼 것이다.

void APlayerCharacter::Shoot()
{
	if(IsAiming) // Aiming == true then
	{
		if(CanShoot)
		{
			IsShoot = true;
			Gun->PullTrigger();
			UE_LOG(LogTemp, Warning, TEXT("One Shot"));
			CanShoot = false;
		}
	}
	else IsShoot = false;
}

 

Gun 은 헤더 파일에서 무기 클래스를 Gun이라는 이름으로 선언한 것이다.

APistol_Gun* Gun;

Pistol_Gun.cpp 에 PullTrigger에 대한 내용이 있다. (앞에 A는 Actor를 의미하며 모든 액터류에는 다 A가 자동으로 붙는다)


발사 구현 코드 전체

void APistol_Gun::PullTrigger()
{
	UE_LOG(LogTemp, Warning, TEXT("In Pulltrigger"));
	UGameplayStatics::SpawnEmitterAttached(MuzzleFlash, Mesh, TEXT("Muzzle"));
	APawn* OwnerPawn = Cast<APawn>(GetOwner());
	if(OwnerPawn == nullptr) return;
	AController* OwnerController = OwnerPawn->GetController();
	if(OwnerController == nullptr) return;

	FVector Location;
	FRotator Rotation;
	OwnerController->GetPlayerViewPoint(Location, Rotation);

	FVector End = Location + Rotation.Vector() * MaxRange;
	FHitResult Hit;
	bool Success = GetWorld()->LineTraceSingleByChannel(Hit, Location, End, ECollisionChannel::ECC_GameTraceChannel1);
	if(Success)
	{
		FVector AimDirection = -Rotation.Vector();
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), Impact, Hit.Location, AimDirection.Rotation());
		//DrawDebugPoint(GetWorld(), Hit.Location, 20, FColor::Red, true);
		AActor* HitActor = Hit.GetActor();
		if(HitActor != nullptr)
		{
			FPointDamageEvent DamageEvent(PrimaryDamage, Hit, AimDirection, nullptr);
			HitActor->TakeDamage(PrimaryDamage, DamageEvent, OwnerController, this);
		}
	}
}

일단 전체를 보고,

위에서 부터 천천히 보겠다!


총 발사 이펙트 / 총의 주인 찾기

	//UE_LOG(LogTemp, Warning, TEXT("In Pulltrigger"));
	UGameplayStatics::SpawnEmitterAttached(MuzzleFlash, Mesh, TEXT("Muzzle"));
	APawn* OwnerPawn = Cast<APawn>(GetOwner());
	if(OwnerPawn == nullptr) return;
	AController* OwnerController = OwnerPawn->GetController();
	if(OwnerController == nullptr) return;

UGameplayStatics::SpawnEmitterAttached(붙일 이펙트, 붙일 대상, 붙일 소켓의 이름)

함수를 사용해서 총구에서 생길 이펙트를 총의 Mesh에 Muzzle이라는 이름을 가진 뼈대 소켓에 붙일 수 있다.

유니티에서 이펙트 GameObject를 Instaniate()를 통해 생성하는것과 비슷해 보인다.

 

그 후, Pawn변수에 이 총을 들고있는 Owner 를 찾아서 넣어줄 것이다. 이 스크립트는 총 스크립트이고, 총을 들고 있는 부모 액터 즉, 플레이어를 찾아내는 과정이다.

 

+ 예외 처리를 위해 if문을 통해 변수에 nullptr 아무것도 들어가지 않았으면 안전하게 함수를 종료하는 처리를 해준다.

 

그 다음, 컨트롤러 변수를 만들고 아까 얻어낸 플레이어 에게서 Getcontroller 를 가져온다.


시점 찾기

	FVector Location;
	FRotator Rotation;
	OwnerController->GetPlayerViewPoint(Location, Rotation);

	FVector End = Location + Rotation.Vector() * MaxRange;

이제 벡터와 회전을 담을 변수를 각각 정해주고,

아까 구한 컨트롤러에게서 플레이어의 시점 즉, 카메라가 바라보는 위치와 회전을 가져온다.

Vector End 에 그 위치에 + 바라보는 방향 * 사거리 를 대입한다. 여기서 사거리는 총의 사거리를 말한다.

 

다시 정리하면, 카메라가 보는 시점으로부터 총의 사거리만큼 쭉 선을 긋는 과정이라 생각하면 된다.


라인 트레이싱 / 피격 판정

FHitResult Hit;
	bool Success = GetWorld()->LineTraceSingleByChannel(Hit, Location, End, ECollisionChannel::ECC_GameTraceChannel1);
	if(Success)
	{
		FVector AimDirection = -Rotation.Vector();
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), Impact, Hit.Location, AimDirection.Rotation());
		//DrawDebugPoint(GetWorld(), Hit.Location, 20, FColor::Red, true);
		AActor* HitActor = Hit.GetActor();
		if(HitActor != nullptr)
		{
			FPointDamageEvent DamageEvent(PrimaryDamage, Hit, AimDirection, nullptr);
			HitActor->TakeDamage(PrimaryDamage, DamageEvent, OwnerController, this);
		}
	}

맞은 곳의 정보를 담을 Hit변수를 FHitResult 형태로 선언한다.

bool 변수인 Success 는 "맞았는지?" 를 의미한다.

 

GetWorld()->LineTraceSingleByChannel(맞은 곳 저장할 변수, 아까 정한 카메라 위치(시작점), 아까 계산한 도착지점, 충돌채널)

함수를 통해 직선 광선을 쏴서 총알이 피격되는 곳을 얻어낼 수 있다.

 

이제 총이 맞은 위치에 탄흔 이펙트를 생성할 것이다.

UGameplayStatics::SpawnEmitterAtLocation(월드, 붙일 이펙트, 맞은 위치, 이펙트의 회전값) 함수를 통해 할 수 있다.

AimDirection 에 마이너스 값을 주는 이유는,

총이 나간 방향의 반대방향으로 이펙트가 튀어야 하기 때문이다.

쉽게말하면 총알 맞은 방향의 정반대로 파편이 튀어야 하기 때문인데, 마이너스로 안하면 아마 이펙트가 총알 방향이랑 똑같이 생성 될 거라 어색하거나, 이펙트가 벽 안쪽에 파묻힐 것이다.

 

그 뒤에 코드는 맞은 액터의 Actor를 가져와서 그 Actor에게 데미지를 주는 DamageEvent를 실행하는 과정이다.

 

 


이렇게 하면 카메라 시점대로 화면 중앙으로 LineTracing을 하고 맞은 위치에 이펙트까지 생성할 수 있다.

 

728x90