총 구조
총은 하나만 쓸 게 아니라 여러개를 사용할 것 이라고 말했듯이 무기마다 다른건 하위 클래스에서 설정할것이고, 모두가 같이 사용할것은
- 기본 속성 설정
- 공격력, 현재 탄약, 최대 탄약, 재장전 상태, 공격 속도
- 재장전 기능
- 재장점 함수는 아마 다들 비슷할것이다. 일정시간 후에 총알이 최대치가 되고 총알이 최대치면 재장전이 안되는
부모클래스 헤더파일 만들기
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Bullet/BulletBase.h"
#include "WeaponBase.generated.h"
UCLASS()
class SHADOW_OF_THE_DESERT_API AWeaponBase : public AActor
{
GENERATED_BODY()
public:
AWeaponBase();
virtual void Reload();
virtual void CompleteReload();
virtual void Attack();
protected:
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* WeaponMesh;
UPROPERTY(EditDefaultsOnly, Category = "Weapon")
float AttackDamage;
UPROPERTY(VisibleAnywhere, Category = "Weapon")
int32 CurrentAmmo;
UPROPERTY(EditDefaultsOnly, Category = "Weapon")
int32 MaxAmmo;//최대 장전 탄창 수
UPROPERTY(EditDefaultsOnly, Category = "Weapon")
bool bIsReloading; // 리로드 중인지 여부
UPROPERTY(EditDefaultsOnly, Category = "Weapon")
float ReloadTime; // 재장전 시간
UPROPERTY(EditDefaultsOnly, Category = "Weapon")
float AttackRate; // 발사 속도 (초 단위)
float LastAttackTime;
UPROPERTY(EditDefaultsOnly, Category = "Weapon")
TSubclassOf<ABulletBase> BulletClass; // 발사할 총알의 클래스 타입
};
재료들을 준비해왔다.
매개변수
- AttackDamage - 총이 갖고있는 공격력이다.
- CurrentAmmo - 현재 장전되어있는 총알의 개수
- MaxAmmo - 최대 장전 탄창 수
- bIsReloading - 리로드 여부
- ReloadTime - 재장전 시간
- AttackRate - 발사속도, 연사 속도
- LastAttackTime - 마지막 공격 시간(연사 속도를 제어하기 위한 장치)
- BulletClass - 발사할 총알의 종류
함수
- AWeaponBase() - 생성자, 매개변수들 초기화
- Reload() - 재장전, 공격 불가능 상태로 전환 후 일정시간이 지난 후 재장전 완료
- CompleteReload() - 재장전 완료, 총알을 탄창만큼 채우고 공격 가능한 상태로 전환
- Attack() - 공격, 총알이 있는지 체크 후 내 카메라가 보는 방향으로 공격하고 어느정도 탄 퍼짐이 있음.
구현
구현은 Rifle이라는 자식클래스에서 하도록 하겠다. 부모클래스는 함수구현을 안한상태
(지금 와서 생각해보면 부모클래스에서 하는게 나아보여서 좀 있다 바꿀 예정)
생성자
ARifle::ARifle()
{
AttackDamage = 5.0f;
CurrentAmmo = 30;
MaxAmmo = 30;
bIsReloading = false;
AttackRate = 0.1f;
ReloadTime = 1.5f;
}
재장전
void ARifle::Reload()
{
// 1. 탄약 체크 조건문
if (CurrentAmmo < MaxAmmo)
{
// 2. 다음 틱(프레임) 예약
GetWorld()->GetTimerManager().SetTimerForNextTick([this]()
{
// 3. 타이머 핸들 생성
FTimerHandle TimerHandle;
// 4. 재장전 완료 타이머 설정
GetWorld()->GetTimerManager().SetTimer(
TimerHandle, // 핸들
this, // 대상 객체
&ARifle::CompleteReload, // 콜백 함수
ReloadTime, // 지연 시간
false // 반복 여부
);
// 5. 재장전 상태 플래그 설정
bIsReloading = true;
});
}
else
{
// 6. 탄창 가득 찬 경우 경고
UE_LOG(LogTemp, Warning, TEXT("Ammo is already full."));
}
}
풀어서 설명하자면
- 현재 총알이 최대 총알보다 작은지 체크(아니라면 이미 총알이 최대치라고 알림)
- 이 월드에 FTimerManager 인스턴스의 다음 프레임 예약을 한다.(현재 바로 하지 않고 다음 프레임에 예약하는 이유는 안정성 때문이다. 한 프레임 내에 객체 상태변화가 2개이상 겹치거나 할 수 있기 때문이다)
- 타이머 핸들 생성
- 재장전 관련 타이머 세팅
- 지금 설정한 타이머 핸들 기준
- 이 객체에 ReloadTime이라는 시간이 지나면
- ARifle::CompleteReload함수를 가져오고
- 반복하지 않겠다.
- 재장전 상태로 활성화.
재장전 완료
void ARifle::CompleteReload()
{
CurrentAmmo = MaxAmmo;
bIsReloading = false;
UE_LOG(LogTemp, Warning, TEXT("Reload Complete! CurrentAmmo: %d, bIsReloading: %s, LastAttackTime: %f"),
CurrentAmmo, bIsReloading ? TEXT("true") : TEXT("false"),
LastAttackTime);
}
- 현재 총알을 최대 탄창 수로 설정
- 재장전 상태 비활성화
- 제대로 연결이 되는지 확인하기 위한 로그(첨에 잘 안됐어서 넣음)
공격
void ARifle::Attack()
{
UE_LOG(LogTemp, Warning, TEXT("CurrentAmmo: %d, bIsReloading: %s, LastAttackTime: %f"),
CurrentAmmo, bIsReloading ? TEXT("true") : TEXT("false"),
LastAttackTime);
if (bIsReloading)
{
UE_LOG(LogTemp, Warning, TEXT("rerererere"))
}
float CurrentTime = GetWorld()->GetTimeSeconds();
if (CurrentAmmo > 0 && !bIsReloading && (CurrentTime - LastAttackTime >= AttackRate))
{
UE_LOG(LogTemp, Warning, TEXT("Shot"));
FRotator CameraRotation;
FVector CameraLocation;
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (PlayerController)
{
PlayerController->GetPlayerViewPoint(CameraLocation, CameraRotation);
FVector WeaponLocation = GetActorLocation() + CameraRotation.Vector() * 300.0f;//총알 발사 위치
//난수(총알 튀는거)
float RandomOffsetX = FMath::FRandRange(-0.1f, 0.1f);
float RandomOffsetY = FMath::FRandRange(-0.1f, 0.1f);
FVector Direction = (CameraRotation.Vector() + FVector(RandomOffsetX, RandomOffsetY, 0)).GetSafeNormal();
ABulletBase* Bullet = GetWorld()->SpawnActor<ABulletBase>(BulletClass, WeaponLocation, FRotator::ZeroRotator);
if (Bullet)
{
Bullet->Initialize(Direction, AttackDamage);
}
CurrentAmmo--;
UE_LOG(LogTemp, Warning, TEXT("END CurrentAmmo: %d, bIsReloading: %s, LastAttackTime: %f"),
CurrentAmmo, bIsReloading ? TEXT("true") : TEXT("false"),
LastAttackTime);
LastAttackTime = CurrentTime;
}
}
if(CurrentAmmo <= 0)
{
UE_LOG(LogTemp, Warning, TEXT("Reloading Plz."));
}
}
- 현재 시간 = 현재 월드의 시간으로 설정
- 조건체크
- 현재 총알이 1개 이상 있는가
- 리로드 상태인가
- 총알을 쏘고, 다음 발사시간이 되었는가?
- 카메라 위치, 카메라 방향 설정
- 플레이어 컨트롤러가 바라보는 방향으로 탄퍼짐 적용해서 방향 설정
- 총알을 내가 설정해놓은 총알로 발사, 데미지와 방향 넣어주기
- 총알 감소
- 마지막 총알 발사 시간 설정
'언리얼 공부 > C++' 카테고리의 다른 글
일반탄환, 관통탄, 폭발탄 (0) | 2025.03.04 |
---|---|
총알만들기 (0) | 2025.02.27 |
순수가상함수, 추상클래스 그리고 인스턴스 (0) | 2025.02.20 |
플로우 차트 (0) | 2025.02.18 |
FPS무기 구조 생각 (0) | 2025.02.17 |