EX 스킬을 구현하기에 앞서, 한번 더 시간을 들여 프로젝트 전체적으로 리팩토링을 진행해주었다.
먼저, BP_Enemy에서 구현했던 One Shot, Shoot 이벤트를 C++로 만들었다.
블루프린트는 기본적으로 같은 로직을 수행하더라도 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이다. 블루프린트 인터페이스 호출로 수행될 수 있도록 하기 위해서이다.
다음으로, 총구 화염 효과 관련 코드를 AGun으로 옮겼다.
권총이든, 소총이든 총구 화염은 재생되어야 하는데, 현재 칸나 권총의 화염은 Pistol 클래스에서, 적 소총의 화염은 BP_Enemy에서 맡고 있다.
또한 화염의 위치를 담당하는 UArrowComponent* Muzzle의 경우도 권총의 경우엔 BP_Pistol에서, 소총의 경우엔 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 스킬을 구현해볼 것이다.
'언리얼 엔진 5 > 개발 일지' 카테고리의 다른 글
[UE5] 블루아카이브 TPS게임 개발일지 (54) - EX 스킬 제작 2 + LSP (0) | 2024.02.02 |
---|---|
[UE5] 블루아카이브 TPS게임 개발일지 (53) - EX 스킬 제작 1 (0) | 2024.02.01 |
[UE5] 블루아카이브 TPS게임 개발일지 (51) - EX 게이지 제작 (0) | 2024.01.28 |
[UE5] 블루아카이브 TPS게임 개발일지 (50) - 맵 제작 (2) | 2024.01.28 |
[UE5] 블루아카이브 TPS게임 개발일지 (49) - 스크린 데미지 효과 적용 (0) | 2024.01.21 |