제대로 따라가기 (8) C++ 프로그래밍 튜토리얼 :: 일인칭 슈팅 C++ 튜토리얼 (3)

작성버전 :: 4.21.0

언리얼 엔진 튜토리얼인 일인칭 슈팅 C++ 튜토리얼에서는 C++ 코드 작업을 통해서 기본적인 일인칭 슈팅(FPS) 게임을 만드는 법을 배울 수 있다.

 

이번 튜토리얼은 각 하위 섹션들의 길이가 길어서 분할되어 작성된다.

 

튜토리얼대로 하면 문제가 발생해서 제대로 따라갈 수 없는 부분으로 동작이 가능하게 수정해야하는 부분은 빨간 블럭으로 표시되어 있다.


이번 튜토리얼에서 새로 배우게 되는 내용은 글 제일 끝에 "이번 섹션에서 배운 것"에 정리된다.

 

수정

 

지난 섹션에서 VisibleDefaultOnly는 버전이 바뀌어서 사라진 지정자라고 했던 부분은 잘못된 부분입니다.

 

VisibleDefaultsOnly는 정상적으로 존재하는 UPROPERTY 지정자입니다. 제가 실수로 VisibleDefaultOnly로 오타를 내서 컴파일러가 지정자가 없다고 에러를 띄웠었습니다. 잘못된 정보로 혼동을 드린 점에 대해서 사과드립니다. 다음부터는 제대로된 확인을 거친 후, 글을 올리도록 하겠습니다.

 

지난 섹션의 잘못된 부분은 수정되었습니다.(지난 섹션 :: 제대로 따라가기 (7) C++ 프로그래밍 튜토리얼 :: 일인칭 슈팅 C++ 튜토리얼 (2))

 

3. 발사체 구현

 

이번 섹션에서는 일인칭 슈팅 게임에서 발사체를 구현하는 방법에 대해서 배울 수 있다.

 

3-1. 게임에 발사체 추가

 

이전 섹션에서 캐릭터 구성을 마쳤으니, 이제 발사체 무기를 구현하여 발사하면 단순한 수류탄 같은 발사체가 화면 중앙에서 발사되어 월드에 충돌할 때까지 날아가도록 만들어보자. 이번 단계에서는 발사체(Projectile)에 쓸 입력을 추가하고 새 코드 클래스를 만들 것이다.

 

발사 액션 매핑 추가

 

편집 메뉴에서 프로젝트 세팅 창을 연다. 그리고 엔진 섹션에서 입력을 선택한 뒤, 액션 매핑에 아래와 같이 "Fire" 라는 입력 세팅을 추가 한다.

 

 

발사체(Projectile) 클래스 추가

 

파일 메뉴에서 새로운 C++ 클래스... 를 선택하고 Actor 클래스를 부모 클래스로 선택하고 다음을 클릭한다.

 

 

새 클래스 이름을 "FPSProjectile"로 하고 클래스 생성을 클릭한다.

 

 

USphereComponent 추가

 

FPSProjectile.h로 가서 USphereComponent의 선언을 다음처럼 추가해준다.

 

UPROPERTY(VisibleDefaultsOnly, Category = "Projectile")
USphereComponent* CollisionComponent;

 

USphereComponenet가 정의되지 않았다고 에러가 발생한다면, USphereComponent 앞에 class 키워드를 붙여주자.

class USphereComponent* CollisionComponent;

 

그 다음엔 FPSProjectile.cpp의 AFPSProjectile::AFPSProjectile() 생성자 함수에서 다음 코드를 추가한다.

CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
CollisionComponent->InitSphereRadius(15.0f);
RootComponent = CollisionComponent;

 

CollisionComponent에서 함수를 호출하려고 할 때, 불완전한 형식은 사용할 수 없다는 에러가 발생하면 "Engine/Classes/Components/SphereComponent.h"를 cpp의 전처리기에 추가해주자.

#include "Engine/Classes/Components/SphereComponent.h"

 

프로젝타일 무브먼트 컴포넌트(Projectile Movement Component) 추가

 

FPSProjectile.h의 하단에 다음 코드를 추가한다.

UPROPERTY(VisibleAnywhere, Category = "Movement")
UProjectileMovementComponent* ProjectileMovementComponent;

 

UProjectileMovementComponent가 정의되지 않았다고 에러가 발생한다면, UProjectileMovementComponent 앞에 class 키워드를 붙여주자.

