게임/UnReal_MakeMyGameStudy
일인칭 슈팅 C++ 튜토리얼
CMS419
2021. 5. 7. 17:43
목적
이 튜토리얼은 C++ 를 사용해서 기본적인 일인칭 슈팅 (FPS) 게임을 만드는 법을 보여드립니다.
목표
이 튜토리얼을 마칠 때 쯤이면 다음과 같은 작업이 가능할 것입니다:
- 프로젝트 구성
- 캐릭터 구현
- 프로젝타일 구현
- 캐릭터 애니메이션
일인칭 슈팅 C++ 튜토리얼
일인칭 슈팅 게임 메커니즘 구현 방법을 배워봅니다.
docs.unrealengine.com
코드
FPSCharacter
FPSCharacter.cpp
/*
마우스 감도나 축 반전과 같은 추가 처리를 해 주려거든,
입력 값을 함수에 전달하기 전 별도의 조정을 가하는 함수를 추가해 주면 되지만,
여기서는 입력을 바로 AddControllerYawInput 과 AddControllerPitchInput 함수에 바인딩하도록 하겠습니다.
*/
#include "FPSCharacter.h"
#include <Engine/Classes/Components/CapsuleComponent.h>
// Sets default values
AFPSCharacter::AFPSCharacter()
{
// 매 프레임 Tick() 호출을 위해 이 캐릭터를 설정합니다. 필요치 않은 경우 꺼 주면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = true;
// 일인칭 카메라 컴포넌트 생성합니다
FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
// 카메라 컴포턴넌트를 캡슐 컴포턴트에 붙입니다.
FPSCameraComponent->SetupAttachment(GetCapsuleComponent());
//카메라 위치를 눈 살짝 위쪽으로 잡습니다.
FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
// 폰의 카메라 로테이션 제어를 혀용합니다.
FPSCameraComponent->bUsePawnControlRotation = true;
//일인칭 메시 컴포넌트
FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
// 소유 플레이어만 이 메시를 볼 수 있다.
FPSMesh->SetOnlyOwnerSee(true);
// FPS 메시를 FPS 카메라에 붙입니다.
FPSMesh->SetupAttachment(FPSCameraComponent);
//일부 환경 섀도잉을 꺼 메시가 하나인 듯 보이는 느낌을 유지합니다.
FPSMesh->bCastDynamicShadow = false;
FPSMesh->CastShadow = false;
// 자신 이외 모두가 일반 몸통 메시를 볼 수 있습니다.
GetMesh()->SetOwnerNoSee(true);
}
// 게임 시작시 또는 스폰시 호출됩니다.
void AFPSCharacter::BeginPlay()
{
Super::BeginPlay();
if (GEngine)
{
// 5초간 디버그 메세지 표시 첫 인수"-1 "Key" 값은 이 메시직를 업데이트 또는 새로고칠 필요가 없음을 나타냅니다.
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCaracter!!"));
}
}
// 매 프레임 호출됩니다.
void AFPSCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// 입력을 위한 함수성 바인딩을 위해 호출됩니다.
void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
//"movement" 바인딩 구성
//InputComponent 는 입력 데이터 처리 방식을 정의하는 컴포넌트입니다.
//InputComponent 는 입력을 받기 원하는 액터에 붙이면 됩니다.
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);
//"action" 바인딩을 구성합니다.
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump);
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);
}
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);
}
/*
Character 베이스 클래스에 대한 인터페이스 (*.h) 파일 안을 보면,
캐릭터 점프에 대한 지원이 내장되어 있는 것을 볼 수 있습니다.
캐릭터 점프는 `bPressedJump` 변수에 묶여 있어서, 점프 동작을 누르면 부울을 `true` 로, 떼면 `false` 로 설정
그렇게 하기 위해 다음의 두 함수를 추가해야 합니다:
*/
void AFPSCharacter::StartJump()
{
bPressedJump = true;
}
void AFPSCharacter::StopJump()
{
bPressedJump = false;
}
/*
고려 해야할게 2가지 있다
1) 발사체 스폰 위치
2) 프로젝타일 클래스 (FPSCharacter 와 그 파생 블루프린트가 스폰할 발사체를 알도록 하기 위한것이다.)
이유: 카메라 스페이스 오프셋 벡터를 사용하여 프로젝타일의 스폰 위치를 결정하게 됩니다.
이 파라미터를 편집가능하게 만들어야 BP_FPSCharacter 블루프린트에서 설정 및 조정할 수 있습니다.
궁극적으로 이 데이터를 토대로 발사체에 대한 초기 위치를 계산할 수 있을 것입니다.
참고로 멀티플레이어 게임을 만드는 경우,
MovementComp 컴포넌트의 Initial Velocity in Local Space (로컬 스페이스의 초기 속도)
옵션 체크를 해제해 줘야 이 발사체가 서버에 제대로 리플리케이트됩니다.
*/
void AFPSCharacter::Fire()
{
//프로젝타일 발사를 시도합니다
if (ProjectileClass)
{
//카메라 트랜스폼을 구합니다.
FVector CameraLocation;
FRotator CameraRotation;
GetActorEyesViewPoint(CameraLocation, CameraRotation);
//MuzzleOffset 을 카메라 스페이스에서 월드 스페이스로 변환합니다.
FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleLocation);
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);
}
}
}
}
FPSCharacter.h
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Camera/CameraComponent.h"
#include "FpsProjectile.h"
#include "FPSCharacter.generated.h"
UCLASS()
class FPSPROJECT_API AFPSCharacter : public ACharacter
{
GENERATED_BODY()
public:
// 이 캐릭터의 프로퍼티 기본값을 설정합니다.
AFPSCharacter();
protected:
// 게임 시작 또는 스폰시 호출됩니다.
virtual void BeginPlay() override;
public:
// 매 프레임 호출됩니다.
virtual void Tick(float DeltaTime) override;
// 입력에 함수성 바인딩을 위해 호출됩니다.
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// 전후 이동 처리
UFUNCTION()
void MoveForward(float Value);
//좌우 이동 처리
UFUNCTION()
void MoveRight(float Value);
// 키를 누르면 점프 플래그를 설정합니다.
UFUNCTION()
void StartJump();
// 키를 떼면 점프 플래그를 지웁니다.
UFUNCTION()
void StopJump();
// FPS 카메라
UPROPERTY(VisibleAnywhere)
UCameraComponent* FPSCameraComponent;
//일인칭 메시 (팔) 소유 플레이어에게만 보이기
UPROPERTY(VisibleDefaultsOnly, Category = "Mesh")
USkeletalMeshComponent* FPSMesh;
UFUNCTION()
void Fire();
/*
EditAnywhere 지정자를 통해 총구 오프셋 값을 블루프린트 에디터의 디폴트 모드나
캐릭터의 아무 인스턴스에 대한 디테일 탭에서 변경할 수 있습니다.
BlueprintReadWrite 지정자를 통해서는 블루프린트 안에서 총구 오프셋의 값을 구하고 설정할 수 있습니다.
*/
// 카메라 위치에서의 총구 오프셋
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
FVector MuzzleOffset;
/*
EditDefaultsOnly 지정자는 프로젝타일 클래스를 블루프린트의
각 인스턴스 상에서가 아니라 블루프린트의 디폴트로만 설정할 수 있다는 뜻입니다.
*/
// 스폰시킬 프로젝타일 클래스
UPROPERTY(EditDefaultsOnly, Category = Projectile)
TSubclassOf<AFPSProjectile> ProjectileClass;
};
FPSHUD
FPSHUD.cpp
#include <Engine/Canvas.h>
#include "FPSHUD.h"
void AFPSHUD::DrawHUD()
{
Super::DrawHUD();
if (CrosshairTexture)
{
// 캔버스 중심을 찾습니다.
FVector2D Center(Canvas->ClipX * 0.5f, Canvas->ClipY * 0.5f);
//텍스처 중심이 캔버스 중심에 맞도록 텍스처의 크기 절반 만큼 오프셋을 줍니다.
FVector2D CrossHairDrawPosition(Center.X - (CrosshairTexture->GetSurfaceWidth() * 0.5f), Center.Y - (CrosshairTexture->GetSurfaceHeight() * 0.5f));
// 중심점에 조준선을 그립니다.
FCanvasTileItem TileItem(CrossHairDrawPosition, CrosshairTexture->Resource, FLinearColor::White);
TileItem.BlendMode = SE_BLEND_Translucent;
Canvas->DrawItem(TileItem);
}
}
FPSHUD.h
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "FPSHUD.generated.h"
/**
*
*/
UCLASS()
class FPSPROJECT_API AFPSHUD : public AHUD
{
GENERATED_BODY()
protected:
// 화면 중앙에 그려질 것입니다.
UPROPERTY(EditAnywhere)
UTexture2D* CrosshairTexture;
public:
// HUD에 대한 주요 드로 콜입니다.
virtual void DrawHUD() override;
};
FPSProjectGameModeBase
FPSProjectGameModeBase.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "FPSProjectGameModeBase.h"
void AFPSProjectGameModeBase::StartPlay()
{
Super::StartPlay();
if (GEngine)
{
//디버그 메시지를 5초간 표시합니다
//"키" (첫 번쨰 인수) 값을 -1 로 하면 이 메시지를 절대 업데이트하거나 새로고칠 필요가 없음을 나타냅니다.
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Hello World, this is FPSGameMode"));
}
}
FPSProjectGameModeBase.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "FPSProjectGameModeBase.generated.h"
/**
*
*/
UCLASS()
class FPSPROJECT_API AFPSProjectGameModeBase : public AGameModeBase
{
GENERATED_BODY()
virtual void StartPlay() override;
};
FPSProjectile
FPSProjectile.cpp
#include "FPSProjectile.h"
// Sets default values
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;
// 시뮬레이션으로 구동시켜줄 것이므로
// CollisionComponent 를 RootComponent 로 만듭니다.
// 구체를 단순 콜리전 표현으로 사용합니다.
CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
// 구체의 콜리전 반경을 설정합니다.
CollisionComponent->InitSphereRadius(15.0f);
// 루트 컴포넌트를 콜리전 컴포넌트로 설정합니다.
RootComponent = CollisionComponent;
// 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;
// 3초 뒤에 죽는다.
InitialLifeSpan = 3.0f;
CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
}
// Called when the game starts or when spawned
void AFPSProjectile::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AFPSProjectile::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// 프로젝타일의 속도를 발사 방향으로 초기화시키는 함수입니다.
void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
{
ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
}
// 프로젝타일에 무언가 맞으면 호출되는 함수입니다.
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);
}
}
FPSProjectile.h
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include <Engine/Classes/Components/SphereComponent.h>
#include <Engine/Classes/GameFramework/ProjectileMovementComponent.h>
#include "FPSProjectile.generated.h"
UCLASS()
class FPSPROJECT_API AFPSProjectile : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AFPSProjectile();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// 구체 콜리전 컴포넌트
UPROPERTY(VisibleDefaultsOnly, Category = "Projectile")
USphereComponent* CollisionComponent;
// 프로젝타일 무브먼트 컴포넌트
UPROPERTY(VisibleAnywhere, Category = "Movement")
UProjectileMovementComponent* ProjectileMovementComponent;
// 프로젝타일이 무언가에 맞으면 호출되는 함수입니다.
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComponent,
FVector NormalImpulse,
const FHitResult& Hit
);
// 발사체의 속도를 발사 방향으로 초기화시킵니다.
void FireInDirection(const FVector& ShootDirection);
};