미리보기

구현내용
플레이어가 가야하는 위치를 월드상에 위치와 거리를 시각적으로 알려주는
가이드 마커를 구현하였습니다
해당 사진은 데스 스트랜딩의 게임 화면입니다
목표 위치를 40m 라고 표시해줍니다
이런 마커의 표시로 게임의 목표를 확실하게 보여줌으로서
플레이어가 플레이를 수월하게 진행할수있도록 도와줍니다.

사용한 클래스
| 사용한 클래스 | 사용 목적 |
| GuideManagerSubsystem(서브시스템) | 월드 마커를 총괄하는 매니저 |
| GuideTargetActor(액터) | 월드 트래커가 추적해야 할 목표 그 자체 인 액터 |
| QuestComponent(액터 컴포넌트) | 플레이어가 가진 퀘스트 데이터 관리 (목표 가 어디인지 판단) |
| GuideTrackerComponent(액터 컴포넌트) | Subsystem과 Widget 사이를 이어주는 중간 다리 (월드 위치를 기준으로 마커의 위치 , 가시성을 실시간 갱신해주는 컴포넌트) |
| GuideMarkerWidget(위젯) | 월드에 표시되는 마커 UI |
우선 월드 마커를 총괄하는 매니저 즉 가이드매니저를 만들어야한다
게임 전체에서 월드 트래커 / 가이드 마커를 관리하는 전역매니저인데 뭘로 만들까 고민을 많이했다
GameInstanceSubsystem을 사용했다
해당 클래스의 특징은 이와 같다
- 레벨이 바뀌어도 살아 있다
- 싱글플레이 1명 기준 전역 시스템에 매우 적합
- 어디서든 접근이 가능
클래스를 추가했다

