Render2DEvent hands you a drawing surface — e.render(). Coordinates are framebuffer pixels, not GUI-scaled units, and every call queues a command flushed after all handlers ran, so call order is draw order.

on<Render2DEvent> { e ->
    val r = e.render()
    val label = "fps " + client.fps()
    val w = r.textWidth(label, 10f) + 12f
    r.blur(8f, 8f, w, 22f, 12f, Colors.rgba(0, 0, 0, 160), 6f)
    r.roundedRect(8f, 8f, w, 22f, 6f, Colors.rgba(10, 12, 15, 120))
    r.text(label, 14f, 14f, 10f, Colors.WHITE)
}

The surface

Method Type Description
r.width() float framebuffer width in px, refreshed each 2D frame
r.height() float framebuffer height in px, refreshed each 2D frame

At GUI Scale 2 a 1920×1080 window still reports width() = 1920. Divide by gameSettings.scaleFactor() to get vanilla GUI units.

Shapes

Method Type Description
r.rect(x, y, width, height, argb) void filled rectangle
r.roundedRect(x, y, width, height, radius, argb) void radius clamped 0..min(width, height)/2
r.roundedRect(x, y, width, height, topLeft, topRight, bottomRight, bottomLeft, argb) void per-corner radii, clockwise from top-left, each clamped the same way
r.outline(x, y, width, height, thickness, argb) void square border inside the rect, thickness clamped 0..min(w, h)/2
r.roundedOutline(x, y, width, height, radius, thickness, argb) void rounded border, stroke aligned INSIDE
r.roundedOutline(x, y, width, height, radius, thickness, align, argb) void null align treated as INSIDE
r.circle(centerX, centerY, radius, argb) void filled circle, negative radius treated as 0
r.ring(centerX, centerY, radius, thickness, argb) void thickness clamped 0..radius
r.triangle(x1, y1, x2, y2, x3, y3, argb) void filled triangle, bounding quad padded 2 px

StrokeAlign

Constant Description
StrokeAlign.INSIDE stroke inside the rect edge, used when align is null
StrokeAlign.CENTER stroke centred on the edge, rect grown by half the thickness
StrokeAlign.OUTSIDE stroke outside the edge, rect grown by the full thickness

The size you pass is always the shape itself; the stroke grows outwards from it.

Gradients and blur

Method Type Description
r.gradient(x, y, width, height, argbFrom, argbTo, horizontal) void horizontal true = left to right, false = top to bottom
r.gradientAngle(x, y, width, height, argbFrom, argbTo, angleDegrees) void linear gradient along (cos a, sin a) in degrees, screen Y grows downward
r.radialGradient(x, y, width, height, argbCenter, argbEdge) void gradient out of the centre, corner radius 0
r.radialGradient(x, y, width, height, radius, argbCenter, argbEdge) void corner radius clamped 0..min(w, h)/2
r.blur(x, y, width, height, radius) void blurs what is already drawn under the region, full opacity, square corners
r.blur(x, y, width, height, radius, argb, cornerRadius) void only the alpha byte of argb is used, one radius for all corners
r.blur(x, y, width, height, radius, argb, tl, tr, bl, br) void per-corner radii in order top-left, top-right, bottom-left, bottom-right (API 2)

Blur reads the framebuffer at its own place in the queue, so it only blurs commands issued before it. The rect is snapped to whole pixels and the blur radius to max(1, round(radius)) px; a width or height that rounds to 0 is skipped.

Text

Method Type Description
r.text(text, x, y, sizePx, argb) void font inter, weight REGULAR
r.text(text, x, y, sizePx, argb, font) void named family, weight REGULAR
r.text(text, x, y, sizePx, argb, weight) void font inter at that weight (API 2)
r.text(text, x, y, sizePx, argb, font, weight) void named family at that weight (API 2)
r.textShadow(text, x, y, sizePx, argb, shadowArgb) void shadow copy queued at +1, +1 px before the text
r.textShadow(text, x, y, sizePx, argb, shadowArgb, font) void named family, weight REGULAR
r.textShadow(text, x, y, sizePx, argb, shadowArgb, weight) void font inter at that weight (API 2)
r.textShadow(text, x, y, sizePx, argb, shadowArgb, font, weight) void named family at that weight (API 2)

