언리얼 공부/C++

일반탄환, 관통탄, 폭발탄

jeongchanhyo 2025. 3. 4. 20:40

저번에 만든 탄환을 베이스로 3종류의 총알을 만들것이다.

1. 일반탄 - 적이나 구조물에 충돌하면 사라지고 적에게 피해를 줌

2. 관통탄 - 구조물에 맞으면 사라지지만 적에게 맞으면 피해를 주고 뚫고 지나감

3. 폭발탄 - 적이나 구조물에 맞으면 폭발하고 범위피해를 줌

 

이렇게 3가지의 온힛을 가진 총알들을 만들어보겠다.

1. 일반탄환

일반탄환은 액터에 부딪히면 사라지게 하고싶고, 부딪힌 액터가 적이라면 데미지를 주고싶다.

 

ANomalBullet::ANomalBullet()
{
	Speed = 1000.0f;
	BulletDamage = 0.0f;
}

//적이나 구조물 중 하나에 부딪히면 사라짐
void ANomalBullet::OnHit(
	UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult
)
{
	if (OtherActor && OtherActor != this)
	{
		UE_LOG(LogTemp, Warning, TEXT("OnHit called with %s"), *OtherActor->GetName());
		if (OtherActor && OtherActor->IsA(AEnemyCharacterAi::StaticClass()))
		{
			AEnemyCharacterAi* HitEnemy = Cast<AEnemyCharacterAi>(OtherActor);
			if (HitEnemy)
			{
				HitEnemy->EnemyTakeDamage(BulletDamage);
			}
			Destroy();
			UE_LOG(LogTemp, Warning, TEXT("Damage Applied: %.2f"), BulletDamage); // [[4]](#__4)
		}
		else
		{
			Destroy();
		}
	}
}

부딪힌 액터가 액터고, 이 액터가 아니라면 EnemyCharacterAi클래스인지 체크하고 맞다면 EnemyTakeDamage를 불러와서 피해를 주고 Destroy 아니라면 그냥 Destroy한다. 어렵지 않다. 참고로 EnemyTakeDamage는 그냥 EnemyCharacterAi클래스에 구현해놓은 HP - Damage이다.

 

2. 관통탄

#include "Bullet/PiercingBullet.h"

APiercingBullet::APiercingBullet()
{
	Speed = 500.0f;
	BulletDamage = 0.0f;
}

void APiercingBullet::OnHit(
	UPrimitiveComponent* OverlappedComponent,
	AActor* OtherActor,
	UPrimitiveComponent* OtherComp,
	int32 OtherBodyIndex,
	bool bFromSweep,
	const FHitResult& SweepResult
)
{
	if (OtherActor == GetOwner())
	{
		return;
	}    

	if (OtherActor && OtherActor != this)
	{
		UE_LOG(LogTemp, Warning, TEXT("OnHit called with %s"), *OtherActor->GetName());
		if (OtherActor && OtherActor->IsA(AEnemyCharacterAi::StaticClass()))
		{
			if (AEnemyCharacterAi* HitEnemy = Cast<AEnemyCharacterAi>(OtherActor))
			{
				if (HitEnemy)
				{
					HitEnemy->EnemyTakeDamage(BulletDamage);
				}
				UE_LOG(LogTemp, Warning, TEXT("Damage Applied: %.2f"), BulletDamage); // [[4]](#__4)
			}
		}
		else
		{
			Destroy();
		}
	}
}

이번에는 관통탄이다.이걸로 잘 되면 좋겠지만 자꾸 총알이 몬스터를 밀고가서 어려웠다. 하지만 이건 코드가 아니라 블루프린트 상에서 비꿔야됐다.

 

스태틱 메쉬는 NoCollision으로 

이렇게 설정해주고

HitCollision은

OverlapDynamic으로 설정을 해주면 몬스터를 뚫고 지나간다... (이것땜에 시간을 엄청 썼다.)

생각보다 잘 안될 때 블루프린트로 해결이 된다.

여기까진 코드로 쉬웠다... 하지만 폭발탄이 문제다

3. 폭발탄

1.접촉 시

void AExplosiveBullet::OnHit(
    UPrimitiveComponent* OverlappedComponent,
    AActor* OtherActor,
    UPrimitiveComponent* OtherComp,
    int32 OtherBodyIndex,
    bool bFromSweep,
    const FHitResult& SweepResult
)
{
    if (OtherActor == GetOwner())
    {
        return;
    }

    UE_LOG(LogTemp, Warning, TEXT("OnHit called with %s"), *OtherActor->GetName());
    if (OtherActor && OtherActor != this)
    {
        Explode(GetActorLocation());
        Destroy();
        Super::BulletEffects();
    }
}

우선 온힛이다. 온힛이 되면 Explode를 터트리고, 제거한 후 폭발이펙트와 사운드가 나오도록 설정했다.

 

