Files
StoryGame_Unity/Assets/02_Scripts/_UI/TextGlow.cs
2026-07-30 13:20:13 +09:00

59 lines
2.5 KiB
C#

using UnityEngine;
using UnityEngine.UIElements;
// 강조 단어를 맥동시키는 글자별(정점) 효과.
//
// TextElement.PostProcessTextVertices에 연결하면 UI Toolkit이 글리프 정점을 만든 직후 이 콜백이
// 불린다. Glyph.vertices는 NativeSlice<Vertex> — 실제 메시 버퍼를 가리키는 뷰라서, Glyph가
// struct로 복사돼도 여기에 쓰면 그대로 화면에 반영된다.
//
// 대상은 <link=glow>로 표시된 글자뿐이다 (DialogMarkup이 붙인다).
// 글리프 순번을 세지 않으므로 태그·공백·줄바꿈에 전혀 영향받지 않는다 —
// Glyph에는 원본 문자 인덱스가 없어서 순번을 세는 방식은 추측에 기대야 한다.
//
// 중요: RGB만 밝게 올리고 **알파는 절대 건드리지 않는다.**
// Typewriter가 아직 안 드러난 글자를 <alpha=#00>으로 숨기는데, 알파를 만지면
// 아직 나오지 않아야 할 강조 단어가 미리 보여 버린다.
public sealed class TextGlow
{
// DialogMarkup이 붙이는 <link=...> 값. 이 값으로 강조 글자를 식별한다.
public const string LINK_ID = "glow";
private TypewriterStyle _style;
public void SetStyle(TypewriterStyle style) => _style = style;
// 맥동이 켜져 있는가 (매 프레임 MarkDirtyRepaint를 부를지 판단용)
public bool IsAnimating => _style != null && _style.GlowStrength > 0f;
public void Process(TextElement.GlyphsEnumerable glyphs)
{
var style = _style;
if (style == null || style.GlowStrength <= 0f) return;
// 0~1 왕복. unscaledTime이라 일시정지(timeScale 0) 중에도 맥동한다
float wave = 0.5f - 0.5f * Mathf.Cos(Time.unscaledTime * style.GlowSpeed * 2f * Mathf.PI);
float boost = wave * style.GlowStrength;
foreach (var glyph in glyphs)
{
if (glyph.linkID != LINK_ID) continue;
var verts = glyph.vertices;
for (int i = 0; i < verts.Length; i++)
{
var v = verts[i];
v.tint = Brighten(v.tint, boost);
verts[i] = v; // NativeSlice는 실제 버퍼를 가리키므로 이 쓰기가 화면에 반영된다
}
}
}
// 흰색 쪽으로 boost만큼 당긴다. 알파는 그대로 유지 — 숨김 상태(alpha 0)를 보존해야 한다.
private static Color32 Brighten(Color32 c, float boost) => new Color32(
(byte)(c.r + (255 - c.r) * boost),
(byte)(c.g + (255 - c.g) * boost),
(byte)(c.b + (255 - c.b) * boost),
c.a);
}