대화구조 수정

This commit is contained in:
2026-07-30 13:20:13 +09:00
parent 2413483853
commit 519e351fa5
15 changed files with 778 additions and 17 deletions

View File

@@ -28,6 +28,10 @@ public class DialogHud : MonoBehaviour
private string _speakerText = string.Empty;
private Typewriter _typewriter;
// 강조 단어([[...]])의 맥동 연출. 지금 대사에 강조가 있을 때만 매 프레임 다시 그린다.
private readonly TextGlow _glow = new();
private bool _hasEmphasis;
// 같은 버전으로 콜백이 중복 호출될 때 헛일을 막는다 (Unity 권장 패턴)
private int _uiVersion = -1;
@@ -66,9 +70,21 @@ private void OnUIReload(PanelRenderer panelRenderer, VisualElement root, int ver
_speakerName = root.Q<Label>("SpeakerName");
_dialogText = root.Q<Label>("DialogText");
// 글자별 정점 후처리 연결 — Label이 리로드로 새로 만들어질 때마다 다시 걸어야 한다
if (_dialogText != null)
_dialogText.PostProcessTextVertices = _glow.Process;
ApplyState(); // 첫 호출에선 _visible=false라 숨김 상태로 시작한다
}
// 맥동은 매 프레임 정점을 다시 만들어야 보이므로 리페인트를 요청한다.
// 강조가 없는 대사에서는 아무 일도 하지 않는다.
private void Update()
{
if (_visible && _hasEmphasis && _glow.IsAnimating && _dialogText != null)
_dialogText.MarkDirtyRepaint();
}
// 화자 이름 + 대사 표시. 대사는 style(없으면 기본 스타일)로 한 글자씩 드러난다.
// - speakerNameOverride가 비어있지 않으면 CharacterData.Name 대신 그 이름을 표시한다 (예: "???")
// - style은 이 대사만의 연출 (DialogNode.Typewriter). 둘 다 비면 한 번에 표시된다.
@@ -81,15 +97,22 @@ private void OnUIReload(PanelRenderer panelRenderer, VisualElement root, int ver
_speakerText = DialogVariables.Format(speakerName); // {key} 토큰 치환
_visible = true;
var effective = style != null ? style : _defaultStyle;
// 토큰 치환 → 강조 표기 펼치기 순서. 토큰 값 안에 [[...]]가 있어도 강조가 적용된다.
string body = DialogMarkup.Expand(DialogVariables.Format(text), effective);
_hasEmphasis = body.Contains("<link=" + TextGlow.LINK_ID);
_glow.SetStyle(effective);
// Begin이 진행 콜백으로 ApplyState를 부르므로 _visible을 먼저 세워 둔다
_typewriter.Begin(DialogVariables.Format(text), style != null ? style : _defaultStyle,
destroyCancellationToken);
_typewriter.Begin(body, effective, destroyCancellationToken);
}
public void Hide()
{
_speakerText = string.Empty;
_visible = false;
_hasEmphasis = false;
_typewriter.Clear(); // 타이핑 사운드 정지 + ApplyState 호출
}
@@ -97,7 +120,17 @@ public void Hide()
// 요소가 아직 없으면(리로드 콜백 전) 조용히 넘어가고, 콜백이 오면 같은 함수로 복원된다.
private void ApplyState()
{
if (_speakerName != null) _speakerName.text = _speakerText;
if (_speakerName != null)
{
_speakerName.text = _speakerText;
// 서술(화자 없는 대사)에서는 이름표를 감추되 **자리는 그대로 남긴다**.
// display:None은 레이아웃에서 요소를 빼버려서 DialogText(flex-grow:1)가 그 공간까지
// 차지해 대사가 위로 올라온다. visibility:Hidden은 position이 Relative인 요소의
// 공간을 유지하므로, 화자가 있으나 없으나 대사 영역 크기가 동일하다.
_speakerName.style.visibility = string.IsNullOrEmpty(_speakerText)
? Visibility.Hidden : Visibility.Visible;
}
if (_dialogText != null)
{