class UProjectileMovementComponent* ProjectileMovementComponent;

 

다시, FPSProjectile.cpp의 AFPSProjectile::AFPSProjectile() 생성자 함수에서 다음 코드를 추가한다.

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에서 함수를 호출하려고 할 때, 불완전한 형식은 사용할 수 없다는 에러가 발생하면 "Engine/Classes/GameFramework/ProjectileMovementComponent.h"를 cpp의 전처리기에 추가해주자.

#include "Engine/Classes/GameFramework/ProjectileMovementComponent.h"

 

발사체 초기 속도 설정

 

FPSProjectile.h로 가서 다음의 함수 선언을 추가한다.

void FireInDirection(const FVector& ShootDirection);

 

이 함수가 발사체의 발사를 담당한다.

 

FPSProjectile.cpp에 함수의 정의부를 추가한다.

void AFPSProjectile::FireInDirection(const FVector& ShootDirection)
{
    ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed;
}

 

발사체의 속력은 ProjectileMovementComponent에 의해 정의되므로 발사 방향만 제공해주면 된다.

 

발사 입력 액션 바인딩

 

FPSCharacter.h로 가서 다음의 함수 선언을 추가한다.

UFUNCTION()
void Fire();

 

FPSCharacter.cpp의 SetupPlayerInputComponent() 함수에 다음 바인딩을 추가한다.

InputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire);

 

그리고, Fire() 함수의 정의부 역시 추가한다.

void AFPSCharacter::Fire()
{
}

 

발사체의 스폰 위치 정의

 

FPSProjectile 액터를 스폰하는 Fire() 함수 구현을 위해서는 발사체를 스폰할 위치와, 스폰해야할 발사체의 클래스를 고려해야 한다.

 

FPSCharacter.h에 다음 코드를 추가한다.

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GamePlay)
FVector MuzzleOffset;

UPROPERTY(EditDefaultsOnly, Category = Projectile)
TSubclassOf<class AFPSProjectile> ProjectileClass;

 

코드 컴파일 및 검사

 

이제 새로 구현된 발사체 코드를 컴파일하고 검사할 차례다. 솔루션 탐색기에서 FPSProject에 우클릭하고 빌드(Build)를 선택하여 프로젝트를 컴파일한다.

 

 

 

3-2. 발사 구현

 

이번 단계에서는 캐릭터가 발사체 발사를 할 수 있도록 Fire() 함수를 구현해본다.

 

Fire 함수 구현

 

FPSCharacter.cpp 상단에 "FPSProjectile.h"를 포함시킨다.

#include "FPSProjectile.h"

 

FPSCharacter.cpp에 만들어둔 Fire() 함수의 바디를 다음과 같이 수정한다.

void AFPSCharacter::Fire()
{
    if (ProjectileClass)
    {
        FVector CameraLocation;
        FRotator CameraRotation;
        GetActorEyesViewPoint(CameraLocation, CameraRotation);

        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 = Instigator;
            AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
            if (Projectile)
            {
                FVector LaunchDirection = MuzzleRotation.Vector();
                Projectile->FireInDirection(LaunchDirection);
            }
        }
    }
}

 

변경사항을 저장하고 프로젝트를 빌드한다.

 

 

프로젝타일 블루프린트 빌드하기

 

다음 링크에서 샘플 메시를 받아서 압축을 풀고 진행해야 한다. "프로젝타일 메시"

 

콘텐츠 브라우저의 파일 창에 우클릭해서 /Game에 임포트... 를 선택해서 임포트 대화창을 연다.

 

 

Sphere.fbx 메시 파일을 찾아서 선택하고 임포트를 선택해서 메시를 프로젝트에 추가하고, 저장 버튼을 클릭해서 임포트된 스태틱 메시를 저장한다.

 

콘텐츠 브라우저에서 Blueprints 폴더에 들어간다.

 

신규 추가 버튼을 클릭하고 블루프린트 클래스를 선택한다.

 

모든 클래스 드롭다운 메뉴를 펼친다음 검색창에 FPSProjectile을 입력하고 FPSProjectile 클래스를 선택한다.

 

 

새 블루프린트의 이름을 BP_FPSProjectile로 정하고 아이콘을 더블클릭해서 블루프린트 에디터를 연다.

 

 