y is the line top; the baseline sits at y + textAscent(...). An unknown family falls back to inter, and null or empty text is skipped.

Measuring text

Method Type Description
r.textWidth(text, sizePx) float advance width in px, font inter, REGULAR
r.textWidth(text, sizePx, font) float width in px for the named family
r.textWidth(text, sizePx, weight) float width in px, font inter (API 2)
r.textWidth(text, sizePx, font, weight) float 0 for null or empty text and for an unresolvable family (API 2)
r.textHeight(sizePx) float line height in px, font inter, REGULAR
r.textHeight(sizePx, font) float line height in px for the named family
r.textHeight(sizePx, weight) float line height in px, font inter (API 2)
r.textHeight(sizePx, font, weight) float single-line height, independent of any string (API 2)
r.textAscent(sizePx) float ascent above the baseline in px, font inter, REGULAR
r.textAscent(sizePx, font) float ascent in px for the named family
r.textAscent(sizePx, weight) float ascent in px, font inter (API 2)
r.textAscent(sizePx, font, weight) float 0 when no family resolves (API 2)
r.textDescent(sizePx) float line height minus ascent in px, font inter, REGULAR
r.textDescent(sizePx, font) float descent in px for the named family
r.textDescent(sizePx, weight) float descent in px, font inter (API 2)
r.textDescent(sizePx, font, weight) float 0 when no family resolves (API 2)

textHeight equals textAscent + textDescent at the same size, family and weight.

Fonts and weights

Method Type Description
font(name, ttfFileInAssetsFolder) void registers a TTF from scripts/assets under that family name
font(name, ttf) void registers a family from TTF bytes, ignored when the name is taken, the array is empty or over 8 MiB (API 2)
client.fonts().register(name, ttfFileInAssetsFolder) void what font(name, file) calls, applied on the next render frame
client.fonts().register(name, ttf) void what font(name, bytes) calls, the array is cloned (API 2)
client.fonts().registered(name) boolean true when a live family has that name, a queued font is not visible yet

A name that already exists — including the client's own inter, jetbrains-mono and minecraft — is ignored, and script families are dropped when the script unloads. The byte form pairs with base64(...): see assets inside the script.

Weight

Constant Description
Weight.THIN wght 100 (API 2)
Weight.EXTRA_LIGHT wght 200 (API 2)
Weight.LIGHT wght 300 (API 2)
Weight.REGULAR wght 400, used when the weight argument is omitted or null (API 2)
Weight.MEDIUM wght 500 (API 2)
Weight.SEMI_BOLD wght 600 (API 2)
Weight.BOLD wght 700 (API 2)
Weight.EXTRA_BOLD wght 800 (API 2)
Weight.BLACK wght 900 (API 2)

Items, heads, icons

Method Type Description
r.item(item, x, y, sizePx) void item stack icon, foreign Item implementations ignored
r.item(itemId, x, y, sizePx) void namespaced id, malformed id ignored, unknown id resolves to air
r.head(player, x, y, sizePx) void player skin head quad, no-op for sizePx ≤ 0, StreamerMode or a missing skin
r.effectIcon(effectId, x, y, sizePx) void status-effect sprite mob_effect/<path>, x/y/size floored to ints

Textures

