diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-10 18:37:04 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-10 18:37:04 +0800 |
| commit | 0bfbeec1abdf937e3658fe07f75de22fdb2e7bea (patch) | |
| tree | 735916fff4b2884fc5d622c4cc934991e9c3c103 /examples | |
| parent | 996dcc5002d1d5961d54d924ae4a44f31898024e (diff) | |
add mouse capture and relative mouse movement
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/corridor.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/examples/corridor.c b/examples/corridor.c new file mode 100644 index 0000000..e0bb81e --- /dev/null +++ b/examples/corridor.c @@ -0,0 +1,296 @@ +/* + * corridor: a small first person level, walls from a tilemap, sprites that face you, and collision on the same map + * + * build: ./build.sh -d -m tile,font obj/corridor + * ./build.sh -d -m tile,font,assets,image obj/corridor (also loads sprites/marker.png from the assets if there is one) + * run: obj/corridor + * + * click to grab the mouse and look around with it, tab lets go. w and s walk, a and d strafe, the arrows turn too + * the walls are quads made from the tilemap every frame, the markers are billboards drawn with depth so walls hide them + * the markers are plain placeholders, put your own sprite in assets/sprites/marker.png to see it in their place + */ + +#include <stdio.h> +#include <math.h> + +#include "konprogram.h" +#include "kontile.h" + +#if !defined(KON_MODULE_TILE) || !defined(KON_MODULE_FONT) +#error "build with: ./build.sh -d -m tile,font obj/corridor" +#endif + +#define COLUMNS 16 +#define ROWS 16 +#define TILE 32 /* map pixels in one world unit, the collision works in those */ +#define WALL_HEIGHT 1.2f +#define MARKERS 5 +#define LOOK_SPEED 0.003f + +#define COL_SKY 0xFF1B1826u +#define COL_DIM 0xFF9A93B3u + +static const char *level[ROWS] = { + "################", + "#..............#", + "#.####....####.#", + "#.#..........#.#", + "#.#..........#.#", + "#.#...####...#.#", + "#.....#..#.....#", + "#.....#..#.....#", + "#.#...#..#...#.#", + "#.#...####...#.#", + "#.#..........#.#", + "#.#..........#.#", + "#.####....####.#", + "#..............#", + "#..............#", + "################", +}; + +typedef struct state { + kon_tilemap_t map; + uint16_t tiles[COLUMNS * ROWS]; + uint8_t solid[2]; + + kon_image *brick, *floor, *marker; + int ownsMarker; + kon_box2_t player; /* map pixels */ + float yaw, pitch; + int forward, strafe, turn; + double time; + kon_vec2_t markers[MARKERS]; /* world units */ +} state_t; + +/*** images made in code ***/ + +static kon_image *makeBrick(void) +{ + kon_image *image = kon_createFramebuffer(32, 32); + int row, i; + + if (!image) return NULL; + kon_clearFramebuffer(image, 0xFF6B5F92); + for (row = 0; row < 4; row++) { + kon_fillRectangle(image, 0, row * 8, 32, 1, 0xFF3A3558); + for (i = 0; i < 2; i++) kon_fillRectangle(image, (row % 2) * 8 + i * 16, row * 8, 1, 8, 0xFF3A3558); + } + return image; +} + +static kon_image *makeFloor(void) +{ + kon_image *image = kon_createFramebuffer(32, 32); + + if (!image) return NULL; + kon_clearFramebuffer(image, 0xFF2A2640); + kon_fillRectangle(image, 0, 0, 16, 16, 0xFF332E4E); + kon_fillRectangle(image, 16, 16, 16, 16, 0xFF332E4E); + return image; +} + +static kon_image *makeMarker(void) +{ + kon_image *image = kon_createFramebuffer(32, 40); + int i; + + if (!image) return NULL; + kon_clearFramebuffer(image, 0xFF6B5F92); + kon_fillRectangle(image, 2, 2, 28, 36, 0xFF332E4E); + for (i = 0; i < 28; i++) { + kon_fillRectangle(image, 2 + i, 2 + i * 36 / 28, 1, 1, 0xFF6B5F92); + kon_fillRectangle(image, 2 + i, 37 - i * 36 / 28, 1, 1, 0xFF6B5F92); + } + return image; +} + +/*** drawing ***/ + +static void quad(kon_framebuffer_t *fb, kon_mat4_t vp, const kon_material_t *material, const kon_light_t *light, + kon_vec3_t a, kon_vec3_t b, kon_vec3_t c, kon_vec3_t d, kon_vec3_t normal, kon_vec2_t uvA, kon_vec2_t uvC) +{ + kon_vertex_t va = { a, normal, uvA }, vb = { b, normal, KON_VEC2(uvC.x, uvA.y) }; + kon_vertex_t vc = { c, normal, uvC }, vd = { d, normal, KON_VEC2(uvA.x, uvC.y) }; + + kon_drawTriangle3D(fb, vp, va, vb, vc, material, light, KON_RENDER_DEFAULT); + kon_drawTriangle3D(fb, vp, va, vc, vd, material, light, KON_RENDER_DEFAULT); +} + +/* a wall face seen from its open side: bottom left and bottom right are as the viewer sees them */ +static void wall(kon_framebuffer_t *fb, kon_mat4_t vp, const kon_material_t *material, const kon_light_t *light, + kon_vec3_t left, kon_vec3_t right, kon_vec3_t normal) +{ + kon_vec3_t topRight = KON_VEC3(right.x, WALL_HEIGHT, right.z), topLeft = KON_VEC3(left.x, WALL_HEIGHT, left.z); + + quad(fb, vp, material, light, left, right, topRight, topLeft, normal, KON_VEC2(0, WALL_HEIGHT), KON_VEC2(1, 0)); +} + +static int open(const state_t *state, int column, int row) +{ + return !state->solid[kon_tilemapAt(&state->map, column, row)]; +} + +static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user) +{ + state_t *state = user; + kon_camera_t camera = kon_cameraDefault(); + kon_light_t light = kon_lightDefault(); + kon_material_t brick = kon_materialTexture(state->brick), floor = kon_materialTexture(state->floor); + kon_vec3_t eye = KON_VEC3((state->player.x + state->player.w * 0.5f) / TILE, 0.6f, (state->player.y + state->player.h * 0.5f) / TILE); + kon_mat4_t vp; + int row, column, i; + float size = (float)COLUMNS; + + camera.position = eye; + camera.target = KON_VEC3(eye.x + sinf(state->yaw) * cosf(state->pitch), eye.y + sinf(state->pitch), eye.z - cosf(state->yaw) * cosf(state->pitch)); + camera.nearZ = 0.05f; + camera.farZ = 40.0f; + vp = kon_cameraViewProjection(&camera, game->width, game->height); + + kon_clearFramebuffer(game, COL_SKY); + light.direction = KON_VEC3(-0.3f, -1.0f, -0.5f); + light.ambient = 0.55f; + + quad(game, vp, &floor, &light, KON_VEC3(0, 0, 0), KON_VEC3(0, 0, size), KON_VEC3(size, 0, size), KON_VEC3(size, 0, 0), + KON_VEC3(0, 1, 0), KON_VEC2(0, 0), KON_VEC2(size, size)); + quad(game, vp, &floor, &light, KON_VEC3(0, WALL_HEIGHT, 0), KON_VEC3(size, WALL_HEIGHT, 0), KON_VEC3(size, WALL_HEIGHT, size), KON_VEC3(0, WALL_HEIGHT, size), + KON_VEC3(0, -1, 0), KON_VEC2(0, 0), KON_VEC2(size, size)); + + for (row = 0; row < ROWS; row++) { + for (column = 0; column < COLUMNS; column++) { + float x = (float)column, z = (float)row; + + if (open(state, column, row)) continue; + if (open(state, column, row - 1)) wall(game, vp, &brick, &light, KON_VEC3(x + 1, 0, z), KON_VEC3(x, 0, z), KON_VEC3(0, 0, -1)); + if (open(state, column, row + 1)) wall(game, vp, &brick, &light, KON_VEC3(x, 0, z + 1), KON_VEC3(x + 1, 0, z + 1), KON_VEC3(0, 0, 1)); + if (open(state, column - 1, row)) wall(game, vp, &brick, &light, KON_VEC3(x, 0, z), KON_VEC3(x, 0, z + 1), KON_VEC3(-1, 0, 0)); + if (open(state, column + 1, row)) wall(game, vp, &brick, &light, KON_VEC3(x + 1, 0, z + 1), KON_VEC3(x + 1, 0, z), KON_VEC3(1, 0, 0)); + } + } + + for (i = 0; i < MARKERS; i++) { + float dx = state->markers[i].x - eye.x, dz = state->markers[i].y - eye.z; + float bob = 0.08f * sinf((float)state->time * 2.0f + (float)i); + float brightness = 1.0f / (1.0f + sqrtf(dx * dx + dz * dz) * 0.18f); + + kon_drawBillboard(game, vp, KON_VEC3(state->markers[i].x, 0.1f + bob, state->markers[i].y), 0.6f, 0.75f, state->marker, KON_RECT(0, 0, 0, 0), brightness, KON_RENDER_DEPTH); + } + + if (program->font) kon_drawText(game, program->font, 8, 16, "click to look with the mouse, tab lets go", COL_DIM); +} + +/*** program ***/ + +static int event(kon_program_t *program, const kon_event_t *ev, void *user) +{ + state_t *state = user; + int down; + + if (ev->type == KON_EVENT_MOUSE_MOVE && kon_isMouseCaptured(program->window)) { + state->yaw += (float)ev->mouseDX * LOOK_SPEED; + state->pitch -= (float)ev->mouseDY * LOOK_SPEED; + if (state->pitch > 1.2f) state->pitch = 1.2f; + if (state->pitch < -1.2f) state->pitch = -1.2f; + return 1; + } + if (ev->type == KON_EVENT_MOUSE_DOWN && !kon_isMouseCaptured(program->window)) { + kon_setMouseCapture(program->window, true); + return 1; + } + if (ev->type == KON_EVENT_KEY_DOWN && ev->key == KON_KEY_TAB) { + kon_setMouseCapture(program->window, !kon_isMouseCaptured(program->window)); + return 1; + } + if (ev->type != KON_EVENT_KEY_DOWN && ev->type != KON_EVENT_KEY_UP) return 0; + down = ev->type == KON_EVENT_KEY_DOWN; + + switch (ev->key) { + case KON_KEY_W: case KON_KEY_UP: state->forward = down ? 1 : (state->forward > 0 ? 0 : state->forward); break; + case KON_KEY_S: case KON_KEY_DOWN: state->forward = down ? -1 : (state->forward < 0 ? 0 : state->forward); break; + case KON_KEY_D: state->strafe = down ? 1 : (state->strafe > 0 ? 0 : state->strafe); break; + case KON_KEY_A: state->strafe = down ? -1 : (state->strafe < 0 ? 0 : state->strafe); break; + case KON_KEY_RIGHT: state->turn = down ? 1 : (state->turn > 0 ? 0 : state->turn); break; + case KON_KEY_LEFT: state->turn = down ? -1 : (state->turn < 0 ? 0 : state->turn); break; + default: return 0; + } + return 1; +} + +static void update(kon_program_t *program, double dt, void *user) +{ + state_t *state = user; + float speed = 2.5f * (float)dt * TILE; + float fx = sinf(state->yaw), fz = -cosf(state->yaw); + float dx = (fx * (float)state->forward - fz * (float)state->strafe) * speed; + float dz = (fz * (float)state->forward + fx * (float)state->strafe) * speed; + + /* the console needs the cursor back */ + if (kon_debugConsoleOpen(program->debug) && kon_isMouseCaptured(program->window)) kon_setMouseCapture(program->window, false); + + state->time += dt; + state->yaw += (float)state->turn * 2.0f * (float)dt; + kon_tilemapMove(&state->map, &state->player, dx, dz, state->solid, 2); +} + +static int init(kon_program_t *program, void *user) +{ + state_t *state = user; + static const kon_vec2_t spots[MARKERS] = { { 3.5f, 6.5f }, { 12.5f, 3.5f }, { 8.0f, 13.0f }, { 5.5f, 11.5f }, { 11.5f, 7.5f } }; + int row, column, i; + + (void)program; + state->map.columns = COLUMNS; + state->map.rows = ROWS; + state->map.tileWidth = TILE; + state->map.tileHeight = TILE; + state->map.tiles = state->tiles; + state->solid[1] = 1; + for (row = 0; row < ROWS; row++) { + for (column = 0; column < COLUMNS; column++) state->tiles[row * COLUMNS + column] = level[row][column] == '#'; + } + + state->brick = makeBrick(); + state->floor = makeFloor(); +#if defined(KON_MODULE_ASSETS) && defined(KON_MODULE_IMAGE) + if (program->assets) state->marker = kon_assetImage(program->assets, "sprites/marker.png"); +#endif + if (!state->marker) { + state->marker = makeMarker(); + state->ownsMarker = 1; + } + if (!state->brick || !state->floor || !state->marker) return 0; + + state->player = KON_BOX2(1.5f * TILE, 13.5f * TILE, 12, 12); + for (i = 0; i < MARKERS; i++) state->markers[i] = spots[i]; + return 1; +} + +static void shutdown(kon_program_t *program, void *user) +{ + state_t *state = user; + + (void)program; + kon_freeFramebuffer(state->brick); + kon_freeFramebuffer(state->floor); + if (state->ownsMarker) kon_freeFramebuffer(state->marker); +} + +int main(void) +{ + state_t state = {0}; + kon_programConfig_t config = {0}; + + config.title = "corridor"; + config.gameWidth = 480; + config.gameHeight = 270; + config.windowWidth = 960; + config.windowHeight = 540; + config.user = &state; + config.init = init; + config.event = event; + config.update = update; + config.draw = draw; + config.shutdown = shutdown; + return kon_programRun(&config); +} |
