gohud

HUD

← 위젯

작은 것들이 예쁨을 정한다

큰 패널은 잘 다듬어도, 눈에 걸리는 건 언제나 작은 데다 — 두 줄로 갈라진 단어, 뚝 잘린 글로우, 비좁은 슬롯. 2026-09-13 에 지적받은 네 가지를 위젯·테마 차원에서 고쳤다. 화면(데모)이 아니라 부품을 고쳤으므로 어느 화면에서든 같은 결과다.

보였던 것원인고친 것
코치마크의 DoneDon/e 줄바꿈이 켜진 버튼은 최소 폭에서 글자 폭을 뺀다 — 좁아지면 단어 안에서 끊는다 GoStyle.fit_words(): 한 단어은 접지 않고, 여러 단어은 가장 긴 단어 폭을 보장 — 번역 키가 아니라 보이는 글자로 판단하고, 언어가 바뀌면 폼이 다시 입힌다
꽉 찬 폭 버튼의 글로우가 왼쪽에서 세로로 잘림 스크롤은 자기 경계에서 무조건 자른다. 오른쪽만 레일 자리 덕에 살아남아 좌우가 달랐다 GoScroll 이 부모 여백을 빌려 경계를 12dp 바깥으로 밀고, 안쪽에서 되돌려 내용은 제자리
슬롯에 시간·아이콘·수량이 세로로 쌓여 비좁음 48dp 안에 세 줄 아이콘은 가운데에 크게, 단축키는 왼쪽 위, 수량은 오른쪽 아래 배지, 남은 시간은 아이콘 위 배지 — 배지 패널은 스킨의 badge_box()
드롭다운 메뉴가 밋밋하고 어느 항목이 골라졌는지 안 보임 항목 호버에 버튼 패널을 쓰고, 라디오 표시는 엔진 기본(흐린 원) 항목 전용 호버 패널(옅은 강조 채움) · 체크박스와 같은 라디오 그림 · 항목 높이를 터치 기준으로
열린 드롭다운 — 옅은 강조 호버 패널과 골라진 항목의 라디오 표시
드롭다운 — 호버 패널과 라디오 표시
퀵슬롯 네 칸 — 왼쪽 위 단축키, 오른쪽 아래 수량 배지, 첫 칸은 남은 시간 배지
슬롯 — 모서리 배지와 남은 시간(sci-fi)

아이콘만 있는 버튼에는 GoStyle.icon_button(icon, action, -1, tooltip_key) 로 설명을 단다 — 마우스 사용자에게는 툴팁이, 화면 낭독기에게는 접근성 이름이 그 한 값에서 나온다. 툴팁은 gohud 가 직접 그린다: 엔진 기본 툴팁은 폭 계산이 어긋나면 글자를 한 자씩 세로로 쪼갠다(실측 폭 1dp).

코치마크 카드는 붙박이를 덮지 않는다

가로 화면에서 카드를 화면 맨 위·아래 끝으로 보내던 규칙이 문제였다 — 바로 그 자리에 헤더와 HUD 조각이 있다. 데모에서 카드가 헤더의 → × 를 덮었다. 이제 카드는 대상과 같은 높이에 놓이고, 자리를 예약한 GoHudAnchor 를 알아서 피하며, 앵커가 아닌 것은 keep_clear 로 알려 준다. 피할 때는 대상을 가리지 않는 방향 중 가장 짧게 옮기고, 다 피할 수 없으면 그대로 둔다 — 카드가 사라지는 것보다 겹치는 편이 낫다. 카드가 본문 위에 떠 있다 고 읽히도록 그림자는 스킨 다이얼 (float_shadow_size·float_shadow_lift·float_shadow_alpha, 사선 패널은 float_glow_size)로 깊이를 둔다.

tour.keep_clear = [header_bar, toolbar]   # 앵커가 아닌 붙박이

마우스와 키보드가 있는 화면

손가락에는 호버가 없다. 그래서 모바일만 보고 만들면 데스크톱에서만 나타나는 두 상태가 검증 없이 지나간다 — 실제로 gohud 의 포커스 링이 강조 버튼 위에서 보이지 않았다. 패널이 바로 강조색인데 링도 강조색이라 1.00:1, 즉 같은 색이 겹쳐 사라진 것이다. 키보드로 옮겨 다니면 "지금 어디" 를 알 수 없었다.

같은 버튼 줄의 세 상태 — 보통, 마우스 호버, 키보드 포커스
위에서부터 보통 · 호버 · 포커스. 채워진 버튼에는 패널과 대비되는 링이 붙는다.

채워진 버튼(GoPrimaryButton · GoDangerSolidButton)은 패널과 대비되는 링을 쓴다 — 어두운 테마에서는 짙은 링, 밝은 테마에서는 흰 링, sci-fi 에서는 어두운 조준 표식이다. 대비 검사가 포커스 링을 그것이 얹히는 패널 위에서 재므로, 팔레트를 바꿔도 이 규칙이 남는다 (WCAG 2.4.11 은 포커스 표시에 3:1 을 요구한다).

