언리얼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)));
}
}
}
}
}