3.5 - 뷰포트에 조준선 추가

일인칭 슈팅 프로젝트의 뷰포트에 조준선을 추가하는 법을 배워봅니다.

Choose your operating system:

Windows

macOS

Linux

이번 단계에서는 게임에 조준선 HUD 요소를 추가하여 조준할 수 있도록 하겠습니다.

조준선 애셋 임포트

시작하기에 앞서, 다음 링크에서 샘플 이미지를 다운로드하고 압축을 풀어주세요:

  1. 콘텐츠 브라우저의 파일창 안에 우클릭하여 애세 임포트 대화창을 엽니다.

  2. /Game... 에 임포트 를 눌러 임포트 대화 상자를 엽니다.

    RightClickImport.png

  3. crosshair.TGA 이미지 파일을 찾아 선택합니다.

  4. 열기 를 클릭하여 프로젝트에 이미지 파일 임포트를 시작합니다.

  5. 저장 버튼을 눌러 임포트된 메시를 저장합니다.

새 HUD 클래스 추가

  1. 파일 메뉴에서 새 C++ 클래스... 를 선택합니다.

  2. 부모 클래스 선택 메뉴가 열립니다. 스크롤해 내려가 HUD 를 부모 클래스로 선택하고 다음 을 클릭합니다.

    ChooseParentHUDClass.png

  3. 새 클래스 이름은 "FPSHUD" 라 하고 생성 을 클릭합니다.

    NameHUDClass.png

  4. Solution Explorer 에서 FPSHUD 클래스 헤더 파일을 위치에서, FPSHUD.h 를 열고 다음 protected 변수를 추가합니다:

    protected:
        // 화면 중앙에 그려질 것입니다.
        UPROPERTY(EditDefaultsOnly)
        UTexture2D* CrosshairTexture;
  5. 다음 함수 선언을 FPSHUD.h 에 추가합니다:

    public:
        // HUD 에 대한 주요 드로 콜입니다.
        virtual void DrawHUD() override;
  6. 이제 FPSHUD.cpp DrawHUD 함수를 구현합니다:

    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);
        }
    }
  7. Visual Studio 에서 FPSHUD.h FPSHUD.cpp 를 저장합니다.

  8. Solution Explorer 에서 FPSProject 위치를 찾습니다.

  9. FPSProject 에 우클릭하고 Build 를 선택하여 프로젝트를 컴파일합니다.

    BuildFPSProject.png

CPP HUD 클래스를 블루프린트로 확장

이제 CPP HUD 클래스를 블루프린트로 확장하기 좋은 시점입니다. C++ 클래스를 블루프린트로 확장하는 방법 관련 상세 정보는 C++ 와 블루프린트 문서 페이지를 참고하세요.

  1. FPSHUD 클래스에 우클릭하여 C++ Class Actions 메뉴를 엽니다.

    CPPClassActionsMenu.png

  2. FPSHUD 기반 블루프린트 클래스 생성 을 클릭하여 블루프린트 클래스 추가 대화창을 엽니다.

    CreateDerivedBPClass.png

  3. 블루프린트 클래스 이름을 BP_FPSHUD 라 하고 Blueprints 폴더를 선택한 뒤 블루프린트 클래스 생성 버튼을 누릅니다.

    AddBPClass.png

  4. 이제 Blueprints 폴더 안에 BP_FPSHUD 라는 블루프린트 클래스가 새로 생성되어 있을 것입니다.

    AddedBPClass.png

  5. 꼭 BP_FPSHUD 블루프린트를 저장한 뒤 블루프린트 에디터를 닫습니다.

기본 HUD 클래스 설정

  1. 편집 메뉴에서 프로젝트 세팅 을 클릭합니다.

  2. 프로젝트 세팅 탭 왼편의 프로젝트 제목줄 아래 맵 & 모드 를 선택합니다.

  3. Default HUD (기본 HUD) 드롭다운 메뉴에서 BP_FPSHUD 를 선택합니다.

    ChooseHUDClass.png

  4. 프로젝트 세팅 메뉴를 닫습니다.

  5. 뒤로 돌아가 BP_FPSHUD 블루프린트 에디터를 엽니다.

  6. 이제, 블루프린트 에디터의 FPSHUD 섹션에 위치한 드롭다운 메뉴를 클릭하고 조준선 텍스처를 선택합니다.

    SelectCrosshairTexture.png

  7. 마지막으로, BP_FPSHUD 블루프린트를 저장하고 블루프린트 에디터를 닫습니다.

