시작하기 전에
이전 섹션인 캐릭터에게 아이템 장착에서 다음 목표를 완료했는지 확인합니다.
리스폰되는 픽업 아이템을 생성하고 레벨에 추가했습니다.
캐릭터가 들고 사용하는, 장착 가능한 다트 발사기를 생성했습니다.
기본 발사체
캐릭터가 다트 발사기를 집을 수 있고, 이 발사기를 사용하기 위한 컨트롤 바인딩이 툴에 있지만, 아직은 다트를 제대로 발사하지 못하고 있습니다. 이 섹션에서는 장착한 아이템에서 다트를 발사하는 발사체 로직을 구현합니다.
언리얼 엔진에는 액터에 추가하여 발사체로 전환할 수 있는 발사체 이동(Projectile Movement) 컴포넌트 클래스가 있습니다. 여기에는 발사체 속력과 탄력성, 중력 스케일 조절 등, 유용한 변수가 많이 포함되어 있습니다.
발사체를 구현하는 수학을 처리하려면 몇 가지 사항을 고려해야 합니다.
발사체의 초기 트랜스폼, 속도 및 방향.
발사체를 캐릭터 중앙에서 스폰할지, 아니면 캐릭터에게 장착된 툴에서 스폰할지 여부.
발사체가 다른 오브젝트와 충돌할 때 보여줄 행동.
발사체 베이스 클래스 생성하기
먼저 베이스 발사체 클래스를 생성한 다음, 이 클래스에서 서브클래스를 생성하여 툴에 맞는 고유한 발사체를 생성합니다.
베이스 발사체 클래스를 설정하려면 다음 단계를 따릅니다.
언리얼 에디터에서 툴(Tools) > 새 C++ 클래스(New C++ Class)를 선택합니다. 액터를 부모 클래스로 선택하고 클래스 이름을
FirstPersonProjectile로 지정합니다. 클래스 생성(Create Class)을 클릭합니다.VS에서
FirstPersonProjectile.h로 이동합니다. 파일 상단에서UProjectileMovementComponent와USphereComponent를 모두 포워드 선언합니다.발사체와 다른 오브젝트 간의 콜리전 시뮬레이션에는 스피어 컴포넌트를 사용합니다.
C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "FirstPersonProjectile.generated.h" class UProjectileMovementComponent; class USphereComponent;BlueprintType및Blueprintable지정자를 추가하여 이 클래스를 블루프린트에 노출합니다.C++UCLASS(BlueprintType, Blueprintable) class FIRSTPERSON_API AFirstPersonProjectile : public AActorFirstPersonProjectile.cpp파일을 열고, 파일 상단에"GameFramework/ProjectileMovementComponent.h"에 대한 include 문을 추가하여 발사체 이동 컴포넌트 클래스를 포함합니다.
#include "FirstPersonProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()오브젝트 충돌 시 발사체 행동 구현하기
발사체를 더 사실적으로 만들려면 발사체가 부딪치는 물체에 약간의 힘(충격량)을 가하도록 하면 됩니다. 예를 들어, 피직스가 활성화된 블록을 쏘면 발사체가 지면을 따라 블록을 살짝 밀어낼 것입니다. 그런 다음, 디폴트 수명이 다할 때까지 그냥 두는 대신 충돌 후 발사체를 제거합니다. OnHit() 함수를 생성하여 이 행동을 구현합니다.
발사체 충돌 시 행동을 구현하려면 다음 단계를 따릅니다.
FirstPersonProjectile.h의public섹션에PhysicsForce라는 이름의float프로퍼티를 정의합니다.여기에
EditAnywhere,BlueprintReadOnly및Category = "Projectile | Physics"를 사용하여UPROPERTY()매크로를 지정합니다.이는 발사체가 무언가에 부딪칠 때 전달되는 힘의 양입니다.
C++// The amount of force this projectile imparts on objects it collides with UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Physics") float PhysicsForce = 100.0f;void함수OnHit()을 정의합니다. 이는 액터가 다른 컴포넌트나 액터와 충돌할 때 호출되는 AActor의 함수입니다. 이 함수는 다음 실행인자를 받습니다.HitComp: 부딪힌 컴포넌트입니다.OtherActor: 부딪힌 액터입니다.OtherComp: 충돌을 생성한 컴포넌트(이 경우 발사체의 CollisionComponent)입니다.NormalImpulse: 충돌의 노멀 충격량입니다.Hit: 시간, 거리, 위치 같은 충돌 이벤트에 대한 자세한 데이터가 포함된FHitResult레퍼런스입니다.
C++// Called when the projectile collides with an object UFUNCTION() void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);FirstPersonProjectile.cpp에 헤더 파일에 정의한OnHit()함수를 구현합니다.OnHit()내부의if구문에서 다음을 확인합니다.OtherActor가 null이 아니며 발사체 자체가 아닌지 확인합니다.OtherComp가 null이 아니며 피직스 시뮬레이션 중인지도 확인합니다.
C++void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit) { // Only add impulse and destroy projectile if we hit a physics if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics()) { } }이 코드는 발사체가 발사체 자체가 아닌, 피직스 시뮬레이션에 참여 중인 다른 오브젝트에 부딪쳤는지 확인합니다.
if구문 내에서AddImpulseAtLocation()을 사용하여OtherComp컴포넌트에 충격량을 추가합니다.발사체의 속도와
PhysicsForce를 곱한 값을 이 함수에 전달하고 발사체 액터의 위치에 적용합니다.C++if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics()) { OtherComp->AddImpulseAtLocation(GetVelocity() * PhysicsForce, GetActorLocation()); }AddImpulseAtLocation()는 특정 월드 스페이스 위치에서 시뮬레이션된 피직스 오브젝트에 순간적인 힘(충격량)을 적용하는 언리얼 엔진의 피직스 함수입니다. 폭발로 무언가가 날아가거나, 총알이 오브젝트에 부딪치거나, 문이 홱 하고 젖혀지며 열리는 등의 충격을 시뮬레이션할 때 유용한 함수입니다.마지막으로, 이 발사체가 다른 액터에 부딪쳤으므로
Destroy()를 호출하여 씬에서 발사체를 제거합니다.
완성된 OnHit() 함수는 다음과 같은 모습이어야 합니다.
void AFirstPersonProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
// Only add impulse and destroy projectile if we hit a physics
if ((OtherActor != nullptr) && (OtherActor != this) && (OtherComp != nullptr) && OtherComp->IsSimulatingPhysics())
{
OtherComp->AddImpulseAtLocation(GetVelocity() * PhysicsForce, GetActorLocation());
Destroy();
}
}발사체의 메시, 무브먼트, 콜리전 컴포넌트 추가하기
그런 다음 발사체에 스태틱 메시와 발사체 이동 로직, 콜리전 스피어를 추가하고 발사체의 이동 방식을 정의합니다.
발사체에 이러한 컴포넌트를 추가하려면 다음 단계를 따릅니다.
FirstPersonProjectile.h의public섹션에서TObjectPtr을UStaticMeshComponent(이름은ProjectileMesh)에 선언합니다. 이는 월드에 있는 발사체의 스태틱 메시입니다.여기에
EditAnywhere,BlueprintReadOnly및Category = "Projectile | Mesh"를 사용하여UPROPERTY()매크로를 지정합니다.C++// Mesh of the projectile in the world UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Mesh") TObjectPtr<UStaticMeshComponent> ProjectileMesh;protected섹션에서 다음을 선언합니다.CollisionComponent라는 이름의USphereComponent에 대한TObjectPtr.ProjectileMovement라는 이름의UProjectileMovementComponent에 대한TObjectPtr.
이 둘 모두에
VisibleAnywhere,BlueprintReadOnly, 및Category = "Projectile | Components"를 사용하여UPROPERTY()매크로를 지정합니다.C++// Sphere collision component UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Projectile | Components") TObjectPtr<USphereComponent> CollisionComponent; // Projectile movement component UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Projectile | Components") TObjectPtr<UProjectileMovementComponent> ProjectileMovement;ProjectileMovementComponent는 무브먼트 로직을 처리합니다. 이는 속도, 중력 및 기타 변수를 기반으로 부모 액터가 이동해야 할 곳을 계산합니다. 그런 다음,
틱중에 발사체에 그 무브먼트를 적용합니다.FirstPersonProjectile.cpp의AFirstPersonProjectile()생성자 함수에서 발사체의 메시, 콜리전 및 발사체 이동 컴포넌트에 대한 디폴트 서브오브젝트를 생성합니다. 그런 다음, 발사체 메시를 콜리전 컴포넌트에 붙입니다.C++// Sets default values AFirstPersonProjectile::AFirstPersonProjectile() { // 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("CollisionComponent")); check(CollisionComponent != nullptr);InitSphereRadius()를 호출하여 반경5.0f로CollisionComponent를 초기화합니다.BodyInstance.SetCollisionProfileName()을사용하여 콜리전 컴포넌트의 콜리전 프로파일 이름을"Projectile"로 설정합니다.언리얼 에디터에서 콜리전 프로파일은 프로젝트 세팅(Project Settings) > 엔진(Engine) > 콜리전(Collision) 아래에 저장되며, 코드에서 사용할 커스텀 콜리전 프로파일을 최대 18개까지 정의할 수 있습니다. 이 'Projectile' 콜리전 프로파일의 디폴트 행동은 블록(Block)으로, 충돌하는 모든 오브젝트에 콜리전을 생성합니다.
C++CollisionComponent->InitSphereRadius(5.0f); // Set the collision profile to the "Projectile" collision preset CollisionComponent->BodyInstance.SetCollisionProfileName("Projectile");앞서 발사체가 무언가에 부딪혔을 때 활성화되도록
OnHit()함수를 정의했지만, 콜리전이 발생했을 때 그걸 알릴 방법이 필요합니다. 충돌을 알리려면AddDynamic()매크로를 사용하여CollisionComponent의OnComponentHitEvent에 함수를 구독시키면 됩니다. 이 매크로에OnHit()함수를 전달합니다.C++// Set up a notification for when this component hits something blocking CollisionComponent->OnComponentHit.AddDynamic(this, &AFirstPersonProjectile::OnHit);CollisionComponent를 발사체의RootComponent로 설정하고, 무브먼트 컴포넌트를 트래킹할UpdatedComponent로 설정합니다.C++// Set as root component RootComponent = CollisionComponent; ProjectileMovement->UpdatedComponent = CollisionComponent;다음 값으로
ProjectileMovement컴포넌트를 초기화합니다.InitialSpeed: 발사체가 스폰될 때 발사체의 초기 속력입니다. 이 속도를3000.0f로 설정합니다.MaxSpeed: 발사체의 최대 속력입니다. 이 속도를3000.0f로 설정합니다.bRotationFollowVelocity: 오브젝트가 속도 방향에 맞춰 회전할지 여부입니다. 예를 들어, 종이비행기가 상승하고 하강할 때 위아래로 기울어지는 식의 행동을 말합니다. 이 값을true로 설정합니다.bShouldBounce: 발사체가 장애물에 맞고 튕길지 여부입니다. 이 값을true로 설정합니다.
C++ProjectileMovement->UpdatedComponent = CollisionComponent; ProjectileMovement->InitialSpeed = 3000.f; ProjectileMovement->MaxSpeed = 3000.f; ProjectileMovement->bRotationFollowsVelocity = true; ProjectileMovement->bShouldBounce = true;
발사체의 수명 설정하기
기본적으로는 발사체가 발사되고 몇 초 후에 사라지게 설정합니다. 하지만 에디터에서 발사체 블루프린트를 파생한 후에는 이 디폴트 수명을 변경하거나 제거하여 레벨을 스티로폼 다트로 채우는 실험을 해볼 수 있습니다!
발사체가 일정한 시간(초) 후에 사라지게 하려면 다음 단계를 따릅니다.
FirstPersonProjectile.h의public섹션에서ProjectileLifespan이라는 float를 선언합니다.여기에
EditAnywhere,BlueprintReadOnly및Category = "Projectile | Lifespan"을 사용하여UPROPERTY()매크로를 지정합니다.이는 발사체의 수명(초)입니다.
C++// Despawn after 5 seconds by default UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Projectile | Lifespan") float ProjectileLifespan = 5.0f;FirstPersonProjectile.cpp의서AFirstPersonProjectile()생성자 함수의 마지막에 발사체의InitialLifeSpan을ProjectileLifespan으로 설정하여 5초 후에 사라지도록 합니다.C++// Disappear after 5.0 seconds by default. InitialLifeSpan = ProjectileLifespan;InitialLifeSpan은 AActor에서 상속된 프로퍼티입니다. 이는 액터가 죽기까지 생존하는 시간을 설정하는 float입니다. 값이0이면 게임이 중단될 때까지 액터가 살아있게 됩니다.
완성된 FirstPersonProjectile.h는 다음과 같은 모습이어야 합니다.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
완성된 생성자 함수 AFirstPersonProjectile()은 다음과 같은 모습이어야 합니다.
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()
{
// 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("CollisionComponent"));
check(CollisionComponent != nullptr);
캐릭터 카메라 방향 구하기
발사체는 다트 발사기 자체에서 스폰되어야 하므로 다트 발사기의 위치와 발사기가 가리키는 방향을 파악하기 위해 몇 가지 계산을 해야 합니다. 발사기는 플레이어 캐릭터에 붙어 있으므로 캐릭터의 위치와 캐릭터가 보는 방향에 따라 이 값이 변경됩니다.
1인칭 캐릭터에는 다트를 발사하는 데 필요한 위치 정보가 일부 포함되어 있으므로, 먼저 캐릭터 클래스를 수정하여 라인 트레이스로 해당 정보를 캡처하고 결과를 반환합니다.
트레이스를 사용하여 캐릭터에서 필요한 정보를 구하려면 다음 단계를 따릅니다.
VS에서 캐릭터의
.h및.cpp파일을 엽니다..h파일의public섹션에서FVector를 반환하는GetCameraTargetLocation()이라는 이름의 새 함수를 선언합니다. 이 함수는 캐릭터가 바라보는 월드의 위치를 반환합니다.C++// Returns the location in the world the character is looking at UFUNCTION() FVector GetCameraTargetLocation();캐릭터의
.cpp파일에서GetCameraTargetLocation()함수를 구현합니다. 먼저 반환할TargetPosition이라는 이름의 FVector를 선언합니다.GetWorld()를 호출하여UWorld에 대한 포인터를 생성합니다.C++// The target position to return FVector TargetPosition; UWorld* const World = GetWorld();if구문을 추가하여World가 null이 아닌지 확인합니다.if구문에서Hit라는 이름의FHitResult를 선언합니다.C++if (World != nullptr) { // The result of the line trace FHitResult Hit;FHitResult는 부딪힌 액터나 컴포넌트, 부딪친 위치 같은 콜리전 쿼리 결과에 대한 정보를 저장하는 UE의 구조체입니다.캐릭터가 바라보는 지점을 찾으려면 캐릭터가 바라보는 방향의 벡터를 따라 멀리 떨어진 지점까지 라인 트레이스를 시뮬레이션합니다. 라인 트레이스가 오브젝트와 충돌하면 월드에서 캐릭터가 바라보는 방향을 알게 됩니다.
TraceStart와 TraceEnd라는 두 개의 const FVector 값을 선언합니다.
TraceStart를FirstPersonCameraComponent의 위치로 설정합니다.TraceEnd를TraceStart에 카메라 컴포넌트의 전방 벡터에 아주 큰 숫자를 곱한 결과를 더한 값으로 설정합니다. 이렇게 하면 캐릭터가 하늘을 쳐다보지 않는 한, 트레이스가 월드의 대부분의 물체와 충돌할 수 있을 만큼 충분히 멀리 뻗어 나갑니다. (캐릭터가 하늘을 보고 있는 경우,TraceEnd는 라인 트레이스의 종단점이 됩니다)C++// Simulate a line trace from the character along the vector they're looking down const FVector TraceStart = FirstPersonCameraComponent->GetComponentLocation(); const FVector TraceEnd = TraceStart + FirstPersonCameraComponent->GetForwardVector() * 10000.0;
UWorld에서LineTraceSingleByChannel()을 호출하여 트레이스를 시뮬레이션합니다. 호출한 함수에Hit,TraceStart,TraceEnd및ECollisionChannel::ECC_Visibility를 전달합니다.이렇게 하면
TraceStart에서TraceEnd까지 보이는 오브젝트와 충돌할 때까지 라인 트레이스를 시뮬레이션하고 그 트레이스 결과를Hit에 저장합니다.ECollisionChannel::ECC_Visibility는 트레이싱에 사용할 채널이며, 이 채널은 트레이스가 충돌을 시도할 오브젝트 타입을 정의합니다.ECC_Visibility는 시야 카메라 검사에 사용합니다.C++World->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECollisionChannel::ECC_Visibility);이제
Hit값에 충돌 위치 및 노멀 같은 충돌 결과에 대한 정보가 포함되었습니다. 또한, 그 충돌이 오브젝트 콜리전의 결과인지도 파악했습니다. 충돌 위치(또는 트레이스 라인의 종단점)는 반환할 카메라 타깃 위치입니다.삼항 연산자를 사용하여, 충돌이 블로킹 히트였다면
TargetPosition을Hit.ImpactPoint로 설정하고, 그렇지 않다면Hit.TraceEnd로 설정합니다. 그런 다음,TargetPosition을 반환합니다.C++TargetPosition = Hit.bBlockingHit ? Hit.ImpactPoint : Hit.TraceEnd; } return TargetPosition;
완성된 GetCameraTargetLocation() 함수는 다음과 같은 모습이어야 합니다.
FVector AADventureCharacter::GetCameraTargetLocation()
{
// The target position to return
FVector TargetPosition;
UWorld* const World = GetWorld();
if (World != nullptr)
{
// The result of the line trace
FHitResult Hit;
DartLauncher::Use()로 발사체 스폰하기
이제 캐릭터가 어디를 보고 있는지 알 방법을 알았으니, 다트 발사기의 Use() 함수에서 나머지 발사체 로직을 구현할 수 있습니다. 발사체를 발사할 위치와 방향을 구한 다음 발사체를 스폰합니다.
발사체가 스폰될 위치와 회전을 구하려면 다음 단계를 따릅니다.
DartLauncher.h에서 파일 상단에AFirstPersonProjectile에 대한 포워드 선언을 추가합니다.public섹션에서 ProjectileClass라는 이름의TSubclassOf<AFirstPersonProjectile>프로퍼티를 선언합니다. 이게 다트 발사기가 스폰하는 발사체가 됩니다. 여기에EditAnywhere및Category = Projectile을 사용하여UPROPERTY()매크로를 지정합니다.이제
DartLauncher.h는 다음과 같아야 합니다.C++// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "AdventureGame/EquippableToolBase.h" #include "AdventureGame/FirstPersonProjectile.h" #include "DartLauncher.generated.h" class AFirstPersonProjectile;DartLauncher.cpp에서"Kismet/KismetMathLibrary.h"에 대한 include 구문을 추가합니다. 발사체 계산은 복잡할 수 있으며, 이 파일에는 발사체를 발사할 때 사용할 수 있는 몇 가지 편리한 함수가 포함되어 있습니다.C++#include "DartLauncher.h" #include "Kismet/KismetMathLibrary.h" #include "AdventureGame/AdventureCharacter.h"DartLauncher의
Use()함수 구현을 시작합니다.GetWorld()를 호출하여UWorld를 얻습니다.if구문을 추가하여World와ProjectileClass가 null이 아닌지 확인합니다.if구문에서OwningCharacter->GetCameraTargetLocation()을 호출하여 캐릭터가 보고 있는 위치를 구합니다.
C++void ADartLauncher::Use() { UWorld* const World = GetWorld(); if (World != nullptr && ProjectileClass != nullptr) { FVector TargetPosition = OwningCharacter->GetCameraTargetLocation(); } }캐릭터가 들고 있는 툴에서 발사체를 스폰하되, 오브젝트 중앙에서 직접 스폰하고 싶지 않을 수도 있습니다. 다트 발사기의
SKM_Pistol메시에는 다트의 정확한 스폰 지점을 설정하는 데 사용할 수 있는 'Muzzle' 소켓이 있습니다.SocketLocation이라는 이름의 새FVector를 선언하고ToolMeshComponent에서GetSocketLocation("Muzzle")을 호출한 결과로 설정합니다.C++// Get the correct socket to spawn the projectile from FVector SocketLocation = ToolMeshComponent->GetSocketLocation("Muzzle");다음으로
SpawnRotation이라는 이름의FRotator를 선언합니다. 이는 발사체가 스폰될 때의 발사체 회전(피치, 요, 롤 값)입니다.이 값을 키즈멧 수학 라이브러리에서
FindLookAtRotation()을 호출하고 플레이어 캐릭터에서 얻은SocketLocation및TargetPosition을 전달한 결과로 설정합니다.C++FRotator SpawnRotation = UKismetMathLibrary::FindLookAtRotation(SocketLocation, TargetPosition);FindLookAtRotation은TargetPosition쪽을 향하기 위해SocketLocation에서 필요한 회전을 계산하고 반환합니다.SpawnLocation이라는 이름의FVector를 선언하고,SocketLocation에SpawnRotation의 전방 벡터와10.0을 곱한 결과를 더한 값으로 설정합니다.총구 소켓이 정확히 발사기의 정면에 있는 것은 아니므로, 발사체를 올바른 위치에서 발사하려면 벡터에 오프셋을 곱해야 합니다.
C++FVector SpawnLocation = SocketLocation + UKismetMathLibrary::GetForwardVector(SpawnRotation) * 10.0;
이제 위치와 회전을 구했으니 발사체를 스폰할 준비가 되었습니다.
발사체를 스폰하려면 다음 단계를 따릅니다.
Use()함수에서 계속ActorSpawnParams라는 이름의FActorSpawnParameters를 선언합니다. 이 클래스에는 액터를 스폰하는 위치와 방법에 대한 정보가 포함되어 있습니다.C++//Set Spawn Collision Handling Override FActorSpawnParameters ActorSpawnParams;ActorSpawnParams의SpawnCollisionHandlingOverride값을ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding으로 설정합니다.다른 액터와 충돌하지 않는 위치(예: 벽 안쪽)에서 발사체를 스폰할 위치를 찾고, 적절한 위치를 찾지 못하면 발사체를 스폰하지 않습니다.
C++ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;SpawnActor()를 사용하여 다트 발사기의 총구에 발사체를 스폰합니다. 이때ProjectileClass,SpawnLocation,SpawnRotation및ActorSpawnParams를 전달합니다.C++// Spawn the projectile at the muzzle World->SpawnActor<AFirstPersonProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams);
완성된 Use() 함수는 다음과 같아야 합니다.
void ADartLauncher::Use()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Using the dart launcher!"));
UWorld* const World = GetWorld();
if (World != nullptr && ProjectileClass != nullptr)
{
FVector TargetPosition = OwningCharacter->GetCameraTargetLocation();
// Get the correct socket to spawn the projectile from
폼 다트 클래스 및 블루프린트 파생하기
이제 모든 스폰 로직이 완료되었으니 진짜 발사체를 빌드할 차례입니다! 다트 발사기 클래스를 실행하려면 AFirstPersonProjectile의 서브클래스가 필요하므로, 레벨에서 사용할 서브클래스를 코드에서 빌드해야 합니다.
게임 내에서 사용할 폼 다트 발사체 클래스를 파생하려면 다음 단계를 따릅니다.
언리얼 에디터에서 툴(Tools) > 새 C++ 클래스(New C++ Class)로 이동합니다.
모든 클래스(All Classes)로 이동한 다음, First Person Projectile을 검색하여 부모 클래스로 선택하고 클래스 이름을
FoamDart로 지정합니다. 클래스 생성(Create Class)을 클릭합니다.VS에서 이 파일들을 그대로 두고, 코드를 저장한 후 컴파일합니다.
이 튜토리얼에서는 FirstPersonProjectile에서 정의한 것 이상의 커스텀 발사체 코드는 구현하지는 않지만, 프로젝트의 필요에 맞게 FoamDart 클래스를 직접 수정할 수 있습니다. 예를 들어, 다트 발사체가 사라지거나 튕기지 않고 오브젝트에 달라붙게 만들 수 있습니다.
폼 다트 블루프린트를 생성하려면 다음 단계를 따릅니다.
언리얼 에디터에서 FoamDart 기반 블루프린트 클래스를 생성하고 이름을
BP_FoamDart로 지정합니다. 이 클래스를FirstPerson/Blueprints/Tools폴더에 저장합니다.블루프린트를 연 상태에서 발사체 메시(Projectile Mesh) 컴포넌트를 선택하고 스태틱 메시(Static Mesh)를 SM_FoamBullet으로 설정합니다.
블루프린트를 저장하고 컴파일합니다.
콘텐츠 브라우저에서
BP_DartLauncher를 엽니다.디테일(Details) 패널의 프로젝타일(Projectile) 섹션에서 발사체 클래스(Projectile Class)를
BP_FoamDart로 설정한 다음, 블루프린트를 저장하고 컴파일합니다.목록에
BP_FoamDart가 보이지 않으면 콘텐츠 브라우저로 이동하여BP_FoamDart를 선택한 다음, 프로젝타일 클래스 프로퍼티로 돌아가 콘텐츠 브라우저에서 선택된 에셋 사용(Use Selected Asset from Content Browser)을 클릭합니다.
드디어 대망의 작품을 공개할 때가 되었습니다. 에셋을 저장하고 플레이(Play)를 클릭합니다. 게임이 시작되면 다트 발사기로 달려가 다트를 집어들 수 있습니다. 왼쪽 마우스 버튼을 누르면 다트 발사기의 총구에서 발사체가 스폰되고 레벨 여기저기에 튕깁니다! 이러한 발사체는 5초 후에 사라지고 충돌하는 오브젝트(플레이어 포함!)에 작은 피직스를 가합니다.
다음 순서
축하합니다! 1인칭 프로그래머 트랙 튜토리얼을 완료하고 그 과정에서 많은 것을 배웠습니다!
커스텀 캐릭터와 무브먼트를 구현하고 픽업과 데이터 에셋을 생성했으며, 자체 발사체가 있는 장착 가능한 툴까지 만들었습니다. 이 프로젝트를 자신만의 프로젝트로 바꾸는 데 필요한 모든 지식을 배웠습니다.
다음은 몇 가지 제안입니다.
다양한 타입의 아이템으로 플레이어의 인벤토리를 확장할 수 있나요? 아이템을 중첩해보는 건 어떨까요?
픽업과 발사체를 결합하여 집을 수 있는 탄약을 만들 수 있나요? 다트 발사기에 이 탄약 시스템을 구현하는 것은 어떨까요?
소모품에 관한 아이디어를 장착할 수 있는 소모품으로 발전시킬 수 있나요? 플레이어가 들고 있는 체력 팩이나 플레이어가 집어서 던질 수 있는 공은 어떨까요?
완성된 코드
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FirstPersonProjectile.generated.h"
class UProjectileMovementComponent;
class USphereComponent;
// Fill out your copyright notice in the Description page of Project Settings.
#include "FirstPersonProjectile.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
// Sets default values
AFirstPersonProjectile::AFirstPersonProjectile()
{
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdventureCharacter.h"
#include "EquippableToolBase.h"
#include "EquippableToolDefinition.h"
#include "ItemDefinition.h"
#include "InventoryComponent.h"
// Sets default values
AAdventureCharacter::AAdventureCharacter()
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "AdventureGame/EquippableToolBase.h"
#include "AdventureGame/FirstPersonProjectile.h"
#include "DartLauncher.generated.h"
class AFirstPersonProjectile;
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DartLauncher.h"
#include "Kismet/KismetMathLibrary.h"
#include "AdventureGame/AdventureCharacter.h"
void ADartLauncher::Use()
{
UWorld* const World = GetWorld();