이번 섹션을 마치면 이런 결과물을 볼 수 있습니다.
목표
이 섹션의 목표는 일인칭 슈팅 게임의 발사체를 구현하는 방법을 보여주는 것입니다.
목적
이 튜토리얼의 이번 섹션을 마치면 다음과 같은 작업을 할 수 있습니다.
- 게임에 발사체 추가
- 슈팅 구현
- 발사체 콜리전 및 수명 구성
- 발사체가 월드와 상호작용하도록 만들기
- 뷰포트에 조준선 추가
단계
- 3.1 - 게임에 발사체 추가하기
- 3.2 - 슈팅 구현하기
- 3.3 - 발사체 콜리전 및 수명 구성하기
- 3.4 - 발사체가 월드와 상호작용하도록 만들기
- 3.5 - 뷰포트에 조준선 추가하기
3.1 - 게임에 발사체 추가하기
이제 캐릭터를 구성했으니, 발사체 무기를 구현할 시간입니다. 수류탄과 비슷한 간단한 발사체를 프로그래밍한 후 화면 중앙에서 발사해서 월드와 충돌할 때까지 날아가도록 해 보겠습니다. 이 단계에서는 발사체를 위한 입력을 추가하고 새 코드 클래스를 생성합니다.
발사 액션 매핑 추가하기
-
메인 메뉴 패널에서 편집 을 클릭하고 프로젝트 세팅 을 선택합니다.
-
프로젝트 세팅 탭 좌측의 엔진(Engine) 헤딩 아래에서 입력(Input) 을 클릭합니다.
-
우측 바인딩(Bindings) 에서 액션 매핑(Action Mappings) 옆의 (+) 기호를 클릭합니다.
-
액션 매핑 좌측의 화살표 를 클릭합니다.
-
텍스트 필드가 표시되면 Fire 라고 입력합니다.
-
드롭다운 메뉴의 마우스(Mouse) 드롭다운 목록에서 왼쪽 마우스 버튼(Left Mouse Button) 을 선택합니다.
-
입력 세팅이 다음과 같은 모습이어야 합니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
-
프로젝트 세팅 메뉴를 닫습니다.
발사체 클래스 추가하기
-
메인 메뉴 패널에서 툴(Tools) 을 클릭하고 새 C++ 클래스...(New C++ Class...) 를 선택합니다.
-
부모 클래스 선택(Choose Parent Class) 창이 나타납니다. 액터(Actor) 를 부모 클래스로 선택하고 다음(Next) 을 클릭합니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
-
새 클래스의 이름을 FPSProjectile 로 지정한 다음, 클래스 생성(Create Class) 을 클릭합니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
USphere 컴포넌트 추가하기
-
C++ 클래스가 생성될 경우 Visual Studio 가
FPSProjectile.h헤더 파일 및FPSProjectile.cpp구현 파일이 열린 상태로 자동으로 나타납니다. -
FPSProjectile.h클래스 헤더 파일로 이동합니다. -
다음과 같이 SphereComponent 헤더 파일을 추가합니다.
FPSProjectile.h
#include "Components/SphereComponent.h" -
FPSProjectile인터페이스의USphereComponent에 레퍼런스를 추가할 경우,FPSProjectile.h의public액세스 지정자 아래에 다음 코드를 추가합니다.FPSProjectile.h
// 스피어 콜리전 컴포넌트입니다. UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; -
FPSProjectile.h는 다음과 같아야 합니다.FPSProjectile.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 이 액터 프로퍼티의 디폴트값 설정 AFPSProjectile(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; public: // 프레임마다 호출 virtual void Tick( float DeltaTime ) override; // 스피어 콜리전 컴포넌트 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; }; -
FPSProjectile.cpp클래스 구현 파일로 이동합니다. -
FPSProjectile.cpp의AFPSProjectile생성자(PrimaryActorTick.bcanEverTick이후)에 다음을 추가합니다.FPSProjectile.cpp
if(!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if(!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; }시뮬레이션이 주도할 것이므로
CollisionComponent를RootComponent로 만듭니다. -
FPSProjectile.cpp는 다음과 같아야 합니다.FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSProjectile.h" // 디폴트값 설정 AFPSProjectile::AFPSProjectile() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; if(!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if(!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; } } // 게임 시작 또는 스폰 시 호출 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 프레임마다 호출 void AFPSProjectile::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); }
발사체 이동 컴포넌트 추가하기
-
FPSProjectile.h클래스 헤더 파일로 이동합니다. -
ProjectileMovementComponent 헤더 파일을 추가합니다.
FPSProjectile.h
#include "GameFramework/ProjectileMovementComponent.h" -
다음 코드를
FPSProjectile.h의public액세스 지정자 아래에 추가합니다.FPSProjectile.h
// 발사체 이동 컴포넌트입니다. UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; -
FPSProjectile.h는 다음과 같아야 합니다.FPSProjectile.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 이 액터 프로퍼티의 디폴트값 설정 AFPSProjectile(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; public: // 프레임마다 호출 virtual void Tick( float DeltaTime ) override; // 스피어 콜리전 컴포넌트입니다. UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 발사체 이동 컴포넌트입니다. UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; }; -
FPSProjectile.cpp클래스 구현 파일로 이동합니다. -
다음 코드 줄을
FPSProjectile.cpp의AFPSProjectile생성자에 추가합니다.FPSProjectile.cpp
if(!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; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } -
FPSProjectile.cpp는 다음과 같아야 합니다.FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSProjectile.h" // 디폴트값 설정 AFPSProjectile::AFPSProjectile() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; if(!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if(!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; } if(!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; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } } // 게임 시작 또는 스폰 시 호출 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 프레임마다 호출 void AFPSProjectile::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); }
발사체의 초기 속도 설정하기
-
FPSProjectile.h클래스 헤더 파일로 이동합니다. -
다음 함수 선언을
FPSProjectile.h의public액세스 지정자 아래에 추가합니다.FPSProjectile.h
// 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void FireInDirection(const FVector& ShootDirection);이 함수는 발사체 발사를 담당합니다.
-
FPSProjectile.h는 다음과 같아야 합니다.FPSProjectile.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 이 액터 프로퍼티의 디폴트값 설정 AFPSProjectile(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; public: // 프레임마다 호출 virtual void Tick( float DeltaTime ) override; // 스피어 콜리전 컴포넌트입니다. UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 발사체 이동 컴포넌트입니다. UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void FireInDirection(const FVector& ShootDirection); }; -
FPSProjectile.cpp클래스 구현 파일로 이동합니다. -
다음 함수 정의를
FPSProjectile.cpp에 추가합니다.FPSProjectile.cpp
// 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }발사체의 속도가
ProjectileMovementComponent에 의해 정의되기 때문에 발사 방향만 제공하면 됩니다. -
FPSProjectile.cpp는 다음과 같아야 합니다.FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSProjectile.h" // 디폴트값 설정 AFPSProjectile::AFPSProjectile() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; if(!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; } if(!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; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } } // 게임 시작 또는 스폰 시 호출 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 프레임마다 호출 void AFPSProjectile::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; }
발사 입력 액션 바인딩하기
-
Visual Studio 의 Solution Explorer 로 이동하여
FPSCharacter.h클래스 헤더 파일을 엽니다. -
다음 함수 선언을
FPSCharacter.h의public액세스 지정자 아래에 추가합니다.FPSCharacter.h
// 발사체 발사를 처리하는 함수입니다. UFUNCTION() void Fire(); -
FPSCharacter.h는 다음과 같아야 합니다.FPSCharacter.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.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(); }; -
Visual Studio 의 Solution Explorer 로 이동하여
FPSCharacter.cpp클래스 구현 파일을 엽니다. -
Fire함수를 바인딩하려면FPSCharacter.cpp:의SetupPlayerInputComponent함수에 다음 코드를 추가합니다.FPSCharacter.cpp
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AFPSCharacter::Fire); -
다음 함수 정의를
FPSCharacter.cpp에 추가합니다.FPSCharacter.cpp
void AFPSCharacter::Fire() { } -
FPSCharacter.cpp는 다음과 같아야 합니다.FPSCharacter.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSCharacter.h" // 디폴트값 설정 AFPSCharacter::AFPSCharacter() { // 이 캐릭터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; // 일인칭 카메라 컴포넌트를 생성합니다. FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera")); check(FPSCameraComponent != nullptr); // 캡슐 컴포넌트에 카메라 컴포넌트를 어태치합니다. FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent())); // 카메라가 눈 약간 위에 위치하도록 합니다. FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight)); // 폰이 카메라 회전을 제어하도록 합니다. FPSCameraComponent->bUsePawnControlRotation = true; // 소유 플레이어의 일인칭 메시 컴포넌트를 생성합니다. FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh")); check(FPSMesh != nullptr); // 소유 플레이어만 이 메시를 볼 수 있습니다. FPSMesh->SetOnlyOwnerSee(true); // FPS 메시를 FPS 카메라에 어태치합니다. FPSMesh->SetupAttachment(FPSCameraComponent); // 일부 인바이런먼트 섀도잉을 비활성화하여 단일 메시 같은 느낌을 보존합니다. FPSMesh->bCastDynamicShadow = false; FPSMesh->CastShadow = false; // 소유 플레이어가 일반(삼인칭) 바디 메시를 볼 수 없습니다. GetMesh()->SetOwnerNoSee(true); } // 게임 시작 또는 스폰 시 호출 void AFPSCharacter::BeginPlay() { Super::BeginPlay(); check(GEngine != nullptr); // 디버그 메시지를 5초간 표시합니다. // -1 '키' 값 실행인자가 메시지가 업데이트되거나 새로고침되지 않도록 방지합니다. GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter.")); } // 프레임마다 호출 void AFPSCharacter::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } //함수 기능을 입력에 바인딩하기 위해 호출 void AFPSCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); // 'movement' 바인딩을 구성합니다. 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); } void AFPSCharacter::StartJump() { bPressedJump = true; } void AFPSCharacter::StopJump() { bPressedJump = false; } void AFPSCharacter::Fire() { }
발사체 스폰 위치 정의하기
-
FPSProjectile액터를 스폰하는 경우,OnFire함수를 구현할 때 고려할 점이 2개 있습니다.- 발사체 스폰 위치
- 발사체 클래스(이를 통해
FPSCharacter및 그 파생 블루프린트가 어떤 발사체를 스폰할지 알 수 있음)
카메라 스페이스 오프셋 벡터를 사용하여 발사체의 스폰 위치를 결정합니다. 이 파라미터를 편집 가능하게 만들어
BP_FPSCharacter블루프린트에서 설정하고 미세조정할 수 있도록 합니다. 결과적으로 이 데이터를 기반으로 발사체의 초기 위치를 계산할 수 있습니다. -
FPSCharacter.h클래스 헤더 파일로 이동합니다. -
다음 코드를
FPSCharacter.h의public액세스 지정자 아래에 추가합니다.FPSCharacter.h
// 카메라 위치로부터의 총구 오프셋입니다. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) FVector MuzzleOffset;EditAnywhere를 사용하면 블루프린트 에디터의 디폴트 모드 내에서나 캐릭터의 아무 인스턴스의 디테일 탭 내에서 총구 오프셋 값을 변경할 수 있습니다.BlueprintReadWrite지정자를 사용하면 블루프린트 내에서 총구 오프셋의 값을 구하고 설정할 수 있습니다. -
다음 코드를
FPSCharacter.h의protected액세스 지정자 아래에 추가합니다.FPSCharacter.h
protected: // 스폰할 발사체 클래스입니다. UPROPERTY(EditDefaultsOnly, Category = Projectile) TSubclassOf<class AFPSProjectile> ProjectileClass;EditDefaultsOnly는 발사체 클래스를 블루프린트의 각 인스턴스가 아닌 블루프린트에서만 디폴트로 설정할 수 있다는 의미입니다. -
FPSCharacter.h는 다음과 같아야 합니다.FPSCharacter.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "FPSCharacter.generated.h" UCLASS() class FPSPROJECT_API AFPSCharacter : public ACharacter { GENERATED_BODY() public: //이 캐릭터의 프로퍼티에 적용되는 디폴트값 설정 AFPSCharacter(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; // 스폰할 발사체 클래스입니다. UPROPERTY(EditDefaultsOnly, Category = Projectile) TSubclassOf<class AFPSProjectile> ProjectileClass; 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(); // 발사체를 발사하는 함수입니다. UFUNCTION() void Fire(); // FPS 카메라 UPROPERTY(VisibleAnywhere) UCameraComponent* FPSCameraComponent; // 일인칭 메시(팔)로, 소유 플레이어에게만 보입니다. UPROPERTY(VisibleDefaultsOnly, Category = Mesh) USkeletalMeshComponent* FPSMesh; // 카메라 위치로부터의 총구 오프셋입니다. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) FVector MuzzleOffset; };
코드 컴파일 및 확인하기
이제 새로 구현한 발사체 코드를 컴파일 및 확인할 시간입니다.
-
Visual Studio 에서 모든 헤더 및 구현 파일을 저장합니다.
-
솔루션 탐색기 로 이동하여 FPSProject 를 선택합니다.
-
FPSProject 를 우클릭하고 빌드 를 선택하여 프로젝트를 컴파일합니다.
이 단계의 목적은 다음 단계로 이동하기 전에 모든 빌드 오류를 잡아내는 것입니다. 이 튜토리얼 범위 밖에서 빌드 오류 또는 경고가 발생했다면 코딩 표준 및 언리얼 엔진 API 레퍼런스 페이지를 참고하시기 바랍니다.
3.2 - 슈팅 구현하기
일인칭 슈팅 캐릭터의 슈팅을 구현하는 방법을 알아봅니다.
발사 함수 구현하기
-
FPSCharacter.h클래스 헤더 파일로 이동합니다. -
FPSCharacter.h에 다음 줄을 추가합니다.FPSCharacter.h
#include "FPSProjectile.h" -
FPSCharacter.cpp클래스 구현 파일로 이동합니다. -
다음
Fire함수 정의를FPSCharacter.cpp에 추가합니다.FPSCharacter.cpp
void AFPSCharacter::Fire() { // 발사체 발사를 시도합니다. if (ProjectileClass) { // 카메라 트랜스폼을 구합니다. FVector CameraLocation; FRotator CameraRotation; GetActorEyesViewPoint(CameraLocation, CameraRotation); // MuzzleOffset이 카메라 살짝 앞에서 발사체를 스폰하도록 설정합니다. MuzzleOffset.Set(100.0f, 0.0f, 0.0f); // MuzzleOffset을 카메라 스페이스에서 월드 스페이스로 변환합니다. 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 = GetInstigator(); // 총구에 발사체를 스폰합니다. AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams); if (Projectile) { // 발사체의 초기 탄도를 설정합니다. FVector LaunchDirection = MuzzleRotation.Vector(); Projectile->FireInDirection(LaunchDirection); } } } } -
FPSCharacter.h는 다음과 같아야 합니다.FPSCharacter.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "Camera/CameraComponent.h" #include "Components/CapsuleComponent.h" #include "FPSProjectile.h" #include "FPSCharacter.generated.h" UCLASS() class FPSPROJECT_API AFPSCharacter : public ACharacter { GENERATED_BODY() public: //이 캐릭터의 프로퍼티에 적용되는 디폴트값 설정 AFPSCharacter(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; // 스폰할 발사체 클래스입니다. UPROPERTY(EditAnywhere, Category = Projectile) TSubclassOf<class AFPSProjectile> ProjectileClass; public: // 프레임마다 호출 virtual void Tick(float DeltaTime) override; //함수 기능을 입력에 바인딩하기 위해 호출 virtual void SetupPlayerInputComponent(class UIComponent* 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(); // 카메라 위치로부터의 총구 오프셋입니다. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) FVector MuzzleOffset; }; -
FPSCharacter.cpp는 다음과 같아야 합니다.FPSCharacter.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSCharacter.h" // 디폴트값 설정 AFPSCharacter::AFPSCharacter() { // 이 캐릭터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; // 일인칭 카메라 컴포넌트를 생성합니다. FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera")); check(FPSCameraComponent != nullptr); // 캡슐 컴포넌트에 카메라 컴포넌트를 어태치합니다. FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent())); // 카메라가 눈 약간 위에 위치하도록 합니다. FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight)); // 폰이 카메라 회전을 제어하도록 합니다. FPSCameraComponent->bUsePawnControlRotation = true; // 소유 플레이어의 일인칭 메시 컴포넌트를 생성합니다. FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh")); check(FPSMesh != nullptr); // 소유 플레이어만 이 메시를 볼 수 있습니다. FPSMesh->SetOnlyOwnerSee(true); // FPS 메시를 FPS 카메라에 어태치합니다. FPSMesh->SetupAttachment(FPSCameraComponent); // 일부 인바이런먼트 섀도잉을 비활성화하여 단일 메시 같은 느낌을 보존합니다. FPSMesh->bCastDynamicShadow = false; FPSMesh->CastShadow = false; // 소유 플레이어가 일반(삼인칭) 바디 메시를 볼 수 없습니다. GetMesh()->SetOwnerNoSee(true); } // 게임 시작 또는 스폰 시 호출 void AFPSCharacter::BeginPlay() { Super::BeginPlay(); check(GEngine != nullptr); // 디버그 메시지를 5초간 표시합니다. // -1 '키' 값 실행인자가 메시지가 업데이트되거나 새로고침되지 않도록 방지합니다. GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter.")); } // 프레임마다 호출 void AFPSCharacter::Tick(float DeltaTime) { Super::Tick( DeltaTime ); } //함수 기능을 입력에 바인딩하기 위해 호출 void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); // 'movement' 바인딩을 구성합니다. 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); } void AFPSCharacter::StartJump() { bPressedJump = true; } void AFPSCharacter::StopJump() { bPressedJump = false; } void AFPSCharacter::Fire() { // 발사체 발사를 시도합니다. if (ProjectileClass) { // 카메라 트랜스폼을 구합니다. FVector CameraLocation; FRotator CameraRotation; GetActorEyesViewPoint(CameraLocation, CameraRotation); // MuzzleOffset이 카메라 살짝 앞에서 발사체를 스폰하도록 설정합니다. MuzzleOffset.Set(100.0f, 0.0f, 0.0f); // MuzzleOffset을 카메라 스페이스에서 월드 스페이스로 변환합니다. 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 = GetInstigator(); // 총구에 발사체를 스폰합니다. AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams); if (Projectile) { // 발사체의 초기 탄도를 설정합니다. FVector LaunchDirection = MuzzleRotation.Vector(); Projectile->FireInDirection(LaunchDirection); } } } } -
Visual Studio 에서
FPSCharacter.h및FPSCharacter.cpp를 저장합니다. -
솔루션 탐색기 로 이동하여 FPSProject 를 선택합니다.
-
FPSProject 를 우클릭하고 빌드 를 선택하여 프로젝트를 컴파일합니다.
발사체 메시 임포트하기
계속하기 전에 다음 링크에서 샘플 메시를 다운로드하고 압축 해제합니다.
-
언리얼 엔진 을 열고 콘텐츠 브라우저 로 이동하여 콘텐츠(Content) 폴더를 엽니다.
-
콘텐츠 브라우저 의 파일 창 내부를 우클릭하여 에셋 임포트 대화창을 엽니다.
여기서는 우클릭 임포트를 다루고 있지만, 콘텐츠 임포트 방법은 3개가 있습니다. 콘텐츠를 임포트하는 방법에 대한 자세한 정보는 에셋을 직접 임포트하기를 참조하세요.
-
/Game...에 임포트(Import to /Game...) 를 클릭하여 임포트(Import) 대화창을 엽니다.
-
이 파일을 다운로드한 위치에서 Sphere.fbx 메시 파일을 찾아 선택합니다.
-
열기(Open) 를 클릭하여 프로젝트에 메시 임포트를 시작합니다.
-
FBX 임포트 옵션(FBX Import Options) 대화창이 나타납니다 모두 임포트(Import All) 를 클릭하여 프로젝트에 메시를 추가합니다.
다음과 같은 스무딩 그룹 관련 오류는 무시하세요.
이 메시는 여전히 일인칭 메시 구성을 보여주며, 향후 섹션에서 구성할 애니메이션과 함께 작동합니다.
-
메인 메뉴 패널에서 파일(File) 을 클릭하고 모두 저장(Save all) 을 선택하여 임포트된 메시를 저장합니다.
발사체의 메시 추가하기
-
Visual Studio 를 열고 솔루션 탐색기 로 이동합니다.
-
솔루션 탐색기 에서
FPSProjectile.h클래스 헤더 파일을 엽니다. -
다음 코드를
FPSProjectile.h의public액세스 지정자 아래에 추가합니다.FPSProjectile.h
// 발사체 메시 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent; -
Visual Studio 의 Solution Explorer 로 이동하여
FPSProjectile.cpp클래스 구현 파일을 엽니다. -
다음 코드를
FPSProjectile.cpp의 생성자에 추가합니다.FPSProjectile.cpp
if(!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("[ADD STATIC MESH ASSET REFERENCE]")); if(Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } } -
언리얼 에디터 를 열고 콘텐츠 브라우저 로 이동한 후 스피어(Sphere) 스태틱 메시를 우클릭하고 레퍼런스 복사(Copy Reference) 를 선택합니다.
-
Visual Studio 를 열고
FPSProjectile.cpp의ProjectileMeshComponent코드로 돌아가[ADD STATIC MESH ASSET REFERENCE]를 복사한 레퍼런스로 대체합니다. 코드가 다음과 비슷해야 합니다.FPSProjectile.cpp
if(!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if(Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } }에셋 레퍼런스 경로는 콘텐츠 브라우저에 스피어 메시를 저장한 위치에 따라 다를 수 있습니다. 또한 복사한 에셋 레퍼런스를 붙여 넣을 경우 에셋의 레퍼런스 경로 앞에 에셋의 타입 이름이 포함되어 있습니다. 여기서는 StaticMesh'/Game/Sphere.Sphere' 일 것입니다. 에셋의 타입 이름(예: StaticMesh)을 레퍼런스 경로에서 제거했는지 확인하세요.
-
FPSProjectile.h는 다음과 같아야 합니다.FPSProjectile.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 이 액터 프로퍼티의 디폴트값 설정 AFPSProjectile(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; public: // 프레임마다 호출 virtual void Tick(float DeltaTime) override; // 스피어 콜리전 컴포넌트 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 발사체 이동 컴포넌트 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void FireInDirection(const FVector& ShootDirection); // 발사체 메시 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent; }; -
FPSProjectile.cpp는 다음과 같아야 합니다.FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSProjectile.h" // 디폴트값 설정 AFPSProjectile::AFPSProjectile() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; } if (!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; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } } } // 게임 시작 또는 스폰 시 호출 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 프레임마다 호출 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; } -
Visual Studio 에서
FPSProjectile.h및FPSProjectile.cpp를 저장합니다. -
솔루션 탐색기 로 이동하여 FPSProject 를 선택합니다.
-
FPSProject 를 우클릭하고 빌드 를 선택하여 프로젝트를 컴파일합니다.
발사체의 머티리얼 추가하기
-
Visual Studio 에서
FPSProjectile.h로 이동하여 다음 코드를FPSProjectile.h의public액세스 지정자 아래에 추가합니다.FPSProjectile.h
// 발사체 머티리얼 UPROPERTY(VisibleDefaultsOnly, Category = Movement) UMaterialInstanceDynamic* ProjectileMaterialInstance; -
FPSProjectile.cpp로 이동하여 다음 코드를if (!ProjectileMeshComponent)생성자 하단에 추가합니다.FPSProjectile.cpp
static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("[ADD MATERIAL ASSET REFERENCE]")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); -
언리얼 엔진 을 열고 콘텐츠 브라우저 의 콘텐츠 폴더로 이동합니다. 콘텐츠 브라우저 의 파일창을 우클릭하고 머티리얼(Material) 을 선택합니다.
-
새 머티리얼 이름을 SphereMaterial 로 지정합니다.
-
새 머티리얼의 노드 그래프 프로퍼티를 다음과 비슷하게 구성합니다.
- 베이스 컬러(Base Color): Base Color 핀에서 드래그한 후 표시된 창에서 Constant3Vector 노드를 찾아 선택하고 (R:1; G:0; B:0) 으로 설정합니다.
- 스페큘러(Specular): Specular 핀에서 드래그한 후 표시된 창에서 Constant 노드를 찾아 선택하고 값(Value) 을 0.5 로 설정합니다.
- 이미시브 컬러(Emissive Color): Emissive Color 에서 드래그한 후 표시된 창에서 Constant 노드를 찾아 선택하고 값(Value) 을 0.05 로 설정합니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
이 단계에서는 기본 머티리얼 에셋을 생성합니다. 복잡한 머티리얼을 제작하는 방법을 알아보려면 머티리얼 사용 및 제작 방법을 읽어 보시기 바랍니다.
-
새 머티리얼의 노드 그래프를 구성한 후 저장 을 클릭하고 콘텐츠 브라우저 로 이동합니다.
-
스피어(Sphere) 머티리얼을 우클릭하고 레퍼런스 복사 를 선택합니다.
-
FPSProjectile.cpp의ProjectileMeshComponent코드로 돌아가[ADD MATERIAL ASSET REFERENCE]를 복사한 레퍼런스로 대체합니다. 코드가 다음과 비슷해야 합니다.FPSProjectile.cpp
static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent);에셋 레퍼런스 경로는 콘텐츠 브라우저에 스피어 머티리얼을 저장한 위치에 따라 다를 수 있습니다. 또한 복사한 에셋 레퍼런스를 붙여 넣을 경우 에셋의 레퍼런스 경로 앞에 에셋의 타입 이름이 포함되어 있습니다. 여기서는 Material'/Game/Sphere.Sphere' 일 것입니다. 에셋의 타입 이름(예: Material)을 레퍼런스 경로에서 제거했는지 확인하세요.
-
FPSProjectile.h는 다음과 같아야 합니다.FPSProjectile.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 이 액터 프로퍼티의 디폴트값 설정 AFPSProjectile(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; public: // 프레임마다 호출 virtual void Tick(float DeltaTime) override; // 스피어 콜리전 컴포넌트 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 발사체 이동 컴포넌트 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void FireInDirection(const FVector& ShootDirection); // 발사체 메시 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent; // 발사체 머티리얼 UPROPERTY(VisibleDefaultsOnly, Category = Movement) UMaterialInstanceDynamic* ProjectileMaterialInstance; }; -
FPSProjectile.cpp는 다음과 같아야 합니다.FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSProjectile.h" // 디폴트값 설정 AFPSProjectile::AFPSProjectile() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; } if (!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; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); } } // 게임 시작 또는 스폰 시 호출 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 프레임마다 호출 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; } -
UE 를 열고 콘텐츠 브라우저 의 Blueprints 폴더로 이동하여 BP_FPSCharacter 파일을 엽니다.
-
필요한 경우 풀 블루프린트 에디터 를 열고 컴포넌트(Components) 패널로 이동하여 BP_FPSCharacter (Self) 컴포넌트를 선택합니다.
-
열린 블루프린트 에디터 에서 디테일 패널로 이동합니다.
-
발사체(Projectile) 섹션을 찾은 후 발사체 클래스(Projectile Class) 옆의 드롭다운에서 FPSProjectile 을 선택합니다.
열린 드롭다운 메뉴에서 FPSProjectile 을 찾을 수 없는 경우 언리얼 엔진 을 다시 실행하세요.
-
컴파일(Compile) 과 저장(Save) 버튼을 클릭합니다.
-
Visual Studio 를 열고 Solution Explorer 로 이동하여 FPSProject 를 선택합니다.
-
FPSProject 를 우클릭하고 빌드 를 선택하여 프로젝트를 컴파일합니다.
-
PIE 모드에서 게임을 실행하여 씬에서 스태틱 메시 및 머티리얼이 스폰되는지 확인합니다.
발사체를 발사하는 동안 아웃라이너(Outliner) 에서 발사체의 수가 계속해서 증가하는 것을 관측할 수 있을 것입니다. 이는 발사체에 정의된 수명이 없기 때문입니다.
다음 튜토리얼 섹션에서는 발사체의 초기 수명을 정의하는 방법을 보여줍니다.
3.3 - 발사체 콜리전 및 수명 구성하기
현재 발사체는 다음과 같은 상태입니다.
- 영구 지속됨(씬 아웃라이너에서 사라지지 않음)
- 월드의 다른 오브젝트와 충돌하지 않음
이 단계에서는 발사체 콜리전 및 수명을 구성합니다.
발사체의 수명 제한하기
-
Visual Studio 를 열고 Solution Explorer 로 이동합니다.
-
솔루션 탐색기 에서
FPSProjectile.cpp클래스 구현 파일을 엽니다. -
다음 코드를
FPSProjectile.cpp의AFPSProjectile::AFPSProjectile()생성자에 추가하여 발사체의 수명을 설정합니다.FPSProjectile.cpp
// 3초 후 발사체를 제거합니다. InitialLifeSpan = 3.0f; -
FPSProjectile.cpp는 다음과 같아야 합니다.FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSProjectile.h" // 디폴트값 설정 AFPSProjectile::AFPSProjectile() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; } if (!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; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); } // 3초 후 발사체를 제거합니다. InitialLifeSpan = 3.0f; } // 게임 시작 또는 스폰 시 호출 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 프레임마다 호출 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; } -
파일을 저장합니다.
-
솔루션 탐색기 로 이동하여 FPSProject 를 선택합니다.
-
FPSProject 를 우클릭하고 빌드 를 선택하여 프로젝트를 컴파일합니다.
-
발사체가 3초 후 소멸하는 것을 확인하기 위해 PIE 모드에서 게임을 실행합니다.
아웃라이너에서 확인한 대로 스폰되는 모든 발사체가 3초 후 씬에서 사라질 것입니다.
발사체의 콜리전 세팅 편집하기
언리얼 엔진은 여러 개의 프리셋 콜리전 채널을 함께 제공하지만, 게임 프로젝트에서 사용할 수 있는 커스터마이징 가능한 채널 또한 제공합니다.
-
커스텀 콜리전 채널을 생성하려면 언리얼 엔진 으로 이동하여 프로젝트 세팅 을 열고 좌측의 엔진 헤딩을 선택한 후 콜리전(Collision) 을 선택합니다. 그런 다음 우측의 프리셋(Preset) 섹션을 펼칩니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
-
오브젝트 채널(Object Channels) 로 이동하고 새 오브젝트 채널...(New Object Channel...) 을 선택하여 새 콜리전 채널을 생성합니다. 새 콜리전 채널의 이름을 Projectile 로 지정하고, 수락(Accept) 을 클릭하기 전에 디폴트 반응(Default Response) 이 차단(Block) 으로 설정되어 있는지 확인합니다.
-
프리셋 에서 신규(New...) 를 선택하고 새 프로파일 이름을 Projectile 로 지정합니다. 다음 이미지를 참고하여 콜리전 프리셋을 설정하고 수락 을 클릭합니다.
이 콜리전 프로파일은 발사체가 스태틱 액터, 다이내믹 액터, 액터 시뮬레이팅 피직스, 비히클, 디스트럭터블 액터에 의해 차단되도록 지정합니다. 또한 이 콜리전 프로파일은 발사체가 폰에 오버랩되도록 지정합니다.
새 콜리전 채널의 세팅 사용하기
-
Visual Studio 를 열고
FPSProjectile.cpp클래스 구현 파일로 이동합니다. -
FPSProjectile생성자에서if (!CollisionComponent)블록의CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));뒤에 다음 줄을 추가합니다.FPSProjectile.cpp
// 스피어의 콜리전 프로파일 이름을 'Projectile'로 설정합니다. CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile")); -
FPSProjectile.cpp는 다음과 같아야 합니다.FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSProjectile.h" // 디폴트값 설정 AFPSProjectile::AFPSProjectile() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 프로파일 이름을 'Projectile'로 설정합니다. CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile")); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; } if (!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; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); } // 3초 후 발사체를 제거합니다. InitialLifeSpan = 3.0f; } // 게임 시작 또는 스폰 시 호출 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 프레임마다 호출 void AFPSProjectile::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void AFPSProjectile::FireInDirection(const FVector& ShootDirection) { ProjectileMovementComponent->Velocity = ShootDirection * ProjectileMovementComponent->InitialSpeed; } -
파일을 저장합니다.
-
솔루션 탐색기 로 이동하여 FPSProject 를 선택합니다.
-
FPSProject 를 우클릭하고 빌드 를 선택하여 프로젝트를 컴파일합니다.
3.4 - 발사체가 월드와 상호작용하도록 만들기
이제 발사체의 콜리전 인터랙션을 탐지할 수 있으므로, 이 콜리전이 반응하는 방식도 결정할 수 있습니다. 이 단계에서는 FPSProjectile 에 콜리전 이벤트에 반응하는 OnHit 함수를 추가해 보겠습니다.
발사체가 콜리전에 반응하도록 만들기
-
FPSProjectile.h.를 엽니다. -
다음 코드를
FPSProjectile.h의public액세스 지정자 아래에 추가합니다.FPSProjectile.h
// 발사체가 어딘가에 부딪힐 때 호출되는 함수입니다. UFUNCTION() void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit); -
FPSProjectile.h는 다음과 같아야 합니다.FPSProjectile.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/SphereComponent.h" #include "GameFramework/ProjectileMovementComponent.h" #include "FPSProjectile.generated.h" UCLASS() class FPSPROJECT_API AFPSProjectile : public AActor { GENERATED_BODY() public: // 이 액터 프로퍼티의 디폴트값 설정 AFPSProjectile(); protected: // 게임 시작 또는 스폰 시 호출 virtual void BeginPlay() override; public: // 프레임마다 호출 virtual void Tick(float DeltaTime) override; // 스피어 콜리전 컴포넌트 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) USphereComponent* CollisionComponent; // 발사체 이동 컴포넌트 UPROPERTY(VisibleAnywhere, Category = Movement) UProjectileMovementComponent* ProjectileMovementComponent; // 발사 방향으로의 발사체 속도를 초기화하는 함수입니다. void FireInDirection(const FVector& ShootDirection); // 발사체 메시 UPROPERTY(VisibleDefaultsOnly, Category = Projectile) UStaticMeshComponent* ProjectileMeshComponent; // 발사체 머티리얼 UPROPERTY(VisibleDefaultsOnly, Category = Movement) UMaterialInstanceDynamic* ProjectileMaterialInstance; // 발사체가 어딘가에 부딪힐 때 호출되는 함수입니다. UFUNCTION() void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit); }; -
FPSProjectile.cpp를 열고 다음 코드를 추가합니다.FPSProjectile.cpp
// 발사체가 어딘가에 부딪힐 때 호출되는 함수입니다. 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); } Destroy(); } -
FPSProjectile생성자에서if (!CollisionComponent)블록의BodyInstance.SetCollisionProfileName:후에 다음 줄을 추가합니다.FPSProjectile.cpp
// 컴포넌트가 어딘가에 부딪힐 때 호출되는 이벤트입니다. CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit); -
FPSProjectile.cpp는 다음과 같아야 합니다.FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "FPSProjectile.h" // 디폴트값 설정 AFPSProjectile::AFPSProjectile() { // 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다. PrimaryActorTick.bCanEverTick = true; if (!RootComponent) { RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent")); } if (!CollisionComponent) { // 스피어를 단순 콜리전 표현으로 사용합니다. CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent")); // 스피어의 콜리전 프로파일 이름을 'Projectile'로 설정합니다. CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile")); // 컴포넌트가 어딘가에 부딪힐 때 호출되는 이벤트입니다. CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit); // 스피어의 콜리전 반경을 설정합니다. CollisionComponent->InitSphereRadius(15.0f); // 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다. RootComponent = CollisionComponent; } if (!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; ProjectileMovementComponent->ProjectileGravityScale = 0.0f; } if (!ProjectileMeshComponent) { ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent")); static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'")); if (Mesh.Succeeded()) { ProjectileMeshComponent->SetStaticMesh(Mesh.Object); } static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'")); if (Material.Succeeded()) { ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent); } ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance); ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f)); ProjectileMeshComponent->SetupAttachment(RootComponent); } // 3초 후 발사체를 제거합니다. InitialLifeSpan = 3.0f; } // 게임 시작 또는 스폰 시 호출 void AFPSProjectile::BeginPlay() { Super::BeginPlay(); } // 프레임마다 호출 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); } Destroy(); }
발사체 콜리전 테스트하기
-
Visual Studio 에서 파일을 저장합니다.
-
솔루션 탐색기 로 이동하여 FPSProject 를 선택합니다.
-
FPSProject 를 우클릭하고 빌드 를 선택하여 프로젝트를 컴파일합니다.
-
빌드가 완료되면 언리얼 에디터 에서 FPSProject 로 돌아갑니다.
-
아웃라이너 로 이동하여 Floor 스태틱 메시를 선택합니다.
-
선택된 Floor 메시를 복사하여 붙여 넣습니다.
-
종횡비 잠금(스케일 행 옆의 자물쇠 아이콘)이 해제되어 있는지 확인하고, Floor2 의 디테일 패널에 있는 트랜스폼(Transform) 섹션의 옵션을 다음과 같이 설정합니다.
- 위치: (0.0, 0.0, 150)
- 회전: (0.0, 0.0, 0.0)
- 스케일: (0.2, 0.2, 3.0)
-
피직스(Physics) 섹션으로 스크롤하고 피직스 시뮬레이션(Simulate Physics) 박스를 체크합니다.
이미지를 클릭하면 확대됩니다.
-
메인 메뉴 패널에서 파일 을 클릭하고 모두 저장 을 선택하여 임포트된 메시를 저장합니다.
-
레벨 에디터 툴바 에서 플레이 버튼을 클릭합니다.
-
발사체가 큐브와 충돌하는지 확인하려면 왼쪽 마우스 버튼을 클릭하여 발사체를 발사하고 레벨에서 큐브를 이동해 봅니다.
[

축하합니다, 발사체가 완성되었습니다!
-
Shift + Esc 를 누르거나 레벨 에디터 툴바 에서 중지 를 클릭하여 PIE 모드를 종료합니다.
3.5 - 뷰포트에 조준선 추가하기
이 단계에서는 발사체를 조준할 수 있도록 게임에 조준선 HUD 엘리먼트를 추가합니다.
조준선 에셋 임포트하기
시작하기 전에 다음 링크에서 샘플 이미지를 다운로드하고 압축 해제합니다.
-
콘텐츠 브라우저 로 이동하여 콘텐츠 폴더를 엽니다.
-
콘텐츠 브라우저 의 파일 창 내부를 우클릭하여 에셋 임포트 대화창을 엽니다.
-
'/Game...에 임포트' 를 클릭하여 임포트 대화창을 엽니다.
-
이 파일을 다운로드한 위치에서 crosshair.TGA 이미지 파일을 찾아 선택합니다.
-
열기 를 클릭하여 프로젝트에 이미지 파일 임포트를 시작합니다.
-
메인 메뉴 패널에서 파일 을 클릭하여 임포트된 메시를 저장합니다.
새 HUD 클래스 추가하기
-
메인 메뉴 패널의 툴 을 클릭한 후 새 C++ 클래스... 에서 새 부모 클래스를 선택합니다.
-
부모 클래스 선택 메뉴가 나타납니다. HUD 를 부모 클래스로 선택하고 다음 을 클릭합니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
-
새 클래스의 이름을 FPSHUD 로 지정한 다음, 클래스 생성 을 클릭합니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
-
C++ 클래스가 생성될 경우 Visual Studio 가
FPSHUD.h헤더 파일 및FPSHUD.cpp구현 파일이 열린 상태로 자동으로 나타납니다. -
FPSHUD.h클래스 헤더 파일로 이동하여 다음 변수를protected액세스 지정자 아래에 추가합니다.FPSHUD.h
protected: // 화면 중앙에 그려집니다. UPROPERTY(EditDefaultsOnly) UTexture2D* CrosshairTexture; -
다음 함수 선언을
FPSHUD.h의public액세스 지정자 아래에 추가합니다.FPSHUD.h
public: // HUD의 기본 드로 콜입니다. virtual void DrawHUD() override; -
다음 헤더 파일을
FPSHUD.h에 추가합니다.FPSHUD.h
#include "Engine/Canvas.h" -
FPSHUD.h는 다음과 같아야 합니다.FPSHUD.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/HUD.h" #include "Engine/Canvas.h" #include "FPSHUD.generated.h" /** * */ UCLASS() class FPSPROJECT_API AFPSHUD : public AHUD { GENERATED_BODY() protected: // 화면 중앙에 그려집니다. UPROPERTY(EditDefaultsOnly) UTexture2D* CrosshairTexture; public: // HUD의 기본 드로 콜입니다. virtual void DrawHUD() override; }; -
FPSHUD.cpp구현 파일을 열고DrawHUD함수를 추가합니다.FPSHUD.cpp
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); } } -
Visual Studio 에서
FPSHUD.h및FPSHUD.cpp를 저장합니다. -
솔루션 탐색기 로 이동하여 FPSProject 를 선택합니다.
-
FPSProject 를 우클릭하고 빌드 를 선택하여 프로젝트를 컴파일합니다.
CPP HUD 클래스를 블루프린트로 확장하기
이제 CPP HUD 클래스를 블루프린트로 확장하기에 적절한 시점입니다. 복습이 필요하다면 C++와 블루프린트 레퍼런스 페이지로 이동하여 C++ 클래스를 블루프린트로 확장하는 방법을 자세히 확인해 보세요.
-
FPSHUD클래스를 우클릭하여 C++ 클래스 액션(C++ Class Actions) 을 엽니다. -
FPSHUD 기반 블루프린트 클래스 생성(Create Blueprint class based on FPSHUD) 을 클릭하여 블루프린트 클래스 추가(Add Blueprint Class) 대화 메뉴를 엽니다.
-
블루프린트 클래스 생성(Create Blueprint Class)버튼을 클릭하기 전에 새 블루프린트 클래스의 이름을 BP_FPSHUD 로 지정하고 Blueprints 폴더를 선택합니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
-
지금 시점에는 Blueprints 폴더 내부에 새로 생성된 BP_FPSHUD 블루프린트 클래스가 있어야 합니다.
-
블루프린트 에디터 를 닫기 전에 BP_FPSHUD 블루프린트를 저장하세요.
디폴트 HUD 클래스 설정하기
-
메인 메뉴 패널에서 편집 을 클릭하고 프로젝트 세팅 을 선택합니다.
-
프로젝트 세팅 탭 좌측의 프로젝트(Project) 헤딩 아래에서 맵 & 모드(Maps & Modes) 를 클릭합니다.
-
디폴트 HUD(Default HUD) 드롭다운 메뉴에서 BP_FPSHUD 를 선택합니다.
이미지를 클릭하면 최대 크기로 볼 수 있습니다.
-
프로젝트 세팅 메뉴를 닫습니다.
-
다시 블루프린트 에디터 에서 BP_FPSHUD 를 엽니다.
-
이제 블루프린트 에디터 의 FPSHUD 섹션에 위치한 드롭다운 메뉴를 클릭하여 조준선 텍스처를 선택합니다.
-
마지막으로, 블루프린트 에디터 를 닫기 전에 BP_FPSHUD 블루프린트를 컴파일 하고 저장 합니다.
HUD 검증하기
-
레벨 에디터 툴바 에서 플레이 버튼을 클릭합니다. 이제 새로 추가된 조준선으로 발사체를 조준할 수 있습니다.
(convert:false) -
Shift + Esc 를 누르거나 레벨 에디터 툴바 에서 중지 를 클릭하여 PIE 모드를 종료합니다.
완성된 섹션 코드
FPSProjectile.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "FPSProjectile.generated.h"
UCLASS()
class FPSPROJECT_API AFPSProjectile : public AActor
{
GENERATED_BODY()
public:
// 이 액터 프로퍼티의 디폴트값 설정
AFPSProjectile();
protected:
// 게임 시작 또는 스폰 시 호출
virtual void BeginPlay() override;
public:
// 프레임마다 호출
virtual void Tick(float DeltaTime) override;
// 스피어 콜리전 컴포넌트
UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
USphereComponent* CollisionComponent;
// 발사체 이동 컴포넌트
UPROPERTY(VisibleAnywhere, Category = Movement)
UProjectileMovementComponent* ProjectileMovementComponent;
// 발사 방향으로의 발사체 속도를 초기화하는 함수입니다.
void FireInDirection(const FVector& ShootDirection);
// 발사체 메시
UPROPERTY(VisibleDefaultsOnly, Category = Projectile)
UStaticMeshComponent* ProjectileMeshComponent;
// 발사체 머티리얼
UPROPERTY(VisibleDefaultsOnly, Category = Movement)
UMaterialInstanceDynamic* ProjectileMaterialInstance;
// 발사체가 어딘가에 부딪힐 때 호출되는 함수입니다.
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);
};
FPSProjectile.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "FPSProjectile.h"
// 디폴트값 설정
AFPSProjectile::AFPSProjectile()
{
// 이 액터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = true;
if (!RootComponent)
{
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("ProjectileSceneComponent"));
}
if (!CollisionComponent)
{
// 스피어를 단순 콜리전 표현으로 사용합니다.
CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent"));
// 스피어의 콜리전 프로파일 이름을 'Projectile'로 설정합니다.
CollisionComponent->BodyInstance.SetCollisionProfileName(TEXT("Projectile"));
// 컴포넌트가 어딘가에 부딪힐 때 호출되는 이벤트입니다.
CollisionComponent->OnComponentHit.AddDynamic(this, &AFPSProjectile::OnHit);
// 스피어의 콜리전 반경을 설정합니다.
CollisionComponent->InitSphereRadius(15.0f);
// 루트 컴포넌트가 콜리전 컴포넌트가 되도록 설정합니다.
RootComponent = CollisionComponent;
}
if (!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;
ProjectileMovementComponent->ProjectileGravityScale = 0.0f;
}
if (!ProjectileMeshComponent)
{
ProjectileMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMeshComponent"));
static ConstructorHelpers::FObjectFinder<UStaticMesh>Mesh(TEXT("'/Game/Sphere.Sphere'"));
if (Mesh.Succeeded())
{
ProjectileMeshComponent->SetStaticMesh(Mesh.Object);
}
static ConstructorHelpers::FObjectFinder<UMaterial>Material(TEXT("'/Game/SphereMaterial.SphereMaterial'"));
if (Material.Succeeded())
{
ProjectileMaterialInstance = UMaterialInstanceDynamic::Create(Material.Object, ProjectileMeshComponent);
}
ProjectileMeshComponent->SetMaterial(0, ProjectileMaterialInstance);
ProjectileMeshComponent->SetRelativeScale3D(FVector(0.09f, 0.09f, 0.09f));
ProjectileMeshComponent->SetupAttachment(RootComponent);
}
// 3초 후 발사체를 제거합니다.
InitialLifeSpan = 3.0f;
}
// 게임 시작 또는 스폰 시 호출
void AFPSProjectile::BeginPlay()
{
Super::BeginPlay();
}
// 프레임마다 호출
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 != nullptr && OtherActor != this && OtherComponent != nullptr && OtherComponent->IsSimulatingPhysics())
{
OtherComponent->AddImpulseAtLocation(ProjectileMovementComponent->Velocity * 100.0f, Hit.ImpactPoint);
}
Destroy();
}
FPSCharacter.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "FPSProjectile.h"
#include "FPSCharacter.generated.h"
UCLASS()
class FPSPROJECT_API AFPSCharacter : public ACharacter
{
GENERATED_BODY()
public:
//이 캐릭터의 프로퍼티에 적용되는 디폴트값 설정
AFPSCharacter();
protected:
// 게임 시작 또는 스폰 시 호출
virtual void BeginPlay() override;
// 스폰할 발사체 클래스입니다.
UPROPERTY(EditAnywhere, Category = Projectile)
TSubclassOf<class AFPSProjectile> ProjectileClass;
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();
// 카메라 위치로부터의 총구 오프셋입니다.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
FVector MuzzleOffset;
};
FPSCharacter.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "FPSCharacter.h"
// 디폴트값 설정
AFPSCharacter::AFPSCharacter()
{
// 이 캐릭터가 프레임마다 Tick()을 호출하도록 설정합니다. 이 설정이 필요 없는 경우 비활성화하면 퍼포먼스가 향상됩니다.
PrimaryActorTick.bCanEverTick = true;
// 일인칭 카메라 컴포넌트를 생성합니다.
FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
check(FPSCameraComponent != nullptr);
// 캡슐 컴포넌트에 카메라 컴포넌트를 어태치합니다.
FPSCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent()));
// 카메라가 눈 약간 위에 위치하도록 합니다.
FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
// 폰이 카메라 회전을 제어하도록 합니다.
FPSCameraComponent->bUsePawnControlRotation = true;
// 소유 플레이어의 일인칭 메시 컴포넌트를 생성합니다.
FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
check(FPSMesh != nullptr);
// 소유 플레이어만 이 메시를 볼 수 있습니다.
FPSMesh->SetOnlyOwnerSee(true);
//FPS 메시를 FPS 카메라에 어태치합니다.
FPSMesh->SetupAttachment(FPSCameraComponent);
// 일부 인바이런먼트 섀도를 비활성화하여 단일 메시 같은 느낌을 보존합니다.
FPSMesh->bCastDynamicShadow = false;
FPSMesh->CastShadow = false;
// 소유 플레이어가 일반(삼인칭) 바디 메시를 볼 수 없습니다.
GetMesh()->SetOwnerNoSee(true);
}
// 게임 시작 또는 스폰 시 호출
void AFPSCharacter::BeginPlay()
{
Super::BeginPlay();
check(GEngine != nullptr);
// 디버그 메시지를 5초간 표시합니다.
// -1 '키' 값 실행인자가 메시지가 업데이트되거나 새로고침되지 않도록 방지합니다.
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("We are using FPSCharacter."));
}
// 프레임마다 호출
void AFPSCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
//함수 기능을 입력에 바인딩하기 위해 호출
void AFPSCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 'movement' 바인딩을 구성합니다.
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);
}
void AFPSCharacter::StartJump()
{
bPressedJump = true;
}
void AFPSCharacter::StopJump()
{
bPressedJump = false;
}
void AFPSCharacter::Fire()
{
// 발사체 발사를 시도합니다.
if (ProjectileClass)
{
// 카메라 트랜스폼을 구합니다.
FVector CameraLocation;
FRotator CameraRotation;
GetActorEyesViewPoint(CameraLocation, CameraRotation);
// MuzzleOffset이 카메라 살짝 앞에서 발사체를 스폰하도록 설정합니다.
MuzzleOffset.Set(100.0f, 0.0f, 0.0f);
// MuzzleOffset을 카메라 스페이스에서 월드 스페이스로 변환합니다.
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 = GetInstigator();
// 총구에 발사체를 스폰합니다.
AFPSProjectile* Projectile = World->SpawnActor<AFPSProjectile>(ProjectileClass, MuzzleLocation, MuzzleRotation, SpawnParams);
if (Projectile)
{
// 발사체의 초기 탄도를 설정합니다.
FVector LaunchDirection = MuzzleRotation.Vector();
Projectile->FireInDirection(LaunchDirection);
}
}
}
}
FPSHUD.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "Engine/Canvas.h"
#include "FPSHUD.generated.h"
/**
*
*/
UCLASS()
class FPSPROJECT_API AFPSHUD : public AHUD
{
GENERATED_BODY()
protected:
// 화면 중앙에 그려집니다.
UPROPERTY(EditDefaultsOnly)
UTexture2D* CrosshairTexture;
public:
// HUD의 기본 드로 콜입니다.
virtual void DrawHUD() override;
};
FPSHUD.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#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);
}
}
축하합니다! 다음과 같은 내용을 배웠습니다.
✓ 게임에 발사체 추가
✓ 슈팅 구현
✓ 발사체 콜리전 및 수명 구성
✓ 발사체가 월드와 상호작용하도록 만들기
✓ 뷰포트에 조준선 추가
이제 다음 섹션에서 캐릭터를 애니메이팅하는 방법을 배울 준비가 되었습니다.