HUD 확인

  1. 레벨 에디터 툴바의 플레이 버튼을 클릭합니다. 새로 추가한 조준선으로 조준이 가능할 것입니다.

    CrosshairsInGame.png

  2. 레벨 에디터의 중지 버튼을 클릭하여 에디터에서 플레이 (PIE) 모드를 빠져나옵니다.

완성 섹션 코드

FPSProjectile.h

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/Actor.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 DeltaSeconds ) override;

    // Sphere collision component.
    UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
    USphereComponent* CollisionComponent;

    // Projectile movement component.
    UPROPERTY(VisibleAnywhere, Category = Movement)
    UProjectileMovementComponent* ProjectileMovementComponent;

    // Function that initializes the projectile's velocity in the shoot direction.
    void FireInDirection(const FVector& ShootDirection);

    // Function that is called when the projectile hits something.
    void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);

};

FPSProjectile.cpp

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "FPSProject.h"
#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;

    // Use a sphere as a simple collision representation.
    CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
    CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
    CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);

    // Set the sphere's collision radius.
    CollisionComponent->InitSphereRadius(15.0f);
    // Set the root component to be the collision component.
    RootComponent = CollisionComponent;

    // Use this component to drive this projectile's movement.
    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;

    // Die after 3 seconds.
    InitialLifeSpan = 3.0f;
}

// Called when the game starts or when spawned.
void AFPSProjectile::BeginPlay()
{
    Super::BeginPlay();
}

// Called every frame.
void AFPSProjectile::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

// Function that initializes the projectile's velocity in the shoot direction.
void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
{
    ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
}

// Function that is called when the projectile hits something.
void AFPSProjectile::OnHit(AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
    if (OtherActor != this && OtherComponent->IsSimulatingPhysics())
    {
        OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
    }
}

FPSCharacter.h

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/Character.h"
#include "FPSCharacter.generated.h"

UCLASS()
class FPSPROJECT_API AFPSCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    // Sets default values for this character's properties.
    AFPSCharacter();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame.
    virtual void Tick( float DeltaSeconds ) override;

    // Called to bind functionality to input.
    virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;

    // Handles input for moving forward and backward.
    UFUNCTION()
    void MoveForward(float Value);

    // Handles input for moving right and left.
    UFUNCTION()
    void MoveRight(float Value);

    // Sets jump flag when key is pressed.
    UFUNCTION()
    void StartJump();

    // Clears jump flag when key is released.
    UFUNCTION()
    void StopJump();

    // Function that handles firing projectiles.
    UFUNCTION()
    void Fire();

    // FPS camera.
    UPROPERTY(VisibleAnywhere)
    UCameraComponent* FPSCameraComponent;

    // First-person mesh (arms), visible only to the owning player.
    UPROPERTY(VisibleDefaultsOnly, Category = Mesh)
    USkeletalMeshComponent* FPSMesh;

    // Gun muzzle's offset from the camera location.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
    FVector MuzzleOffset;

    // Projectile class to spawn.
    UPROPERTY(EditDefaultsOnly, Category = Projectile)
    TSubclassOf<class AFPSProjectile> ProjectileClass;
};

FPSCharacter.cpp

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "FPSProject.h"
#include "FPSCharacter.h"
#include "FPSProjectile.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;

    // Create a first person camera component.
    FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
    // Attach the camera component to our capsule component.
    FPSCameraComponent->SetupAttachment(GetCapsuleComponent());
    // Position the camera slightly above the eyes.
    FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
    // Allow the pawn to control camera rotation.
    FPSCameraComponent->bUsePawnControlRotation = true;

    // Create a first person mesh component for the owning player.
    FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
    // Only the owning player sees this mesh.
    FPSMesh->SetOnlyOwnerSee(true);
    // Attach the FPS mesh to the FPS camera.
    FPSMesh->SetupAttachment(FPSCameraComponent);
    // Disable some environmental shadowing to preserve the illusion of having a single mesh.
    FPSMesh->bCastDynamicShadow = false;
    FPSMesh->CastShadow = false;

    // The owning player doesn't see the regular (third-person) body mesh.
    GetMesh()->SetOwnerNoSee(true);
}

