TIL - 상호작용 시스템 완료 및 디버깅

Kyu_·2026년 6월 22일

31주차

목록 보기
1/6

상호작용 시스템 (feature/NPC-Interaction)

기록일: 2026-06-22
브랜치: feature/NPC-Interaction
관련 파일: NSInteractionComponent, NSInteractable, NSInteractableNPCBase, NSPartsNPC, NSPetNPC, NSPartEquipWidget, NSPetUpgradeWidget, NSPlayerCharacterBase


전체 구조 한눈에 보기

[플레이어 폰]
  └── NSInteractionComponent     감지 + 입력 처리 + RPC 담당
        └── DetectionSphere      근접 오버랩 감지
        └── PromptWidgetComponent 화면에 띄우는 상호작용 프롬프트

[NPC 액터]
  └── ANSInteractableNPCBase     NPC 공통 베이스 (ACharacter + INSInteractable)
        └── ANSPartsNPC          파츠 장착 NPC
        └── ANSPetNPC            펫 강화 NPC

상호작용 흐름

① 플레이어가 NPC에 접근
   DetectionSphere 오버랩 → Candidates 목록에 추가 → Tick 활성화

② Tick마다 UpdateActiveTarget() 실행
   후보 중 가장 가까운 INSInteractable 대상 → ActiveTarget
   해당 NPC의 PromptAnchor 위치에 프롬프트 위젯 표시

③ F키 입력 → TryInteract()
   클라이언트에서 CanInteract 사전 검사 (반응성용)
   → Server_RequestInteract(Target) RPC 전송

④ 서버에서 Server_RequestInteract_Implementation 실행
   CanInteract 재검증 (서버 권위)
   → 통과 시 Client_OnInteractApproved(Target) RPC 전송

⑤ 클라이언트에서 Client_OnInteractApproved_Implementation 실행
   Execute_OnInteract(Target, PC) 호출
   → ANSPartsNPC or ANSPetNPC의 OnInteract_Implementation 실행
   → 해당 UI 위젯 생성 + AddToViewport

인터페이스: INSInteractable

모든 상호작용 가능한 액터가 구현해야 하는 인터페이스.

함수설명실행 위치
CanInteract(PC)이 플레이어가 상호작용 가능한지 판단클라(Tick 표시용) + 서버(검증용)
OnInteract(PC)실제 상호작용 처리 (UI 오픈 등)클라이언트
GetPromptText()프롬프트에 표시할 텍스트클라이언트
GetPromptWorldLocation()프롬프트 위젯이 뜰 월드 위치클라이언트

Execute_OnInteract(Target, PC) 형태로 호출하는 이유: UHT가 자동 생성하는 정적 디스패처로, C++ 구현체든 BP 구현체든 올바른 쪽으로 알아서 디스패치한다.


NPC 베이스: ANSInteractableNPCBase

ACharacter + INSInteractable 조합. 공통 로직 구현.

  • CanInteract_Implementation: PlayerStateNSPlayerProgressComponent에서 IsNPCUnlocked(NPCId) 확인. NPCId는 에디터 Details → NPC 카테고리에서 설정.
  • PromptText: 에디터 Details → NPC 카테고리에서 직접 입력.
  • PromptAnchor: 프롬프트 위젯이 뜰 위치. 에디터에서 드래그로 조정 가능.
  • DetectionCollision: NSInteractionComponentDetectionSphere가 오버랩 감지에 사용.

NSInteractionComponent 주요 설계 포인트

비로컬 폰 격리 (EnableLocalInteraction)

리슨 서버에서 서버가 모든 플레이어 폰을 관리하기 때문에, 아무 처리 없이 두면 클라이언트 폰의 상호작용 UI가 호스트 화면에도 표시되는 문제가 생긴다.

해결: 로컬 전용 셋업(오버랩 바인딩 + 콜리전 활성화 + 위젯 클래스)을 EnableLocalInteraction() 하나로 집중하고, 비로컬 폰에서는 아무것도 하지 않는다.

생성자: DetectionSphere 콜리전 = NoCollision (기본 OFF)

EnableLocalInteraction() 호출 시점:
  - BeginPlay      → 스탠드얼론 / 리슨 호스트 폴백
  - PossessedBy    → 서버 / 리슨 호스트 경로  
  - OnRep_Controller → 클라이언트 본인 폰 경로 (컨트롤러 리플리케이션 도착 시)

IsLocallyControlled() == false → 즉시 리턴, 아무것도 안 함
결과
스탠드얼론 / 리슨 호스트 본인 폰활성
리슨 서버 위의 원격 플레이어 폰비활성 (콜리전 OFF, 바인딩 없음, 위젯 없음)
클라이언트 본인 폰OnRep_Controller에서 활성

Tick 최적화

오버랩 중인 후보가 없으면 Tick을 끈다. 후보가 생기면(BeginOverlap) 켜고, 전부 사라지면(EndOverlap) 다시 끈다.

서버/클라 역할 분리

단계실행 위치이유
오버랩 감지 + 프롬프트 표시클라이언트 로컬UI는 로컬에서만
CanInteract (Tick)클라이언트 로컬프롬프트 표시 여부 판단용
TryInteract → Server RPC클라→서버서버에 검증 요청
CanInteract (서버 검증)서버실제 권위 검증
OnInteract → UI 오픈클라이언트UI는 서버에서 열 수 없음

UI: NSPartEquipWidget / NSPetUpgradeWidget

함수역할
OpenForInteractor(PC)AddToViewport + 마우스 커서 표시 + GameAndUI 입력 모드
RequestClose()변경사항 있으면 저장 확인 다이얼로그, 없으면 즉시 닫기
CloseWidget()RemoveFromParent + 마우스 숨김 + GameOnly 입력 모드 복구

닫기(ESC) 처리: NativeOnKeyDown에서 Escape 키 감지 → RequestClose() 호출 → FReply::Handled() 반환으로 이벤트 소비. PIE에서는 ESC가 세션 종료로 먼저 잡히므로 테스트 시 BP 닫기 버튼 사용.


파일 목록

파일역할
Interaction/Component/NSInteractionComponent.h/.cpp감지 + RPC + 프롬프트
Interaction/Core/NSInteractable.h인터페이스 정의
Interaction/NPC/NSInteractableNPCBase.h/.cppNPC 공통 베이스
Interaction/NPC/NSPartsNPC.h/.cpp파츠 NPC
Interaction/NPC/NSPetNPC.h/.cpp펫 NPC
UI/Interaction/NSPartEquipWidget.h/.cpp파츠 장착 UI
UI/Interaction/NSPetUpgradeWidget.h/.cpp펫 강화 UI
UI/Interaction/NSInteractionPromptWidget.h/.cpp머리 위 프롬프트 위젯
Character/Player/NSPlayerCharacterBase.h/.cppInteractionComp 소유, PossessedBy/OnRep_Controller 훅

0개의 댓글