Chọn đúng cửa sổ bật lên
Cửa sổ bật lên nào cũng cắt ngang người chơi. Trước khi chọn, hãy trả lời đúng một câu hỏi: người chơi có buộc phải trả lời rồi game mới chạy tiếp không? Nếu có, hãy chặn lại. Nếu không, đừng bao giờ chặn — một tấm thẻ dừng cả game chỉ để báo một tin mà người chơi chẳng hề hỏi là cách nhanh nhất khiến họ tắt game của bạn.
- Một câu hỏi cần câu trả lời — mua, xóa, rời đi, ghi đè →
GoDialogs.confirm(). Một điều cần ghi nhận →alert(). Một trong vài lựa chọn →choose(). - Một màn hình riêng — cài đặt, bảng nhân vật, một món hàng trong cửa hàng →
GoSurfaceở giữa màn hình. - Một danh sách dài hoặc một trang quản lý — túi đồ, hộp thư, bạn bè →
GoSheettrồi lên từ đáy trên điện thoại,GoDrawertrượt vào từ bên hông trên màn hình rộng. - Điều gì đó về thứ vừa được nhấn — chỉ số của một món đồ, các thao tác với nó →
GoPopoverhoặcGoContextMenungay bên cạnh. - Tin tức mà game vẫn chạy tiếp được — đã lưu, đã vứt bỏ, được mời →
GoSnackbar,GoNoticehoặcGoPromptCard. Những thứ này không bao giờ chiếm lấy màn hình. - Dạy cách điều khiển →
GoCoachMark. Lệnh cheat trong lúc phát triển →GoConsole.
Mọi cửa sổ bật lên trong một bảng
| Cửa sổ bật lên | Dừng game | Back / Escape | Chạm ra ngoài | Dùng cho |
|---|---|---|---|---|
GoDialogs confirm · alert | Có | Đóng lại — confirm trả lời false, alert trả về | Chỉ đóng khi có dismiss_on_scrim | Câu hỏi cần câu trả lời |
GoDialogs.choose | Có | Đóng lại — trả lời -1 | Chỉ đóng khi có dismiss_on_scrim | Chọn một trong vài thứ |
GoSurface | Có | Yêu cầu đóng cửa sổ trên cùng | Yêu cầu đóng khi có dismiss_on_scrim | Cửa sổ của riêng bạn |
GoSheet | Có | Đóng cả bảng trượt | Đóng lại | Túi đồ, danh sách cửa hàng, hộp thư |
GoDrawer | Có | Chỉ nút Back của Android | Đóng lại | Danh sách bên hông trên màn hình rộng |
GoPopover | Cho tới khi đóng | Đóng lại | Đóng lại — và không đi tiếp xuống dưới | Chi tiết vật phẩm và kỹ năng |
Danh sách của GoCombobox | Cho tới khi đóng | Đóng lại | Đóng lại | Danh sách chọn có tìm kiếm |
GoContextMenu · danh sách thả xuống · ô chọn | Menu của engine | Menu của engine | Đóng lại | Thao tác, danh sách ngắn |
GoSnackbar | Không | — | Tới được game | Đã lưu · Hoàn tác · Thử lại |
GoPromptCard | Không | — | Tới được game | Lời mời, yêu cầu giao dịch |
GoNotice | Không bao giờ | — | Tới được game | Một dòng cố định trên HUD |
GoBanner | Không — nó nằm ngay trong trang | — | — | "Bạn đang ngoại tuyến. Thử lại" |
GoCoachMark | Chỉ tấm thẻ của nó | Kết thúc chuyến tham quan | Tới được game | Chuyến tham quan ở lần chạy đầu |
GoConsole | Chỉ tấm bảng của nó | Phím Escape đóng nó | Tới được game | Lệnh debug |
Mỗi cái mở trên lớp nào, và vì sao lại theo thứ tự đó, xem ở Lớp, Back và thao tác nhập.

GoDialogs
GoSurface
GoSheet
GoDrawer
GoPopover
GoContextMenu
GoSnackbar
GoPromptCard
GoCoachMark
GoConsoleMọi ảnh trên trang này đều là cùng một game nhỏ — máu và mana ở góc trên bên trái, xu ở góc trên bên phải, cần điều khiển và ba
ô nhanh ở dưới — trong default_dark, với một cửa sổ bật lên đang mở phía trên. Cùng cửa sổ
bật lên đó trong cả mười bốn diện mạo →
Cấu tạo của một cửa sổ bật lên
Gần như mọi cửa sổ bật lên có chặn trong gohud — hộp thoại, bảng trượt, popover, danh sách của combobox và cửa sổ
của riêng bạn — đều là một node duy nhất, GoSurface. Học các phần của nó một lần là bạn biết tất cả.

| Phần | Là gì | Chạm tới bằng cách nào |
|---|---|---|
| Lớp | CanvasLayer mà cửa sổ bật lên nằm trên. Số của nó quyết định nó che những gì. | Do bạn đặt với GoSurface; có sẵn với những cái còn lại (bảng) |
| Lớp phủ mờ | Tấm màn phủ lên mọi thứ phía sau. Nó hứng những cú chạm trượt ra ngoài tấm thẻ. | scrim_transparent, dismiss_on_scrim, màu scrim của theme |
| Tấm thẻ | Chính cái cửa sổ — mặt tấm nền của theme, bo tròn, vát góc, rèn hay như kẹo thạch tùy diện mạo. | card, alpha |
| Phần đầu | Nút quay lại (tùy chọn), tiêu đề (tối đa hai dòng, sau đó là "…"), nút đóng. | set_title() · set_title_key() · set_back() · close_enabled · show_header |
| Thanh công cụ | Một hàng ghim ngay dưới phần đầu — ô tìm kiếm, chip lọc. Ẩn cho tới khi bạn dùng. | toolbar |
| Phần thân | Phần cuộn được. Nội dung đặt ở đây. | body · scroll_body · clear() |
| Dòng trạng thái | Một dòng ghim giữa phần thân và chân trang — "Kiểm tra tên thất bại", "Đang lưu…". | set_status_text() · set_status_key() · clear_status() |
| Chân trang | Các nút ghim ở đáy. Ẩn cho tới khi bạn dùng. | footer |
toolbar, dòng trạng thái và footer; chúng đứng yên trong khi phần thân cuộn.
Hộp thoại mà bạn await
GoDialogs hỏi rồi chờ. Một node, ba kiểu câu hỏi — và câu trả lời quay về ngay trên cùng dòng
đó, nên code đọc theo đúng thứ tự mà người chơi trải qua.
var dialogs := GoDialogs.new()
add_child(dialogs)
if await dialogs.confirm("Buy the starter pack?", "300 gems for a sword, a shield and 10 potions.",
"Buy", "Not now"):
buy_starter_pack()