// Called when the game starts or when spawned.
void AFPSCharacter::BeginPlay()
{
    Super::BeginPlay();

    if (GEngine)
    {
        // Put up a debug message for five seconds. The -1 "Key" value (first argument) indicates that we will never need to update or refresh this message.
        GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter."));
    }
}

// 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);

    // Set up "movement" bindings.
    PlayerInputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);

    // Set up "look" bindings.
    PlayerInputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
    PlayerInputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);

    // Set up "action" bindings.
    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)
{
    // Find out which way is "forward" and record that the player wants to move that way.
    FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
    AddMovementInput(Direction, Value);
}

void AFPSCharacter::MoveRight(float Value)
{
    // Find out which way is "right" and record that the player wants to move that way.
    FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
    AddMovementInput(Direction, Value);
}

void AFPSCharacter::StartJump()
{
    bPressedJump = true;
}

void AFPSCharacter::StopJump()
{
    bPressedJump = false;
}

void AFPSCharacter::Fire()
{
    // Attempt to fire a projectile.
    if (ProjectileClass)
    {
        // Get the camera transform.
        FVector CameraLocation;
        FRotator CameraRotation;
        GetActorEyesViewPoint(CameraLocation, CameraRotation);

        // Transform MuzzleOffset from camera space to world space.
        FVector MuzzleLocation = CameraLocation + FTransform(CameraRotation).TransformVector(MuzzleOffset);
        FRotator MuzzleRotation = CameraRotation;
        // Skew the aim to be slightly upwards.
        MuzzleRotation.Pitch += 10.0f; 
        UWorld* World = GetWorld();
        if (World)
        {
            FActorSpawnParameters SpawnParams;
            SpawnParams.Owner = this;
            SpawnParams.Instigator = Instigator;
            // Spawn the projectile at the muzzle.
            AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
            if (Projectile)
            {
                // Set the projectile's initial trajectory.
                FVector LaunchDirection = MuzzleRotation.Vector();
                Projectile->FireInDirection(LaunchDirection);
            }
        }
    }
}

FPSHUD.h

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameFramework/HUD.h"
#include "FPSHUD.generated.h"

/**
    * 
    */
UCLASS()
class FPSPROJECT_API AFPSHUD : public AHUD
{
    GENERATED_BODY()

public:
    // Primary draw call for the HUD.
    virtual void DrawHUD() override;

protected:  
    // This will be drawn at the center of the screen.
    UPROPERTY(EditDefaultsOnly)
    UTexture2D* CrosshairTexture;   
};

FPSHUD.cpp

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "FPSProject.h"
#include "FPSHUD.h"

void AFPSHUD::DrawHUD()
{
    Super::DrawHUD();

    if (CrosshairTexture)
    {
        // Find the center of our canvas.
        FVector2D Center(Canvas->ClipX * 0.5f, Canvas->ClipY * 0.5f);

        // Offset by half of the texture's dimensions so that the center of the texture aligns with the center of the Canvas.
        FVector2D CrossHairDrawPosition(Center.X - (CrosshairTexture->GetSurfaceWidth() * 0.5f), Center.Y - (CrosshairTexture->GetSurfaceHeight() * 0.5f));

        // Draw the crosshair at the centerpoint.
        FCanvasTileItem TileItem(CrossHairDrawPosition, CrosshairTexture->Resource, FLinearColor::White);
        TileItem.BlendMode = SE_BLEND_Translucent;
        Canvas->DrawItem(TileItem);
    }
}

축하합니다! 지금까지 배우신 내용은 다음과 같습니다:

✓ 게임에 프로젝타일 추가 ✓ 발사 구현 ✓ 프로젝타일 콜리전과 수명 구성 ✓ 프로젝타일이 월드와 상호작용하도록 만들기 ✓ 뷰포트에 조준선 추가

다음 섹션에서는 캐릭터 애니메이션 방법을 배워보도록 하겠습니다.

언리얼 엔진 문서의 미래를 함께 만들어주세요! 더 나은 서비스를 제공할 수 있도록 문서 사용에 대한 피드백을 주세요.
설문조사에 참여해 주세요
건너뛰기