1์ธ์นญ ์ํ ํํ ๋ฆฌ์ผ
https://docs.unrealengine.com/5.3/ko/first-person-shooter-tutorial-in-unreal-engine/
1. ํ๋ก์ ํธ ๊ตฌ์ฑํ๊ธฐ
1) ํ๋ก์ ํธ ์์ฑ
์ ํ๋ก์ ํธ ๋ด ๋งต์ ์์ฑํ๊ณ , ๊ทธ ๋งต์ ์๋ํฐ ์์ ๋งต์ผ๋ก ์ง์ ํ๋ค.
- ๊ฒ์ ํ๋ก์ ํธ ์นดํ ๊ณ ๋ฆฌ ๋ด ๊ธฐ๋ณธ(Blank) ํ ํ๋ฆฟ ์ ํ
- C++ ํ๋ก์ ํธ ํ์ ์ ํ
- ์์์ฉ ์ฝํ ์ธ ๋นํ์ฑํ
- ํ๋ก์ ํธ ์ด๋ฆ FPSProject ์ง์
- FPS ๋งต ์์ฑ
- ํ๋ก์ ํธ ์ธํ ์์ ์๋ํฐ ์์ ๋งต ์ค์
2) ๊ฒ์๋ชจ๋ C++ ํด๋์ค๋ฅผ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ก ํ์ฅํ๊ธฐ
๊ฒ์๋ชจ๋ C++ ํด๋์ค๋ฅผ ๋ฒ ์ด์ค๋ก ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ฅผ ์์ฑํ ํ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ฅผ ๋ํดํธ ๊ฒ์๋ชจ๋๋ก ์ค์ ํ๋ค. ์ด๋ ๊ธฐ๋ณธ์ผ๋ก ์์ฑ๋ ๊ฒ์๋ชจ๋ C++ ํด๋์ค๊ฐ ์๋ค๋ฉด C++ ํด๋์ค ๋ง๋ฒ์ฌ๋ก ์์ฑ์ด ํ์ํ๋ค.
ํ ์คํธ๋ฅผ ์ํด ๊ฒ์๋ชจ๋ C++ ํด๋์ค์ ๋ก๊ทธ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ๋ทฐํฌํธ์ ๋ก๊ทธ๊ฐ ๋์ค๊ฒ ๋๋ค.
2. ์บ๋ฆญํฐ ๊ตฌํํ๊ธฐ
1) ์บ๋ฆญํฐ ์์ฑํ๊ธฐ
Visual Studio ์๋ฃจ์ ์์ .h ๋ฐ .cpp ํ์ผ์ ์ง์ ์ถ๊ฐํ ์๋ ์์ง๋ง, C++ ํด๋์ค ๋ง๋ฒ์ฌ๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก์ ํธ์ ์ ํด๋์ค๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข๋ค. C++ ํด๋์ค ๋ง๋ฒ์ฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์์ง์ด UE๋ณ๋ก ์ ํฉํ ๋งคํฌ๋ก๋ฅผ ๊ตฌ์ฑํ๋ ํค๋ ๋ฐ ์์ค ํ ํ๋ฆฟ์ ์์ฑํ๋ค.
์ ๋จ๊ณ์ ๊ฒ์๋ชจ๋์ ๋์ผํ๊ฒ, ์บ๋ฆญํฐ C++ ํด๋์ค๋ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ ํ์ฅํ ํ ๊ธฐ๋ณธ ์บ๋ฆญํฐ๋ก ์ค์ ํ๋ค. ๊ธฐ๋ณธ ๊ฒ์๋ชจ๋์์ ๋ํดํธ ํฐ ํด๋์ค์ ์ง์ ํ๋ฉด ๋๋ค.
2) ์บ๋ฆญํฐ ์์ง์ด๊ธฐ
WASD ํค์ ์ ๋ ฅ ์ถ ๋งคํ์ ๊ตฌ์ฑํ์ฌ ์ ์บ๋ฆญํฐ๊ฐ ๋งต์ ์ด๋ ํ๋๋ก ํ ๊ฒ์ด๋ค. ํ๋ก์ ํธ ์ธํ ์์ ๋ค์๊ณผ ๊ฐ์ด ์ถ ๋งคํ์ ๊ตฌ์ฑํ๋ค.
- MoveForward: W, S
- MoveRight: D, A
๋ค์์ผ๋ก ํ๋ ์ด์ด ์ ๋ ฅ ์ปดํฌ๋ํธ๋ฅผ ๊ตฌ์ฑํ๊ณ FPSCharacter ํด๋์ค์ ํจ์๋ฅผ ๊ตฌํํ๋ค.
- InputComponent๋ ์ ๋ ฅ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐฉ์์ ์ ์ํ๋ ์ปดํฌ๋ํธ๋ก ์ ๋ ฅ์ ์์ ํ ์กํฐ์ ๋ถ์ผ ์ ์๋ค.
// Fill out your copyright notice in the Description page of Project Settings.
#include "FPSCharacter.h"
// Sets default values
AFPSCharacter::AFPSCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AFPSCharacter::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AFPSCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 'movement' ๋ฐ์ธ๋ฉ์ ๊ตฌ์ฑํฉ๋๋ค.
PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
}
void AFPSCharacter::MoveForward(float Value)
{
// ์ด๋๊ฐ '์'์ธ์ง ์ฐพ๊ณ , ํ๋ ์ด์ด๊ฐ ํด๋น ๋ฐฉํฅ์ผ๋ก ์ด๋ํ๊ณ ์ ํ๋ค๋ ๊ฒ์ ๊ธฐ๋กํฉ๋๋ค.
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
void AFPSCharacter::MoveRight(float Value)
{
// ์ด๋๊ฐ '์ค๋ฅธ์ชฝ'์ธ์ง ์ฐพ๊ณ , ํ๋ ์ด์ด๊ฐ ํด๋น ๋ฐฉํฅ์ผ๋ก ์ด๋ํ๊ณ ์ ํ๋ค๋ ๊ฒ์ ๊ธฐ๋กํฉ๋๋ค.
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}
์ฌ๊ธฐ๊น์ง ํ๋ฉด ์บ๋ฆญํฐ๊ฐ ์, ๋ค, ์์์ผ๋ก ์ด๋ํ ์ ์๊ฒ ๋๋ค.
3) ๋ง์ฐ์ค๋ก ์นด๋ฉ๋ผ ์์ง์ด๊ธฐ
์บ๋ฆญํฐ์ ์์ ๋ฐ ์ด๋ ๋ฐฉํฅ์ ๋ง์ฐ์ค๋ก ์กฐ์ ํ๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๊ฒ์ด๋ค. ์ถ ๋งคํ๊ณผ ์ ์ฌํ๊ฒ ํ๋ก์ ํธ ์ธํ ์์ ํ์ ์ถ ๋งคํ์ ๊ตฌ์ฑํ๋ค.
- Turn: Mouse X
- LookUp: Mouse Y
ํ์ ๋ฐ ๋ฐ๋ผ๋ณด๊ธฐ์ ๋ํ ๋ง์ฐ์ค ์ ๋ ฅ์ ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
- Character ๋ฒ ์ด์ค ํด๋์ค์ AddControllerYawInput, AddControllerPitchInput ํจ์๊ฐ ์ ์๋์ด ์์ด ๋ฐ์ธ๋ฉ๋ง ํ๋ฉด ๋๋ค.
void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 'movement' ๋ฐ์ธ๋ฉ์ ๊ตฌ์ฑํฉ๋๋ค.
PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
// 'look' ๋ฐ์ธ๋ฉ์ ๊ตฌ์ฑํฉ๋๋ค.
PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);
}
4) ์บ๋ฆญํฐ์ ๋ฉ์ ์ถ๊ฐํ๊ธฐ
ํํ ๋ฆฌ์ผ์ ์ํ ๋ฉ์๋ฅผ ์ ์ฅํ๊ณ ์ํฌํธํ์ฌ ์ค๋นํ๋ค.
์บ๋ฆญํฐ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ฅผ ์ฐ ํ ๋ฉ์ ์ปดํฌ๋ํธ์ ์ค๋นํ ๋ฉ์๋ฅผ ์ ํํ๋ค. ์ถ๊ฐ๋ก ํธ๋์คํผ์ ์ค์ ํ์ฌ SkeletalMeshComponent๋ฅผ CapsuleComponent์ ์ ๋ ฌํ๋ค.
5) ์นด๋ฉ๋ผ ๋ทฐ ๋ณ๊ฒฝํ๊ธฐ
๋ํดํธ ์นด๋ฉ๋ผ๋ ๋ฉ์์ ๋ชฉ ์์ชฝ์ ์์นํ์ฌ ๊ฐ์ ์ด ํ์ํ๋ค. ์์น, ํ๋ ์ค๋ธ ๋ทฐ ๋ฑ ์นด๋ฉ๋ผ์ ํ๋กํผํฐ๋ฅผ ์กฐ์ ํ ๋ ์ฌ์ฉํ ์ ์๋ FPS ์นด๋ฉ๋ผ๋ฅผ ๊ตฌ์ฑํ ๊ฒ์ด๋ค.
๋จผ์ , ์บ๋ฆญํฐ ํด๋์ค ํค๋ ํ์ผ์์ ํฌํจ๋ ํ์ผ ๋ชฉ๋ก์ ํ์ฅํ์ฌ ์นด๋ฉ๋ผ ๊ด๋ จ ๊ธฐ๋ฅ์ ์ก์ธ์ค ํ ์ ์๋๋ก ์์ ํ๋ค.
์ด๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์ถ๊ฐํ ์ฝ๋์ ์์น๋ฅผ ํ์ธํด ๋ณด์!!!!!!!!!!
#include "FPSCharacter.generated.h" ์์ ์์นํ๋๋ก ํด์ผ ํ๋ค.
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
๊ทธ ํ cpp ํ์ผ์์ ์นด๋ฉ๋ผ ์ปดํฌ๋ํธ ์์ฑ๊ณผ ์บก์ ์ปดํฌ๋ํธ ์๋ ์นด๋ฉ๋ผ๋ฅผ ๋ถ์ด๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค. ๊ทธ๋ฆฌ๊ณ ์นด๋ฉ๋ผ ์์น์ ํฐ์ด ์นด๋ฉ๋ผ์ ํ์ ์ ์ ์ดํ๋๋ก ํ๋ค.
AFPSCharacter::AFPSCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// ์ผ์ธ์นญ ์นด๋ฉ๋ผ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค.
FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
check(FPSCameraComponent != nullptr);
// ์บก์ ์ปดํฌ๋ํธ์ ์นด๋ฉ๋ผ ์ปดํฌ๋ํธ๋ฅผ ์ดํ์นํฉ๋๋ค.
FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent()));
// ์นด๋ฉ๋ผ๊ฐ ๋ ์ฝ๊ฐ ์์ ์์นํ๋๋ก ํฉ๋๋ค.
FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
// ํฐ์ด ์นด๋ฉ๋ผ ํ์ ์ ์ ์ดํ๋๋ก ํฉ๋๋ค.
FPSCameraComponent->bUsePawnControlRotation = true;
}
6) ์บ๋ฆญํฐ์ 1์ธ์นญ ๋ฉ์ ์ถ๊ฐํ๊ธฐ
FPS ๊ฒ์์ ๋ง๋ค ๋๋ ๋ณดํต ํ ๋ฐ๋ ๋ฉ์์ ๋ฌด๊ธฐ ๋ฐ ์ ๋ฉ์๋ผ๋ ๋ณ๋์ ์บ๋ฆญํฐ ๋ฉ์ 2๊ฐ๋ฅผ ์ฌ์ฉํ๋ค. ํ ๋ฐ๋ ๋ฉ์๋ 3์ธ์นญ ์์ ์์ ์บ๋ฆญํฐ๋ฅผ ๋ณผ ๋ ์ฌ์ฉ๋๋ฉฐ, ํ๋ ์ด์ด๊ฐ 1์ธ์นญ ์์ ์ผ๋ก ๊ฒ์์ ๋ณผ ๋๋ ์จ๊ฒจ์ง๋ค. ๋ฌด๊ธฐ ๋ฐ ์ ๋ฉ์๋ ์ผ๋ฐ์ ์ผ๋ก ์นด๋ฉ๋ผ์ ๋ถ์ด ์์ผ๋ฉฐ, ํ๋ ์ด์ด๊ฐ ๋งต์ 1์ธ์นญ ์์ ์ผ๋ก ๋ณผ ๋๋ง ํ์๋๋ค.
1์ธ์นญ ๋ฉ์๋ฅผ ์ํ ์ปดํฌ๋ํธ๋ฅผ ์บ๋ฆญํฐ ํค๋ ํ์ผ์ ์ถ๊ฐํ ํ ๋ค์๊ณผ ๊ฐ์ด C++ ํ์ผ์ ์์ฑ์ ํจ์์ ์ถ๊ฐํ๋ค.
- SetOnlyOwnerSee๋ ์ด ๋ฉ์๊ฐ ์ด ์บ๋ฆญํฐ์ ๋น์ํ PlayerController ์๋ง ๋ณด์ธ๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค.
// ์์ ํ๋ ์ด์ด์ ์ผ์ธ์นญ ๋ฉ์ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค.
FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
check(FPSMesh != nullptr);
// ์์ ํ๋ ์ด์ด๋ง ์ด ๋ฉ์๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
FPSMesh->SetOnlyOwnerSee(true);
// FPS ๋ฉ์๋ฅผ FPS ์นด๋ฉ๋ผ์ ์ดํ์นํฉ๋๋ค.
FPSMesh->SetupAttachment(FPSCameraComponent);
// ์ผ๋ถ ์ธ๋ฐ์ด๋ฐ๋จผํธ ์๋๋ฅผ ๋นํ์ฑํํ์ฌ ๋จ์ผ ๋ฉ์ ๊ฐ์ ๋๋์ ๋ณด์กดํฉ๋๋ค.
FPSMesh->bCastDynamicShadow = false;
FPSMesh->CastShadow = false;
// ์์ ํ๋ ์ด์ด๊ฐ ์ผ๋ฐ(์ผ์ธ์นญ) ๋ฐ๋ ๋ฉ์๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
GetMesh()->SetOwnerNoSee(true);
1์ธ์นญ ๋ฉ์๋ฅผ ์ํฌํธํ๋ค. ์ด๋ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ํ ์ํฌํธ๊ฐ ํ์ํ๋ค.
1์ธ์นญ ๋ฉ์๋ฅผ 1์ธ์นญ ๋ฉ์ ์ปดํฌ๋ํธ์ ์ถ๊ฐํ ํ ์คํํ๋ฉด, ๊ธฐ์กด ํ ๋ฉ์๋ ๋ณด์ด์ง ์๊ณ 1์ธ์นญ ๋ฉ์๋ง ๋ณด์ผ ๊ฒ์ด๋ค. ๋ง์ฝ, ์ฌ์ ํ ํ ๋ฉ์๊ฐ ๋ณด์ธ๋ค๋ฉด ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค์์ ๋ฉ์ ๋ํ ์ผ ๋ด ๋ ๋๋ง ์ค์ ์ ๋ณ๊ฒฝํ๋ฉด ๋๋ค.
3. ๋ฐ์ฌ์ฒด ๊ตฌํํ๊ธฐ
1) ๋ฐ์ฌ์ฒด ์ถ๊ฐํ๊ธฐ
C++ ํด๋์ค ๋ง๋ฒ์ฌ๋ฅผ ํตํด FPSProjectile ์กํฐ๋ฅผ ์์ฑํ ํ ๋ฐ์ฌ์ฒด๊ฐ ๋ USphere ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๋ค.
- USphereComponent CollisionComponent: Sphere ์ฝ๋ฆฌ์ ์ปดํฌ๋ํธ์ด๋ค
- ProjectileMovementComponent: ๋ฐ์ฌ์ฒด ์ด๋ ์ปดํฌ๋ํธ์ด๋ค.
- FireInDirection ํจ์: ๋ฐ์ฌ ๋ฐฉํฅ์ผ๋ก์ ๋ฐ์ฌ์ฒด ์๋๋ฅผ ์ด๊ธฐํํ๋ค.
AFPSProjectile::AFPSProjectile()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
if (!RootComponent)
{
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
}
if (!CollisionComponent)
{
// ์คํผ์ด๋ฅผ ๋จ์ ์ฝ๋ฆฌ์ ํํ์ผ๋ก ์ฌ์ฉํฉ๋๋ค.
CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
// ์คํผ์ด์ ์ฝ๋ฆฌ์ ๋ฐ๊ฒฝ์ ์ค์ ํฉ๋๋ค.
CollisionComponent->InitSphereRadius(15.0f);
// ๋ฃจํธ ์ปดํฌ๋ํธ๊ฐ ์ฝ๋ฆฌ์ ์ปดํฌ๋ํธ๊ฐ ๋๋๋ก ์ค์ ํฉ๋๋ค.
RootComponent = CollisionComponent;
}
if (!ProjectileMovementComponent)
{
// ์ด ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ ์ด ๋ฐ์ฌ์ฒด์ ์ด๋์ ์ฃผ๋ํฉ๋๋ค.
ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
ProjectileMovementComponent->SetUpdatedComponent(CollisionComponent);
ProjectileMovementComponent->InitialSpeed = 3000.0f;
ProjectileMovementComponent->MaxSpeed = 3000.0f;
ProjectileMovementComponent->bRotationFollowsVelocity = true;
ProjectileMovementComponent->bShouldBounce = true;
ProjectileMovementComponent->Bounciness = 0.3f;
ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
}
}
// ๋ฐ์ฌ ๋ฐฉํฅ์ผ๋ก์ ๋ฐ์ฌ์ฒด ์๋๋ฅผ ์ด๊ธฐํํ๋ ํจ์์
๋๋ค.
void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
{
ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
}
์ด์ ๋ฐ์ฌ์ฒด๋ฅผ ํํํ ๋ฉ์์ ๋จธํฐ๋ฆฌ์ผ์ ์ถ๊ฐํ ๊ฒ์ด๋ค.
ํํ ๋ฆฌ์ผ์์ ๋ฐ์ฌ์ฒด ๋ฉ์(Sphere)๋ฅผ ์ ์ฅํ๊ณ ์ํฌํธ ํ๋ค. ๊ธฐ์กด์๋ C++ ํด๋์ค๋ฅผ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ก ์์ฑํ ํ ๊ทธ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค์ ๋ฉ์๋ฅผ ์ค์ ํ๋ ๋ฐฉ์์ผ๋ก ์งํํ์ง๋ง, ์ง๊ธ์ ๋ ํผ๋ฐ์ค ๋ณต์ฌ๋ฅผ ํตํด ์ ์ฉํด ๋ณผ ๊ฒ์ด๋ค.
Sphere ์คํํฑ ๋ฉ์๋ฅผ ์ฐํด๋ฆญํ๊ณ ๋ ํผ๋ฐ์ค ๋ณต์ฌ(Copy Reference)๋ฅผ ์ ํํ ํ ์ฝ๋์ ์ ์ฉํ๋ค.
์์ ๋ ํผ๋ฐ์ค ๊ฒฝ๋ก๋ ์ฝํ ์ธ ๋ธ๋ผ์ฐ์ ์ ์คํผ์ด ๋จธํฐ๋ฆฌ์ผ์ ์ ์ฅํ ์์น์ ๋ฐ๋ผ ๋ค๋ฅผ ์ ์๋ค.
๋ํ ๋ณต์ฌํ ์์ ๋ ํผ๋ฐ์ค๋ฅผ ๋ถ์ฌ ๋ฃ์ ๊ฒฝ์ฐ ์์ ์ ๋ ํผ๋ฐ์ค ๊ฒฝ๋ก ์์ ์์ ์ ํ์ ์ด๋ฆ์ด ํฌํจ๋์ด ์๋ค. ์ฌ๊ธฐ์๋ Material'/Game/Sphere.Sphere' ์ผ ๊ฒ์ด๋ค. ์์ ์ ํ์ ์ด๋ฆ(์: Material)์ ๋ ํผ๋ฐ์ค ๊ฒฝ๋ก์์ ์ ๊ฑฐํ๋์ง ํ์ธํด์ผ ํ๋ค.
AFPSProjectile::AFPSProjectile()
{
if (!ProjectileMeshComponent)
{
ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
if (Mesh.Succeeded())
{
ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
}
}
}
๋ค์์ผ๋ก๋ ๋ฐ์ฌ์ฒด์ ๋จธํฐ๋ฆฌ์ผ์ ์ถ๊ฐํ ๊ฒ์ด๋ค. ์ฝํ ์ธ ๋ธ๋ผ์ฐ์ ์์ ๋จธํฐ๋ฆฌ์ผ์ ์์ฑํ ํ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ๋ค.
๋ฉ์์ ๋์ผํ๊ฒ ๋ ํผ๋ฐ์ค ๋ณต์ฌ๋ฅผ ์ด์ฉํด ๋ฉ์ ์ปดํฌ๋ํธ์ ๋จธํฐ๋ฆฌ์ผ์ ์ถ๊ฐํ๋ค.
AFPSProjectile::AFPSProjectile()
{
static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
if (Material.Succeeded())
{
ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
}
ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
ProjectileMeshComponent->SetupAttachment(RootComponent);
}
2) ์ํ ๊ตฌํํ๊ธฐ
๋ฐ์ฌ์ฒด ์กํฐ ์ถ๊ฐ๊ฐ ์๋ฃ๋์๋ค๋ฉด, ํ๋ก์ ํธ ์ธํ ์์ ๋ฐ์ฌ์ฒด ์์ฑ์ ์ํ ์ก์ ๋งคํ์ ๊ตฌ์ฑํ๋ค.
- Fire: ์ผ์ชฝ ๋ง์ฐ์ค ๋ฒํผ
๊ทธ ํ ์บ๋ฆญํฐ์ ์ก์ ๋งคํ์ ๋ํด ๋ฐ์ธ๋ฉ์ด ํ์ํ๋ค. ์ผ์ชฝ ๋ง์ฐ์ค ๋ฒํผ์ ๋๋ฅด๋ฉด Fire ํจ์๊ฐ ์คํ๋๋ค.
void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
}
Fire ํจ์๋ ๋ฐ์ฌ์ฒด ์์ฑ์ ํ๋ฉฐ ์๋๋ฅผ ์ด๊ธฐํํ๋ค. ์ด๋, ์ด๊ตฌ ๋ฐฉํฅ(MuzzleOffset)๊ณผ ๋ฐ์ฌ์ฒด ํด๋์ค(ProjectileClass)๋ ์บ๋ฆญํฐ๊ฐ ๊ฒฐ์ ํ๋ฉฐ ์ด ๊ฐ์ผ๋ก ๋ฐ์ฌ์ฒด๋ฅผ ์์ฑํ๋ค.
class FPSPROJECT_API AFPSCharacter : public ACharacter
{
protected:
// ์คํฐํ ๋ฐ์ฌ์ฒด ํด๋์ค์
๋๋ค.
UPROPERTY(EditDefaultsOnly, Category = Projectile)
TSubclassOf<class AFPSProjectile> ProjectileClass;
public:
// ๋ฐ์ฌ์ฒด ๋ฐ์ฌ๋ฅผ ์ฒ๋ฆฌํ๋ ํจ์์
๋๋ค.
UFUNCTION()
void Fire();
// ์นด๋ฉ๋ผ ์์น๋ก๋ถํฐ์ ์ด๊ตฌ ์คํ์
์
๋๋ค.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
FVector MuzzleOffset;
};
void AFPSCharacter::Fire()
{
// ๋ฐ์ฌ์ฒด ๋ฐ์ฌ๋ฅผ ์๋ํฉ๋๋ค.
if (ProjectileClass)
{
// ์นด๋ฉ๋ผ ํธ๋์คํผ์ ๊ตฌํฉ๋๋ค.
FVector CameraLocation;
FRotator CameraRotation;
GetActorEyesViewPoint(CameraLocation, CameraRotation);
// MuzzleOffset์ด ์นด๋ฉ๋ผ ์ด์ง ์์์ ๋ฐ์ฌ์ฒด๋ฅผ ์คํฐํ๋๋ก ์ค์ ํฉ๋๋ค.
MuzzleOffset.Set(100.0f, 0.0f, 0.0f);
// MuzzleOffset์ ์นด๋ฉ๋ผ ์คํ์ด์ค์์ ์๋ ์คํ์ด์ค๋ก ๋ณํํฉ๋๋ค.
FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset);
// ์กฐ์ค์ด ์ด์ง ์๋ฅผ ํฅํ๋๋ก ์๊ณกํฉ๋๋ค.
FRotator MuzzleRotation = CameraRotation;
MuzzleRotation.Pitch += 10.0f;
UWorld* World = GetWorld();
if (World)
{
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = GetInstigator();
// ์ด๊ตฌ์ ๋ฐ์ฌ์ฒด๋ฅผ ์คํฐํฉ๋๋ค.
AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
if (Projectile)
{
// ๋ฐ์ฌ์ฒด์ ์ด๊ธฐ ํ๋๋ฅผ ์ค์ ํฉ๋๋ค.
FVector LaunchDirection = MuzzleRotation.Vector();
Projectile->FireInDirection(LaunchDirection);
}
}
}
}
์ด์ ๋จ๊ณ์์ ์ถ๊ฐํ ๋ฐ์ฌ์ฒด๋ฅผ ์บ๋ฆญํฐ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค์ ์ถ๊ฐํ๋ฉด, ์ผ์ชฝ ๋ง์ฐ์ค ๋ฒํผ์ ๋๋ ์ ๋ ๋ฐ์ฌ์ฒด๊ฐ ๋ฐ์ฌ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
3) ๋ฐ์ฌ์ฒด ์๋ช ์ ํํ๊ธฐ
ํ์ฌ ๋ฐ์ฌ์ฒด๋ ์๊ตฌ ์ง์๋๊ณ ์์ด ์๋ช ์ ์ ํํด์ผ ํ๋ค. ์กํฐ ํด๋์ค์์ ์ ๊ณตํ๋ InitialLifeSpan๋ก ์์ฑ์์ ์ถ๊ฐํ์ฌ ๋ฐ์ฌ์ฒด๋ฅผ 3์ด ํ์ ์ ๊ฑฐํ ๊ฒ์ด๋ค.
AFPSProjectile::AFPSProjectile()
{
// 3์ด ํ ๋ฐ์ฌ์ฒด๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
InitialLifeSpan = 3.0f;
}
4) ๋ฐ์ฌ์ฒด ์ฝ๋ฆฌ์
๋ฐ์ฌ์ฒด๊ฐ ์๋์ ๋ค๋ฅธ ์ค๋ธ์ ํธ์ ์ถฉ๋๋๋๋ก ์ถ๊ฐํ ๊ฒ์ด๋ค. ๋จผ์ ์ฝ๋ฆฌ์ ์ธํ ํธ์ง์ด ํ์ํ๋ค. ์ธ๋ฆฌ์ผ์ ์ฌ๋ฌ ๊ฐ์ ์ฝ๋ฆฌ์ ์ฑ๋์ ํจ๊ป ์ ๊ณตํ์ง๋ง, ์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅํ ์ฑ๋ ๋ํ ์ ๊ณตํ๋ค.
ํ๋ก์ ํธ ์ธํ ์ ์ฝ๋ฆฌ์ ์์ ๋จผ์ ์ ์ค๋ธ์ ํธ ์ฑ๋์ ์์ฑํ๋ค.
- Block: ๊ฐ๋ก๋งํ
ํ๋ฆฌ์ ์์ ์ ํ๋กํ์ผ์ ์์ฑํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋ฆฌ์ ํ๋ฆฌ์ ์ ์ค์ ํ๋ค.
์ ์ฝ๋ฆฌ์ ์ฑ๋์ ์ธํ ์ FPSProjectile ์กํฐ์ ์ ์ฉํ๋ฉด, ์ด์ ๋ฐ์ฌ์ฒด์ ์ฝ๋ฆฌ์ ์ธํฐ๋์ ์ ํ์งํ ์ ์๊ณ ์ด ์ฝ๋ฆฌ์ ์ด ๋ฐ์ํ๋ ๋ฐฉ์๋ ๊ฒฐ์ ํ ์ ์๋ค.
AFPSProjectile::AFPSProjectile()
{
if (!CollisionComponent)
{
// ์คํผ์ด์ ์ฝ๋ฆฌ์ ํ๋กํ์ผ ์ด๋ฆ์ 'Projectile'๋ก ์ค์ ํฉ๋๋ค.
CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
// ์ปดํฌ๋ํธ๊ฐ ์ด๋๊ฐ์ ๋ถ๋ชํ ๋ ํธ์ถ๋๋ ์ด๋ฒคํธ์
๋๋ค.
CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
}
}
// ๋ฐ์ฌ์ฒด๊ฐ ์ด๋๊ฐ์ ๋ถ๋ชํ ๋ ํธ์ถ๋๋ ํจ์์
๋๋ค.
void AFPSProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
if (OtherActor != this && OtherComponent->IsSimulatingPhysics())
{
OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
}
Destroy();
}
ํ ์คํธ๋ฅผ ํ๋ฉด, ๋ฐ์ฌ์ฒด๊ฐ ํ๋ธ์ ์ถฉ๋ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. ์ด๋ ์ถฉ๋๋๋ ํ๋ธ๋ ํผ์ง์ค ์๋ฎฌ๋ ์ด์ (Simulate Physics)์ด ์ฒดํฌ๋์ด ์์ด์ผ ํ๋ค.
4. ์บ๋ฆญํฐ ์ ๋๋ฉ์ด์ ์ถ๊ฐํ๊ธฐ
1) ์บ๋ฆญํฐ ์ ๋๋ฉ์ดํ ํ๊ธฐ
์์ํ๊ธฐ ์ ์ ์ํ ์ ๋๋ฉ์ด์ ์ ์ํฌํธ ํ๋ค. ์ ๋๋ฉ์ด์ ์ํฌํธ๋ ์ค์ผ๋ ํค ์ ํ์ด ํ์ํ๋ฐ, ์ด๋ ๋์ ๋ฉ์๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ ํํ์ง ์์ผ๋ฉด ๋์ค์ ๋งต ์ ๋๋ฉ์ด์ ์์ ๋ฌธ์ ๊ฐ ๋ ์ ์๋ค.
์ํฌํธ ๋ฒํผ ๋๋ ์ ๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ฉด, ์ ๋๋ฉ์ด์ ํญ ๋ด ๊ฐ์ฅ ๊ฐ๊น์ด ๋ฐ์ด๋๋ฆฌ์ ์ค๋ ์ ํ์ฑํ์ํค๋ฉด ๋๋ค.
์ ๋๋ฉ์ด์ ๋ธ๋ฃจํ๋ฆฐํธ๋ ์์ฑํ๋ค. ์ ๋๋ฉ์ด์ ๊ณผ ๋์ผํ ์ค์ผ๋ ํค์ ์ ํํ ํ ๋ถ๋ชจ ํด๋์ค๋ ์ ๋ ์ธ์คํด์ค(AnimInstance)๋ก ์ ํํ๋ค.
์์ฑํ ์ ๋๋ฉ์ด์ ๋ธ๋ฃจํ๋ฆฐํธ๋ฅผ ์ฐ ๋ค, IsRunning ์ด๋ฆ์ ๊ฐ์ง ๋ถ์ธ(Boolean) ๋ณ์๋ฅผ ์ถ๊ฐํ๋ค. ์ ๋๋ฉ์ด์ ์ ์คํ ์ดํธ ๋จธ์ ์ ๊ตฌ๋ํ ์ ๋๋ฉ์ด์ ํธ๋์ง์ ๋ณ์๋ฅผ ์์ฑํ ๊ฒ์ด๋ค.
2) ์ด๋ฒคํธ ๊ทธ๋ํ ๊ตฌ์ฑํ๊ธฐ
๊ฒ์์ด ์คํ๋๋ ๋์ ์์ฑํ ์ ๋๋ฉ์ด์ ํธ๋์ง์ ๋ณ์๊ฐ ์ ๋๋ก ๋ฐ๋ํ๋๋ก ์ ๋๋ฉ์ด์ ์ ์ด๋ฒคํธ ๊ทธ๋ํ๋ฅผ ํธ์งํ ๊ฒ์ด๋ค. ์ ๋๋ฉ์ด์ ๋ธ๋ฃจํ๋ฆฐํธ ๋ด ์ด๋ฒคํธ ๊ทธ๋ํ์์ Event Blueprint Update Animation ๋ ธ๋๋ฅผ ์ ํํ๋ค. ์ด ๋ ธ๋๋ ์ ๋๋ฉ์ด์ ์ด ์ ๋ฐ์ดํธ๋ ๋๋ง๋ค ์คํ ์ดํธ ๋ณ์๋ฅผ ์ ๋ฐ์ดํธํ์ฌ ํญ์ ๋ณ์๊ฐ ๊ฒ์ ์คํ ์ดํธ์ ๋๊ธฐํ๋๋๋ก ํ ์ ์๋ค.
IsRunning ๋ณ์๋ฅผ ๋ณ๊ฒฝํ๋ ์ด๋ฒคํธ ๊ทธ๋ํ์ด๋ค.
- ์ ๋๋ฉ์ด์ ์ด ์ ๋ฐ์ดํธ๋ ๋ ์คํ๋๋ค.
- ์บ๋ฆญํฐ์ ์๋๋ฅผ ๊ฐ์ ธ์ ์๋ ๋ฒกํฐ์ ๊ธธ์ด๋ฅผ ๋ฐํํ๋ค.
- ๊ทธ ๊ธธ์ด๊ฐ 0๋ณด๋ค ํฌ๋ค๋ฉด ์ ์ง ์ํ๊ฐ ์๋๊ธฐ ๋๋ฌธ์, 0๋ณด๋ค ํด ๋ IsRunning ๋ณ์๋ true๊ฐ ๋๊ณ ์์ ๋ false๊ฐ ๋๋ค.
3) ์ ๋๋ฉ์ด์ ์คํ ์ดํธ ๋จธ์ ์ถ๊ฐํ๊ธฐ
์ด์ ๋ง๋ ๋ณ์๋ฅผ ํ์ฉํ๋ ์คํ ์ดํธ ๋จธ์ ์ ์์ฑํ ๊ฒ์ด๋ค. AnimGraph์์ ์คํ ์ดํธ ๋จธ์ ์ ์ถ๊ฐํ๊ณ Final Animation Pose ๋ ธ๋์ Result ์ ๋ ฅ ์คํ ํ์ ์ฐ๊ฒฐํ๋ค.
ํํ ๋ฆฌ์ผ์์๋ ์ ์คํ ์ดํธ ๋จธ์ ์ถ๊ฐ...(Add New State Machine...)๋ฅผ ์ ํํ๋ผ๊ณ ๋์ด ์๋ค.
๋ง์ฝ ์ ๋ณด์ธ๋ค๋ฉด, State Machine์ ์ ํํ๋ฉด ์์ฑ๋๋ค.
์์ฑํ ์คํ ์ดํธ ๊ทธ๋ํ๋ฅผ ์ด๊ณ ํ์ํ ์คํ ์ดํธ๋งํผ ์ถ๊ฐํ๋๋ฐ, ์ด์ ์ ์ํฌํธ ํ ์ ๋๋ฉ์ด์ ์๋งํผ ์คํ ์ดํธ๋ฅผ ์์ฑํ๋ฉด ๋๋ค.
์์ฑ์ด ์๋ฃ๋ ์คํ ์ดํธ์ ์ ๋๋ฉ์ด์ ์ ์คํํ๋ ๋ ธ๋๋ฅผ ์ฐ๊ฒฐํ๋ค. ์ด ๊ณผ์ ์ ์ถ๊ฐํ ์คํ ์ดํธ๋งํผ ๋ฐ๋ณตํ๋ค.
์ด์ ์คํ ์ดํธ ๊ทธ๋ํ์์ ์คํ ์ดํธ ๋ ธ๋ ์ฌ์ด์ ํธ๋์ง์ ์ ์ฐ๊ฒฐํด์ผ ํ๋ค.
- Entry ๋ ธ๋์์ Idle ์คํ ์ดํธ ๋ ธ๋๋ก ์ฐ๊ฒฐํ๋ค.
- Idle ์คํ
์ดํธ ๋
ธ๋์ Run ์คํ
์ดํธ ๋
ธ๋๋ฅผ ์๋ฐฉํฅ์ผ๋ก ์ฐ๊ฒฐํ๋ค.
- Idle์์ Run ํธ๋์ง์ ์ IsRunning ๋ณ์๋ฅผ ์ ๋ ฅ๋ฐ๋๋ค.
- Run์์ Idle ํธ๋์ง์ ์ IsRunning ๋ณ์์ Not ๊ฐ์ ์ ๋ ฅ๋ฐ๋๋ค.
4) ์ ๋๋ฉ์ด์ ๋ฐ ์บ๋ฆญํฐ ๋ธ๋ฃจํ๋ฆฐํธ ์ฐ๊ฒฐํ๊ธฐ
์ด์ ์บ๋ฆญํฐ ๋ธ๋ฃจํ๋ฆฐํธ ํด๋์ค๋ก ์ด๋ํ์ฌ FPSMesh์ ์ ๋ ํด๋์ค์ ๋ฐฉ๊ธ ์์ ํ ์ ๋๋ฉ์ด์ ๋ธ๋ฃจํ๋ฆฐํธ๋ฅผ ์ค์ ํ๋ค.
์ด์ํ๊ฒ ๊ตณ์ด์์ด ๋ณด์๋ FPSMesh๊ฐ ์์ฐ์ค๋ฝ๊ฒ ์์ง์ด๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.