confirm()
confirm(…, true) — không thể hoàn tác
alert()
choose()
ActionLayout.HORIZONTALBa kiểu câu hỏi
confirm(title, body, ok_text := "", cancel_text := "", extra := "",
args := {}, destructive := false) -> bool
alert(title, body, ok_text := "", extra := "", args := {}) # await nó để chờ
choose(title, options: Array, cancel_text := "", translate := false) -> int
confirm_key(…) · alert_key(…) # khóa dịch
| Lệnh gọi | Trả về | Dùng cho |
|---|---|---|
confirm() | true hoặc false | Có hay không — mua, xóa, rời đi, ghi đè. |
alert() | không gì cả — await nó để chờ | Điều người chơi buộc phải thấy: một phần thưởng, một lần mất kết nối. |
choose() | chỉ số của lựa chọn, hoặc -1 | Một trong vài thứ. Mỗi lựa chọn là một String hoặc {text, icon, danger, subtitle}. |
confirm_key() · alert_key() | như trên | Tương tự nhưng dùng khóa dịch — chữ đổi theo khi đổi ngôn ngữ trong lúc hộp thoại đang mở. |
Nhãn nút để trống sẽ lùi về chữ của chính bộ kit (confirm, cancel trong GoConfig.text_keys).
extra được thêm vào phần thân trên một dòng mới. args điền các {placeholders} trong tiêu đề
và phần thân: confirm("Buy {item}?", "It costs {price} gold.", "Buy", "", "", {"item": name, "price": 300}).
Lối thoát nào cũng có câu trả lời
| Người chơi… | confirm | alert | choose |
|---|---|---|---|
| nhấn OK / một lựa chọn | true | trả về | chỉ số của lựa chọn |
| nhấn Hủy | false | — | -1 |
| nhấn ×, Escape hoặc nút Back của Android | false | trả về | -1 |
chạm vào lớp phủ mờ (chỉ khi có dismiss_on_scrim) | false | trả về | -1 |
node GoDialogs rời khỏi cây | false | trả về | -1 |
Vì vậy code sau await luôn chạy — không câu trả lời nào bị treo lơ lửng, kể cả khi scene đổi ngay dưới hộp thoại.
Thao tác không thể hoàn tác
Hãy truyền destructive = true cho việc không thể hoàn tác. Nút OK trở thành nút tô đặc
Tone.DANGER_SOLID — chữ trắng trên nền đỏ ở mọi diện mạo, trong khi một nhãn nguy hiểm chỉ pha màu sẽ phải tối
dần tới gần đen trên theme sáng mới đủ dễ đọc. Sắc thái được đặt lại ở mỗi lần gọi, vì cửa sổ được tái sử dụng.
if await dialogs.confirm("Delete this save?", "Slot 2 — Aria, level 12. This cannot be undone.",
"Delete", "Keep it", "", {}, true):
delete_save(2)
Bố cục nút
action_layout | Làm gì |
|---|---|
VERTICAL (mặc định) | Các nút xếp chồng, rộng hết cỡ. An toàn với mọi ngôn ngữ — một nhãn tiếng Đức hay tiếng Nga dài không bao giờ bị ép. |
HORIZONTAL | Một hàng, mỗi nút rộng một nửa. |
AUTO | Chỉ dùng một hàng khi cả hai nhãn vừa nửa tấm thẻ trên một dòng; còn không thì xếp chồng. Được quyết định lại khi đổi ngôn ngữ hoặc đổi kích thước. |
action_gap là khoảng cách giữa các nút, body_gap là khoảng trống phía trên chúng — hãy để nó rộng hơn
khoảng cách giữa các nút để câu hỏi và lựa chọn đọc ra thành hai phần. Nếu chỉ muốn áp cho một hộp thoại, hãy gọi
set_next_action_layout() trước khi mở nó. Nút duy nhất của một alert luôn rộng hết cỡ.
Mỗi lúc một cái
Một confirm() thứ hai trong lúc cái trước còn mở sẽ trả về false ngay — hộp thoại không bao giờ vô
tình chồng lên hộp thoại. Bật queue_when_busy thì nó xếp hàng chờ tới lượt; alert() thì luôn chờ.
queue_limit (8) giới hạn độ dài hàng chờ, và khi tràn thì yêu cầu cũ nhất được trả lời false.
pending() đếm các yêu cầu đang chờ và clear_pending() trả lời false cho tất cả.
choose() không bao giờ xếp hàng: nó trả về -1 khi đã có một hộp thoại đang mở.
Điện thoại và máy để bàn
choose() mở ra thành một bảng trượt từ đáy trên Android và iOS — nơi ngón cái ở — và thành một tấm thẻ ở
giữa trên máy để bàn. Một confirm bị giới hạn ở max_width (420dp) và canh giữa. Hãy đặt layer_index,
max_width và các export khác trước add_child; chúng được áp khi node vào cây.
var picked := await dialogs.choose("Sort the bag by", [
{"text": "Newest first", "icon": GoIconSet.CLOCK},
{"text": "Rarity", "icon": GoIconSet.STAR},
{"text": "Drop everything", "icon": GoIconSet.TRASH, "danger": true}])
if picked >= 0:
sort_bag(picked)
GoSurface — cửa sổ của riêng bạn
Khi một cửa sổ bật lên không chỉ là một câu hỏi — cài đặt, bảng nhân vật, một món đồ để mua — hãy dựng nó trên
GoSurface. Vùng an toàn, bàn phím ảo, hàng ghim, cuộn, Back, focus và đổi kích thước đều đã được lo sẵn.
var layer := CanvasLayer.new()
layer.layer = 50
add_child(layer)
var window := GoSurface.new()
window.set_title("Settings")
window.close_requested.connect(layer.queue_free)
window.body.add_child(GoStyle.toggle("Music", false))
window.body.add_child(GoStyle.slider(0.0, 1.0, 0.05))
var row := GoStyle.row()
row.add_child(GoStyle.button("Cancel", window.request_close))
row.add_child(GoStyle.button("Save", save_and_close, GoStyle.Tone.PRIMARY))
window.footer.add_child(row)
window.footer.visible = true
layer.add_child(window)
close_requested. Ẩn hay giải phóng
cửa sổ là việc của màn hình sở hữu nó — chỉ màn hình đó biết "đóng" nghĩa là lưu, bỏ đi, hay hỏi lại trước.
Ba vị trí đặt

