[25.06.20] :: 가디언 앤 시커 프로젝트 16

chooha·2025년 6월 20일

가디언앤시커

목록 보기
16/25

📝 개발일지 - 크로스헤어 피격 피드백 애니메이션 문제

👨‍💻 오늘의 개발 작업

오늘은 메르시 캐릭터의 크로스헤어 피격 피드백 애니메이션이 표시되지 않는 문제를 해결했음

활을 당겼을 때 크로스헤어 줌인/줌아웃은 정상적으로 작동하는데, 적을 맞췄을 때 피격 피드백 애니메이션만 표시되지 않는 이상한 상황이었음

알고 보니 팀원 분이 작성하신 코드를 제대로 숙지하지 못했고, 데디케이티드 서버 환경을 생각하지 못해서 생긴 오류였음


💡 오늘의 5분 기록

1. 문제 확인 - 피격 피드백 애니메이션 미표시

크로스헤어 기능을 테스트하니까 뭔가 이상했음

정상 작동하는 기능들:

  • 활 당길 때: 크로스헤어 줌인 ✅
  • 활 놓을 때: 크로스헤어 줌아웃 ✅

작동하지 않는 기능:

  • 적 피격 시: 크로스헤어 피격 피드백 애니메이션 ❌

로그를 확인해보니 화살이 적과 충돌은 감지하지만, 크로스헤어 피드백 함수가 호출되지 않고 있었음

2. 원인 파악 - 서버 전용 OnBeginOverlap과 IsLocallyControlled 충돌

첫 번째 단서: BeginOverlap 바인딩 조건

void AGS_SeekerMerciArrow::BeginPlay()
{
    Super::BeginPlay();
    if (HasAuthority()) // 서버에서만 바인딩!
    {
        if (CollisionComponent)
        {
            CollisionComponent->OnComponentBeginOverlap.AddDynamic(this, &AGS_SeekerMerciArrow::OnBeginOverlap);
        }
    }    
}

두 번째 단서: 로컬 컨트롤 체크

if (AGS_Merci* MerciPlayer = Cast<AGS_Merci>(GetOwner()))
{
    if (MerciPlayer->IsLocallyControlled()) // 데디케이티드 서버에서는 항상 false!
    {
        MerciPlayer->ShowCrosshairHitFeedback();
    }
}

핵심 문제

  • OnBeginOverlap은 서버에서만 실행됨 (HasAuthority() 조건)
  • 하지만 데디케이티드 서버에서는 어떤 플레이어도 IsLocallyControlled()가 false
  • 따라서 크로스헤어 피드백 함수가 절대 호출되지 않음

3. 왜 줌인/줌아웃은 작동했을까?

DrawBow/ReleaseArrow 함수 분석

void AGS_Merci::DrawBow(UAnimMontage* DrawMontage)
{
    if (IsLocallyControlled() && !GetDrawState()) // 클라이언트에서 먼저 실행
    {
        if (WidgetCrosshair)
        {
            WidgetCrosshair->PlayAimAnim(true); // UI 업데이트
        }
    }

    if (!HasAuthority())
    {
        Server_DrawBow(DrawMontage); // 서버로 RPC 전송
        return;
    }
    // 서버 로직...
}

차이점 발견

  • 줌인/줌아웃: 클라이언트에서 먼저 UI 업데이트 → 서버로 RPC
  • 피격 피드백: 서버에서만 감지 → 클라이언트 체크 실패

4. 해결 과정 - Client RPC 패턴 적용

기존 방식 (실패)

서버: 피격 감지 → IsLocallyControlled() 체크 → 항상 false → 함수 호출 안됨

개선된 방식 (성공)

서버: 피격 감지 → Client RPC 전송 → 해당 클라이언트: 피드백 실행

구현한 Client RPC

// GS_Merci.h
UFUNCTION(Client, Reliable)
void Client_ShowCrosshairHitFeedback();

// GS_Merci.cpp  
void AGS_Merci::Client_ShowCrosshairHitFeedback_Implementation()
{
    if (!WidgetCrosshair) return;
    
    WidgetCrosshair->PlayHitFeedback();
}

OnBeginOverlap 수정

// 기존 코드 (실패)
if (MerciPlayer->IsLocallyControlled())
{
    MerciPlayer->ShowCrosshairHitFeedback();
}

// 수정된 코드 (성공)
if (AGS_Merci* MerciPlayer = Cast<AGS_Merci>(GetOwner()))
{
    MerciPlayer->Client_ShowCrosshairHitFeedback(); // Client RPC 호출
}

5. 성과와 깨달은 점

크로스헤어 피격 피드백이 정상적으로 작동하게 되었음!

데디케이티드 서버 네트워킹 핵심 교훈

  • 서버에서 이벤트 감지 → Client RPC로 UI 업데이트
  • 클라이언트 전용 로직은 Client RPC나 클라이언트에서 직접 처리

📚 개발 참고

언리얼 네트워킹 패턴 정리

1. 클라이언트 입력 → Server RPC → 서버 로직 처리
2. 서버 이벤트 감지 → Client RPC → 클라이언트 UI 업데이트  
3. 서버 상태 변경 → Replication → 모든 클라이언트 동기화
4. Multicast RPC → 모든 클라이언트에서 동시 실행 (VFX, SFX 등)

핵심 포인트

  • HasAuthority(): 서버에서만 true, 로직 처리용
  • IsLocallyControlled(): 해당 클라이언트에서만 true, UI 처리용
  • Client RPC: 특정 클라이언트에게만 전송, UI 피드백에 최적
  • Multicast RPC: 모든 클라이언트 동시 실행, 공통 효과용
  • 데디케이티드 서버: 어떤 플레이어도 로컬 컨트롤하지 않음

참고 자료

0개의 댓글