Hello, Natiny

Your first native Natiny app needs one header and one library. It opens a window, runs a game loop, and draws a moving square without an asset pipeline.

Create main.c:

C
#include "natiny.h"
#include <stddef.h>

static NatinyWindow window;
static float x = -80.0f;

static void frame(float dt, void* userdata)
{
    (void)userdata;

    x += 180.0f * dt;
    if (x > (float)natiny_window_get_width(window)) {
        x = -80.0f;
    }

    natiny_window_bind(window);
    natiny_render_clear(0.07f, 0.09f, 0.13f, 1.0f);
    natiny_render_set_color(0.66f, 0.76f, 0.22f, 1.0f);
    natiny_render_rectangle(x, 260.0f, 80.0f, 80.0f);
    natiny_window_unbind();
}

int main(void)
{
    if (!natiny_backend_init(NATINY_BACKEND_AUTO)) {
        return 1;
    }

    window = natiny_window_create("Hello, Natiny", 800, 600);
    if (!window) {
        natiny_backend_shutdown();
        return 1;
    }
    natiny_window_set_scale_mode(window, NATINY_SCALE_MODE_HIDPI);

    natiny_backend_loop(frame, NULL);
    natiny_backend_shutdown();
    return 0;
}

Build and run it

The program needs natiny.h and one static archive, both out of an SDK bundle, and a C compiler:

TEXT
my_game/
├── include/
│   └── natiny.h
├── lib/
│   └── libnatiny_<platform>.a
└── main.c

The commands differ by platform, so each has a page of its own: Windows, Linux, macOS, Android and Web. Every one of them ends with a dark window and a green square moving across it.

Understand the program in four steps

1. Start the engine

C
if (!natiny_backend_init(NATINY_BACKEND_AUTO)) {
    return 1;
}

A graphics backend is the platform-specific renderer Natiny uses to talk to the GPU. natiny_backend_init prepares the engine and selects one. NATINY_BACKEND_AUTO chooses Direct3D 12 on Windows, Vulkan on Linux, Metal on macOS, and WebGPU in the browser.

The function returns zero when initialization fails, so the program stops before trying to create graphics resources.

2. Create a window with sharp display scaling

C
window = natiny_window_create("Hello, Natiny", 800, 600);
if (!window) {
    natiny_backend_shutdown();
    return 1;
}
natiny_window_set_scale_mode(window, NATINY_SCALE_MODE_HIDPI);

natiny_window_create opens a window whose requested size is 800 by 600. Its NatinyWindow value is a handle that identifies the window in later calls. A web build maps its one window to the page's canvas.

A high-density display can provide more pixels than the requested window size. natiny_window_set_scale_mode keeps the 800 by 600 drawing space while using those extra pixels for sharper shapes. The window scaling guide explains all three modes and when to choose each one.

3. Update once per frame

C
static void frame(float dt, void* userdata)
{
    (void)userdata;

    x += 180.0f * dt;
    if (x > (float)natiny_window_get_width(window)) {
        x = -80.0f;
    }

    /* Draw the frame here. */
}

natiny_backend_loop calls frame once per frame. dt is the elapsed time in seconds since the previous frame. The second parameter is a user pointer; this small example does not need it.

The square moves at 180 drawing units per second. Multiplying movement by dt keeps that speed independent of the display refresh rate. Once the square leaves the window width, it starts again just outside the left edge.

4. Draw the frame into the window

C
natiny_window_bind(window);
natiny_render_clear(0.07f, 0.09f, 0.13f, 1.0f);
natiny_render_set_color(0.66f, 0.76f, 0.22f, 1.0f);
natiny_render_rectangle(x, 260.0f, 80.0f, 80.0f);
natiny_window_unbind();

A render target is the image that receives drawing commands. natiny_window_bind makes the window the current render target and starts its frame. natiny_window_unbind completes and presents that frame.

Between those calls, the drawing functions use a small amount of current state:

  1. natiny_render_clear fills the background;
  2. natiny_render_set_color selects the colour for following draw calls;
  3. natiny_render_rectangle draws at (x, 260) with a size of 80 by 80 drawing units.

Window coordinates begin at the top-left. X grows to the right and Y grows downward.

When the last window closes, the loop returns. natiny_backend_shutdown then releases every engine resource that remains.

Change the first program

Try one small change at a time:

  • change the four values passed to natiny_render_set_color;
  • replace the rectangle with natiny_render_circle(x, 300.0f, 40.0f);
  • change 180.0f to control the speed;
  • move the state into a struct and pass it through the loop's user pointer;
  • draw a second shape before unbinding the window.

You now have the essential game loop: update state, bind a target, draw a frame. Input, textures, models, physics, sound, and custom NSL shaders all build on that same loop.