CENTER — xác nhận, cài đặt
BOTTOM — danh sách, trang quản lý
ANCHOR — cạnh một control
GoDatePicker trong cửa sổ CENTERANCHOR cần anchor_control. Tấm thẻ nằm bên phải nó nếu anchor_min_width vừa,
không thì sang phía rộng hơn, không nữa thì vào trong màn hình, và mở lên trên khi phía trên còn nhiều chỗ hơn. Nó bám theo
một điểm neo đang di chuyển ở mỗi khung hình. anchor_width (320) và anchor_max_height (520) quyết định kích thước.
Nó to tới đâu
| Số đo | Quy tắc | Đổi bằng |
|---|---|---|
| Khoảng trống | Vùng an toàn, trừ bàn phím, trừ lề màn hình (16dp, không bao giờ quá 10% cạnh ngắn). | — |
| Chiều rộng | 94% khoảng trống khi cầm dọc, 72% khi cầm ngang, tối đa 480dp. | max_width · GoConfig.surface_width_ratio_* |
| Chiều cao | Cao bằng nội dung (fit_content), tới 68% khoảng trống, chặn ở 72%. Một tấm thẻ canh giữa không đổi kích thước được có thể cao tới 94% để khỏi phải cuộn. | height_ratio · max_height_ratio · max_height |
| Mật độ | Thu gọn khi đặt compact, khi màn hình thấp dưới 420dp (điện thoại nằm ngang), hoặc khi nội dung không vừa: ít đệm hơn, khoảng cách nhỏ hơn, tiêu đề nhỏ hơn. Nó đo cả hai cách và giữ cách rộng rãi hơn mà vẫn vừa — không bị giật. | compact |
| Đổi kích thước | Với resizable, tiêu đề là tay nắm: kéo nó trong khoảng từ 30% tới mức chặn. height_changed(ratio) báo kết quả. | resizable |
Tỉ lệ vượt mức chặn sẽ bị cắt về, kèm một cảnh báo trong bản build debug. Bàn phím đẩy tấm thẻ lên và cuộn ô đang có focus vào tầm nhìn; vùng an toàn giữ nó tránh xa tai thỏ và thanh cử chỉ trên Android và iOS.
Thuộc tính
| Thuộc tính | Mặc định | Làm gì |
|---|---|---|
placement | CENTER | CENTER, BOTTOM hoặc ANCHOR |
dismiss_on_scrim | GoConfig.dismiss_on_scrim (false) | Chạm vào lớp phủ mờ là yêu cầu đóng. Một cú kéo bắt đầu bên trong tấm thẻ thì không. |
scrim_transparent | false | Giữ cho game vẫn nhìn thấy được — lớp phủ mờ vẫn hứng các cú chạm bên ngoài. |
close_enabled | true | Hiện × và cho Escape, Back yêu cầu đóng. |
show_header · scroll_body · resizable | true · true · false | Hàng đầu, phần thân cuộn được, tay nắm để kéo. |
fit_content · compact | true · false | Kích thước theo nội dung; bắt đầu ở dạng thu gọn. |
fade_in | GoConfig.surface_fade_in (false) | Cho tấm thẻ hiện dần khi mở. Tức thì khi có reduce_motion. |
alpha | -1 (của theme) | Độ đục của mặt tấm thẻ, dạng tỉ lệ. Chỉ mặt thẻ mờ đi — chữ thì không bao giờ. |
initial_focus | không có | Control nhận focus khi mở. |
add_child: scrim_transparent, show_header, resizable và
scroll_body được áp khi cửa sổ vào cây. dismiss_on_scrim, close_enabled,
fade_in và initial_focus được đọc trực tiếp lúc chạy.
Tín hiệu và lệnh gọi
| Tên | Là gì |
|---|---|
close_requested | ×, Escape, nút Back của Android hoặc cú chạm vào lớp phủ mờ — và request_close(). Chỉ cửa sổ trên cùng phát ra. |
back_requested · set_back(action) | Nút quay lại ở phần đầu, cho cửa sổ có nhiều trang. Một Callable rỗng sẽ ẩn nó. |
height_changed(ratio) | Sau một cú kéo tay nắm. |
is_top() · GoSurface.is_any_open() | Đây có phải cửa sổ nhận Back không — có cửa sổ nào đang mở không. |
relayout() | Dàn lại bố cục ngay bây giờ. Đổi một thuộc tính kích thước thì nó đã tự làm việc này. |
Bảng trượt và ngăn kéo
Một danh sách cần chỗ sẽ trồi lên từ đáy trên điện thoại cầm dọc — nơi ngón cái ở — và trượt vào từ bên hông trên máy tính bảng hay máy để bàn, nơi một bảng trượt sẽ che mất game.

