/* * platform: a box that runs and jumps around a tilemap, and a ray that follows the mouse * * build: ./build.sh -d -m tile,font obj/platform * run: obj/platform * * left and right or a and d run, space or up jumps. the line from the box to the mouse stops at the first solid tile * the console is not needed, but with the debug module the grave key opens it and the variables gravity and jump are there */ #include #include #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/platform" #endif #define TILE 16 #define COLUMNS 40 #define ROWS 22 #define STEP (1.0 / 120.0) #define COL_BACKGROUND 0xFF1B1826u #define COL_TILE 0xFF4A4468u #define COL_TILE_TOP 0xFF6B5F92u #define COL_PLAYER 0xFFF2A7C3u #define COL_RAY 0xFF8FD3C8u #define COL_HIT 0xFFF3DDA0u #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_box2_t player; kon_vec2_t velocity; int onGround; double leftover, coyote; int left, right, jumpHeld; float gravity, jump; int mouseX, mouseY; int rayHit; kon_hit2_t hit; } state_t; /*** program ***/ static int event(kon_program_t *program, const kon_event_t *ev, void *user) { state_t *state = user; if (ev->type == KON_EVENT_MOUSE_MOVE) { kon_windowToGame(program->display, ev->mouseX, ev->mouseY, &state->mouseX, &state->mouseY); } else if (ev->type == KON_EVENT_KEY_DOWN || ev->type == KON_EVENT_KEY_UP) { int down = ev->type == KON_EVENT_KEY_DOWN; if (ev->key == KON_KEY_LEFT || ev->key == KON_KEY_A) state->left = down; else if (ev->key == KON_KEY_RIGHT || ev->key == KON_KEY_D) state->right = down; else if (ev->key == KON_KEY_SPACE || ev->key == KON_KEY_UP) state->jumpHeld = down; else return 0; } else { return 0; } return 1; } static void step(state_t *state, float dt) { float run = (float)(state->right - state->left) * 120.0f; int flags; state->velocity.x += (run - state->velocity.x) * (state->onGround ? 18.0f : 8.0f) * dt; state->velocity.y += state->gravity * dt; if (state->coyote > 0.0) state->coyote -= dt; if (state->jumpHeld && state->coyote > 0.0) { state->velocity.y = -state->jump; state->coyote = 0.0; } /* letting go early cuts the jump short */ if (!state->jumpHeld && state->velocity.y < -60.0f) state->velocity.y = -60.0f; flags = kon_tilemapMove(&state->map, &state->player, state->velocity.x * dt, state->velocity.y * dt, state->solid, 2); if (flags & (KON_HIT_LEFT | KON_HIT_RIGHT)) state->velocity.x = 0.0f; if (flags & (KON_HIT_UP | KON_HIT_DOWN)) state->velocity.y = 0.0f; state->onGround = (flags & KON_HIT_DOWN) != 0; if (state->onGround) state->coyote = 0.08; } static void update(kon_program_t *program, double dt, void *user) { state_t *state = user; kon_vec2_t from = KON_VEC2(state->player.x + state->player.w * 0.5f, state->player.y + state->player.h * 0.5f); kon_vec2_t dir = KON_VEC2((float)state->mouseX - from.x, (float)state->mouseY - from.y); float length = kon_vec2Length(dir); int row, column; (void)program; state->leftover += dt; while (state->leftover >= STEP) { step(state, (float)STEP); state->leftover -= STEP; } /* the ray against every solid tile, nothing clever, the nearest one wins */ state->rayHit = 0; if (length < 1.0f) return; dir = kon_vec2Scale(dir, 1.0f / length); for (row = 0; row < ROWS; row++) { for (column = 0; column < COLUMNS; column++) { kon_hit2_t hit; if (!state->solid[kon_tilemapAt(&state->map, column, row)]) continue; if (!kon_rayBox(from, dir, length, KON_BOX2(column * TILE, row * TILE, TILE, TILE), &hit)) continue; if (!state->rayHit || hit.time < state->hit.time) { state->hit = hit; state->rayHit = 1; } } } } static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user) { state_t *state = user; int row, column, ex, ey; int fromX = (int)(state->player.x + state->player.w * 0.5f), fromY = (int)(state->player.y + state->player.h * 0.5f); kon_clearFramebuffer(game, COL_BACKGROUND); for (row = 0; row < ROWS; row++) { for (column = 0; column < COLUMNS; column++) { if (!state->solid[kon_tilemapAt(&state->map, column, row)]) continue; kon_fillRectangle(game, column * TILE, row * TILE, TILE, TILE, COL_TILE); if (!state->solid[kon_tilemapAt(&state->map, column, row - 1)]) kon_fillRectangle(game, column * TILE, row * TILE, TILE, 2, COL_TILE_TOP); } } ex = state->rayHit ? (int)state->hit.point.x : state->mouseX; ey = state->rayHit ? (int)state->hit.point.y : state->mouseY; kon_drawLine(game, fromX, fromY, ex, ey, COL_RAY); if (state->rayHit) kon_fillCircle(game, ex, ey, 2, COL_HIT); kon_fillRectangle(game, (int)state->player.x, (int)state->player.y, (int)state->player.w, (int)state->player.h, COL_PLAYER); if (program->font) kon_drawText(game, program->font, 24, 40, "arrows or a d to run, space to jump", COL_DIM); } static int init(kon_program_t *program, void *user) { state_t *state = user; int row, column; 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->player = KON_BOX2(40, 280, 10, 14); state->gravity = 700.0f; state->jump = 260.0f; kon_debugVar(program->debug, "gravity", KON_VAR_FLOAT, &state->gravity); kon_debugVar(program->debug, "jump", KON_VAR_FLOAT, &state->jump); return 1; } int main(void) { state_t state = {0}; kon_programConfig_t config = {0}; config.title = "platform"; config.gameWidth = 640; config.gameHeight = 352; config.windowWidth = 1280; config.windowHeight = 704; config.user = &state; config.init = init; config.event = event; config.update = update; config.draw = draw; return kon_programRun(&config); }