-
일인칭 슈팅 C++ 튜토리얼게임/UnReal_MakeMyGameStudy 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); };
'게임 > UnReal_MakeMyGameStudy' 카테고리의 다른 글
컴포넌트와 콜리전 (0) 2021.05.02 게임 조종 카메라 (0) 2021.05.01 출발!! (0) 2021.05.01