GoSheet
GoDrawer · LEFT
GoDrawer · RIGHTGoSheet — các trang từ dưới lên
var sheet := GoSheet.new()
add_child(sheet)
func show_bag(items: Array) -> void:
sheet.open("Bag")
var search := GoStyle.line_edit("Search the bag…")
search.text_changed.connect(filter_bag)
sheet.toolbar().add_child(search)
sheet.toolbar().visible = true
for item in items:
sheet.body.add_child(GoStyle.list_button(GoIconSet.BOX, item.name, show_item.bind(item),
Color.TRANSPARENT, item.description, false, GoIconSet.CHEVRON_RIGHT))
sheet.add_footer(GoStyle.button("Close", sheet.close, GoStyle.Tone.PRIMARY))
- Nó là một
CanvasLayer(lớp 10) chứa mộtGoSurfacekiểuBOTTOMđổi kích thước được — nên nó nằm trên HUD, và người chơi có thể kéo nó cao hơn. open(title)bắt đầu một trang mới: nó dọn phần thân, thanh công cụ và nút quay lại, và giải phóng những gìadd_footer()đã thêm vào. Gọi lại để hiện một trang khác trong cùng bảng trượt;set_back()hiện nút quay lại để trở về trang đầu.open_key()nhận một khóa dịch.close()ẩn nó đi — không giải phóng, nên lầnopen()tiếp theo là tức thì.closedvàpage_changedbáo cho bạn biết khi nào.height_ratio(0.6) là chiều cao lúc mở;max_height_rationâng mức chặn;dismissable(true) đóng nó khi chạm vào lớp phủ mờ.
set_back() — chỉ nút quay lại trên màn hình
mới lùi về một trang. Nếu muốn Back lùi một bước, hãy tự xử lý close_requested trên sheet.surface.
GoDrawer — từ bên hông
var drawer := GoDrawer.new()
drawer.side = GoDrawer.Side.LEFT
drawer.follow_text_direction = true # phía bắt đầu: bên trái, hoặc bên phải trong tiếng Ả Rập
add_child(drawer)
drawer.body.add_child(GoNavBar.drawer_list([{"icon": GoIconSet.HOME, "text": "Town"},
{"icon": GoIconSet.MAP, "text": "World map"}], 0, go_to))
drawer.open("Menu")
- Lớp 80. Rộng 42% màn hình (
width_ratio), trong khoảngmin_width(320dp) tớimax_width(420dp) — trên điện thoại nó che gần hết màn hình, đó là lý do bảng trượt là lựa chọn tốt hơn ở đó. - Tấm nền chạm tới mép màn hình; chỉ nội dung lùi vào trong vùng an toàn, nên một góc bo tròn không bao giờ cắt mất hàng đầu tiên.
LEFTnghĩa là bên trái màn hình, kể cả trong tiếng Ả Rập. Hãy bậtfollow_text_directionkhi điều quan trọng là "phía bắt đầu";effective_side()cho bạn biết nó đã nằm ở đâu.open(title)giữ nguyên phần thân — chỉ cần điền một lần.set_title("")ẩn phần đầu.motion_seconds(0.2) là thời gian trượt;reduce_motionlàm nó tức thì.
drawer.close() từ phím tắt của riêng bạn — và lưu ý rằng GoSurface.is_any_open() không tính ngăn kéo;
hãy hỏi drawer.is_open().
Thẻ và menu bên cạnh một control
Thứ người chơi vừa nhấn xứng đáng có câu trả lời ngay cạnh đầu ngón tay, chứ không phải ở giữa màn hình.

GoPopover
GoContextMenu
GoStyle.dropdown()
GoStyle.select()
GoComboboxGoPopover — tấm thẻ trên thứ vừa được nhấn
slot.pressed.connect(func() -> void:
var card := GoStyle.item_card({"icon": GoIconSet.POTION, "title": "Health potion",
"subtitle": "Heals 50 HP · 12 left"}, false)
GoPopover.open(slot, card, {"width": 280}))
Một cửa sổ kiểu ANCHOR với lớp, điểm neo và phần dọn dẹp đã nối sẵn — mười dòng mà game nào cũng viết đi viết
lại. Mỗi lúc một cái: mở cái thứ hai sẽ đóng cái thứ nhất, vì những tấm thẻ chồng lên nhau sẽ chôn mất màn hình và chẳng
ai biết tấm nào thuộc về ô nào. Lớp phủ mờ thì trong suốt — một tấm thẻ mở ra để so trang bị không được làm tối chính món trang bị đó —
nhưng nó vẫn hứng cú chạm đầu tiên bên ngoài, cú chạm đó đóng tấm thẻ và không đi tiếp xuống dưới.
| Tùy chọn | Mặc định | Làm gì |
|---|---|---|
title | "" | Phần đầu có ×. Để trống là không có phần đầu. |
width · max_height | 320 · 520 | Kích thước của tấm thẻ. |
dismissable | true | Chạm ra ngoài là đóng. |
compact · alpha · translate | false · -1 · false | Khoảng cách thu gọn, độ đục của mặt thẻ, tiêu đề là khóa dịch. |
layer | 95 | CanvasLayer mà nó mở lên. |
GoPopover.close() và GoPopover.is_open() là hàm tĩnh. open() trả về cửa sổ đó — hoặc
null khi điểm neo không nằm trong cây, nên hãy kiểm tra trước khi await shown.close_requested.
dismissable = false mà không có tiêu đề, chẳng có gì trên màn hình đóng được nó — chỉ Escape, Back hoặc lệnh gọi của chính bạn.
GoContextMenu — nhấn giữ và bấm chuột phải
var menu := [
{"text": "Use", "icon": GoIconSet.POTION, "action": use},
{"separator": true},
{"text": "Drop", "icon": GoIconSet.TRASH, "action": drop, "danger": true}]
GoContextMenu.attach(slot, menu)
var more := GoStyle.icon_button(GoIconSet.MORE, Callable(), -1, &"more")
more.pressed.connect(func() -> void: GoContextMenu.open_at(more, menu))
- Bấm chuột phải là mở ngay. Ngón tay giữ nửa giây (
HOLD_SECONDS) là mở; di chuyển quá 12dp thì hủy — nên cuộn một danh sách không bao giờ làm bật ra menu. - Mỗi mục là
{text, action, icon, disabled, checked, separator, danger, translate}. Hãy truyền mộtCallablethay cho mảng khi các mục phụ thuộc vào thứ vừa được nhấn; nó được gọi mỗi lần menu mở. dangerđặt "⚠ " trước chữ — menu của engine chỉ tô màu được tất cả các mục cùng một lúc.- Đây là
PopupMenucủa engine, đượcGoStyle.style_popup()khoác áo. Nó không được tính là một cửa sổ đang mở.
⋯ nhìn thấy được
(open_at(), như ở trên), hoặc dạy nó một lần bằng coach mark.
Danh sách thả xuống, ô chọn và nút tách đôi
var sort := GoStyle.dropdown("Sort", ["Highest score", "Most wins", {"text": "Friends only", "disabled": true}],
func(index: int) -> void: sort_board(index))
var quality := GoStyle.select(["Low", "Medium", "High", "Ultra"], "Quality")
quality.item_selected.connect(func(index: int) -> void: settings.quality = index)
var send := GoSplitButton.make("Send", send_now, ["Send later", "Save as draft"])
Cả ba đều mở menu bật lên của engine: dropdown() là một MenuButton chạy một thao tác,
select() là một OptionButton giữ một giá trị, GoSplitButton là một thao tác chính cộng một menu trên
mũi tên của nó (chosen(index)). Menu cố ý giữ đục hoàn toàn — một menu hiện trong cửa sổ OS riêng sẽ biến các
pixel trong suốt thành màu đen.
GoCombobox — danh sách có tìm kiếm
var friend := GoCombobox.make(friend_names, -1, "Find a friend")
friend.picked.connect(func(index: int) -> void: invite(friend_names[index]))
Khi được nhấn, nó mở một danh sách neo trên lớp 96 với lớp phủ mờ trong suốt. Từ search_threshold (8) mục trở lên, một
ô tìm kiếm xuất hiện và nhận focus; nó khớp ở bất kỳ đâu trong chữ hoặc gợi ý, không phân biệt hoa thường, và khi không có
kết quả thì nó nói rõ. list_width (0 = bằng chiều rộng của nút, tối thiểu 180dp) quyết định chiều rộng danh sách.
Cửa sổ bật lên không bao giờ dừng game
Tin tức mà người chơi đọc được ngay trong lúc chơi. Không cái nào có lớp phủ mờ; chạm vào bất cứ đâu ngoài chính tấm thẻ là đi thẳng tới game.

