Экран это окно поверх игры: своё меню клиента, панель, окошко настроек. Сделать его можно двумя способами. screen берёт на себя всю раскладку: ты просто перечисляешь, из чего окно состоит, а движок сам всё расставляет и оживляет. raw_screen даёт пустой холст: рисуешь и ловишь клики сам. В девяти случаях из десяти нужен первый.
Лейаут это движок раскладки. Ты не считаешь координаты, а складываешь элементы в строки и колонки, как кубики. Декоратор screen(title, width, height) превращает функцию в меню. В функцию приходит ui, через него и строится окно: контейнеры ui.column и ui.row открываются через with, а всё, что вызвано внутри блока, попадает внутрь.
@screen("Моё меню", 400, 300)
def menu(ui):
with ui.column(gap=8, pad=12, bg=Color(20, 20, 24), fill=True):
ui.text("Заголовок", size=10, weight="bold", color=Color(255, 255, 255))
with ui.row(gap=6, align="center"):
ui.button("Кнопка", on_click=lambda: client.msg("клик"))Функция-строитель вызывается заново каждый раз, когда меню открывают. Значит, всё, что считается один раз на открытии (список модулей, текущая категория), спокойно пиши прямо в ней. А значения, которые меняются на ходу, передавай функциями: их движок будет дёргать каждый кадр, и заново пересобирать окно ради них не придётся.
Декоратор возвращает объект меню. У него есть menu.open(), menu.close() и свойство menu.opened. Проще всего открывать меню кнопкой в настройках модуля, тогда у него сразу появится место в интерфейсе клиента.
m = Module("Nova Menu", "Other")
Button(m, "Открыть меню").action(lambda: menu.open())Второй способ это реакция на включение модуля через module_toggled. Игрок жмёт бинд, модуль включается, мы открываем меню и тут же гасим модуль обратно. Получается модуль-кнопка с горячей клавишей, и клавишу игрок выбирает сам. Ловить нажатие напрямую тоже можно, событием key, но тогда клавиша будет зашита в скрипт.
m = Module("Nova Menu", "Other")
@events.module_toggled
def on_toggle(event):
if event.getModule().getName() == "Nova Menu" and event.isState():
menu.open()
m.setEnabled(False) # сработал и выключилсяraw_screen это режим рисуй сам. Твоя функция вызывается каждый кадр, координаты абсолютные, холст это весь экран игры: d.width и d.height дают его размер. Ни лейаута, ни виджетов, только ты и холст.
| Метод контекста | Что делает |
|---|---|
d.rect(x, y, w, h, color=None) | Прямоугольник |
d.rounded_rect(x, y, w, h, radius=6, color=None) | Прямоугольник со скруглёнными углами |
d.border(x, y, w, h, radius=6, thickness=1, color=None) | Только рамка, без заливки |
d.client_rect(x, y, w, h, alpha=1.0, drag=0.0, squircle=3) | Фон в фирменном стиле клиента |
d.text / d.centered_text / d.right_text | (value, x, y, size=8, weight="medium", color=None, outline=None, outline_width=1): текст слева, по центру или справа от точки. outline это цвет обводки вокруг букв |
d.icon(name, x, y, size=8, color=None) | Иконка клиента по имени |
d.image(texture, x, y, w, h, color=None) | Картинка, загруженная через assets.image() |
d.font(weight="medium", size=8) | Шрифт, если нужен явно |
d.raw() | Полный набор методов рисования, тех же, что в render_2d |
d.mouse_x / d.mouse_y / d.delta | Курсор и доля тика |
@raw_screen("Raw", 400, 300)
def render(d):
w, h = 200, 60
x = d.width / 2 - w / 2 # d.width это ширина всего экрана
y = d.height / 2 - h / 2
d.rounded_rect(x, y, w, h, 10, Color(16, 17, 20, 235))
d.centered_text("Голый экран", d.width / 2, y + 14, size=10, weight="bold")
d.centered_text("мышь: %d, %d" % (d.mouse_x, d.mouse_y), d.width / 2, y + 34,
size=7, color=Color(255, 255, 255, 150))В обычном меню клики ловят сами узлы дерева: стиль on_click вешается не только на кнопку, но и на контейнер. Поэтому кликабельной можно сделать целую строку или карточку, а cursor="hand" покажет игроку, что она живая.
@screen("Клики", 260, 140)
def menu(ui):
with ui.column(gap=8, pad=12, bg=Color(20, 20, 24), fill=True):
# кликабельна вся строка целиком
with ui.row(fill_width=True, pad=(8, 10), radius=8, bg=Color(30, 31, 36),
align="center", justify="between", cursor="hand",
on_click=lambda: client.modules.toggle("Aura")):
ui.text("Aura", size=8, color=Color(255, 255, 255))
ui.text(lambda: "ON" if client.modules.enabled("Aura") else "OFF",
size=7, color=Color(150, 150, 160))
ui.button("Закрыть", on_click=lambda: menu.close())У голого экрана виджетов нет, поэтому клик приходит целиком тебе, через декоратор on_click. Обработчик получает три аргумента: координаты мыши и кнопку строкой ("left", "right", "middle"). Попал ли клик в твой прямоугольник, проверяешь сам. Это цена свободы.
BTN = (140, 90, 120, 26) # x, y, w, h
def hit(mx, my, box):
x, y, w, h = box
return x <= mx <= x + w and y <= my <= y + h
@raw_screen("Raw Click", 400, 200)
def render(d):
x, y, w, h = BTN
hovered = hit(d.mouse_x, d.mouse_y, BTN)
d.rounded_rect(x, y, w, h, 8,
Color(86, 124, 252) if hovered else Color(40, 42, 50))
d.centered_text("Нажми меня", x + w / 2, y + 9, size=8, weight="semibold")
@render.on_click
def click(mx, my, btn):
if btn == "left" and hit(mx, my, BTN):
client.msg("Кнопка нажата")
render.close()
m = Module("Raw Demo", "Other")
Button(m, "Открыть").action(lambda: render.open())