> For the complete documentation index, see [llms.txt](https://docs.nursultan.fun/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nursultan.fun/interface/2d-render.md).

# 2D render

The `Render2DEvent` event fires every frame on the HUD layer. It carries `render()`, the screen size `width()` × `height()` and `tickDelta()`.

All drawing goes through `e.render()`. Coordinates are UI pixels, the same ones `width()` and `height()` are in. Colours are ordinary `Int`s shaped `0xAARRGGBB`, and `Colors` is the easy way to build them.

```kotlin
on<Render2DEvent> { e ->
    val r = e.render()
    r.roundedRect(8f, 8f, 160f, 40f, 8f, Colors.rgba(0, 0, 0, 160))
    r.text("Nursultan", 16f, 16f, 14f, Colors.WHITE)
    r.text("fps " + client.fps(), 16f, 32f, 10f, Colors.CYAN)
}
```

## Shapes

| Method                                               | What it draws         |
| ---------------------------------------------------- | --------------------- |
| `rect(x, y, w, h, argb)`                             | a filled rectangle    |
| `roundedRect(x, y, w, h, radius, argb)`              | with rounded corners  |
| `gradient(x, y, w, h, argbFrom, argbTo, horizontal)` | a two-colour gradient |
| `outline(x, y, w, h, thickness, argb)`               | the border only       |
| `circle(cx, cy, radius, argb)`                       | a circle              |
| `ring(cx, cy, radius, thickness, argb)`              | a ring                |
| `triangle(x1, y1, x2, y2, x3, y3, argb)`             | a triangle            |

## Blur

Blurs whatever is already drawn under the given area — that is how you make backdrops for panels:

```kotlin
r.blur(8f, 8f, 160f, 40f, 12f)
r.blur(8f, 8f, 160f, 40f, 12f, Colors.rgba(0, 0, 0, 80), 8f)
```

The second form also fills the area with a colour and rounds the corners.

## Text

```kotlin
r.text("hi", 10f, 10f, 12f, Colors.WHITE)
r.textShadow("hi", 10f, 10f, 12f, Colors.WHITE, Colors.rgba(0, 0, 0, 200))

r.textWidth("hi", 12f)
r.textHeight(12f)
```

You measure width to fit a backdrop around the text:

```kotlin
val text = "health " + player.health()
val width = r.textWidth(text, 10f) + 12f
r.roundedRect(6f, 6f, width, 18f, 5f, Colors.rgba(0, 0, 0, 140))
r.text(text, 12f, 11f, 10f, Colors.WHITE)
```

## Fonts

Without a font argument you get the client's font. You can ask for the vanilla one or register your own:

```kotlin
r.text("hi", 10f, 10f, 12f, Colors.WHITE, "minecraft")

// once, at the top level of the script
font("myfont", "my-font.ttf")

// then by name
r.text("hi", 10f, 30f, 12f, Colors.WHITE, "myfont")
```

The TTF file goes into the scripts folder next to the script itself.

## Images and icons

```kotlin
r.item(inventory.held(), 10f, 10f, 16f)          // an item
r.item("minecraft:diamond", 30f, 10f, 16f)       // by id
r.head(playerEntity, 50f, 10f, 16f)              // a player's head
r.effectIcon("speed", 70f, 10f, 16f)             // an effect icon
r.texture("minecraft:textures/item/apple.png", 90f, 10f, 16f, 16f)
r.image("logo.png", 110f, 10f, 32f, 32f)         // a file from the scripts folder
```

## A world point on the screen

`project` turns world coordinates into screen coordinates — that is how nametags are done:

```kotlin
on<Render2DEvent> { e ->
    val r = e.render()
    for (target in world.players()) {
        if (target.isSelf()) continue
        val point = r.project(target.position().add(0.0, target.height() + 0.4, 0.0))
        if (!point.visible()) continue
        val name = target.name()
        r.text(name, point.x() - r.textWidth(name, 8f) / 2f, point.y(), 8f, Colors.WHITE)
    }
}
```

`visible()` is `false` when the point is behind you or off screen.

## Smoothness

There are more frames than ticks, so positions from `position()` will judder. For drawing use `renderPosition()`, which is already smoothed. If you compute something yourself, use `tickDelta()` from the event.

## Do not do heavy work per frame

The handler runs 60+ times a second. Do not walk every entity, shoot rays or ask for predictions inside it — compute in `ClientTickEvent`, stash it in a variable, and only draw in the frame.
