IInteractable 인터페이스로 확장성 제공┌─────────────────────────────────────────────────────────────┐
│ CharacterBase_GAS │
│ (플레이어 캐릭터 - 부모 클래스) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │InteractionComponent│ │ CameraComponent │ │
│ │ (20Hz Timer) │ │ (좌표 기준점) │ │
│ │ - Trace Actor │ │ │ │
│ │ - FocusedActor │ │ │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
↓ FocusedActor 정보 전달
↓ (GetFocusedActor)
┌─────────────────────────────────────────────────────────────┐
│ MyProjectHUD │
│ (HUD UI 렌더링 - 10Hz Timer) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │UpdateInteractionUI│ │ DrawHUD │ │
│ │ (0.1초 주기) │ │ (매 프레임) │ │
│ │ - WorldToScreen │ │ - E 키 렌더링 │ │
│ │ - 위치 변환 │ │ - 2D 그리기 │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
↓ 상호작용 인터페이스 조회
↓ (Implements<IInteractable>)
┌─────────────────────────────────────────────────────────────┐
│ PuzzleActor (상호작용 객체) │
│ (IInteractable, IPickupable 구현) │
├─────────────────────────────────────────────────────────────┤
│ - Interact(): 상호작용 로직 │
│ - GetUIWorldLocation(): UI 표시 위치 │
│ - BeginHighlight(): 강조 표시 시작 │
│ - EndHighlight(): 강조 표시 종료 │
└─────────────────────────────────────────────────────────────┘
역할 : 플레이어 주변의 상호작용 가능한 액터를 주기적으로 감지
속성 :
TraceInterval: 0.05초 (20Hz) - Trace 실행 주기InteractionDistance: 1000.0 단위 - 감지 거리FocusedActor: 현재 감지된 상호작용 객체핵심 함수 :
void BeginPlay()
{
// Timer 시작 - 0.05초마다 UpdateTrace 호출
GetWorld()->GetTimerManager().SetTimer(
TraceTimerHandle,
this,
&UInteractionComponent::UpdateTrace,
TraceInterval, // 0.05초
true // 반복
);
}
void UpdateTrace()
{
// Trace 실행 및 FocusedActor 업데이트
FHitResult HitResult;
if (TraceForInteractable(HitResult))
{
// Interactable 인터페이스를 구현한 객체 감지
FocusedActor = HitResult.GetActor();
}
}
bool TraceForInteractable(FHitResult& HitResult)
{
// 카메라에서 Ray 발사 (앞으로 1000 단위)
// Interactable 인터페이스 구현 여부 확인
}
최적화 :
Tick 사용 안 함 (매 프레임 60Hz 실행)Timer 사용 (20Hz만 실행) → 약 66% 성능 개선역할: 월드 좌표를 화면 좌표로 변환하고 UI 렌더링
구성:
UpdateInteractionUI() - 위치 계산 (Timer: 0.1초)DrawHUD() - UI 렌더링 (매 프레임)void AMyProjectHUD::UpdateInteractionUI()
{
// 1단계: 플레이어 캐릭터 가져오기
ACharacterBase_GAS* PlayerCharacter = Cast<ACharacterBase_GAS>(
UGameplayStatics::GetPlayerCharacter(GetWorld(), 0)
);
// 2단계: 상호작용 컴포넌트 가져오기
UInteractionComponent* InteractionComp =
PlayerCharacter->FindComponentByClass<UInteractionComponent>();
// 3단계: 감지된 액터 가져오기
AActor* FocusedActor = InteractionComp->GetFocusedActor();
// 4단계: 인터페이스에서 월드 위치 조회
IInteractable* Interactable = Cast<IInteractable>(FocusedActor);
FVector WorldLocation = Interactable->GetUIWorldLocation(); // 3D 좌표
// 5단계: 화면 좌표로 변환 (ProjectWorldToScreen)
FVector2D ScreenPosition;
APlayerController* PC = GetOwningPlayerController();
bool bIsOnScreen = PC->ProjectWorldLocationToScreen(
WorldLocation, // 입력: 3D 월드 좌표
ScreenPosition // 출력: 2D 화면 좌표 (0~1920, 0~1080)
);
// 6단계: 오프셋 적용 및 캐시에 저장
CachedScreenPosition = ScreenPosition + UIOffset; // 위치 조정
bShouldShowUI = true; // 그리기 허용 플래그
}
3D 월드 좌표 (X, Y, Z)
↓ [ProjectWorldToScreen]
2D 화면 좌표 (ScreenX, ScreenY)
예시:
- 월드 좌표: (750, -30, 124.5)
- 화면 좌표: (425.28, 271.10) ← 화면의 정확한 위치!
void AMyProjectHUD::DrawHUD()
{
Super::DrawHUD();
// 조건 확인: UI를 그려야해?
if (!bShouldShowUI)
return;
// 텍스처 또는 텍스트로 UI 그리기
if (InteractionTexture)
{
// 텍스처 기반 렌더링 (권장)
FVector2D DrawPosition = CachedScreenPosition - (UISize * 0.5f);
DrawTexture(
InteractionTexture, // E 키 아이콘 텍스처
DrawPosition.X, // 화면 X 좌표
DrawPosition.Y, // 화면 Y 좌표
UISize.X, // 너비 (픽셀)
UISize.Y, // 높이 (픽셀)
0.0f, 0.0f, 1.0f, 1.0f, // UV 좌표 (전체 표시)
FLinearColor::White // 색상 (흰색)
);
}
else
{
// 텍스트 기반 렌더링 (폴백)
DrawText(
TEXT("E"),
FLinearColor::White,
CachedScreenPosition.X,
CachedScreenPosition.Y,
GEngine->GetLargeFont(),
2.0f // 크기
);
}
}
역할: 상호작용 가능한 객체들이 따라야 할 계약 정의
// Interactable.h
UINTERFACE(MinimalAPI)
class UInteractable : public UInterface
{
GENERATED_BODY()
};
class MYPROJECT_API IInteractable
{
GENERATED_BODY()
public:
// 필수 구현 함수
virtual void Interact(UInteractionComponent* Interactor) = 0;
// 선택적 구현 함수 (기본 구현 제공)
virtual void BeginHighlight() {} // 강조 시작
virtual void EndHighlight() {} // 강조 종료
// HUD 위치 반환 (중요!)
virtual FVector GetUIWorldLocation() const { return FVector::ZeroVector; }
};
class APuzzleActor : public AActor, public IInteractable
{
public:
// 상호작용 로직
virtual void Interact(UInteractionComponent* Interactor) override
{
// 실제 상호작용 처리
Solve();
}
// UI 표시 위치 (예: 액터 위쪽)
virtual FVector GetUIWorldLocation() const override
{
return GetActorLocation() + FVector(0, 0, 100);
}
};
BP_GASGameMode
BP_MyProjectHUD 블루프린트 생성:
MyProjectHUDGameMode에서
[0.0s] 게임 시작
│
├─→ CharacterBase_GAS BeginPlay
│ └─→ InteractionComponent BeginPlay
│ └─→ Timer 시작 (0.05초 주기)
│
├─→ MyProjectHUD BeginPlay
│ └─→ Timer 시작 (0.1초 주기)
│
[0.05s] InteractionComponent Timer 콜백 #1
│
├─→ UpdateTrace() 실행
│ ├─→ LineTraceSingleByChannel 실행
│ ├─→ HitResult 분석
│ └─→ FocusedActor = PuzzleActor_C_0 (감지!)
│
[0.1s] MyProjectHUD Timer 콜백 #1
│
├─→ UpdateInteractionUI() 실행
│ ├─→ InteractionComponent->GetFocusedActor()
│ │ └─→ PuzzleActor_C_0 반환
│ │
│ ├─→ IInteractable->GetUIWorldLocation()
│ │ └─→ (750, -30, 124.5) 반환
│ │
│ ├─→ PC->ProjectWorldLocationToScreen()
│ │ ├─ 입력: (750, -30, 124.5)
│ │ └─ 출력: (425.28, 271.10) ← 화면 좌표!
│ │
│ └─→ CachedScreenPosition = (425.28, 251.10) + UIOffset
│
[0.1s] DrawHUD() - 매 프레임 실행
│
├─→ bShouldShowUI 확인
│ └─→ true (UI 표시)
│
├─→ DrawTexture(
│ InteractionTexture,
│ 425.28, ← X 좌표 (화면 중앙 근처)
│ 251.10, ← Y 좌표 (약간 위)
│ 50, 50, ← 크기 (50x50 픽셀)
│ ...
│ )
│
└─→ 화면에 "E" 키 아이콘 표시! ✅
| 항목 | Tick 방식 | Timer 방식 (현재) |
|---|---|---|
| 호출 빈도 | 60Hz (매 프레임) | 20Hz (0.05초) |
| 프로세스 | 60회/초 | 20회/초 |
| 성능 절약 | - | 66% 감소 |
| 반응성 | 즉시 | 50ms 지연 (거의 안 느껴짐) |
// InteractionComponent: 빠른 감지 (20Hz)
// - Trace는 계산 비용이 큼
// - 0.05초 = 50ms (반응성 우수)
TraceInterval = 0.05f; // 20Hz
// HUD: 느린 렌더링 (10Hz)
// - 위치 계산은 가벼움
// - 0.1초 = 100ms (DrawHUD가 매 프레임 어차피 실행)
UpdateInterval = 0.1f; // 10Hz
[World Coordinate]
↓
(750, -30, 124.5) ← PuzzleActor의 UI 위치
↓
[ProjectWorldToScreen] ← 핵심 변환 함수!
↓
[Screen Coordinate]
↓
(425.28, 271.10) ← 화면 픽셀 좌표
↓
[UIOffset 적용]
↓
(425.28, 251.10) ← 최종 표시 위치
↓
[DrawTexture]
↓
화면에 "E" 렌더링!
bool UInteractionComponent::TraceForInteractable(FHitResult& HitResult)
{
// 카메라 위치와 방향 가져오기
APlayerController* PlayerController =
OwnerCharacter->GetController<APlayerController>();
FVector TraceStartLocation;
FRotator TraceDirection;
PlayerController->GetPlayerViewPoint(
TraceStartLocation, // 카메라 위치
TraceDirection // 카메라 방향
);
// Trace 끝점 계산 (앞으로 1000 단위)
FVector TraceEndLocation =
TraceStartLocation + (TraceDirection.Vector() * InteractionDistance);
// LineTrace 실행
UKismetSystemLibrary::LineTraceSingle(
this,
TraceStartLocation, // 시작점
TraceEndLocation, // 끝점
ECC_Visibility, // 추적 채널
false,
ActorsToIgnore, // 플레이어는 무시
EDrawDebugTrace::None,
HitResult, // 결과 저장
true
);
// 결과 분석
if (HitResult.bBlockingHit)
{
AActor* HitActor = HitResult.GetActor();
// IInteractable 인터페이스 구현 여부 확인
if (HitActor->Implements<UInteractable>())
{
return true; // 상호작용 가능한 객체 발견!
}
}
return false; // 상호작용 가능한 객체 없음
}
// 3D 월드 좌표 → 2D 화면 좌표 변환
void AMyProjectHUD::UpdateInteractionUI()
{
// ...
// PuzzleActor의 UI 위치 가져오기 (월드 좌표)
FVector WorldLocation = Interactable->GetUIWorldLocation();
// 예: (750, -30, 124.5) ← 액터 위에서 약간 위쪽
// 중요: ProjectWorldToScreen 사용!
// 이 함수가 3D → 2D 변환을 담당합니다
FVector2D ScreenPosition;
PC->ProjectWorldLocationToScreen(WorldLocation, ScreenPosition);
// 결과: (425.28, 271.10) ← 화면 좌표로 변환됨!
// 오프셋 적용 (UI를 조금 위쪽에 표시)
CachedScreenPosition = ScreenPosition + UIOffset;
// UIOffset은 보통 (0, -50) 정도로 설정
}
void AMyProjectHUD::DrawHUD()
{
// DrawHUD는 매 프레임 호출됨
// 하지만 bShouldShowUI로 조건 제어
if (!bShouldShowUI) return;
// 텍스처 크기 조정 (중심을 기준으로)
FVector2D DrawPosition = CachedScreenPosition - (UISize * 0.5f);
// 예: CachedScreenPosition이 (425, 250)일 때
// UISize가 (50, 50)이면
// DrawPosition = (400, 225) ← 좌상단 코너
// Canvas에 렌더링
DrawTexture(
InteractionTexture,
DrawPosition.X, // 400
DrawPosition.Y, // 225
UISize.X, // 50
UISize.Y, // 50
0.0f, // UV X 시작
0.0f, // UV Y 시작
1.0f, // UV X 끝
1.0f, // UV Y 끝
FLinearColor::White // 색상 (흰색)
);
}