언리얼C++의 스마트포인터

핵심 대원칙

  • **UObject(액터, 컴포넌트 등)**는 엔진의 **GC(가비지 컬렉터)**가 관리합니다.
  • **비-UObject(일반 C++ 클래스)**는 **RAII(스마트 포인터)**로 관리합니다.
  • 절대 원칙: 두 체계를 섞어 쓰지 마십시오. (예: UObject를 TSharedPtr로 관리 금지)

1. UObject 전용 포인터 (GC 연동)

종류 역할 및 특징
TObjectPtr<T> [기본] 강한 참조. 멤버 변수 선언 시 UPROPERTY()와 함께 사용 필수.
TWeakObjectPtr<T> [약한 참조] 객체 수명에 관여 안 함. IsValid() 체크 후 사용. 순환 참조 방지.
TSoftObjectPtr<T> [지연 로딩] 에셋 경로만 가짐. 메모리에 없어도 됨. 필요할 때 로드(LoadSynchronous).
TStrongObjectPtr<T> [특수] UPROPERTY를 쓸 수 없는 곳(비-UObject 클래스 등)에서 UObject를 살려둘 때 사용.

2. 비-UObject 전용 포인터 (일반 C++ 스마트 포인터)

종류 역할 및 특징
TSharedPtr<T> [공유] 여러 곳에서 참조. 참조 카운트가 0이 되면 해제. (UI, 에디터 툴 등)
TSharedRef<T> [공유/필수] TSharedPtr와 같으나 Null 불가능 보장.
TWeakPtr<T> [약한 참조] TSharedPtr를 참조하되 소유권 없음. Pin()으로 접근.
TUniquePtr<T> [단일] 오직 한 곳에서만 소유. 복사 불가, 이동만 가능. 가장 가벼움.

3. RAII (Resource Acquisition Is Initialization)

  • 개념: 객체가 소멸될 때 자원(메모리)을 자동으로 해제하는 패턴.
  • 적용: 비-UObject 스마트 포인터(TUniquePtr 등)의 작동 원리입니다.
  • 주의: UObject는 GC가 수명을 결정하므로 RAII 패턴(소멸자 자동 해제)에 의존하지 않습니다.

언리얼 

UI 작업

#include "SpartaGameState.h"
#include "Kismet/GameplayStatics.h"
#include "SpawnVolume.h"
#include "CoinItem.h"
#include "SpartaGameInstance.h"
#include "SpartaPlayerController.h"
#include "Components/TextBlock.h"
#include "Blueprint/UserWidget.h"

ASpartaGameState::ASpartaGameState()
{
	Score = 0;
	SpawnedCoinCount = 0;
	CollectedCoinCount = 0;
	LevelDuration = 30.0f;
	CurrentLevelIndex = 0;
	MaxLevels = 3;

	LevelMapNames = { "BasicLevel", "IntermediateLevel", "AdvancedLevel" };
}

void ASpartaGameState::BeginPlay()
{
	Super::BeginPlay();

	StartLevel();

	GetWorldTimerManager().SetTimer(
		HUDUpdateTimerHandle,
		this,
		&ASpartaGameState::UpdateHUD,
		0.1f,
		true
	);
}

int32 ASpartaGameState::GetScore() const
{
	return Score;
}

void ASpartaGameState::AddScore(int32 Amount)
{
	if (UGameInstance* GameInstance = GetGameInstance())
	{
		USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
		if (SpartaGameInstance)
		{
			SpartaGameInstance->AddToScore(Amount);
		}
	}
}


void ASpartaGameState::StartLevel()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController))
		{
			SpartaPlayerController->ShowGameHUD();
		}
	}

	if (UGameInstance* GameInstance = GetGameInstance())
	{
		USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
		if (SpartaGameInstance)
		{
			CurrentLevelIndex = SpartaGameInstance->CurrentLevelIndex;
		}
	}


	SpawnedCoinCount = 0;
	CollectedCoinCount = 0;

	//Array Empty()
	TArray<AActor*> FoundVolumes;

	//레벨에서 대상 타입의 액터를 다 긁어옴.
	UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundVolumes);
	ASpawnVolume* SpawnVolume = FoundVolumes.IsEmpty() ? nullptr : Cast<ASpawnVolume>(FoundVolumes[0]);
	if (SpawnVolume == nullptr)
	{
		UE_LOG(LogTemp, Error, TEXT("SpawnVolume Error."));
	}

	if (SpawnVolume)
	{
		const int32 ItemToSpawn = 40;
		for (int32 i = 0; i < ItemToSpawn; i++)
		{

			AActor* SpawnedActor = SpawnVolume->SpawnRandomItem();
			if (SpawnedActor && SpawnedActor->IsA(ACoinItem::StaticClass()))
			{
				SpawnedCoinCount++;
			}
		}
	}


	GetWorldTimerManager().SetTimer(
		LevelTimerHandle,
		this,
		&ASpartaGameState::OnLevelTimeUp,
		LevelDuration,
		false
	);

}

void ASpartaGameState::OnLevelTimeUp()
{
	EndLevel();
}

void ASpartaGameState::EndLevel()
{
	GetWorldTimerManager().ClearTimer(LevelTimerHandle);

	if (UGameInstance* GameInstance = GetGameInstance())
	{
		USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
		if (SpartaGameInstance)
		{
			AddScore(Score);
			CurrentLevelIndex++;
			SpartaGameInstance->CurrentLevelIndex = CurrentLevelIndex;
		}
	}

	if (CurrentLevelIndex >= MaxLevels)
	{
		OnGameOver();
		return;
	}

	if (LevelMapNames.IsValidIndex(CurrentLevelIndex))
	{
		UGameplayStatics::OpenLevel(GetWorld(), LevelMapNames[CurrentLevelIndex]);
	}
	else
	{
		OnGameOver();
	}
}

void ASpartaGameState::OnGameOver()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController))
		{
			SpartaPlayerController->ShowMainMenu(true);
		}
	}
}

void ASpartaGameState::OnCoinCollected()
{
	CollectedCoinCount++;

	UE_LOG(LogTemp, Warning, TEXT("Coin Collected : %d / %d "),
		CollectedCoinCount,
		SpawnedCoinCount
	);

	if (SpawnedCoinCount >= 0 && CollectedCoinCount >= SpawnedCoinCount)
	{
		EndLevel();
	}
}

void ASpartaGameState::UpdateHUD()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController))
		{
			if (UUserWidget* HUDWidget = SpartaPlayerController->GetHUDWidget())
			{
				if (UTextBlock* TimeText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Time"))))
				{
					float RemainingTime = GetWorldTimerManager().GetTimerRemaining(LevelTimerHandle);
					TimeText->SetText(FText::FromString(FString::Printf(TEXT("Time : %.1f"), RemainingTime)));
				}

				if (UTextBlock* ScoreText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Score"))))
				{
					if (UGameInstance* GameInstance = GetGameInstance())
					{
						USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
						if (SpartaGameInstance)
						{
							ScoreText->SetText(FText::FromString(FString::Printf(TEXT("Score : %d"), SpartaGameInstance->TotalScore)));
						}
					}
				}

				if (UTextBlock* LevelText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Level"))))
				{

					LevelText->SetText(FText::FromString(FString::Printf(TEXT("Level : %d"), CurrentLevelIndex + 1)));
				}
			}
		}
	}
}

+ Recent posts