GuideManagerSubsystem 구현 코드
#include "GuideManagerSubsystem.h"
#include "GuideTargetActor.h"
#include "EngineUtils.h"
void UGuideManagerSubsystem::StartGuide(UGuideStepData* activeData)
{
stepData = activeData;
ActivateStep(0);
}
void UGuideManagerSubsystem::StartSingleTarget(FName targetID)
{
if (currentTarget)
{
currentTarget->SetGuideActive(false);
currentTarget = nullptr;
}
stepData = nullptr;
currentStepIndex = INDEX_NONE;
currentTarget = FindTargetByID(targetID);
if (currentTarget)
currentTarget->SetGuideActive(true);
}
void UGuideManagerSubsystem::EndSingleTarget(FName targetID)
{
if (currentTarget)
{
currentTarget->SetGuideActive(false);
currentTarget = nullptr;
}
currentTarget = FindTargetByID(targetID);
if (currentTarget)
currentTarget->SetGuideActive(false);
}
void UGuideManagerSubsystem::ClearCurrentGuide()
{
if (currentTarget)
{
currentTarget->SetGuideActive(false);
currentTarget = nullptr;
}
stepData = nullptr;
currentStepIndex = INDEX_NONE;
}
void UGuideManagerSubsystem::ActivateStep(int32 stepIndex)
{
if (!stepData || !stepData->guideSteps.IsValidIndex(stepIndex))
return;
if (currentTarget)
currentTarget->SetGuideActive(false);
currentStepIndex = stepIndex;
FName targetID = stepData->guideSteps[stepIndex].targetID;
currentTarget = FindTargetByID(targetID);
if (currentTarget)
currentTarget->SetGuideActive(true);
}
void UGuideManagerSubsystem::NotifyTargetReached(AGuideTargetActor* reachedTarget)
{
if (reachedTarget != currentTarget)
return;
ActivateStep(currentStepIndex + 1);
}
AGuideTargetActor* UGuideManagerSubsystem::FindTargetByID(FName id)
{
for (TActorIterator<AGuideTargetActor> it(GetWorld()); it; ++it)
{
if (it->guidID == id)
return *it;
}
return nullptr;
}
AGuideTargetActor* UGuideManagerSubsystem::GetCurrentTarget() const
{
return currentTarget;
}
FText UGuideManagerSubsystem::GetCurrentDescription() const
{
if (!stepData || !stepData->guideSteps.IsValidIndex(currentStepIndex))
return FText::GetEmpty();
return stepData->guideSteps[currentStepIndex].description;
}
다음은 서브시스템과 위젯 사이를 이어주는 중간역할을 하는 GuideTrackerComponent이다
목표의 위치를 판단 그리고 거리가 몇 인지 계산해준다
위치의 판단은 가이드액터 와 플레이어와 거리를 서로 빼줘서 거리를 구하고
언리얼 거리 단위인 cm 를 100으로 나눠 m로 변환하여 ~m로 표기하였다
GuideManagerSubsystem 구현 코드
#include "GuideTrackerComponent.h"
#include "GuideManagerSubsystem.h"
#include "GuideTargetActor.h"
#include "GuideMarkerWidget.h"
#include "GameFramework/Character.h"
#include "GameFramework/PlayerController.h"
#include "Engine/World.h"
UGuideTrackerComponent::UGuideTrackerComponent()
{
PrimaryComponentTick.bCanEverTick = true;
}
void UGuideTrackerComponent::BeginPlay()
{
Super::BeginPlay();
guideManager = GetWorld()->GetGameInstance()->GetSubsystem<UGuideManagerSubsystem>();
}
void UGuideTrackerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!guideManager)
return;
guideTarget = guideManager->GetCurrentTarget();
float dist = GetDistanceToTarget();
int32 meterDist = FMath::RoundToInt(dist / 100.f);
FText distText = FText::FromString(FString::Printf(TEXT("%dm"), meterDist));
AGuideTargetActor* target = guideManager->GetCurrentTarget();
if (!target)
return;
target->UpdateDistanceText(distText);
}
float UGuideTrackerComponent::GetDistanceToTarget() const
{
if (!guideTarget || !GetOwner())
return -1.f;
return FVector::Dist(GetOwner()->GetActorLocation(), guideTarget->GetActorLocation());
}
FVector UGuideTrackerComponent::GetDirectionToTarget() const
{
if (!guideTarget || !GetOwner())
return FVector::ZeroVector;
return guideTarget->GetActorLocation() - GetOwner()->GetActorLocation().GetSafeNormal();
}
bool UGuideTrackerComponent::HasValidTarget() const
{
return guideTarget != nullptr;
}
GuideTargetActor이다
가이드 트래커가 추적해야할 대상이자
가이드 트래커는 해당 GuideTargetActor의 guideID로
guideID로 GuideTargetActor를 구분, 추적한다
GuideManagerSubsystem 구현 코드 (헤더)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GuideTargetActor.generated.h"
UCLASS()
class PORTFOLIOMS_API AGuideTargetActor : public AActor
{
GENERATED_BODY()
public:
AGuideTargetActor();
protected:
virtual void BeginPlay() override;
public:
UPROPERTY(EditAnywhere, Category="Settings")
FName guidID;
UPROPERTY(EditAnywhere, Category = "Settings")
float reachRadius = 150.f;
public:
UPROPERTY()
class UGuideManagerSubsystem* guideManager;
public:
UPROPERTY(VisibleAnywhere)
USceneComponent* sceneComp;
UPROPERTY(VisibleAnywhere)
class USphereComponent* sphereComp;
UFUNCTION()
void OnReachOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void SetGuideActive(bool bActive);
public:
UPROPERTY(EditAnywhere, Category = "Settings")
class UWidgetComponent* markerWidgetComp;
public:
class UGuideMarkerWidget* GetMarkerWidget() const;
void UpdateDistanceText(const FText& distText);
};
GuideManagerSubsystem 구현 코드 (구현부)
#include "GuideTargetActor.h"
#include "Components/CapsuleComponent.h"
#include "GuideManagerSubsystem.h"
#include "GameFramework/Character.h"
#include "Components/WidgetComponent.h"
#include "GuideMarkerWidget.h"
#include "Components/SphereComponent.h"
AGuideTargetActor::AGuideTargetActor()
{
PrimaryActorTick.bCanEverTick = false;
sceneComp = CreateDefaultSubobject<USceneComponent>(TEXT("rootComp"));
SetRootComponent(sceneComp);
sphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("ReachSphereComp"));
sphereComp->SetupAttachment(RootComponent);
sphereComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
sphereComp->SetCollisionResponseToAllChannels(ECR_Ignore);
sphereComp->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
sphereComp->SetGenerateOverlapEvents(true);
markerWidgetComp = CreateDefaultSubobject<UWidgetComponent>(TEXT("markerWidgetComp"));
markerWidgetComp->SetupAttachment(RootComponent);
markerWidgetComp->SetWidgetSpace(EWidgetSpace::Screen);
markerWidgetComp->SetDrawSize(FVector2D(40.f, 40.f));
markerWidgetComp->SetVisibility(false);
}
void AGuideTargetActor::BeginPlay()
{
Super::BeginPlay();
guideManager = GetWorld()->GetGameInstance()->GetSubsystem<UGuideManagerSubsystem>();
sphereComp->SetSphereRadius(reachRadius);
sphereComp->OnComponentBeginOverlap.AddDynamic(this, &AGuideTargetActor::OnReachOverlap);
}
void AGuideTargetActor::OnReachOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (!OtherActor)
return;
if (!OtherActor->IsA<ACharacter>())
return;
guideManager->NotifyTargetReached(this);
}
void AGuideTargetActor::SetGuideActive(bool bActive)
{
markerWidgetComp->SetVisibility(bActive);
sphereComp->SetCollisionEnabled(bActive ? ECollisionEnabled::QueryOnly : ECollisionEnabled::NoCollision);
}
UGuideMarkerWidget* AGuideTargetActor::GetMarkerWidget() const
{
if (!markerWidgetComp)
return nullptr;
return Cast<UGuideMarkerWidget>(markerWidgetComp->GetUserWidgetObject());
}
void AGuideTargetActor::UpdateDistanceText(const FText& distText)
{
if (UGuideMarkerWidget* marker = GetMarkerWidget())
marker->SetDistText(distText);
}
GuideMarkerWidget 이다
이렇게 구상되어 있다.
위젯에서는 888 이라는 text를 변경하는 함수가 있고
핑이 표시될때 나오는 애니메이션을 만들었다

결과
월드트래커컴포넌트가 특정 id를 가진 가이트타겟액터를 추적합니다
거리에 따라 m로 표시됩니다


영상
'Unreal 프로젝트 다이어리 > 두번째 프로젝트' 카테고리의 다른 글
| Unreal - 퀵슬롯 (0) | 2025.12.27 |
|---|---|
| Unreal - 퀘스트 시스템2 (0) | 2025.12.22 |
| Unreal - 대화 Npc (0) | 2025.12.22 |
| Unreal - 퀘스트 시스템 (0) | 2025.12.19 |
| Unreal - 상호작용 프롬프트 (4) | 2025.12.19 |