game/prompt/62_animation_and_feedback_s...

9.1 KiB

62. 애니메이션 및 피드백 동기화 관련 이슈 진행 현황

1. 문제 발생 현상

  • 플레이어 공격 실패(MISS) 시, 화면에 MISS 텍스트가 두 번 올라오는 현상 발생.
  • 적의 방어 실패(FAILED) 시에도 유사한 중복 텍스트 현상 발생.
  • 로그상 ([UI Debug] Feedback Event)으로는 이벤트가 한 번만 발생했지만, UI에는 두 번 표시됨.
  • 특히, "적이 방어 행동(Action Phase, Phase 1)을 했을 때만" 문제가 발생한다고 특정됨.

2. 진단 및 해결 시도

2.1. 원인 가설

  1. BattleScreen 인스턴스 중복: 가장 유력한 가설. 하나의 EffectEvent가 발생했을 때, 여러 BattleScreen 인스턴스가 각자 이벤트를 받아 화면에 피드백 텍스트를 띄우는 경우.
    • [UI Debug] BattleScreen initialized: ${hashCode} 로그로 확인 필요. 현재 로그에서는 단 한 번만 찍히는 것으로 보이나, 화면에는 중복 현상이 발생하고 있음. 이는 BattleScreen 인스턴스 자체의 중복보다는, 동일 인스턴스 내에서의 렌더링 또는 이벤트 처리 문제임을 시사.
  2. _addFloatingEffect 내부의 setState 문제 / FloatingFeedbackText 위젯의 생명주기 문제: setState 호출 시 _floatingFeedbackTexts 리스트에 위젯이 중복으로 추가되거나, 위젯 렌더링 과정에서 불필요한 복제가 발생하는 경우.
    • _floatingFeedbackTexts.clear() 로직 도입 및 eventId 기반 중복 체크로 리스트 데이터 중복은 해결. 그러나 화면상 중복 표시는 지속.
  3. UI 렌더링 타이밍/시각적 착시: FloatingFeedbackText 위젯의 생명주기가 꼬여서 이전 텍스트가 완전히 사라지기 전에 새 텍스트가 뜨거나, 애니메이션이 반복되는 것처럼 보이는 착시.

2.2. 현재까지 적용된 주요 조치

A. 피드백 텍스트 중복 출력 방지 및 디버깅 강화

  • EffectEvent eventId 기반 중복 체크 (UI 레벨): _addFloatingEffect 함수에서 eventId를 기반으로 동일한 이벤트에 대한 피드백 텍스트가 이미 리스트에 있다면 추가하지 않도록 _floatingFeedbackTexts.any((e) => e.eventId == event.id) 로직 추가.
  • _floatingFeedbackTexts.clear() 도입: 새로운 피드백 텍스트(MISS/FAILED)가 뜰 때, 기존의 모든 피드백 텍스트를 리스트에서 제거한 후 추가하도록 수정. (화면에 항상 하나의 피드백 텍스트만 유지).
  • addPostFrameCallback 제거: _addFloatingEffectWidgetsBinding.instance.addPostFrameCallback 제거 (불필요한 비동기 지연 및 잠재적 문제 방지).
  • 디버그 로그 추가:
    • [UI Debug] BattleScreen initialized: ${hashCode} (BattleScreen 초기화 횟수 확인용. 로그상 1회).
    • [UI Debug] Feedback Event: ${event.id}, Type: ${event.feedbackType} (_addFloatingEffect 호출 확인용. 로그상 1회).
    • FloatingFeedbackTextevent.id 전체 표시 (화면상 ID 일치 여부 확인용).
    • [Logic Debug] 로그 추가 (BattleProvider 이벤트 발행 시점 확인용).

B. 비동기 턴 흐름 안정화 (Transaction ID 패턴)

  • _turnTransactionId 패턴 도입: BattleProviderint _turnTransactionId를 추가하여 턴 전환 시 ID를 증가시키고, 모든 Future.delayed 콜백 내에서 ID를 체크하여 이전 턴의 "좀비 타이머"가 다음 턴 로직을 침범하지 않도록 방지.

C. 적의 방어 행동 타이밍 및 시각화 개선

  • 방어도 계산식 수정: _generateEnemyIntent에서 baseDef * 2 상수를 제거하여 Risky 방어가 과도하게 적용되지 않도록 수정.
  • "Just-in-Time" 방어 도입: 플레이어가 공격하기 직전(playerAction 시작 시)에 적이 방어(성공/실패) 행동을 발동하고, 500ms 딜레이 후 플레이어 공격 로직 진행.
  • 방어 애니메이션 추가: BattleAnimationWidgetanimateDefense 메서드 추가 (좌우 흔들림).
  • _addFloatingEffect 로직 통합:
    • showEffect는 순수 UI만 담당 (딜레이, handleImpact 호출 제거).
    • 공격/방어 애니메이션은 성공/실패 모두 실행. (animateAttack 또는 animateDefense).
    • MISS/FAILED 시에는 500ms 딜레이 후 handleImpact.
    • 적/플레이어 공격 MISSRiskLevel.safe 애니메이션 강제.
    • 적 공격 애니메이션 중 intent 정보 숨김.
    • Phase 1 방어 행동 시 딜레이 조절: skipAnimations 설정에 따라 딜레이(1.5초 또는 0.5초) 적용.

