Всё, что скрипт показывает поверх игры (панельки, счётчики, полоски здоровья, надписи), рисуется на плоском слое над экраном. Самый прямой путь туда это событие render_2d: клиент зовёт его каждый кадр и даёт тебе кисточку, которой можно рисовать.
Подписка обычная. В функцию приходит событие, а из него берут кисточку — event.getContext(). Её принято звать ctx.
mod = Module("Hello2D", "Visuals")
@events.render_2d
def draw(event):
if not mod.isEnabled():
return
ctx = event.getContext()
ctx.drawRoundedRect(10, 10, 120, 24, border(8), Color(0, 0, 0, 180))
ctx.drawText(font("medium", 8), "Rockstar", 20, 18, Color(255, 255, 255))Рядом живёт брат-близнец pre_render_2d. Кисточка та же самая, но зовут его раньше, до того как нарисуется интерфейс клиента. Значит всё из pre_render_2d ляжет под интерфейсом, а из render_2d поверх всего.
Ещё два места есть у меню клиента: menu_render рисует под его панелями,post_menu_render поверх них. Оба идут внутри анимации открытия, а координаты панелей лежат в глобале menu, так что нарисованное живёт вместе с меню. Подробности в разделе menu справочника API.
Рисование работает как аппликация: что нарисовано позже, то лежит сверху. Поэтому идут снизу вверх — сначала тень, потом фон, потом обводка, и только потом текст.
@events.render_2d
def draw(event):
ctx = event.getContext()
bg = Color(15, 16, 20, 220)
ctx.drawShadow(40, 40, 160, 60, 14, border(10), Color(0, 0, 0, 120))
ctx.drawRoundedRect(40, 40, 160, 60, border(10), bg)
ctx.drawRoundedBorder(40, 40, 160, 60, 1, border(10), Color(255, 255, 255, 30))Почти каждый метод хочет цвет (Color), а если фигура скруглённая, то ещё и радиус углов (border). Про то и другое есть страница Цвета, шрифты, ассеты.
Если нужно временно сдвинуть или повернуть всё, что рисуешь дальше, есть пара ctx.pushMatrix() и ctx.popMatrix(): первый запоминает текущее положение холста, второй возвращает как было. Пару всегда закрывай, иначе поедет весь остальной интерфейс.
Вот чем рисуют формы. Порядок аргументов важен: лишний или пропущенный уронит скрипт целиком.
| Метод | Аргументы | Что это |
|---|---|---|
drawRect | (x, y, w, h, color) | Прямоугольник с острыми углами |
drawRoundedRect | (x, y, w, h, border, color) | Прямоугольник со скруглёнными углами |
drawRoundedBorder | (x, y, w, h, thickness, border, color) | Только рамка, без заливки |
drawSquircle | (x, y, w, h, squirt, border, color) | Мягкий квадрат, как иконки на айфоне |
drawSquircleBorder | (x, y, w, h, thickness, squirt, border, color) | Рамка такого же мягкого квадрата |
drawShadow | (x, y, w, h, softness, border, color) | Мягкая тень под фигурой |
drawBlurredRect | (x, y, w, h, blurRadius, border, color) | Размывает картинку игры под фигурой |
drawLiquidGlass | (x, y, w, h, squirt, power, border, color) | Жидкое стекло: фон преломляется, как через каплю |
drawClientRect | (x, y, w, h, alpha, dragAnim, squircle) | Фон в стиле клиента, чтобы блок выглядел родным |
drawLoadingRect | (x, y, w, h, progress, border, color) | Полоса загрузки, progress от 0 до 1 |
drawArc | (x, y, size, thickness, startAngle, endAngle, color) | Дуга, то есть кусок окружности |
drawCircleProgress | (cx, cy, radius, thickness, progress, color) | Круглый индикатор, cx и cy это центр |
drawDashedBorder | (x, y, w, h, thickness, border, dash, gap, color, l, t, r, b) | Пунктирная рамка, последние четыре числа включают стороны |
pushMatrix / popMatrix | () | Запомнить и вернуть положение холста |
Соберём что-то настоящее: полоску здоровья цели, которую бьёт Aura. Обрати внимание, как доля здоровья превращается в ширину заливки и как панель складывается из слоёв.
mod = Module("TargetHP", "Visuals")
@events.render_2d
def draw(event):
if not mod.isEnabled():
return
target = world.target() # кого сейчас бьёт Aura, None если никого
if target is None:
return
ctx = event.getContext()
x, y, w, h = 20, 20, 140, 34
# доля здоровья: 1.0 это полное, 0.0 это смерть
hp = float(target.getHealth()) / max(1.0, float(target.getMaxHealth()))
hp = max(0.0, min(1.0, hp))
ctx.drawShadow(x, y, w, h, 12, border(8), Color(0, 0, 0, 110))
ctx.drawRoundedRect(x, y, w, h, border(8), Color(14, 15, 18, 225))
ctx.drawText(font("semibold", 7), str(target.getNameForScoreboard()), x + 8, y + 6,
Color(255, 255, 255))
# сначала серая дорожка, поверх неё заливка по доле здоровья
ctx.drawRoundedRect(x + 8, y + 21, w - 16, 5, border(3), Color(255, 255, 255, 30))
ctx.drawRoundedRect(x + 8, y + 21, (w - 16) * hp, 5, border(3), Color(86, 124, 252))Прежде чем писать текст, надо взять шрифт: font(насколько жирный, размер). Жирность выбирают из готовых: noto, regular, medium, semibold, bold, roundbold.
| Метод | Аргументы | Что это |
|---|---|---|
drawText | (font, text, x, y, color) | Обычный текст, x это левый край строки |
drawCenteredText | (font, text, x, y, color) | То же, но x это середина строки |
drawRightText | (font, text, x, y, color) | То же, но x это правый край строки |
drawTextWithShadow | (font, text, x, y, color, shadowColor, offX, offY, blur) | Текст с тенью |
drawTextWithOutline | (font, text, x, y, color, outlineColor, thickness[, steps]) | Текст с обводкой: цвет контура и его ширина в пикселях (до 8). Есть drawCenteredTextWithOutline и drawRightTextWithOutline |
drawFadeoutText | (font, text, x, y, color, fadeStart, fadeEnd) | Текст, который тает к правому краю. Спасает от длинных ников |
Часто надо знать заранее, сколько места займёт надпись, например чтобы подложка была ровно по ней. Померить умеет сам клиент.
@events.render_2d
def draw(event):
ctx = event.getContext()
text = "%d FPS" % int(mc.getCurrentFps())
# меряем текст и добавляем поля: по 8 пикселей слева и справа
w = client.font_width("medium", 8, text) + 16
h = client.font_height("medium", 8) + 10
ctx.drawRoundedRect(10, 10, w, h, border(6), Color(0, 0, 0, 170))
ctx.drawText(font("medium", 8), text, 18, 15, Color(255, 255, 255))Иконки клиента рисуются по имени, искать файл не надо. Есть, например: logo, search, setting, check, xmark, trash, copy, cube, keyboard, arrows, back, иконки категорий (category/combat, category/movement и так далее) и иконки интерфейса (hud/player, hud/world, hud/potion).
| Метод | Аргументы | Что это |
|---|---|---|
drawIcon | (name, x, y, size[, color]) | Иконка клиента по имени, цветом её можно перекрасить |
drawItem | (itemStack, x, y, size) | Предмет из игры: меч, зелье, броня |
drawHead | (entity, x, y, size, border, color) | Голова игрока или моба |
drawTexture | (image, x, y, w, h[, color]) | Своя картинка |
drawRoundedTexture | (image, x, y, w, h, border[, color]) | Своя картинка со скруглёнными углами |
Свою картинку сначала загружают через assets.image(путь). Путь считается от папки со скриптами. Загружать нужно один раз, в теле скрипта, а не внутри рисования.
mod = Module("Watermark", "Visuals")
logo = assets.image("assets/logo.png") # грузим один раз при загрузке скрипта
@events.render_2d
def draw(event):
if not mod.isEnabled():
return
ctx = event.getContext()
name = str(mc.getSession().getUsername()) # свой ник
# ширина плашки: ник плюс место под лого слева и иконку справа
w = client.font_width("semibold", 8, name) + 62
ctx.drawBlurredRect(8, 8, w, 24, 24, border(8), Color(10, 10, 12, 160))
ctx.drawRoundedTexture(logo, 14, 13, 14, 14, border(3), Color(255, 255, 255))
ctx.drawText(font("semibold", 8), name, 34, 15, Color(255, 255, 255))
ctx.drawIcon("hud/player", w - 22, 13, 14, Color(255, 255, 255, 140))