컴포넌트 탭에서 CollisionComponent를 클릭하고 컴포넌트 추가 드롭다운 목록에서 Static Mesh를 찾아서 추가하고 이름은 ProjectileMeshComponent로 한다.

 

 

디테일 탭의 Static Mesh 섹션에서 "없음"이라고 된 드롭다운 메뉴를 클릭해서 Sphere 스태틱 메시를 선택한다.

 

 

X, Y, Z 스케일 값을 0.09로 설정한다. 자물쇠 아이콘을 클릭하면 세 축을 모두 고정시켜 상대 비율이 유지된다.

 

 

ProjectileMeshComponent의 콜리전 프리셋 값을 NoCollision으로 설정한다. (콜리전에 이 스태틱 메시가 아닌 SphereComponent를 사용한다.)

 

 

블루프린트를 컴파일, 저장한 뒤 블루프린트 에디터를 닫는다.

 

BP_FPSCharacter를 더블클릭해서 블루프린트 에디터를 열고 Projectile Class 프로퍼티를 찾은 다음 BP_FPSProjectile로 설정한다.

 

 

그리고 Muzzle Offset 프로퍼티를 {100, 0, 0}으로 설정하여 발사체를 카메라 약간 앞에서 생성되게 한다.

 

 

블루프린트를 컴파일, 저장 후 블루프린트 에디터를 닫는다.

 

게임 내 발사체 발사 테스트

 

레벨 에디터에서 플레이 버튼을 클릭하여 PIE 모드에 들어가서 발사체를 발사해보자.

 

좌클릭하면 발사체가 월드로 발사된다.

 

 

 

Esc키를 누르거나 중지 버튼을 클릭하여 PIE 모드를 빠져나가자.

 

 

 

 

 

3-3. 발사체 콜리전 및 수명 구성

 

지금 만들어진 발사체는 아무리 긴 시간이 지나도 사라지지 안고, 월드의 다른 오브젝트와 충돌하지 않는 상태이다. 이번 단계에서는 발사체의 충돌과 수명을 구성한다.

 

발사체의 수명 기간 제한

 

FPSProjectile.cpp에서 AFPSProjectile::AFPSProjectile() 생성자 함수에 다음 코드를 추가하여 발사체의 수명을 설정한다.

InitialLifeSpan = 3.0f;

 

발사체의 충돌 세팅 편집

 

언리얼 엔진에는 여러가지 프리셋 콜리전 채널이 포함되어 있으나, 커스터마이징 프리셋 콜리전 채널을 만들 수도 있다.

 

프로젝트 세팅 창의 엔진 섹션에서 콜리전을 선택하면 콜리전 채널 프리셋들을 확인할 수 있다.

 

 

 

 

새 오브젝트 채널... 을 선택하여 콜리전 채널을 새로 만든다. 새로운 콜리전 채널 이름을 "Projectile"이라 하고 기본 반응(Default Response)를 Block으로 설정하여 수락을 누른다.

 

 

프리셋에 새... 버튼을 클릭해서 새 프로필의 이름을 "Projectile"로 하고, 다음 이미지를 참고해서 콜리전 프리셋을 설정하자.

 

 

새 콜리전 채널 세팅 사용

 

FPSProjectile.cpp의 생성자에서 CollisionComponent 설정 코드의 아래에 다음 줄을 추가한다.

CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));

 

변경사항을 저장하고 솔루션 탐색기에서 FPSProject를 우클릭해서 빌드를 선택해서 프로젝트를 컴파일한다.

 

 

 

3-4. 월드와 상호작용하는 프로젝타일

 

프로젝타일의 콜리전 상호작용을 감지할 수 있게 되었으니, 이제 그 콜리전에 어떻게 반응할지를 결정할 차례다. 이번 단계에서는 콜리전 이벤트에 반응하는 FPSProjectile에 OnHit() 함수를 추가할 것이다.

 

프로젝타일이 콜리전에 반응하도록 만들기

 

FPSProjectile.h를 열어서 클래스 정의에 다음 코드를 추가한다.

UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);

 

FPSProjectile.cpp에서 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);
    }
}

 

그리고 FPSProjectile 생성자에 CollisionComponent 생성 코드 뒤에 다음 코드를 추가한다.

CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);

 

변경사항들을 저장하고 솔루션 탐색기에서 FPSProject에 우클릭한 뒤, 빌드를 선택해서 프로젝트를 컴파일한다.

 

 