Method Type Description
r.texture(identifier, x, y, width, height) void Minecraft resource identifier, full 0..1 UV, malformed identifier ignored
r.texture(texture, x, y, width, height) void full 0..1 UV, skipped while the GL id is 0
r.texture(texture, x, y, width, height, u0, v0, u1, v1) void explicit UV rectangle 0..1, u0/v0 top-left, v grows downward
r.image(fileInAssetsFolder, x, y, width, height) void PNG from scripts/assets, decoded and uploaded once
texture(identifier) Texture? handle for a Minecraft resource id, null when malformed or empty
image(fileInAssetsFolder) Texture? handle for a PNG in scripts/assets, null when rejected, missing or empty
image(name, png) Texture? handle for PNG bytes, null for a blank name, empty bytes or over 8 MiB (API 2)
client.textures().resource(identifier) Texture? what texture(identifier) calls
client.textures().image(fileInAssetsFolder) Texture? what image(file) calls, uploaded on first draw
client.textures().image(name, png) Texture? what image(name, bytes) calls, released when the script is unloaded (API 2)
handle.name() String resource identifier string or the image name
handle.glId() int OpenGL texture id, 0 off the client main thread or before upload
handle.ready() boolean true when glId() is non-zero
handle.width() int mip-0 width in px, 0 off the main thread or when unavailable
handle.height() int mip-0 height in px, 0 off the main thread or when unavailable

A file path is resolved against scripts/assets, with a one-time-warned fallback to the scripts root — see assets. skinTexture() on a player or a tab entry returns the same kind of handle.

Colours

Every colour is an int shaped 0xAARRGGBB.

Method Type Description
Colors.TRANSPARENT int 0x00000000
Colors.WHITE int 0xFFFFFFFF
Colors.BLACK int 0xFF000000
Colors.GRAY int 0xFF808080
Colors.RED int 0xFFFF5555
Colors.GREEN int 0xFF55FF55
Colors.BLUE int 0xFF5555FF
Colors.YELLOW int 0xFFFFFF55
Colors.ORANGE int 0xFFFFA500
Colors.CYAN int 0xFF55FFFF
Colors.MAGENTA int 0xFFFF55FF
Colors.rgb(red, green, blue) int packs opaque ARGB, channels clamped 0..255
Colors.rgba(red, green, blue, alpha) int packs ARGB, all channels clamped 0..255
Colors.red(argb) int red channel 0..255
Colors.green(argb) int green channel 0..255
Colors.blue(argb) int blue channel 0..255
Colors.alpha(argb) int alpha channel 0..255
Colors.withAlpha(argb, alpha) int replaces alpha clamped 0..255, keeps RGB
Colors.fade(argb, factor) int multiplies alpha by factor clamped 0..1
Colors.mix(first, second, amount) int per-channel lerp including alpha, amount clamped 0..1

A world point on the screen

Method Type Description
r.project(worldPosition) Projection world position to screen pixels, rounded
p.visible() boolean false when the point projects behind the camera
p.x() float screen X in framebuffer px, 0 when not visible
p.y() float screen Y in framebuffer px, 0 when not visible

Clipping

Method Type Description
r.pushScissor(x, y, width, height) void clips later draws to the rect, intersected with the enclosing one (API 5)
r.popScissor() void drops one clip level (API 5) (no effect: the scissor stack is empty)

The rect is in framebuffer px, rounded and clamped to the frame; a negative width or height becomes 0. Every scissor a render handler leaves open is dropped when that handler returns.

Blending

Method Type Description
r.blend() BlendMode mode applied to later shapes and textures, ALPHA when a handler starts (API 7)
r.blend(mode) void sets it for later shapes and textures, null means ALPHA (API 7)

Text, items, heads, blur and shader quads keep their own blending; the modes are listed on Your own geometry. INVERT flips every pixel under the shape, weighted by its alpha — the blend of the vanilla crosshair.

Shaders

Method Type Description
r.shader(shader, x, y, width, height) void draws a quad with a script shader, uniforms snapshotted at call time

Foreign Shader implementations are ignored, and the quad is skipped while compilation failed. Compiling one and setting uniforms: shaders.

Last updated