summaryrefslogtreecommitdiff
path: root/kon/kon2d.c
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-12 03:03:33 +0800
committerKonAki <konaki@anokonaki.org>2026-10-12 03:03:33 +0800
commitb77cac581ac41d069f20a24e07211cc200c653eb (patch)
treed237ac74320d242b8488110560cdd9c47d7d34fe /kon/kon2d.c
parent5e2858a4c8b298149bc01ea5dd215cb1f702c8e3 (diff)
add 2d module: platformer body, camera, frame animation
Diffstat (limited to 'kon/kon2d.c')
-rw-r--r--kon/kon2d.c196
1 files changed, 196 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);
+}