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:
#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:
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
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
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
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
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:
natiny_render_clearfills the background;natiny_render_set_colorselects the colour for following draw calls;natiny_render_rectangledraws 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.0fto 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.