도망치는 것만으로는 살아남을 수 없다. 맵에 널린 자원을 활용해 몬스터의 경로를 차단할 장애물을 세우는 UOblivioCrafting 컴포넌트를 본격적으로 개발했다. 이 과정에서 C++ 객체지향의 중요성과 시각적 피드백(UX)의 힘을 크게 배웠다.
1. 크래프팅 컴포넌트 모듈화 및 레이캐스트(Raycast) 배치
크래프팅 로직이 캐릭터 클래스를 무겁게 만들지 않도록, UOblivioCrafting이라는 별도의 액터 컴포넌트(Actor Component)로 완전히 모듈화 했다.
- 아이템 선택: 플레이어가 키보드 1~7번을 누르면 컨트롤러에서 입력값(Magnitude)을 받아 SelectObstacle()로 넘기고, 미리 설정된 CraftingRecipes 배열에서 설치할 장애물 클래스를 불러온다.
- 실시간 미리 보기 배치: UpdatePreviewLocation() 함수를 통해 매 프레임 마우스 커서 위치로 레이캐스트(Raycast)를 쏜다. 플레이어가 마우스를 움직일 때마다 투명한 '고스트(Ghost) 액터'가 마우스를 따라다니도록 트랜스폼(Transform)을 업데이트했다.
2. 다이내믹 머테리얼(MID)을 이용한 시각적 피드백
설치 가능 여부를 텍스트가 아닌 직관적인 색상으로 보여주기 위해 고스트 머테리얼을 동적으로 제어했다.
- 장애물 베이스 클래스(AObstacleBase)에 SetGhostMode 함수를 만들고, 다이내믹 머테리얼 인스턴스(MID)를 생성했다.
- 자원이 충분하고 설치 거리 내에 있으면(bCanPlace == true) 파라미터(TintColor)를 초록색으로, 자원이 부족하거나 너무 멀면 빨간색으로 실시간으로 변경되게 만들었다.
- 고스트 모드일 때는 플레이어의 길을 막지 않게 콜리전(Collision)을 끄고, 최종 설치가 완료되면(OnPlaced) 원래의 물리 머테리얼로 복구시켰다.
3. 트러블슈팅: 접근 권한(Inaccessible) 에러와 객체지향적 해결
- 문제 발생: 크래프팅 컴포넌트에서 장애물을 지을 수 있는지 검사(CanAfford) 하기 위해, AObstacleBase에 있는 WoodCost와 IronCost 변수를 가져오려다 빌드 에러(is inaccessible)가 발생했다. 변수들이 protected로 보호되어 있었기 때문이다.
- 해결 및 깨달음: 변수를 무작정 public으로 열어버릴 수도 있었지만, 이미 만들어두었던 GetWoodCost(), GetIronCost()라는 게터(Getter) 함수를 활용해 접근하는 정석적인 방식을 택했다. 데이터를 안전하게 캡슐화(Encapsulation)하면서 외부 컴포넌트와 통신하는 객체지향의 기본을 다시 한번 체감한 순간이었다.
코드:
1. 실시간 미리보기(Ghost) 트랜스폼 업데이트 (OblivioCrafting.cpp)
void UOblivioCrafting::UpdatePreviewLocation()
{
APlayerController* PC = Cast<APlayerController>(UGameplayStatics::GetPlayerController(GetWorld(), 0));
if (!PC || !PreviewActor) return;
FHitResult Hit;
bool bHit = PC->GetHitResultUnderCursor(ECC_Visibility, false, Hit);
if (bHit)
{
// 플레이어와 마우스 커서 사이의 거리 계산
float Distance = FVector::Dist(GetOwner()->GetActorLocation(), Hit.Location);
PreviewActor->SetActorLocation(Hit.Location);
PreviewActor->SetActorRotation(CurrentPreviewRotation);
// 설치 사거리 내에 있는지 검사
if (Distance <= MaxPlacementDistance)
{
bool bCanPlace = CanAfford(PreviewActor);
PreviewActor->SetGhostMode(true, bCanPlace);
}
else
{
// 사거리를 벗어나면 무조건 빨간색(false) 피드백
PreviewActor->SetGhostMode(true, false);
}
}
}
2. 다이내믹 머테리얼을 활용한 색상 피드백 (ObstacleBase.cpp)
void AObstacleBase::SetGhostMode(bool bIsGhost, bool bCanPlace)
{
CurrentState = bIsGhost ? EObstacleState::Ghost : EObstacleState::Placed;
if (bIsGhost)
{
if (GhostMaterial && MeshComponent)
{
// 다이내믹 머테리얼 인스턴스(MID) 생성
UMaterialInstanceDynamic* DynamicGhostMat = MeshComponent->CreateDynamicMaterialInstance(0, GhostMaterial);
if (DynamicGhostMat)
{
// 조건에 따른 색상 결정 (설치 가능: 초록 / 불가능: 빨강)
FLinearColor TargetColor = bCanPlace ?
FLinearColor(0.0f, 1.0f, 0.0f, 0.5f) :
FLinearColor(1.0f, 0.0f, 0.0f, 0.5f);
DynamicGhostMat->SetVectorParameterValue(FName("TintColor"), TargetColor);
}
}
// 고스트 모드일 때는 길을 막지 않도록 콜리전 비활성화
if (MeshComponent) MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
// ... (설치 완료 시 원본 머테리얼 복구 로직 생략) ...
}
고스트 머테리얼이 초록색에서 빨간색으로 스무스하게 바뀌는 것을 보았을 때의 쾌감이 컸다. 개발자가 텍스트 창으로 "자원이 부족합니다"라고 띄우는 것보다, 시스템 자체가 붉은빛으로 안 된다고 피드백을 주는 것이 유저 경험(UX) 측면에서 훨씬 세련된 방식임을 배웠다. C++ 클래스 간의 통신(캐릭터 ↔ 크래프팅 컴포넌트 ↔ 장애물 액터) 구조를 짜느라 머리가 아팠지만, 덕분에 구조적으로 아주 탄탄한 건축 시스템이 완성되었다.