3. 남아있는 문제 (현재 진단)

  • 로그상 [UI Debug] Feedback Event는 한 번만 찍히지만, 화면에는 MISS 텍스트가 두 번 표시됨.
    • [UI Debug] BattleScreen initialized 로그도 1회만 찍히므로 BattleScreen 인스턴스 중복은 아님.
    • 이벤트가 두 번 발행되거나 _addFloatingEffect 함수가 두 번 호출되는 것도 아님. (로그 증거).
    • _floatingFeedbackTexts.clear() 로직 도입으로 리스트에 두 번 추가될 수도 없음.
    • 가장 유력한 가설: _addFloatingEffect 내부의 setState가 호출된 후, Flutter의 위젯 트리 리빌드 및 렌더링 과정에서 FloatingFeedbackText 위젯이 어떤 이유로 인해 화면에 두 번 그려지거나 (복제되거나), 또는 렌더링 과정에서 잔상이 남아 두 번으로 보이는 것.
      • 이것은 Flutter의 렌더링 파이프라인이나 Stack / Positioned 위젯의 상호작용과 관련된 심도 깊은 UI 버그일 가능성이 높음.

4. 다음 단계 제안

  • 화면상 MISS 텍스트의 ID 확인: 화면에 보이는 두 개의 MISS 텍스트의 ID가 정확히 동일한지 확인 필요 (현재 event.id 전체를 표시하도록 수정됨).
    • ID가 동일하다면: 하나의 FeedbackTextData 객체가 UI에 중복 렌더링되는 문제. (Key 문제, Stack 리빌드 문제 등)
    • ID가 다르다면: _addFloatingEffect 자체가 두 번 호출된 것. (로그가 하나라는 것과 모순됨. 로그 시스템 확인 필요)
  • Flutter DevTools (Inspector) 활용: 다른 환경에서 Widget Inspector를 통해 Stack 아래에 FloatingFeedbackText 위젯이 실제로 몇 개나 존재하는지 확인하는 것이 가장 정확합니다.

현재까지의 모든 문제 해결 노력은 BattleProvider 내의 로직 중복이나 타이밍 오류를 잡는 데 초점을 맞췄습니다. 하지만 MISS 텍스트 중복 문제는 BattleScreen (UI) 쪽에서 발생하는 현상으로 보입니다.

5. 최근 조치 및 해결 (2025-12-08 Update)

5.1. 적 방어 턴 멈춤 현상 (Hang) 해결

  • 문제: 적이 방어(Defend) 행동을 할 때, 특히 실패하거나 피드백이 있는 경우 턴이 넘어가지 않고 멈추는 현상 발생.
  • 원인: BattleProvider.handleImpact 메서드 내에 isSuccess == false 또는 feedbackType != null인 경우 조기 반환(Early Return)하는 로직이 있었는데, 여기서 ActionType.defend를 예외 처리하지 않아 방어 실패 시 턴 종료 로직(_endEnemyTurn)이 호출되지 않았음. (기존 로직이 공격 중심이라 방어 실패를 고려하지 않음).
  • 해결: handleImpact의 조기 반환 조건에 && event.type != ActionType.defend를 추가하여, 방어 이벤트는 실패하더라도 정상적인 턴 종료 로직을 타도록 수정함.

5.2. 선제 방어 (Pre-emptive Defense) 로직 재도입 및 개선

  • 배경: 기존의 "적 턴에 방어" 방식은 직관적(Intent가 보이면 바로 방어해야 함)이지 않고, "Just-in-Time" 방식(플레이어 공격 시점에 방어 끼어들기)은 "두 번 행동하는 느낌"을 주어 어색했음.
  • 해결: "턴 시작 시 방어(Start-of-Turn Defense)" 방식으로 변경.
    1. 플레이어 턴 시작 시 (_startPlayerTurn): 적의 Intent가 Defend라면, 즉시 방어도를 적용하고 로그를 출력함. (애니메이션 없음).
    2. 플레이어 행동: 이미 방어도가 올라간 적을 공격함. (끼어드는 연출 없음).
    3. 적 턴 (_startEnemyTurn): 이미 방어 행동을 했으므로, 별도의 애니메이션 없이 "방어 태세 유지" 로그만 출력하고 턴을 종료함.
  • 결과: "의도가 보이면 이미 방어 상태"라는 직관과 일치하며, 불필요한 연출 끊김 현상을 해결함.

5.3. UI 및 설정 정리

  • 디버그 로그 제거: BattleProviderBattleScreen에 남아있던 [Logic Debug], [UI Debug] 등의 print 문을 주석 처리하거나 제거하여 콘솔을 정리함.
    • 특히 화면상 MISS(1234) 처럼 ID가 노출되던 문제를 해결하기 위해 BattleScreen의 텍스트 보간 로직 수정.
  • UI 복구: 임시로 가려두었던 적의 Intent UI (CharacterStatusCard)를 다시 주석 해제하여 보이도록 복구함.
  • 설정 변경: SettingsProvider에서 _enableEnemyAnimations 기본값을 true로 변경하여 적 애니메이션이 기본적으로 켜지도록 함.