2. 폭발함수

void AExplosiveBullet::Explode(FVector Location)
{
    TArray<AActor*> IgnoredActors;

    // 플레이어 캐릭터 추가
    if (APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0))
    {
        IgnoredActors.AddUnique(PlayerPawn);
        UE_LOG(LogTemp, Warning, TEXT("Added Player: %s"), *GetNameSafe(PlayerPawn));
    }

    AController* DamageInstigator = nullptr;
    if (AActor* ValidInstigator = GetInstigator())
    {
        IgnoredActors.AddUnique(ValidInstigator);
        DamageInstigator = ValidInstigator->GetInstigatorController();
    }

    // 모든 적을 가져와서 범위 내의 적을 찾아서 데미지 적용
    TArray<AActor*> AllEnemies;
    UGameplayStatics::GetAllActorsOfClass(GetWorld(), AEnemyCharacterAi::StaticClass(), AllEnemies);
    for (AActor* Actor : AllEnemies)
    {
        if (Actor && FVector::Dist(Actor->GetActorLocation(), Location) <= ExplosionRadius)
        {
            // 적에게 데미지 적용
            if (AEnemyCharacterAi* Enemy = Cast<AEnemyCharacterAi>(Actor))
            {
                Enemy->EnemyTakeDamage(BulletDamage);
                UE_LOG(LogTemp, Warning, TEXT("Enemy %s took damage: %.2f"), *Enemy->GetName(), BulletDamage);
            }
        }
    }

    // 원형 피해 적용 (적은 제외하고)
    UGameplayStatics::ApplyRadialDamage(
        GetWorld(),
        BulletDamage,
        Location,
        ExplosionRadius,
        nullptr,
        IgnoredActors,
        this, // Damage Causer
        DamageInstigator // Controller
    );

    // 디버그 로그
    UE_LOG(LogTemp, Warning, TEXT("Damage Applied: %.2f"), BulletDamage);
    DrawDebugSphere(GetWorld(), Location, ExplosionRadius, 12, FColor::Red, false, 3.0f);
}

내 플레이어 캐릭터를 IgnoredActors에 추가한 후 데미지 적용 시 IgnoredActors를 제외한 적에게 피해를 입히는 형식이였다. 데미지가 제대로 적용 됐지만 문제는 내 캐릭터가 폭발 범위 내에 있으면 전부 다 데미지를 받지 않는것이다. 그 이유는 찾지 못했지만 새로운 해결책을 찾아냈다.

어차피 범위 내에 있는 객체중에 EnemyTakeDamage 함수를 가지고 있는건 적 캐릭터 뿐이니까 그냥 피어싱 불릿과 똑같은느낌으로 처리하면 되지 않을까 하고 새로 만들어봤다.

void AExplosiveBullet::Explode(FVector Location)
{
    TArray<AActor*> IgnoredActors;

    // 1. 무시할 액터 설정 (플레이어와 발사자)
    if (APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0))
    {
        IgnoredActors.AddUnique(PlayerPawn);
    }

    if (AActor* InstigatorActor = GetInstigator())
    {
        IgnoredActors.AddUnique(InstigatorActor);
    }

    // 2. 범위 내 모든 AEnemyCharacterAi 찾기
    TArray<AActor*> AllEnemies;
    UGameplayStatics::GetAllActorsOfClass(
        GetWorld(),
        AEnemyCharacterAi::StaticClass(),
        AllEnemies
    );

    // 3. 범위 내 적에게 직접 EnemyTakeDamage 호출
    for (AActor* Enemy : AllEnemies)
    {
        if (Enemy &&
            FVector::Dist(Enemy->GetActorLocation(), Location) <= ExplosionRadius)
        {
            if (AEnemyCharacterAi* EnemyChar = Cast<AEnemyCharacterAi>(Enemy))
            {
                // 적 클래스의 EnemyTakeDamage 직접 호출
                EnemyChar->EnemyTakeDamage(BulletDamage);
                UE_LOG(LogTemp, Warning, TEXT("Enemy %s took damage"), *EnemyChar->GetName());
            }
        }
    }

    // 4. 디버그 및 이펙트 처리
    DrawDebugSphere(GetWorld(), Location, ExplosionRadius, 12, FColor::Red, false, 3.0f);
    Destroy();
}

이렇게 하니까 아주 잘된다

이것들을 만들면서 배운점

내 코드만 의심하지 말고 블루프린트도 의심하자!

로그는 도중도중에 계속 넣어두자!

'언리얼 공부 > C++' 카테고리의 다른 글

총알만들기  (0) 2025.02.27
총 만들기  (0) 2025.02.25
순수가상함수, 추상클래스 그리고 인스턴스  (0) 2025.02.20
플로우 차트  (0) 2025.02.18
FPS무기 구조 생각  (0) 2025.02.17