프로젝타일 콜리전 테스트

 

빌드 완료 후, 언리얼 에디터로 돌아가서 Floor 스태틱 메시를 복사 & 붙여넣기를 한다.

 

이름이 Floor2인 바작 메시 사본의 비율 고정이 풀렸는지 확인하고 스케일을 {0.2, 0.2, 3.0}으로 설정하고 위치를 {320, 0, 170}으로 조정한다.

 

 

Floor2의 디테일 패널에서 Physics 섹션을 찾은 뒤 Simulate Physics 옵션을 체크한다.

 

 

맵을 저장하고 BP_FPSProjectile을 더블클릭하여 블루프린트 에디터를 열고 컴포넌트 탭에서 ProjectileMeshComponent를 클릭한다.

 

디테일 패널에서 Collision 아래의 Collision Presets 프로퍼티를 Projectile로 설정한다.

 

 

블루프린트를 컴파일, 저장한 뒤 블루프린트 에디터를 닫는다.

 

플레이 버튼을 눌러 PIE 모드에 들어간다. 좌클릭으로 상자를 향해 발사체를 발사하면 상자가 발사체에 맞고 튕겨나가는 모습을 볼 수 있다.

 

 

레벨 에디터에서 중지 버튼을 눌러서 PIE 모드에서 빠져나간다.

 

 

3-5. 뷰포트에 조준선 추가

 

이번 단계에서는 게임에 조준선 HUD 요소를 추가하려 조준할 수 있도록 해보자.

 

조준선 애셋 임포트

 

시작하기에 앞서, 다음 링크에서 샘플 이미지를 다운로드하고 압축을 푼다. "샘플 조준선 이미지"

 

콘텐츠 브라우저의 파일 창에 우클릭하여 /Game에 임포트... 를 눌러서 임포트 대화 상자를 연다.

 

 

crosshair.TGA 이미지 파일을 찾아서 임포트한다.

 

새 HUD 클래스 추가

 

파일 메뉴에서 새로운 C++ 클래스... 을 선택한다.

 

부모 클래스 선택 메뉴가 열리면 HUD 클래스를 찾아서 부모 클래스로 선택하고 다음을 클릭한다.

 

 

클래스 이름에 "FPSHUD"를 입력하고 클래스를 생성한다.

 

 

FPSHUD.h에 다음 변수와 함수 코드를 추가한다.

protected:
    UPROPERTY(EditAnywhere)
    UTexture2D* CrosshairTexture;

public:
    virtual void DrawHUD() override;

 

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

 

DrawHUD() 함수를 구현할 때, Canvas 기능과 관련해서 불완전한 형식의 클래스에 대한 에러가 발생할 수 있다. 이 부분은 "Engine/Canvas.h"를 포함시켜주면 해결된다.

#include "Engine/Canvas.h"

 

변경사항을 저장하고 솔루션 탐색기에서 FPSProject를 우클릭해서 빌드를 선택하고 프로젝트를 컴파일한다.

 

 

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

 

콘텐츠 브라우저에서 FPSHUD 클래스에 우클릭해서 FPSHUD 기반 블루프린트 클래스 생성을 선택한다.

 

 

BP_FPSHUD라는 이름으로 Blueprints 폴더에 블루프린트 클래스를 생성한다.

 

 

기본 HUD 클래스 설정

 

프로젝트 세팅의 맵 & 모드에서 HUD를 새로 생성한 BP_FPSHUD로 설정해준다.

 

 

세팅이 끝났으면 BP_FPSHUD 블루프린트 에디터를 열고 Crosshair Texture를 crosshair로 설정해준다.

 

 

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

 

HUD 확인

 

레벨 에디터에서 플레이 해보면 새로 추가한 조준선이 보일 것이다.

 

 

레벨 에디터의 중지 버튼을 눌러서 PIE 모드를 빠져나오자.

 

 

 

 

 

 


 

이번 섹션에서 배운 것

 

1. UProjectileMovementComponent

UProjectileMovementComponent* ProjectileMovementComponent;

발사체의 이동을 처리해주는 컴포넌트

ProjectileMovementComponent->InitialSpeed = 3000.0f;

발사체의 초기 속도 변수. 이 값이 0이면 Velocity 값을 속도로 사용하고, 0이 아니면 Velocity 값을 무시한다.

ProjectileMovementComponent->MaxSpeed = 3000.0f;