GoSnackbar
GoNotice
GoPromptCard
GoStyle.tooltip_node()GoSnackbar — lời nhắn kèm nút Hoàn tác
var snack := GoSnackbar.new()
add_child(snack)
if await snack.post({"text": "Dropped {name}", "args": {"name": item.name}, "icon": GoIconSet.TRASH,
"actions": ["Undo"], "seconds": 5.0}) == 0:
restore(item)
Khóa của post() | Làm gì |
|---|---|
text · title · args · translate | Lời nhắn, một dòng in đậm tùy chọn, các {placeholders}, khóa dịch. |
tone · icon | Màu nhấn (GoTheme.SUCCESS, WARNING…) và một biểu tượng. |
actions · closable | Các nút — String hoặc {text, action} — và một nút ×. |
seconds | Nó ở lại bao lâu. Số âm: notice_duration_ms của theme. 0: cho tới khi bị gạt đi — khi đó hãy cho nó một nút. |
post() trả về chỉ số của nút được nhấn, hoặc -1 khi hết giờ hoặc bị gạt đi. Các lần post
xếp hàng (queue_limit 4, cái cũ nhất bị bỏ với -1), và cùng một chữ được post lại trong lúc đang hiện thì chỉ nạp lại
bộ đếm giờ. Nó nằm ở đáy (hoặc ở đỉnh, placement), phía trên vùng an toàn và bàn phím, trên lớp 90 — vẫn đọc được
khi nằm trên ngăn kéo. Với tap_to_dismiss (bật), chạm vào một tấm thẻ không có nút là đóng nó; chạm ra ngoài tấm thẻ
thì luôn tới được game. show_text() và show_key() là dạng viết một dòng.
GoNotice — một dòng trên HUD
var notice := GoNotice.new()
hud_corner.add_child(notice)
notice.show_text("Game saved", GoTheme.SUCCESS)
notice.custom_minimum_size.x = notice.preferred_width(340)
Một tấm nền đơn giản do bạn tự đặt — trong một GoHudAnchor, ở một góc, ở bất cứ đâu. Nó không nhận thao tác lẫn focus,
nên nhấn xuyên qua nó sẽ tới nút nằm bên dưới, và lời nhắn mới thay thế lời nhắn cũ. Hãy cho nó một chiều rộng:
preferred_width(limit) trả về chiều rộng tự nhiên của chữ ngắn và cho chữ dài xuống dòng trong limit.
set_content() hiện một control bất kỳ thay cho chữ.
GoPromptCard — câu hỏi có thể chờ
var spot := GoHudAnchor.new()
spot.spot = GoHudAnchor.Spot.TOP_CENTER
spot.reserve_space = false
spot.avoid_peers = true # lùi xuống dưới các thanh máu thay vì che chúng
hud.add_child(spot)
var invite := GoPromptCard.new()
spot.add_child(invite)
invite.set_icon(GoIconSet.USERS, Color.TRANSPARENT, true)
invite.set_title("Party invite from Ann")
invite.set_subtitle("Dragon's Lair · 3 / 4 members")
invite.set_actions([{"text": "Accept", "action": accept, "primary": true},
{"text": "Decline", "action": invite.hide}])
invite.fit_width(340)
invite.show()
get_tree().create_timer(15.0).timeout.connect(invite.hide)
- Nó bắt đầu ở trạng thái ẩn — hãy gọi
show(). Nó không có bộ đếm giờ riêng; hãy thêm một cái nếu câu hỏi có hạn. - Các nút của nó không đóng nó — các thao tác tự quyết định. × (
set_closable(true)) phátclosed. fit_width(width)đặt chiều rộng và dự đoán chiều cao từ font, nên không thứ gì được dàn theo nó bị nhảy một khung hình sau đó.- Mặt của nó theo
hud_alpha— nó là một phần của HUD, không phải một cửa sổ.
GoBanner và tooltip
GoBanner.make("You're offline.", [{"text": "Retry", "action": reconnect}], GoIconSet.WARNING) nằm ngay trong
trang, phía trên nội dung, và không bao giờ hết giờ; một nút sẽ gạt nó đi trừ khi nó có "keep": true.
GoStyle.tooltip_node(text) là thứ cần trả về từ _make_custom_tooltip() — engine vẽ tấm nền
(TooltipPanel), gohud vẽ chữ, xuống dòng khi quá 260dp. Tooltip là dành cho chuột; đừng bao giờ giấu thứ người chơi cần phía sau một tooltip.
Chuyến tham quan và bảng điều khiển

