Unreal - 가이드 마커

2025. 12. 22. 06:28·Unreal 프로젝트 다이어리/두번째 프로젝트

미리보기

구현내용

플레이어가 가야하는 위치를 월드상에 위치와 거리를 시각적으로 알려주는

가이드 마커를 구현하였습니다

 

해당 사진은 데스 스트랜딩의 게임 화면입니다

목표 위치를 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
'Unreal 프로젝트 다이어리/두번째 프로젝트' 카테고리의 다른 글
  • Unreal - 퀵슬롯
  • Unreal - 퀘스트 시스템2
  • Unreal - 대화 Npc
  • Unreal - 퀘스트 시스템
lucodev
lucodev
언리얼 포폴개발 일기
  • lucodev
    루코 개발테이블
    lucodev
  • 전체
    오늘
    어제
    • 분류 전체보기 (236)
      • Unreal 프로젝트 다이어리 (132)
        • 첫번째 프로젝트 (73)
        • 두번째 프로젝트 (59)
      • Unreal 팁 (8)
      • Unreal 디버깅 (8)
      • C++ 프로그래머스 (52)
        • Stack,Queue (7)
        • Hash (4)
        • Heap (2)
        • Sort (5)
        • Exhaustive search (5)
        • Greedy (2)
        • BFS , DFS (7)
        • Graph (2)
        • Dynamic Programming (1)
        • C++ Math (2)
        • 기타 문제 (14)
      • C++ 백준 (5)
      • C++ 팁 (1)
      • 개인 코테 & 스타디 <비공개> (29)
        • 코드 개인보관함 (9)
        • 코딩테스트+@ (11)
        • 알고리즘 스타디 (6)
        • 알고리즘 스타디 과제 (3)
        • 비공개 (0)
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 링크

  • 공지사항

  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 태그

    언리얼 behaviortree
    언리얼 컷씬
    언리얼
    언리얼 인터렉션
    unreal 세키로
    unreal 인벤토리
    unreal npc
    언리얼 비헤이비어트리
    언리얼 parkour
    언리얼 세키로
    언리얼 behavior tree
    언리얼 상호작용
    언리얼 파쿠르
    unreal inventory
    언리얼 시퀀스
    unreal 파쿠르
    언리얼 인벤토리
    unreal 상호작용
    unreal
    언리얼 ui
  • hELLO· Designed By정상우.v4.10.3
lucodev
Unreal - 가이드 마커
상단으로

티스토리툴바