HUD 위젯

GoHudAnchor — 화면 아홉 자리

안전영역과 가장자리 여백을 알아서 지킨다. landscape_spot 을 주면 가로 화면에서 다른 자리로 옮겨 간다 — 세로에서 아래 가운데에 있던 조작부를 가로에서는 오른쪽 아래로 보내는 식이다.

왜 필요한가. HUD 조각마다 앵커와 오프셋을 손으로 쓰면, 노치가 있는 기기에서 체력바가 카메라 구멍에 가리고 가로 모드에서 조작 버튼이 제스처 바에 걸린다. 그 계산을 한 곳에 둔다.

본문이 HUD 뒤로 흐르지 않게

스크롤되는 화면이 HUD 와 한 화면에 있으면, 본문은 HUD 뒤를 지나간다 — 글자끼리 겹쳐 둘 다 못 읽게 된다. 이 애드온에서 실제로 두 번 일어났다: 입력칸 자리표시자가 퀵슬롯 사이로 삐져나왔고, 설정 토글의 손잡이가 체력바 위에 얹혔다.

form.avoid_hud = true                  # 보이는 GoHudAnchor 를 모두 피한다
joystick_anchor.reserve_space = false  # 손을 얹을 때만 나타나는 것은 빼고

칸마다 잃는 면적이 가장 작은 방향으로 피한다. 오른쪽 위의 체력바는 세로 화면에서는 아래로 (높이의 13%), 가로 화면에서는 옆으로(폭의 21%) 비껴간다 — 가로에서 세로로만 피하면 본문이 화면의 27% 를 잃는다. 이미 물러난 만큼은 다음 칸을 잴 때 빼고 본다.

HUD 끼리도 겹친다

아홉 자리는 화면을 나눌 뿐, 조각들이 서로 안 겹친다는 보장은 아니다. 위쪽 가운데에 뜨는 스낵바는 폭이 넓어 오른쪽 위 체력바 위에 그대로 얹혔다 — 값이 가려져 남은 체력을 읽을 수 없었다.

notice_anchor.avoid_peers = true      # 고정 HUD 아래로 내려앉는다
notice_anchor.reserve_space = false   # 떠 있는 동안 본문을 밀지도 않는다

비키는 것은 세로로만이다 — 좌우로도 밀면 가운데 정렬이던 알림이 뜰 때마다 다른 자리에 나타나 눈이 따라가지 못한다. 그리고 피하는 쪽은 비키지 않기로 한 칸뿐이다: 둘 다 서로를 피하면 두 칸이 영원히 자리를 맞바꾼다.

GoBar — 체력·마나·경험치

var hp := GoBar.new()
hp.label_text = "HP"
hp.ink = GoUi.color(GoTheme.DANGER_FILL)   # 채움 전용 토큰
hp.set_values(320, 500)                    # "320 / 500"

값 변화는 0.18초 보간한다 — 체력이 뚝뚝 끊겨 움직이면 얼마나 맞았는지 읽히지 않는다. reduce_motion 이면 즉시 바뀐다. 큰 수는 12.3k 로 줄여 막대 폭이 흔들리지 않게 한다.

GoSlot — 퀵슬롯 한 칸

아이콘·수량·쿨다운·단축키를 패널 한 장에 담는다. 작은 원 위에 배지를 따로 달면 슬롯마다 실제 폭이 달라져 줄이 흔들린다.

보이는 크기와 터치 크기가 다르다. 슬롯을 촘촘히 놓으면 48dp 터치 상자가 이웃과 겹친다. 겹친 자리는 중심이 더 가까운 슬롯이 가져간다 — touch_peers 에 서로를 넣어 주면 된다.

keyboard_focus 는 기본으로 꺼져 있다 — 퀵슬롯 여덟 칸이 Tab 순회에서 키보드 사용자와 설정 컨트롤 사이를 막지 않게. 키보드·게임패드가 유일한 입력일 때 켠다.

GoJoystick — 가상 조이스틱

모드동작
FIXED늘 같은 자리. 위치를 기억하기 쉽다
FOLLOW처음 누른 자리에 나타난다. 큰 화면에서 손가락을 옮겨도 잡힌다
RELATIVE나타난 뒤 손가락을 계속 따라간다. 오래 끄는 이동에 편하다

moved 로 오는 Vector2 는 길이가 0~1 이라 그대로 속도에 곱하면 된다 — 화면 크기나 DPI 에 따라 값이 달라지지 않는다.

GoIconButton — 작게 보이고 크게 눌린다

헤더의 닫기 버튼이 제목보다 커 보이면 안 되지만, 36dp 사각형은 손가락으로 못 누른다. 그래서 노드는 작게, 히트 판정은 노드 밖까지 넓힌다.

형제가 입력을 받지 않을 때만 안전하다. 넓힌 영역이 옆 버튼을 덮으면 옆 버튼이 안 눌린다. 아이콘 버튼을 나란히 둘 때는 touch_peers 로 서로를 알려 준다.