GoCoachMark
GoConsoleGoCoachMark — chuyến tham quan các control thật
var tour := GoCoachMark.new()
tour.keep_clear = [top_bar]
tour_layer.add_child(tour)
tour.finished.connect(func(completed: bool) -> void: save.tutorial_done = true)
tour.start([{"target": bag_button, "title": "Bag", "body": "Everything you pick up lands here."},
{"target": map_button, "title": "Map", "body": "Tap to open the full map."}])
- Một vòng sáng quanh control thật và một tấm thẻ bên cạnh. Nhấn vào mục tiêu là sang bước kế (tín hiệu
pressedcủa nó, hoặc"signal"trong bước); Tiếp và Bỏ qua nằm trên tấm thẻ. Mục tiêu đang ẩn thì được bỏ qua. - Tấm thẻ tránh khỏi giữa màn hình (
avoid_center_band), tránh các điểm neo HUD và tránhkeep_clear. - Trong lúc có bất kỳ
GoSurfacenào đang mở, chuyến tham quan ẩn đi và chờ. Back hoặc Escape kết thúc nó vớifinished(false). - Nó không có lớp riêng — hãy thêm nó sau cùng, hoặc trên một lớp nằm trên HUD.
GoConsole — lệnh cheat trong lúc phát triển
var console := GoConsole.new()
add_child(console)
console.register("give", "give <item> <count>", func(args: PackedStringArray) -> String:
return "gave %s x%s" % [args[0], args[1]] if args.size() >= 2 else "give <item> <count>")
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed(&"dev_console"): console.toggle()
Lớp 200 — trên tất cả, nên bạn debug được ngay trên một hộp thoại — và nó vẫn chạy khi cây đang tạm dừng. Một bảng lệnh gợi ý lệnh khi bạn gõ; mũi tên Lên và Xuống duyệt lịch sử; Escape đóng nó. Không có phím mặc định: hãy tự gán phím của bạn.
debug_only (bật) chặn nó trong bản build release — cả open() lẫn run(). Lệnh cheat lọt vào
tay người chơi sẽ kết liễu nền kinh tế của một game ngay trong ngày đầu tiên.
Lớp, Back và thao tác nhập
Ba quy tắc quyết định người chơi chạm được vào đâu khi có thứ gì đó đang mở: cái gì nằm trên cùng, ai sở hữu Back, và ai nhận cú chạm.
Cái gì nằm trên cùng
| Lớp | Thứ nằm ở đó | Đặt bằng |
|---|---|---|
| 5 | HUD của bạn (tùy bạn chọn) | — |
| 10 | GoSheet | CanvasLayer.layer |
| ~50 | Các cửa sổ GoSurface của riêng bạn, menu tạm dừng | CanvasLayer của bạn |
| 80 | GoDrawer | CanvasLayer.layer |
| 90 | GoSnackbar | layer_index |
| 95 | GoPopover | tùy chọn "layer" |
| 96 | Danh sách của GoCombobox | — |
| 100 | GoDialogs | layer_index |
| 200 | GoConsole | layer_index |
Thứ tự này là có chủ ý: snackbar báo điều vừa xảy ra, nên nó phải đọc được khi nằm trên ngăn kéo; popover thuộc về thứ vừa được nhấn, nên nó nằm trên thứ đó; hộp thoại đặt một câu hỏi và che cả hai; bảng điều khiển che tất cả, vì đó là thứ bạn mở ra khi mọi thứ khác đã hỏng.
Ai sở hữu Back
- Chỉ cửa sổ trên cùng trả lời. Trong các cửa sổ đang mở, cái nằm trên lớp cao nhất (rồi tới cái mới nhất trong cây)
nhận Escape, nút hủy của tay cầm (
ui_cancel), nút Back của Android và cú chạm vào lớp phủ mờ. Các cửa sổ xếp chồng đóng từng cái một, không bao giờ cùng lúc. - Nút Back của Android được xử lý trễ một khung hình, nên một lần nhấn không bao giờ đóng được hai cửa sổ.
- Trong lúc còn bất kỳ thứ gì sở hữu Back đang mở, nút Back của Android không thoát ứng dụng (
GoBackPolicyđếm số chủ sở hữu và khôi phụcquit_on_go_backsau khi cái cuối cùng đóng). Cửa sổ bật lên của riêng bạn mà nhận Back thì nên gọiGoBackPolicy.acquire()vàrelease()— cả trong_exit_tree. - Ngăn kéo, chuyến tham quan coach mark và biểu mẫu sẽ nhường bước khi có một cửa sổ đang mở — Back đi tới cửa sổ đó trước.
GoConfig.close_on_back(bật) là công tắc dành cho các cửa sổ.
Ai nhận cú chạm
- Một cửa sổ phủ toàn màn hình và chặn thao tác — kể cả lớp phủ mờ trong suốt. Không thứ gì trên lớp thấp hơn bị nhấn trong lúc nó mở; các lớp phía trên nó (một snackbar) thì vẫn nhấn được.
- Cuộn chuột và cử chỉ chụm ngón cũng dừng lại ở cửa sổ: cuộn quá cuối danh sách không bao giờ làm cuộn game.
- Thao tác chơi không đi qua control —
_unhandled_inputcho việc di chuyển, kéo camera — nên tạm ngưng:if GoSurface.is_any_open(): return. Điều đó bao trùm hộp thoại, bảng trượt, popover và combobox; với ngăn kéo thì hỏi riêngis_open(). - Snackbar, notice, prompt card và coach mark chỉ nhận thao tác trên chính tấm thẻ của chúng; ở mọi chỗ khác, game nhận.
Focus
Cửa sổ mở bằng con trỏ thì không focus vào đâu cả — không có vòng focus nào lóe lên khi chạm. Mở từ bàn phím hoặc tay cầm thì nút đóng
(hoặc initial_focus) nhận focus. Focus ở yên trong cửa sổ khi nó còn mở và trở về control đã giữ nó
khi cửa sổ đóng. Hộp thoại lần nào cũng focus vào OK.
Tạm dừng game
Tạm dừng cây thì cửa sổ bật lên cũng tạm dừng — chỉ bảng điều khiển vẫn hoạt động. Với menu tạm dừng, hãy cho lớp của nó
process_mode = PROCESS_MODE_ALWAYS; khi đó cửa sổ, các nút của nó và Back vẫn chạy trong lúc game dừng.
Cùng một cửa sổ bật lên ở mọi diện mạo
Hộp thoại xác nhận ở đầu trang này, trên cùng một game, chỉ đổi preset. Code y hệt nhau — theme, skin và biểu tượng lo phần còn lại.

