ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 일인칭 슈팅 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

    댓글

Designed by Tistory.