언리얼 공부/C++

총 만들기

jeongchanhyo 2025. 2. 25. 12:58

총 구조

총은 하나만 쓸 게 아니라 여러개를 사용할 것 이라고 말했듯이 무기마다 다른건 하위 클래스에서 설정할것이고, 모두가 같이 사용할것은 

  • 기본 속성 설정
    • 공격력, 현재 탄약, 최대 탄약, 재장전 상태, 공격 속도
  • 재장전 기능
    • 재장점 함수는 아마 다들 비슷할것이다. 일정시간 후에 총알이 최대치가 되고 총알이 최대치면 재장전이 안되는

부모클래스 헤더파일 만들기

#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."));
    }
}

풀어서 설명하자면

  1. 현재 총알이 최대 총알보다 작은지 체크(아니라면 이미 총알이 최대치라고 알림)
  2. 이 월드에 FTimerManager 인스턴스의 다음 프레임 예약을 한다.(현재 바로 하지 않고 다음 프레임에 예약하는 이유는 안정성 때문이다. 한 프레임 내에 객체 상태변화가 2개이상 겹치거나 할 수 있기 때문이다)
  3. 타이머 핸들 생성
  4. 재장전 관련 타이머 세팅
    1. 지금 설정한 타이머 핸들 기준
    2. 이 객체에 ReloadTime이라는 시간이 지나면
    3. ARifle::CompleteReload함수를 가져오고
    4. 반복하지 않겠다.
  5. 재장전 상태로 활성화.

재장전 완료

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);
}
  1. 현재 총알을 최대 탄창 수로 설정
  2. 재장전 상태 비활성화
  3. 제대로 연결이 되는지 확인하기 위한 로그(첨에 잘 안됐어서 넣음)

공격

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. 현재 시간 = 현재 월드의 시간으로 설정
  2. 조건체크
    1. 현재 총알이 1개 이상 있는가
    2. 리로드 상태인가
    3. 총알을 쏘고, 다음 발사시간이 되었는가?
  3. 카메라 위치, 카메라 방향 설정
  4. 플레이어 컨트롤러가 바라보는 방향으로 탄퍼짐 적용해서 방향 설정
  5. 총알을 내가 설정해놓은 총알로 발사, 데미지와  방향 넣어주기
  6. 총알 감소
  7. 마지막 총알 발사 시간 설정

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

일반탄환, 관통탄, 폭발탄  (0) 2025.03.04
총알만들기  (0) 2025.02.27
순수가상함수, 추상클래스 그리고 인스턴스  (0) 2025.02.20
플로우 차트  (0) 2025.02.18
FPS무기 구조 생각  (0) 2025.02.17