default_dark
default_light
scifi_dark
scifi_light
medieval_dark
medieval_light
material_light Mới
material_dark Mới
kids_light Mới
kids_dark Mới
comic_light Mới
comic_dark Mới
arcade_light Mới
arcade_dark MớiMỗi diện mạo đổi những gì
| Diện mạo | Cửa sổ | Phần đầu và các nút |
|---|---|---|
| Mặc định | Bo tròn (18dp), bóng mềm, đục 80% trên nền game | Tiêu đề và × đơn giản; nút chính nhô lên rồi lún xuống |
| Sci-fi | Góc cắt với viền neon và ánh sáng tỏa, viền nhuốm màu nhấn | Dấu focus dạng ngoặc góc |
| Trung cổ | Khung rèn — sắt và da thuộc, hoặc giấy da — với đinh tán và hoa văn góc | Tiêu đề bằng Cinzel |
| Material 3 | Góc 28dp, đệm 24dp, đục hoàn toàn | Tiêu đề 24sp bằng Roboto; nút hình viên thuốc với tấm nền 40dp trong vùng chạm 48dp |
| Trẻ em | Tấm thẻ như kẹo thạch với bóng mềm, đục hoàn toàn | Dải băng tiêu đề màu nắng (màu bạc hà vào ban đêm), nút đóng tròn hình viên kẹo, các nút bẹp xuống khi nhấn |
| Truyện tranh | Viền mực đậm và bóng đổ sắc nét, đục hoàn toàn | Ô tiêu đề màu vàng, nút đóng tròn viền mực, các nút mất bóng đổ khi nhấn |
| Arcade | Một bảng dày — khung màu bao quanh lòng bảng sáng màu — đục hoàn toàn | Dải băng tiêu đề vàng kim với tiêu đề trắng viền mực, phím đóng tròn màu đỏ, các phím sơn màu bật nảy khi nhấn |
Khoác áo cho cửa sổ bật lên từ skin
Skin quyết định những phần mà Theme không với tới. Các hook dưới đây là những cái mà cửa sổ bật lên gọi — hãy ghi đè chúng trong lớp con
GoSkin của riêng bạn; mặc định chúng không làm gì cả.
| Hook | Nó khoác áo cho gì |
|---|---|
title_plate_box() · title_plate_ink() · title_plate_min_size() | Tấm nền phía sau tiêu đề cửa sổ. Tiêu đề canh giữa trên đó và co lại, tới cỡ tối thiểu (15), để nằm gọn trên một dòng. |
dress_close_button(button) | Nút × của cửa sổ — kids biến nó thành một viên kẹo tròn, comic thành một đĩa tròn viền mực, arcade thành một phím tròn màu đỏ. |
dress_title(label) | Nhãn tiêu đề của cửa sổ — arcade viền mực cho tiêu đề màu trắng. |
press_feedback(control) | Chạy ngay khoảnh khắc ngón tay chạm vào một nút do gohud tạo ra — kids làm nó bẹp xuống, arcade làm nó bật nảy. |
floating_box() · notice_box() · drawer_box() · banner_box() · overlay_box() | Mặt của prompt card và thẻ coach, snackbar và notice, ngăn kéo, banner, bảng điều khiển. |
coach_ring_box() · draw_coach_pointer() | Vòng sáng và con trỏ của chuyến tham quan. |
Nhìn xuyên được bao nhiêu
Mỗi loại cửa sổ bật lên theo độ đục riêng, nên một thẻ HUD có thể giữ nhẹ trong khi hộp thoại vẫn đặc: cửa sổ, bảng trượt, hộp thoại,
popover và combobox theo panel_alpha; ngăn kéo và thẻ coach theo card_alpha; prompt card và
bảng điều khiển theo hud_alpha; snackbar và notice theo notice_alpha; menu theo popup_alpha. Material và kids
làm tất cả đều đục; comic cũng vậy, trừ prompt card và bảng điều khiển ở mức 92%, và arcade cũng thế, trừ hai loại đó ở mức 94%. Ghi đè một loại bằng GoConfig.container_alpha_overrides, tất cả bằng
container_alpha (một tỉ lệ — 0.95), hoặc một cửa sổ bằng alpha của nó. Chỉ mặt nền mờ đi; chữ thì
không bao giờ trong suốt. Độ đục chi tiết →
Thiết lập định hình mọi cửa sổ bật lên
Các trường của GoConfig (GoUi.config). Vài trường được sao chép lúc một cửa sổ được tạo, nên hãy đặt chúng
trước khi dựng UI — hoặc gọi GoUi.refresh() sau khi đổi.
| Trường | Mặc định | Làm gì |
|---|---|---|
surface_max_width · surface_max_height | 480 · 700 | Cỡ lớn nhất mà một cửa sổ đạt tới. |
surface_height_ratio · surface_max_height_ratio | 0.68 · 0.72 | Chiều cao thông thường và mức chặn (cũng là giới hạn khi kéo). |
surface_fit_max_height_ratio | 0.94 | Một tấm thẻ canh giữa cao theo nội dung được lớn tới đâu trước khi phải cuộn. |
surface_width_ratio_portrait · …_landscape | 0.94 · 0.72 | Chiều rộng tính theo tỉ lệ của khoảng trống. |
dismiss_on_scrim | false | Chạm vào lớp phủ mờ đóng cửa sổ và hộp thoại. Được sao chép lúc cửa sổ được tạo. |
surface_fade_in · fade_seconds | false · 0.14 | Cho cửa sổ hiện dần, và nhanh tới đâu. |
close_on_back | true | Escape, ui_cancel và nút Back của Android đóng cửa sổ trên cùng. |
close_button_visual | 36 | Nút × trông to bao nhiêu — vùng chạm của nó vẫn là 48dp. |
suppress_pointer_focus_ring | true | Không có vòng focus khi cửa sổ được mở bằng chạm hoặc chuột. |
container_alpha · container_alpha_overrides | -1 · {} | Độ đục của mọi mặt nền, hoặc theo từng loại (GoTheme.BOX_*). |
reduce_motion | false | Không hiện dần, không trượt, không nhấp nháy, không bẹp. |
min_touch_size | 48 | Chiều cao của các nút trong prompt card và coach mark. |
respect_safe_area | true | Giữ cửa sổ tránh xa tai thỏ và thanh cử chỉ. |
text_keys · text_overrides | — | Chữ của chính bộ kit: đóng, quay lại, xác nhận, hủy, tiếp, xong, bỏ qua, tìm kiếm, trống… |
sound_cues · rung phản hồi | — | Những gì GoFeedback phát khi mở, đóng, xác nhận, hủy và chạm. |
Công thức
Sao chép, dán, đổi chữ. Mọi lệnh gọi ở đây đều được đối chiếu với mã nguồn.
Một lần mua có ghi giá
func buy(item_name: String, price: int) -> void:
if await dialogs.confirm("Buy {item}?", "It costs {price} gold.", "Buy", "", "",
{"item": item_name, "price": price}):
purchase(item_name)
Cửa sổ cài đặt với nút Lưu được ghim
func open_settings() -> void:
var layer := CanvasLayer.new()
layer.layer = 50
add_child(layer)
var window := GoSurface.new()
window.set_title("Settings")
window.dismiss_on_scrim = true
window.close_requested.connect(layer.queue_free)
window.body.add_child(GoStyle.section("Sound", false))
window.body.add_child(GoStyle.toggle("Music", false))
window.body.add_child(GoStyle.slider(0.0, 1.0, 0.05))
var row := GoStyle.row()
row.add_child(GoStyle.button("Cancel", window.request_close))
row.add_child(GoStyle.button("Save", func() -> void:
save_settings()
layer.queue_free(), GoStyle.Tone.PRIMARY))
window.footer.add_child(row)
window.footer.visible = true
layer.add_child(window)
Biểu mẫu nói rõ chỗ sai
var field := GoField.make("Name", GoStyle.line_edit(), "3–12 letters")
window.body.add_child(field)
create_button.pressed.connect(func() -> void:
if name_is_taken():
field.set_error("That name is taken")
window.set_status_text("Name check failed", GoTheme.DANGER)
else:
window.clear_status()
create_character())
Chọn ngày trong bảng trượt
sheet.open("Delivery date")
var picker := GoDatePicker.make({"year": 2026, "month": 10, "day": 14}, func(date: Dictionary) -> void:
delivery = date
sheet.close())
picker.min_date = GoDatePicker.today()
sheet.body.add_child(picker)
Menu tạm dừng
func pause() -> void:
get_tree().paused = true
var layer := CanvasLayer.new()
layer.layer = 50
layer.process_mode = Node.PROCESS_MODE_ALWAYS # vẫn chạy trong lúc game tạm dừng
add_child(layer)
var menu := GoSurface.new()
menu.set_title("Paused")
menu.close_requested.connect(func() -> void:
get_tree().paused = false
layer.queue_free())
menu.body.add_child(GoStyle.button("Resume", menu.request_close, GoStyle.Tone.PRIMARY))
menu.body.add_child(GoStyle.button("Settings", open_settings))
menu.body.add_child(GoStyle.button("Quit to title", quit_to_title, GoStyle.Tone.DANGER))
layer.add_child(menu)
Lối chơi biết chờ cửa sổ bật lên
func _unhandled_input(event: InputEvent) -> void:
if GoSurface.is_any_open() or drawer.is_open():
return
handle_gameplay(event)
Những cái bẫy
- Một
GoSurfacekhông bao giờ tự giải phóng. Hãy kết nốiclose_requested, không thì × chẳng làm gì cả. - Tùy chọn đặt sau
add_child—scrim_transparent,show_header,resizable,scroll_body, cùnglayer_indexvàmax_widthcủaGoDialogs— không được áp dụng. - Một
confirm()thứ hai trả vềfalsengay. Hãy bậtqueue_when_busynếu hai câu hỏi có thể gặp nhau. - Enter là xác nhận. OK giữ focus, kể cả ở hộp thoại không thể hoàn tác.
- Back trên bảng trượt đóng cả bảng trượt, chứ không phải trang con.
- Escape không đóng ngăn kéo, và
is_any_open()không nhìn thấy nó. - Popover hay danh sách combobox chặn game cho tới khi đóng, kể cả với lớp phủ mờ trong suốt — và một popover có
dismissable = falsemà không có tiêu đề thì chẳng có gì trên màn hình đóng được nó. - Prompt card bắt đầu ở trạng thái ẩn và không có thời hạn.
- Snackbar có
seconds: 0mà không có nút thì không bao giờ rời đi. Và theo mặc định nó hứng các cú chạm trên tấm thẻ của nó. - Cú nhấn giữ không ai biết là tính năng không ai dùng. Hãy hiện thêm một nút
⋯. - Cây bị tạm dừng thì cửa sổ bật lên của bạn cũng dừng. Hãy dùng
PROCESS_MODE_ALWAYStrên lớp của nó. - Notice không có chiều rộng sẽ co về mức hẹp nhất — hãy dùng
preferred_width(). - Đừng bao giờ hỏi trong snackbar, đừng bao giờ báo tin bằng hộp thoại. Câu hỏi sẽ biến mất khi bị lờ đi; tin tức thì không nên dừng game.