이번 개발의 목표는 아무 플레이어 캐릭터에 직접 만든 컴포넌트를 붙이는 것 만으로 슈팅 시스템이 생기는 것을 목표로 하였다.
1. WeaponComponent 생성
캐릭터에 붙일 컴포넌트를 만들기 위해 ActorComponent를 상속받는 WeaponComponent 클래스를 생성한다.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Player/WeaponData.h"
#include "Components/SkeletalMeshComponent.h"
#include "Player/Ui/UiComponent.h"
#include "WeaponComponent.generated.h"
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class PROJECT_4_API UWeaponComponent : public UActorComponent
{
GENERATED_BODY()
public:
UWeaponComponent();
protected:
virtual void BeginPlay() override;
public:
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon")
UWeaponData *WeaponData;
UFUNCTION(BlueprintCallable, Category = "Fire") // For debug
void FireWeapon();
};
2. 스켈레탈 메시 가져오기
BeginPlay에서 캐릭터의 스켈레탈 메시를 가져와 총구 위치를 참조한다.
void UWeaponComponent::BeginPlay()
{
Super::BeginPlay();
ACharacter* OwnerCharacter = Cast<ACharacter>(GetOwner());
if (OwnerCharacter)
{
SkeletalMeshComponent = OwnerCharacter->GetMesh();
}
}
3. 총 발사 로직
(1) 총구 위치 계산
스켈레탈 메시에서 SocketTransform을 가져와 월드 좌표계를 기준으로 총구 위치와 방향을 구한다.
본 프로젝트는 언리얼에서 제공하는 Paragon 에셋을 활용하였으며, 각자 본인이 원하는 스켈레탈 메시의 총구 위치에 해당하는 Socket 이름을 참조하면 된다.
FTransform SocketTransform = SkeletalMeshComponent->GetSocketTransform("Muzzle", RTS_World);
FVector MuzzleLocation = SocketTransform.GetLocation();
FRotator MuzzleRotation = SocketTransform.GetRotation().Rotator();
(2) 화면 중심 방향 계산
화면 중심에서 월드 방향으로 Ray를 쏘고, 충돌 여부에 따라 발사체 방향을 설정한다.
// 화면 중심 좌표 계산
FVector2D ViewportSize;
GEngine->GameViewport->GetViewportSize(ViewportSize);
FVector2D ScreenCenter(ViewportSize.X / 2, ViewportSize.Y / 2);
// 화면 중심에서 월드 방향 가져오기
FVector WorldLocation, WorldDirection;
UGameplayStatics::DeprojectScreenToWorld(UGameplayStatics::GetPlayerController(GetWorld(), 0), ScreenCenter, WorldLocation, WorldDirection);
// Ray 설정
FVector Start = WorldLocation;
FVector End = Start + (WorldDirection * 10000); // Ray 길이
FHitResult HitResult;
FVector HitLocation;
if (GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility))
{
HitLocation = HitResult.Location; // 충돌한 위치
}
else
{
HitLocation = End; // 충돌하지 않으면 Ray 끝점
}
// 발사 방향 계산
FVector ShootDirection = (HitLocation - MuzzleLocation).GetSafeNormal();
(3) 산탄 효과 추가
AimSize 값을 활용하여 총알이 랜덤하게 퍼지는 효과를 추가한다.
float AimSize = UiComponent->GetAimSize();
float RandomConeHalfAngle = FMath::DegreesToRadians(AimSize / 100.0f);
ShootDirection = FMath::VRandCone(ShootDirection, RandomConeHalfAngle);
(4) 발사체 생성 및 발사
ShootDirection에 따라 발사체를 생성하고, 설정된 속도로 발사한다.
FActorSpawnParameters SpawnParams;
AProjectile* Projectile = GetWorld()->SpawnActor<AProjectile>(AProjectile::StaticClass(), MuzzleLocation, ShootDirection.Rotation(), SpawnParams);
if (Projectile)
{
Projectile->ShootInDirection(ShootDirection, WeaponData->ProjectileSpeed);
}
다음 글에서는 발사체, 즉 Projectile 클래스의 구현에 대해 알아보도록 하자
'Unreal 5 > FPS Shooting' 카테고리의 다른 글
5. [Unreal 5 / C++] 발사체와 오브젝트 풀링 (0) | 2025.01.08 |
---|---|
4. [Unreal 5 / C++] 반동(Camera Shake)과 연사 (2) | 2025.01.07 |
3. [Unreal 5 / C++] 동적 크로스헤어 구현 (1) | 2025.01.03 |
2. [Unreal 5 / C++] 발사체 구현 하기 (1) | 2025.01.02 |