summaryrefslogtreecommitdiff
path: root/kon
diff options
context:
space:
mode:
Diffstat (limited to 'kon')
-rw-r--r--kon/kon2d.c196
-rw-r--r--kon/kon2d.h112
2 files changed, 308 insertions, 0 deletions
diff --git a/kon/kon2d.c b/kon/kon2d.c
new file mode 100644
index 0000000..2cc9fdd
--- /dev/null
+++ b/kon/kon2d.c
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * kon2d.c
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "kon2d.h"
+
+#include <math.h>
+
+#define STEP (1.0 / 120.0)
+#define MAX_FRAME 0.25
+
+/*** platformer ***/
+
+void kon_platformerInit(kon_platformer_t *p, kon_box2_t box)
+{
+ kon_platformer_t zero = {0};
+
+ *p = zero;
+ p->box = box;
+ p->runSpeed = 120.0f;
+ p->groundAccel = 18.0f;
+ p->airAccel = 8.0f;
+ p->gravity = 700.0f;
+ p->jumpSpeed = 260.0f;
+ p->jumpCut = 60.0f;
+ p->maxFall = 400.0f;
+ p->coyoteTime = 0.08f;
+ p->bufferTime = 0.1f;
+}
+
+int kon_platformerStep(kon_platformer_t *p, const kon_tilemap_t *map, const uint8_t *solid, int solidCount, float dt)
+{
+ float run = (float)p->moveX * p->runSpeed;
+ float ease = (p->onGround ? p->groundAccel : p->airAccel) * dt;
+ int flags;
+
+ if (ease > 1.0f) ease = 1.0f;
+ p->velocity.x += (run - p->velocity.x) * ease;
+ p->velocity.y += p->gravity * dt;
+ if (p->velocity.y > p->maxFall) p->velocity.y = p->maxFall;
+
+ if (p->jumpHeld && !p->jumpWas) p->buffer = p->bufferTime;
+ p->jumpWas = p->jumpHeld;
+ if (p->buffer > 0.0f) p->buffer -= dt;
+ if (p->coyote > 0.0f) p->coyote -= dt;
+
+ if (p->buffer > 0.0f && p->coyote > 0.0f) {
+ p->velocity.y = -p->jumpSpeed;
+ p->buffer = 0.0f;
+ p->coyote = 0.0f;
+ }
+ if (!p->jumpHeld && p->velocity.y < -p->jumpCut) p->velocity.y = -p->jumpCut;
+
+ flags = kon_tilemapMove(map, &p->box, p->velocity.x * dt, p->velocity.y * dt, solid, solidCount);
+
+ if (flags & (KON_HIT_LEFT | KON_HIT_RIGHT)) p->velocity.x = 0.0f;
+ if (flags & (KON_HIT_UP | KON_HIT_DOWN)) p->velocity.y = 0.0f;
+ p->onGround = (flags & KON_HIT_DOWN) != 0;
+ if (p->onGround) p->coyote = p->coyoteTime;
+ return flags;
+}
+
+void kon_platformerUpdate(kon_platformer_t *p, const kon_tilemap_t *map, const uint8_t *solid, int solidCount, double dt)
+{
+ if (!(dt > 0.0)) return;
+ if (dt > MAX_FRAME) dt = MAX_FRAME;
+
+ p->leftover += (float)dt;
+ while (p->leftover >= (float)STEP) {
+ kon_platformerStep(p, map, solid, solidCount, (float)STEP);
+ p->leftover -= (float)STEP;
+ }
+}
+
+/*** camera ***/
+
+static float clampAxis(float value, float view, float bounds)
+{
+ if (bounds <= view) return (bounds - view) * 0.5f;
+ if (value < 0.0f) return 0.0f;
+ if (value > bounds - view) return bounds - view;
+ return value;
+}
+
+void kon_camera2Init(kon_camera2_t *cam, int viewWidth, int viewHeight, float boundsWidth, float boundsHeight)
+{
+ cam->x = cam->y = 0.0f;
+ cam->viewWidth = viewWidth;
+ cam->viewHeight = viewHeight;
+ cam->boundsWidth = boundsWidth;
+ cam->boundsHeight = boundsHeight;
+ cam->stiffness = 8.0f;
+ cam->x = clampAxis(0.0f, (float)viewWidth, boundsWidth);
+ cam->y = clampAxis(0.0f, (float)viewHeight, boundsHeight);
+}
+
+void kon_camera2Snap(kon_camera2_t *cam, float targetX, float targetY)
+{
+ cam->x = clampAxis(targetX - (float)cam->viewWidth * 0.5f, (float)cam->viewWidth, cam->boundsWidth);
+ cam->y = clampAxis(targetY - (float)cam->viewHeight * 0.5f, (float)cam->viewHeight, cam->boundsHeight);
+}
+
+void kon_camera2Follow(kon_camera2_t *cam, float targetX, float targetY, float dt)
+{
+ float wantX = clampAxis(targetX - (float)cam->viewWidth * 0.5f, (float)cam->viewWidth, cam->boundsWidth);
+ float wantY = clampAxis(targetY - (float)cam->viewHeight * 0.5f, (float)cam->viewHeight, cam->boundsHeight);
+ float ease = 1.0f;
+
+ if (cam->stiffness > 0.0f && dt > 0.0f) ease = 1.0f - expf(-cam->stiffness * dt);
+ cam->x += (wantX - cam->x) * ease;
+ cam->y += (wantY - cam->y) * ease;
+}
+
+int kon_camera2X(const kon_camera2_t *cam)
+{
+ return (int)floorf(cam->x + 0.5f);
+}
+
+int kon_camera2Y(const kon_camera2_t *cam)
+{
+ return (int)floorf(cam->y + 0.5f);
+}
+
+/*** animation ***/
+
+void kon_animPlay(kon_anim_t *anim, int first, int count, float fps, int loop)
+{
+ if (anim->first == first && anim->count == count && anim->fps == fps && anim->loop == loop) return;
+ anim->first = first;
+ anim->count = count;
+ anim->fps = fps;
+ anim->loop = loop;
+ anim->time = 0.0f;
+}
+
+void kon_animStep(kon_anim_t *anim, float dt)
+{
+ if (anim->count <= 0 || !(dt > 0.0f)) return;
+ anim->time += dt;
+ if (anim->loop) {
+ float length = (float)anim->count / anim->fps;
+
+ if (anim->fps > 0.0f && anim->time >= length) anim->time = fmodf(anim->time, length);
+ }
+}
+
+int kon_animFrame(const kon_anim_t *anim)
+{
+ int n;
+
+ if (anim->count <= 0) return anim->first;
+ if (!(anim->fps > 0.0f)) return anim->first;
+ n = (int)(anim->time * anim->fps);
+ if (n >= anim->count) n = anim->loop ? n % anim->count : anim->count - 1;
+ return anim->first + n;
+}
+
+int kon_animDone(const kon_anim_t *anim)
+{
+ if (anim->loop || anim->count <= 0) return 0;
+ return anim->fps > 0.0f && (int)(anim->time * anim->fps) >= anim->count;
+}
+
+void kon_drawFrame(kon_framebuffer_t *fb, const kon_image *sheet, int frameWidth, int frameHeight, int index, int x, int y, int flip)
+{
+ kon_rect_t src = kon_frameRect(sheet, frameWidth, frameHeight, index);
+
+ if (src.w <= 0) return;
+ if (!flip) {
+ kon_drawImageRegion(fb, x, y, sheet, src);
+ return;
+ }
+ kon_drawImageEx(fb, sheet, src, (float)x + (float)frameWidth * 0.5f, (float)y + (float)frameHeight * 0.5f,
+ (float)frameWidth * 0.5f, (float)frameHeight * 0.5f, -1.0f, 1.0f, 0.0f, KON_FILTER_NEAREST);
+}
diff --git a/kon/kon2d.h b/kon/kon2d.h
new file mode 100644
index 0000000..7a58dc6
--- /dev/null
+++ b/kon/kon2d.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * kon2d.h
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef KON2D_H
+#define KON2D_H
+
+#include "konsofren.h"
+#include "koncollide.h"
+#include "kontile.h"
+
+/*** platformer ***/
+
+/* a box that runs and jumps through a tilemap. the game sets moveX and jumpHeld every frame, calls kon_platformerUpdate and reads
+ box, velocity and onGround back. everything is in map pixels and seconds. kon_platformerInit fills in the tuning values,
+ change them afterwards as you like */
+typedef struct kon_platformer {
+ kon_box2_t box;
+ kon_vec2_t velocity;
+ int onGround;
+
+ int moveX; /* -1 left, 0 stand, 1 right */
+ int jumpHeld;
+
+ float runSpeed;
+ float groundAccel, airAccel; /* how fast velocity.x closes in on the run speed, per second */
+ float gravity;
+ float jumpSpeed;
+ float jumpCut; /* letting go of jump early slows the rise to this speed */
+ float maxFall;
+ float coyoteTime; /* seconds after leaving a ledge a jump still works */
+ float bufferTime; /* seconds before landing a jump press is remembered */
+
+ /* private */
+ float coyote, buffer, leftover;
+ int jumpWas;
+} kon_platformer_t;
+
+void kon_platformerInit(kon_platformer_t *p, kon_box2_t box);
+
+/* one step of dt seconds, dt should be small and steady. returns the KON_HIT_ flags of the move */
+int kon_platformerStep(kon_platformer_t *p, const kon_tilemap_t *map, const uint8_t *solid, int solidCount, float dt);
+
+/* takes the frame time and runs steps of 1/120 of a second, so the jump feels the same at any frame rate */
+void kon_platformerUpdate(kon_platformer_t *p, const kon_tilemap_t *map, const uint8_t *solid, int solidCount, double dt);
+
+/*** camera ***/
+
+/* x, y is the map pixel at the top left of the view. bounds is the size of the map in pixels, the view never leaves it
+ (and a map smaller than the view is centred) */
+typedef struct kon_camera2 {
+ float x, y;
+ int viewWidth, viewHeight;
+ float boundsWidth, boundsHeight;
+ float stiffness; /* how quickly it catches up, around 6 to 12 feels right, 0 or less follows exactly */
+} kon_camera2_t;
+
+void kon_camera2Init(kon_camera2_t *cam, int viewWidth, int viewHeight, float boundsWidth, float boundsHeight);
+
+/* puts the target in the middle of the view at once */
+void kon_camera2Snap(kon_camera2_t *cam, float targetX, float targetY);
+
+/* eases towards having the target in the middle */
+void kon_camera2Follow(kon_camera2_t *cam, float targetX, float targetY, float dt);
+
+/* whole pixels, what to pass to kon_drawTilemap so nothing shimmers */
+int kon_camera2X(const kon_camera2_t *cam);
+int kon_camera2Y(const kon_camera2_t *cam);
+
+/*** animation ***/
+
+/* plays count frames starting at frame first of a sheet at fps frames a second */
+typedef struct kon_anim {
+ int first, count;
+ float fps;
+ int loop;
+ float time;
+} kon_anim_t;
+
+/* starts over, unless it is already playing the same thing */
+void kon_animPlay(kon_anim_t *anim, int first, int count, float fps, int loop);
+void kon_animStep(kon_anim_t *anim, float dt);
+
+/* the frame to draw, the last one when a non looping animation has finished */
+int kon_animFrame(const kon_anim_t *anim);
+int kon_animDone(const kon_anim_t *anim);
+
+/* draws frame index of a sheet of frameWidth by frameHeight frames with its top left at x, y */
+void kon_drawFrame(kon_framebuffer_t *fb, const kon_image *sheet, int frameWidth, int frameHeight, int index, int x, int y, int flip);
+
+#endif