티스토리 뷰
🟩 구현 목표
- FGrenadeStat 구조체와 데이터 테이블을 활용하여 코드 수정 없는 발사체 확장 시스템을 구축한다.
- 물리 엔진의 IgnoreActorWhenMoving을 사용하여 발사체의 자가 충돌 및 폭발 문제를 해결한다.
- FTimerHandle을 이용한 스택형 자동 충전 시스템을 구현하여 게임플레이의 전략적 요소를 더한다.
🟧 1. 데이터 기반 설계: 구조체와 데이터 테이블 활용
다양한 발사체(수류탄, 유도탄 등)를 유연하게 추가하기 위해 모든 성능 지표와 에셋 정보를 FTableRowBase를 상속받은 구조체로 데이터화했다.
// GrenadeStat.h 핵심 구조체 정의
USTRUCT(BlueprintType)
struct FGrenadeStat : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSubclassOf<class AGrenadeProjectile> GrenadeClass; // 발사체 클래스
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Damage; // 폭발 데미지
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float CooldownTime; // 발사 간격
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float RegenTime; // 1발 충전 시간
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 MaxCharges; // 최대 보유량
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<class UParticleSystem> ExplosionEffect; // 폭발 효과
};
🟧 2. 발사체 물리 충돌 예외 처리
발사체가 생성되는 즉시 캐릭터 본인의 콜리전과 충돌하여 터지는 문제를 방지하기 위해 물리 엔진 수준에서 액터 무시 설정을 적용했다.
// GrenadeProjectile.cpp: 물리 엔진 예외 처리
void AGrenadeProjectile::BeginPlay()
{
Super::BeginPlay();
AActor* MyOwner = GetOwner();
if (MyOwner)
{
// 물리 엔진에게 주인(플레이어)은 이동 시 무시하도록 명령
if (SphereComp)
{
SphereComp->IgnoreActorWhenMoving(MyOwner, true);
}
// 주인 캐릭터의 루트 컴포넌트에서도 이 발사체를 무시하게 설정
if (MyOwner->GetRootComponent())
{
UPrimitiveComponent* OwnerRoot = Cast<UPrimitiveComponent>(MyOwner->GetRootComponent());
if (OwnerRoot)
{
OwnerRoot->IgnoreActorWhenMoving(this, true);
}
}
}
}
🟧 3. 소켓 기반 발사 및 오프셋 보정
어깨 런처 소켓(grenade_front)에서 발사될 때, 시각적 자연스러움과 물리적 안전을 위해 전방 오프셋을 적용하고 발사체에 데이터를 전달한다.
// WeaponComponent.cpp: 발사 위치 계산 및 스폰
void UWeaponComponent::LaunchGrenade()
{
FVector SpawnLocation = OwnerChar->GetMesh()->GetSocketLocation(TEXT("grenade_front"));
// 캐릭터 정면으로 20만큼 밀어내어 자가 충돌 방지 및 자연스러운 연출
SpawnLocation += OwnerChar->GetActorForwardVector() * 20.f;
FRotator SpawnRotation = OwnerChar->GetActorRotation();
FActorSpawnParameters Params;
Params.Owner = OwnerChar;
Params.Instigator = OwnerChar;
AGrenadeProjectile* Projectile = GetWorld()->SpawnActor<AGrenadeProjectile>(
Stat->GrenadeClass, SpawnLocation, SpawnRotation, Params);
if (Projectile)
{
// 데이터 테이블에서 로드한 스탯을 생성된 발사체에 직접 주입
Projectile->Damage = Stat->Damage;
Projectile->ExplosionEffect = Stat->ExplosionEffect;
}
}
🟧 4. 스택형 자동 충전(Regeneration) 시스템
사용 후 일정 시간이 지나면 수류탄이 자동으로 하나씩 충전되는 로직을 FTimerHandle로 구현하여 전투의 지속성을 확보했다.
// WeaponComponent.cpp: 자동 충전 시스템
void UWeaponComponent::RegenerateGrenade()
{
if (CurrentGrenadeCount < MaxCharges)
{
CurrentGrenadeCount++;
UE_LOG(LogTemp, Log, TEXT("수류탄 1개 충전됨 현재 개수 : %d / %d"), CurrentGrenadeCount, Stat->MaxCharges);
// 최대 보유량에 도달하면 타이머를 중지하여 자원 낭비 최적화
if (CurrentGrenadeCount >= MaxCharges)
{
GetWorld()->GetTimerManager().ClearTimer(GrenadeRegenTimerHandle);
UE_LOG(LogTemp, Log, TEXT("수류탄 충전 완료"));
}
}
}
🟧핵심 요약
- 데이터 기반 설계: 모든 무기와 기술의 수치를 데이터 테이블화하여 코드 수정 없이 밸런스 튜닝이 가능하다.
- 물리 안정성: 단순한 충돌 체크 로직을 넘어 물리 엔진의 예외 처리 API를 활용해 자가 충돌 버그를 근본적으로 차단했다.
- 전투 흐름 최적화: 자동 충전 시스템을 도입함으로써 플레이어가 자원을 아끼면서도 적절한 타이밍에 스킬을 쏟아붓는 전투 환경을 조성했다.
'내일배움캠프 Unreal_7기 > 본캠프' 카테고리의 다른 글
| [CH 3] 팀프로젝트 - 수류탄 데칼 시스템 디버깅 및 발사 효과 구현 (0) | 2026.02.23 |
|---|---|
| [CH 3] 팀프로젝트 - 무기 및 에임 시스템 고도화 (0) | 2026.02.20 |
| [CH 3] 팀프로젝트 - 무기 시스템 고도화 및 데이터 기반 확장성 확보 (0) | 2026.02.11 |
| [CH 3] 팀프로젝트 - 쿼터뷰 쌍권총 사격 및 파티클 시스템 최적화 (0) | 2026.02.10 |
| [CH 3] 팀프로젝트 - 쌍권총 연사 시스템 구현 (0) | 2026.02.09 |

