언리얼 엔진 5/개발 일지

[UE5] 블루아카이브 TPS게임 개발일지 (52) - 리팩토링

ciel45 2024. 1. 30. 23:20

EX 스킬을 구현하기에 앞서, 한번 더 시간을 들여 프로젝트 전체적으로 리팩토링을 진행해주었다.

 

먼저, BP_Enemy에서 구현했던 One Shot, Shoot 이벤트를 C++로 만들었다.

총을 한발 쏘는 기존의 One Shot 이벤트이다.

 

 

블루프린트는 기본적으로 같은 로직을 수행하더라도 C++보다 성능이 떨어지기 때문에, 호출이 빈번한 기능은 C++로 만드는 것이 좋다. One Shot도 그러한 경우이다.

void AEnemy::OneShot()
{
	if (!TargetCharacter)
		return;

	// 플레이어를 바라보도록
	FRotator DesiredRotation = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), TargetCharacter->GetActorLocation());

	//피치 설정
	Pitch = DesiredRotation.Pitch;
	if (TargetCharacter->bIsCrouched) //타겟이 앉아있다면, 즉 엄폐중이면 조금 위를 조준 (엄폐물 위로 쏘도록)
		Pitch += 5;

	//그 외 설정
	SetActorRotation(FRotator(0.f, DesiredRotation.Yaw, DesiredRotation.Roll));

	//투사체 스폰
	FVector SpawnLocation = BulletStartPos->GetComponentLocation();
	FRotator SpawnRotation = BulletStartPos->GetComponentRotation();
	//투사체 방향에 랜덤성 부여
	SpawnRotation.Pitch += FMath::RandRange(-3.f, 3.f);
	SpawnRotation.Yaw += FMath::RandRange(-3.f, 3.f);

	FActorSpawnParameters Param;
	Param.Instigator = this;
	GetWorld()->SpawnActor<AProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, Param);

	UGameplayStatics::PlaySoundAtLocation(GetWorld(), GunSound, GetActorLocation(), 1.f, 1.f, 0.f, GunSoundAttenuation);
	AssultRifle->PlayMuzzleFlashEffect();
	
	return;
}

 

작성하고 나니 해석하기에 오히려 블루프린트보다 깔끔해진 것 같다.

 

연발 사격을 위해 이 One Shot을 호출하는 Shoot 함수도, 원래는 블루프린트에 있던 것을 C++로 다시 만들었다.

void AEnemy::Shoot()
{
	GetWorldTimerManager().SetTimer(ShootTimer, this, &AEnemy::OneShot, .1f, true);
}

 

Shoot 함수는 BlueprintCallable이다. 블루프린트 인터페이스 호출로 수행될 수 있도록 하기 위해서이다.

Attack 이벤트 마지막에 C++의 Shoot 함수가 달려있는 것을 볼 수 있다.

 

다음으로, 총구 화염 효과 관련 코드를 AGun으로 옮겼다.

권총이든, 소총이든 총구 화염은 재생되어야 하는데, 현재 칸나 권총의 화염은 Pistol 클래스에서, 적 소총의 화염은 BP_Enemy에서 맡고 있다. 

또한 화염의 위치를 담당하는 UArrowComponent* Muzzle의 경우도 권총의 경우엔 BP_Pistol에서, 소총의 경우엔 BP_Enemy에서 만들어서 쓰고 있다. 

따라서 현재는 상당히 좋지 않은 디자인이라고 할 수 있다.

원래 적 소총의 화염은 One Shot 블루프린트 함수 내부에서 재생하고 있었다. 기준점인 BulletStartPos도 BP_Enemy에서 대충 만든 것을 사용하였다.

 

이러한 문제점을 해결하기 위해, AGun에 UParticleSystem* 타입 MuzzleFlashEffect, UArrowComponent* 타입 Muzzle(화염 위치 지정 용도)를 만들고, PlayMuzzleFlashEffect() 함수를 만들었다.

//Gun.h 중 일부

public:
	UFUNCTION(BlueprintCallable)
	void PlayMuzzleFlashEffect();
    
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
	UArrowComponent* Muzzle;
   
protected:
	UPROPERTY(EditDefaultsOnly, meta = (AllowPrivateAccess = "true"), Category = VFX)
	UParticleSystem* MuzzleFlashEffect;
// Sets default values
AGun::AGun()
{
 	/* 생략 */
    // 생성자에서 Muzzle 초기화
	Muzzle = CreateDefaultSubobject<UArrowComponent>(TEXT("Muzzle"));
	Muzzle->SetupAttachment(GetMesh());
}
void AGun::PlayMuzzleFlashEffect()
{
	if (MuzzleFlashEffect)
	{
		UGameplayStatics::SpawnEmitterAttached(
			MuzzleFlashEffect, Muzzle, NAME_None, FVector::ZeroVector, FRotator::ZeroRotator, FVector::One() * 20.f);
	}
}

 

그리고 BP_Pistol, BP_AssultRifle에서 Muzzle의 위치를 잘 조정해주고 MuzzleFlashEffect를 할당해주었다.

 

 

마지막으로, 원래 Enemy에 소총을 Child Actor로 달아놓았는데, 이제는 BeginPlay에서 SpawnActor로 만들어서 장착하도록 하였다.

몰랐던 사실인데, Child Actor는 성능 이슈가 좀 있다. 캐릭터에 액터를 부착시키고자 한다면, 차라리 SpawnActor로 소환해서 쓰는 것이 더 성능이 좋다고 한다.

 

// Enemy.h 중 일부
private:	
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
	TSubclassOf<AGun> AssultRifleClass;

	UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
	AGun* AssultRifle;
// Called when the game starts or when spawned
void AEnemy::BeginPlay()
{
	Super::BeginPlay();
	
	if (AssultRifleClass)
	{
		FActorSpawnParameters Param;
		Param.Owner = this;
		AssultRifle = GetWorld()->SpawnActor<AGun>(AssultRifleClass, Param);
		if(AssultRifle)
			AssultRifle->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetIncludingScale, FName("Rifle_Socket"));
	}
}

 

덤으로 이렇게 함에 따라 아까 OneShot 함수에서 AssultRifle->PlayMuzzleFlashEffect()를 한 것 처럼 AEnemy 클래스에서 AGun의 함수를 자유롭게 쓸 수 되었다.

 

 

 

리팩토링을 통해 구조적, 성능적 이점을 모두 챙길 수 있었다.

이제부터는 정말 EX 스킬을 구현해볼 것이다.