1. 개요
이번 포스팅에서는
총기 격발 및 재장전 기능을 구현해 보았습니다.
C++와 블루프린트를 함께 사용하여 유연한 시스템을 구성했습니다.
2. 기능 요구 사항
- 좌클릭 시 총알 발사
- 탄창 수 관리 (예: 20발)
- 탄환이 0일 때 발사 불가
- 연사/단발 모드 기능
3. 구현 과정
3.1 총기 클래스 헤더 파일
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "BaseGun.generated.h"
UENUM(BlueprintType)
enum class EFireMode : uint8
{
FullAuto, // 연사
SemiAuto, // 단발
};
UCLASS()
class SPLATOON_API ABaseGun : public AActor
{
GENERATED_BODY()
public:
ABaseGun();
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Gun")
TObjectPtr<USceneComponent> RootComp;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Gun")
TObjectPtr<USceneComponent> FrontOfGun;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Gun")
TObjectPtr<UStaticMeshComponent> StaticMeshComp;
virtual void BeginPlay() override;
/* Fire */
public:
// 플레이어 격발시 호출
UFUNCTION(BlueprintCallable)
void FirePressed();
// 플레이어 격발 중지시 호출
UFUNCTION(BlueprintCallable)
void FireReleased();
protected:
// 탄환이 격발되는 시간 간격
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Gun|Fire")
float FireBulletInterval;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Gun|Fire")
EFireMode FireMode;
FTimerHandle FireTimerHandle;
void Fire();
/* Reload */
public:
// 재장전 시작시 호출
UFUNCTION(BlueprintCallable)
void ReloadStart();
// 재장전 종료
UFUNCTION(BlueprintCallable)
void ReloadStop();
protected:
// 탄환이 장전되는 시간 간격
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Gun|Reload")
float ReloadBulletInterval;
FTimerHandle ReloadTimerHandle;
void Reload();
/* Bullets */
protected:
// TODO: AActor -> BaseBullet
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gun|Bullets")
TSubclassOf<AActor> BulletClass;
// 남은 탄환 수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gun|Bullets")
int32 RemainingBullets;
// 최대 탄환 수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gun|Bullets")
int32 MaxRemainingBullets;
};
3.2 격발 기능
#include "BaseGun.h"
#include "Kismet/GameplayStatics.h"
void ABaseGun::FirePressed()
{
Fire();
}
void ABaseGun::FireReleased()
{
if (UWorld* World = GetWorld())
{
World->GetTimerManager().ClearTimer(FireTimerHandle);
}
}
void ABaseGun::Fire()
{
// 1. 남은 탄환 확인
if (RemainingBullets <= 0) return;
// 2. 탄환 감소
RemainingBullets -= 1;
// 3. 탄환 생성
if (UWorld* World = GetWorld())
{
World->SpawnActor<AActor>(
BulletClass,
FrontOfGun->GetComponentLocation(),
FrontOfGun->GetComponentRotation()
);
}
// 4. 연사 모드일 경우 타이머 설정
if (FireMode == EFireMode::FullAuto)
{
if (UWorld* World = GetWorld())
{
World->GetTimerManager().SetTimer
(
FireTimerHandle,
this,
&ABaseGun::Fire,
FireBulletInterval
);
}
}
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Fire / RemainingBullets = %d"), RemainingBullets));
}
3.3 재장전 기능
void ABaseGun::ReloadStart()
{
if (UWorld* World = GetWorld())
{
// 한 발씩 지속적으로 장전
World->GetTimerManager().SetTimer(
ReloadTimerHandle,
this,
&ABaseGun::Reload,
ReloadBulletInterval,
true
);
}
}
void ABaseGun::ReloadStop()
{
if (UWorld* World = GetWorld())
{
World->GetTimerManager().ClearTimer(ReloadTimerHandle);
}
}
void ABaseGun::Reload()
{
if (RemainingBullets >= MaxRemainingBullets) return;
RemainingBullets += 1;
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Reload / RemainingBullets = %d"), RemainingBullets));
}
4. 블루프린트 추가 작업
- 무기별 총알 생성 위치 수정
5. 테스트
5.1 격발 기능
좌클릭 시 연사 시작
좌클릭 뗄 경우 연사 종료

5.2 재장전 기능
특정 키 입력시 지속적으로 재장전 시작
특정 키 뗄 경우 재장전 종료

✅ 기대 결과:
- 연사 시작 및 중지
- 재장전 시작 및 중지
- 탄환 0일 때 격발 중지
- 원하는 위치에서 탄환 생성
⚠️ 발견된 이슈:
- 버그: 빠르게 연타 시 재장전 딜레이 무시 → bCanFire 변수를 통한 조건 추가로 해결
- 개선점: 피격 대상에 데미지 적용 시스템 추가 예정
6. 마무리 및 개선 아이디어
- 무기 사운드 추가
- 사격 이펙트 추가
GitHub에서 코드 확인 👉 [🔗 GitHub 링크]
'Unrael' 카테고리의 다른 글
| [Animation] 웅크려 이동하기 (Crouching) (0) | 2025.04.22 |
|---|---|
| [UE] 게임플레이 태그 (GameplayTag) (0) | 2025.03.17 |
| [UE/Settings] 언리얼 에디터 기본 설정 (0) | 2025.02.19 |
| [UE/Tools] 스태틱 메시를 스켈레탈 메시로 (1) | 2025.02.18 |
| [UE/Project] 기능 명세서 작성 (0) | 2025.02.17 |