발사체에 허용되는 최대 속력

ProjectileMovementComponent->bRotationFollowsVelocity = true;

이 값이 참이면, 발사체의 회전이 이동 방향에 맞춰 매 프레임 업데이트된다.

ProjectileMovementComponent->bShouldBounce = true;

이 값이 참이면, 간단한 바운스가 시뮬레이션 된다. 접촉 시뮬레이션을 중지하려면 이 값을 false로 설정한다.

ProjectileMovementComponent->Bounciness = 0.3f;

충돌의 법선 방향으로 반발이 발생한 이후에 유지되는 속도의 비율

ProjectileMovementComponent->Velocity;

발사체의 속도

 

2. AActor

FVector EyeLocation;
FRotator EyeRotation;

GetActorEyesViewPoint(EyeLocation, EyeRotation);

액터의 시점을 가져오는 함수. 이 시점은 액터의 붙은 카메라의 위치와 회전이 아니라 실제 액터의 눈에 해당하는 위치와 회전이다.

InitialLifeSpan = 3.0f;

액터의 수명을 결정하는 변수. 0으로 설정하면 시간이 지나도 소멸하지 않는다. 이 값은 초기 값이기 때문에, 플레이가 시작된 이후에는 수정해서는 안된다.

 

3. FActorSpawnParameters

FActorSpawnParameters SpawnParams;

액터를 스폰할 때 사용되는 매개변수들을 담는 구조체

SpawnParams.Owner;

이 액터를 생성한 액터. 생성한 액터가 따로 없다면 NULL로 둘 수 있다.

 

4. UWorld

GetWorld()->SpawnActor<T>(Class, Location, Rotation, SpawnParams);

월드에 액터를 스폰하는 함수이다.

 

5. UPrimitiveComponent

Target->AddImpulseAtLocation(Impulse, Location);

대상 물체에 충돌 효과를 주는 함수.

 


 

일인칭 슈팅 C++ 프로그래밍 튜토리얼은 이 다음에 한 섹션이 더 남아있지만, 다음 섹션은 프로그래밍 작업보다는 에디터에서 진행되는 애니메이션 작업 튜토리얼이다. 그렇기 때문에 이번 제대로 따라가기 :: 일인칭 슈팅 C++ 튜토리얼은 여기서 끝마친다.

 

다음 섹션을 이어서 배우고 싶디만 링크를 통해서 섹션 4. 캐릭터 애니메이션 추가를 따라가면 된다.

 

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형
  1. qqq 2020.01.12 16:14

    그대로 따라했는데 발사체가 사라지질 않습니다... 뭐가 문제일까요?
    혹시나 해서 공식문서의 완성 코드로 덮어 씌워봤으나 여전히 사라지지 않습니다.
    언리얼 엔진 4.23 버전입니다.

    • qqq 2020.01.15 21:32

      처음부터 다시 했더니 또 되네요...
      혼란을 드려 죄송합니다.

    • wergia 2020.01.22 10:22 신고

      차근차근 따라가다보면 잘 되는 경우가 많습니다. ㅎㅎ

  2. NogameNoHope 2020.09.25 03:25

    다른 버전은 모르겠는데 4.25에서는
    SpawnParams.Instigator = Instigator;
    이 부분에서 에러가 뜹니다.
    확인 결과 Actor.h 내부에 Instigator 가 private로 지정되있어서 직접 접근을 금지시켰는데
    SpawnParams.Instigator = GetInstigator();
    이렇게 바꿔주니까 해결되는 것 같습니다.

    항상 잘 보고 있습니다.

    • wergia 2020.10.20 00:10 신고

      언리얼은 자꾸 버전마다 바뀌는 부분이 심하네요 ㅜㅜ

  3. Kkk 2022.07.18 23:26

    Fire함수를 호출할때, if(ProjectileClass)문을 통과하지 못하는것으로 보아, ProjectileClass가 명시되지 않는거 같은데, 이거 어떻게 해결해야할까요?

    • wergia 2022.07.18 23:47 신고

      https://youtu.be/qDrQ1NSuq_Y?t=291

      영상 이부분에서 ProjectilClass 프로퍼티에 할당해주는 부분을 수행하셨나요?

  4. Kkk 2022.07.19 17:00

    네 수행하였는데도 SpawnActor failed because no class was specified라고 뜹니다

+ Recent posts