aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--1bitjam.c27
-rw-r--r--Makefile36
-rw-r--r--animation.c9
-rw-r--r--animation.h12
-rw-r--r--asset.c4
-rw-r--r--asset.h1
-rw-r--r--config.h32
-rw-r--r--convanim.c79
-rw-r--r--intermediate/guy.bmpbin394 -> 12426 bytes
-rw-r--r--intermediate/guy_fall_left.anm2
-rw-r--r--intermediate/guy_fall_right.anm2
-rw-r--r--intermediate/guy_idle_left.anm2
-rw-r--r--intermediate/guy_idle_right.anm2
-rw-r--r--intermediate/guy_jump_left.anm2
-rw-r--r--intermediate/guy_jump_right.anm2
-rw-r--r--intermediate/guy_run_left.anm7
-rw-r--r--intermediate/guy_run_right.anm7
-rw-r--r--intermediate/map.bmpbin0 -> 30858 bytes
-rw-r--r--intermediate/mask.bmpbin0 -> 16522 bytes
-rw-r--r--map.c68
-rw-r--r--map.h60
-rw-r--r--obj.h32
-rw-r--r--physics.c204
-rw-r--r--physics.h19
-rw-r--r--plat.c12
-rw-r--r--plat.h4
-rw-r--r--player.c132
-rw-r--r--rect.c16
-rw-r--r--rect.h2
-rw-r--r--render.h2
31 files changed, 751 insertions, 27 deletions
diff --git a/.gitignore b/.gitignore
index 0a86950..40ef3ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ tags
pack.h
/data/
/convimg
+/convanim
/packassets
/1bitjam
/distrib
diff --git a/1bitjam.c b/1bitjam.c
index c2a84e1..619876e 100644
--- a/1bitjam.c
+++ b/1bitjam.c
@@ -1,7 +1,9 @@
#include "asset.h"
#include "config.h"
+#include "map.h"
#include "maths.h"
#include "memory.h"
+#include "obj.h"
#include "plat.h"
#include "rect.h"
#include "render.h"
@@ -11,9 +13,8 @@ int entrypoint(int argc, const char** argv, Arena* m) {
App* a;
FPS f;
Renderer r;
- Rect rect = { 0, 0, 32, 32 };
- Rect rect2 = { 0, 0, 8, 8 };
- int x = 0, y = 0;
+ Player player;
+ Map map;
(void)argc;
(void)argv;
init_maths();
@@ -25,25 +26,19 @@ int entrypoint(int argc, const char** argv, Arena* m) {
a = new_app(&h, game_name);
init_audio();
init_fps(&f, default_mpf);
+ generate_floor(&map, 0);
+ init_player(&player);
while (a->o) {
fps_begin(&f);
while (f.now >= f.next && a->o) {
app_begin(a);
+
+ update_player(&player, a, &map);
+
ren_begin(&r, a->fb, viewport_w, viewport_h);
ren_clear(&r);
- if (a->btn_states[btn_left] & btn_state_pressed)
- x--;
- if (a->btn_states[btn_right] & btn_state_pressed)
- x++;
- if (a->btn_states[btn_up] & btn_state_pressed)
- y--;
- if (a->btn_states[btn_down] & btn_state_pressed)
- y++;
- if (a->btn_states[btn_shoot] & btn_state_just_pressed)
- play_sound(30);
- ren_text(&r, 30, 60, "Hello");
- ren_map(&r, 10, 5, &rect, get_bitmap(asset_id_hello_img));
- ren_map(&r, x, y, &rect2, get_bitmap(asset_id_guy_img));
+ render_map(&map, &r);
+ ren_player(&player, &r);
ren_end(&r);
app_end(a);
fps_update(&f);
diff --git a/Makefile b/Makefile
index 24a80cd..4c755b9 100644
--- a/Makefile
+++ b/Makefile
@@ -15,6 +15,7 @@ target = 1bitjam
int_dir = intermediate
data_dir = data
convimg = convimg
+convanim = convanim
packassets = packassets
pack = pack.h
@@ -43,19 +44,35 @@ endif
sources = \
1bitjam.c \
+ animation.c \
asset.c \
+ map.c \
maths.c \
memory.c \
+ physics.c \
plat.c \
+ player.c \
rect.c \
render.c \
image_sources = \
$(int_dir)/guy.bmp \
- $(int_dir)/hello.bmp
-
-objects = $(sources:%.c=%.o)
-images = $(image_sources:$(int_dir)/%.bmp=$(data_dir)/%.img)
+ $(int_dir)/map.bmp \
+ $(int_dir)/mask.bmp
+
+anim_sources = \
+ $(int_dir)/guy_fall_left.anm \
+ $(int_dir)/guy_fall_right.anm \
+ $(int_dir)/guy_idle_left.anm \
+ $(int_dir)/guy_idle_right.anm \
+ $(int_dir)/guy_jump_left.anm \
+ $(int_dir)/guy_jump_right.anm \
+ $(int_dir)/guy_run_left.anm \
+ $(int_dir)/guy_run_right.anm \
+
+objects = $(sources:%.c=%.o)
+images = $(image_sources:$(int_dir)/%.bmp=$(data_dir)/%.img)
+animations = $(anim_sources:$(int_dir)/%.anm=$(data_dir)/%.anm)
all: $(target) $(pack)
@@ -65,15 +82,22 @@ $(objects): %.o : %.c | $(pack)
$(images): $(data_dir)/%.img : $(int_dir)/%.bmp | $(convimg) $(data_dir)
./$(convimg) $< $@
-$(pack): $(packassets) $(images)
+$(animations): $(data_dir)/%.anm : $(int_dir)/%.anm | $(convanim) $(data_dir)
+ ./$(convanim) $< $@
+
+$(pack): $(packassets) $(images) $(animations)
./$(packassets) \
$(pack) \
$(data_dir) \
+ $(notdir $(animations)) \
$(notdir $(images))
$(convimg): convimg.c
$(tool_compiler) convimg.c $(cflags) -o $@
+$(convanim): convanim.c
+ $(tool_compiler) convanim.c $(cflags) -o $@
+
$(packassets): packassets.c
$(tool_compiler) packassets.c $(cflags) -o $@
@@ -89,5 +113,7 @@ clean:
rm $(pack)
rm -r $(data_dir)
rm $(target)
+ rm $(convimg)
+ rm $(convanim)
-include $(sources:%.c=%.d)
diff --git a/animation.c b/animation.c
new file mode 100644
index 0000000..388e29d
--- /dev/null
+++ b/animation.c
@@ -0,0 +1,9 @@
+#include "animation.h"
+#include "rect.h"
+
+void update_anim(const Animation* a, int* f, Rect* r) {
+ f[0]++;
+ if (f[0] >= a->fc * a->s)
+ f[0] = 0;
+ *r = ((const Rect*)&a[1])[f[0] / a->s];
+}
diff --git a/animation.h b/animation.h
new file mode 100644
index 0000000..983fed8
--- /dev/null
+++ b/animation.h
@@ -0,0 +1,12 @@
+#ifndef animation_h
+#define animation_h
+
+struct Rect;
+
+typedef struct Animation {
+ int fc, s;
+} Animation;
+
+void update_anim(const Animation* a, int* f, struct Rect* r);
+
+#endif
diff --git a/asset.c b/asset.c
index 78085ec..31a6d8c 100644
--- a/asset.c
+++ b/asset.c
@@ -8,3 +8,7 @@ const unsigned char* get_asset(Asset_ID id) {
const struct Bitmap* get_bitmap(Asset_ID id) {
return (const struct Bitmap*)get_asset(id);
}
+
+const struct Animation* get_animation(Asset_ID id) {
+ return (const struct Animation*)get_asset(id);
+}
diff --git a/asset.h b/asset.h
index 74cf66b..09b631e 100644
--- a/asset.h
+++ b/asset.h
@@ -7,5 +7,6 @@
const unsigned char* get_asset(Asset_ID id);
const struct Bitmap* get_bitmap(Asset_ID id);
+const struct Animation* get_animation(Asset_ID id);
#endif
diff --git a/config.h b/config.h
index 418e7f4..2fc4561 100644
--- a/config.h
+++ b/config.h
@@ -1,20 +1,42 @@
#ifndef config_h
#define config_h
+#include "maths.h"
+
#define game_name "1bit Game Jam"
-#define memory_size (1024 * 8)
-#define app_memory_size (1024 * 4)
+#define memory_size (1024 * 32)
+#define app_memory_size (1024 * 16)
#define max_pc_window_w 3000
#define max_pc_window_h 3000
-#define viewport_w 128
-#define viewport_h 128
+#define viewport_w 320
+#define viewport_h 240
#define default_scale 3
-#define default_mpf 50
+#define default_mpf 20
+
+#define map_w 20
+#define map_h 15
+#define map_tile_size 16
#define audio_buffer_size 64
#define audio_sample_rate 8000
+#define main_gravity (f1 / 4)
+#define main_gravity_ramp (f1)
+
+#define max_velocity (f1 * 4)
+
+#define player_move_force (f1 / 2)
+#define player_air_move_force (f1 / 10)
+#define player_max_vel 853
+#define player_friction (f1 / 4 + f1 / 20)
+#define player_stop_thresh 128
+#define player_jump_power (2 << fbits)
+#define player_jump_power_air (f1)
+#define player_shoot_cooldown 20
+#define player_attack_frames 10
+#define player_lunge_force (f1)
+
#endif
diff --git a/convanim.c b/convanim.c
new file mode 100644
index 0000000..80a1b4a
--- /dev/null
+++ b/convanim.c
@@ -0,0 +1,79 @@
+#include "rect.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define max_rects 32
+
+const char* next_line(const char* line) {
+ const char* c;
+ for (c = line; *c != '\n'; c++) {
+ if (!*c) return 0;
+ }
+ return c + 1;
+}
+
+int line_len(const char* line) {
+ const char* c;
+ int r = 0;
+ for (c = line; *c && *c != '\n'; c++, r++);
+ return r;
+}
+
+int main(int argc, char** argv) {
+ FILE* infile;
+ const char* line;
+ char* src;
+ int size;
+ int speed;
+ Rect rects[max_rects];
+ Rect r;
+ int fc = 0;
+ if (argc < 3) {
+ fprintf(stderr, "Usage: %s infile outfile.\n", argv[0]);
+ return 1;
+ }
+ infile = fopen(argv[1], "rb");
+ if (!infile) {
+ fprintf(stderr, "Failed to open %s.\n", argv[1]);
+ return 2;
+ }
+ fseek(infile, 0, SEEK_END);
+ size = ftell(infile);
+ rewind(infile);
+ src = malloc(size + 1);
+ fread(src, 1, size, infile);
+ src[size] = 0;
+ fclose(infile);
+ line = src;
+ speed = (int)strtol(line, 0, 10);
+ line = next_line(line);
+ for (; line; line = next_line(line)) {
+ int ll = line_len(line);
+ char* line2 = malloc(ll + 1);
+ line2[ll] = 0; /* lmao */
+ memcpy(line2, line, ll);
+ if (sscanf(line,
+ "{%d,%d,%d,%d}",
+ &r.x,
+ &r.y,
+ &r.w,
+ &r.h
+ ) != 4) break;
+ rects[fc++] = r;
+ }
+ free(src);
+ {
+ FILE* outfile = fopen(argv[2], "wb");
+ if (!outfile) {
+ fprintf(stderr, "Failed to open %s.\n", argv[2]);
+ return 2;
+ }
+ fwrite(&fc, 1, 4, outfile);
+ fwrite(&speed, 1, 4, outfile);
+ fwrite(rects, fc, sizeof r, outfile);
+ fclose(outfile);
+ }
+ return 0;
+}
diff --git a/intermediate/guy.bmp b/intermediate/guy.bmp
index 9a881e0..c7b155d 100644
--- a/intermediate/guy.bmp
+++ b/intermediate/guy.bmp
Binary files differ
diff --git a/intermediate/guy_fall_left.anm b/intermediate/guy_fall_left.anm
new file mode 100644
index 0000000..61e8acd
--- /dev/null
+++ b/intermediate/guy_fall_left.anm
@@ -0,0 +1,2 @@
+1
+{ 32, 16, 16, 16 }
diff --git a/intermediate/guy_fall_right.anm b/intermediate/guy_fall_right.anm
new file mode 100644
index 0000000..1919fdd
--- /dev/null
+++ b/intermediate/guy_fall_right.anm
@@ -0,0 +1,2 @@
+1
+{ 32, 0, 16, 16 }
diff --git a/intermediate/guy_idle_left.anm b/intermediate/guy_idle_left.anm
new file mode 100644
index 0000000..fe05177
--- /dev/null
+++ b/intermediate/guy_idle_left.anm
@@ -0,0 +1,2 @@
+1
+{ 0, 16, 16, 16 }
diff --git a/intermediate/guy_idle_right.anm b/intermediate/guy_idle_right.anm
new file mode 100644
index 0000000..86616a0
--- /dev/null
+++ b/intermediate/guy_idle_right.anm
@@ -0,0 +1,2 @@
+1
+{ 0, 0, 16, 16 }
diff --git a/intermediate/guy_jump_left.anm b/intermediate/guy_jump_left.anm
new file mode 100644
index 0000000..acec84e
--- /dev/null
+++ b/intermediate/guy_jump_left.anm
@@ -0,0 +1,2 @@
+1
+{ 16, 16, 16, 16 }
diff --git a/intermediate/guy_jump_right.anm b/intermediate/guy_jump_right.anm
new file mode 100644
index 0000000..633f46e
--- /dev/null
+++ b/intermediate/guy_jump_right.anm
@@ -0,0 +1,2 @@
+1
+{ 16, 0, 16, 16 }
diff --git a/intermediate/guy_run_left.anm b/intermediate/guy_run_left.anm
new file mode 100644
index 0000000..4d13a8e
--- /dev/null
+++ b/intermediate/guy_run_left.anm
@@ -0,0 +1,7 @@
+4
+{ 0, 16, 16, 16 }
+{ 16, 16, 16, 16 }
+{ 16, 16, 16, 16 }
+{ 0, 16, 16, 16 }
+{ 32, 16, 16, 16 }
+{ 32, 16, 16, 16 }
diff --git a/intermediate/guy_run_right.anm b/intermediate/guy_run_right.anm
new file mode 100644
index 0000000..b6098c5
--- /dev/null
+++ b/intermediate/guy_run_right.anm
@@ -0,0 +1,7 @@
+4
+{ 0, 0, 16, 16 }
+{ 16, 0, 16, 16 }
+{ 16, 0, 16, 16 }
+{ 0, 0, 16, 16 }
+{ 32, 0, 16, 16 }
+{ 32, 0, 16, 16 }
diff --git a/intermediate/map.bmp b/intermediate/map.bmp
new file mode 100644
index 0000000..13ee910
--- /dev/null
+++ b/intermediate/map.bmp
Binary files differ
diff --git a/intermediate/mask.bmp b/intermediate/mask.bmp
new file mode 100644
index 0000000..0363e3d
--- /dev/null
+++ b/intermediate/mask.bmp
Binary files differ
diff --git a/map.c b/map.c
new file mode 100644
index 0000000..855f636
--- /dev/null
+++ b/map.c
@@ -0,0 +1,68 @@
+#include "asset.h"
+#include "map.h"
+#include "rect.h"
+#include "render.h"
+
+#define null_tile 0xffff
+
+Rect tile_rects[] = {
+#define x(n, x, y) \
+ { x, y, map_tile_size, map_tile_size },
+ tiles_xmacro
+#undef x
+};
+
+void generate_floor(Map* m, int seed) {
+ int e = map_w * map_h, i;
+ for (i = 0; i < e; i++) {
+ m->tiles[i] = null_tile;
+ m->collision[i] = 0;
+ }
+ e = map_w * (map_h - 1);
+ for (i = 0; i < map_w; i++) {
+ int idx = i + e;
+ m->tiles[idx] = tile_stone;
+ m->collision[idx] = 1;
+ }
+#define set(x, y) \
+ m->tiles[x + y * map_w] = tile_brick; \
+ m->collision[x + y * map_w] = 1;
+ set(0, 13);
+ set(0, 12);
+ set(0, 11);
+ set(1, 11);
+ set(2, 11);
+ set(3, 11);
+ set(4, 11);
+ set(19, 13);
+ set(19, 12);
+ set(19, 11);
+ set(19, 10);
+ set(19, 9);
+ set(19, 8);
+#undef set
+ m->tiles[18 + 13 * map_w] = tile_stone_ramp9;
+ m->collision[18 + 13 * map_w] = 6;
+ m->tiles[1 + 12 * map_w] = tile_stone_ramp13;
+ m->collision[1 + 12 * map_w] = 10;
+ m->tiles[2 + 12 * map_w] = tile_stone_ramp14;
+ m->collision[2 + 12 * map_w] = 11;
+}
+
+void render_map(const Map* m, Renderer* r) {
+ const Bitmap* tm = get_bitmap(asset_id_map_img);
+ int t = 0, x, y;
+ for (y = 0; y < map_h; y++) {
+ for (x = 0; x < map_w; x++, t++) {
+ int tile = m->tiles[t];
+ if (tile != null_tile)
+ ren_map(
+ r,
+ x * map_tile_size,
+ y * map_tile_size,
+ &tile_rects[tile],
+ tm
+ );
+ }
+ }
+}
diff --git a/map.h b/map.h
new file mode 100644
index 0000000..806de4d
--- /dev/null
+++ b/map.h
@@ -0,0 +1,60 @@
+#ifndef map_h_included
+#define map_h_included
+
+#include "config.h"
+
+struct Renderer;
+
+#define tiles_xmacro \
+ x(tile_stone, 0, 0) \
+ x(tile_block1, 16, 0) \
+ x(tile_block2, 32, 0) \
+ x(tile_brick, 48, 0) \
+ x(tile_ladder1, 64, 0) \
+ x(tile_ladder2, 80, 0) \
+ x(tile_stone_ramp1, 96, 0) \
+ x(tile_stone_ramp2, 112, 0) \
+ x(tile_stone_ramp3, 128, 0) \
+ x(tile_stone_ramp4, 144, 0) \
+ x(tile_stone_ramp5, 0, 16) \
+ x(tile_stone_ramp6, 16, 16) \
+ x(tile_stone_ramp7, 32, 16) \
+ x(tile_stone_ramp8, 48, 16) \
+ x(tile_stone_ramp9, 64, 16) \
+ x(tile_stone_ramp10, 80, 16) \
+ x(tile_stone_ramp11, 96, 16) \
+ x(tile_stone_ramp12, 112, 16) \
+ x(tile_stone_ramp13, 128, 16) \
+ x(tile_stone_ramp14, 144, 16) \
+ x(tile_brick_ramp5, 0, 32) \
+ x(tile_brick_ramp6, 16, 32) \
+ x(tile_brick_ramp7, 32, 32) \
+ x(tile_brick_ramp8, 48, 32) \
+ x(tile_brick_ramp9, 64, 32) \
+ x(tile_brick_ramp10, 80, 32) \
+ x(tile_brick_ramp11, 96, 32) \
+ x(tile_brick_ramp12, 112, 32) \
+ x(tile_brick_ramp13, 128, 32) \
+
+typedef enum {
+#define x(n, x, y) \
+ n,
+ tiles_xmacro
+#undef x
+ tile_count
+} Map_Tile;
+
+typedef struct Map {
+ unsigned short tiles[map_w * map_h];
+ unsigned short collision[map_w * map_h];
+ /* collision:
+ * 0 = nothing
+ * 1 = tile
+ * >2 = mask
+ */
+} Map;
+
+void generate_floor(Map* m, int seed);
+void render_map(const Map* m, struct Renderer* r);
+
+#endif
diff --git a/obj.h b/obj.h
new file mode 100644
index 0000000..9a331b7
--- /dev/null
+++ b/obj.h
@@ -0,0 +1,32 @@
+#ifndef obj_h
+#define obj_h
+
+#include "rect.h"
+
+struct App;
+struct Renderer;
+struct Map;
+
+typedef enum {
+ face_left,
+ face_right
+} Face;
+
+typedef struct {
+ int x, y, vx, vy;
+ int frame;
+ int anim;
+ int grounded, headbutted, on_ramp, jumping;
+ Face face;
+ Rect rect;
+} Player;
+
+void init_player(Player* p);
+void update_player(
+ Player* p,
+ struct App* app,
+ const struct Map* map
+);
+void ren_player(Player* p, struct Renderer* r);
+
+#endif
diff --git a/physics.c b/physics.c
new file mode 100644
index 0000000..b1a8db1
--- /dev/null
+++ b/physics.c
@@ -0,0 +1,204 @@
+#include "asset.h"
+#include "config.h"
+#include "map.h"
+#include "maths.h"
+#include "physics.h"
+#include "rect.h"
+#include "render.h"
+
+int test_bit(
+ unsigned* data,
+ int x,
+ int y,
+ int w
+) {
+ int idx = x + y * w;
+ return data[idx >> 5] & (1 << (idx & 0x1f));
+}
+
+static int solve_mask(
+ unsigned char t,
+ int* x,
+ int* y,
+ int* vy,
+ const Rect* col,
+ const Rect* r,
+ const Rect* tr,
+ int* od
+) {
+ int n, d = 0;
+ const Bitmap* b = get_bitmap(asset_id_mask_img);
+ unsigned* mp;
+ int xoff = 0;
+ int mx = r->x + r->w / 2;
+ int my = (r->y + r->h) - f1;
+ mx -= tr->x;
+ my -= tr->y;
+ mx >>= fbits;
+ my >>= fbits;
+ if (mx < 0 || mx >= map_tile_size)
+ return 0;
+ if ((n = my - map_tile_size) >= 0 && n < 4)
+ my = map_tile_size - 1;
+ mp = (unsigned*)&b[1];
+ xoff = (t & 0xf) * map_tile_size;
+ if (my >= map_tile_size) return 0;
+ if (my < 0 || *vy < 0) goto up;
+ if (test_bit(mp, mx + xoff, my, b->w)) {
+ d = -1;
+ my = map_tile_size - 1;
+ } else {
+up:
+ my = (r->y - tr->y) >> fbits;
+ if (my < 0) {
+ if (my >= -4) my = 0;
+ else return 0;
+ }
+ if (my >= map_tile_size) return 0;
+ if (test_bit(mp, mx + xoff, my, b->w)) d = 1;
+ my = 0;
+ }
+ if (!d) return 0;
+ if (d < 0 && *vy <= 0) return 0;
+ if (d > 0 && *vy >= 0) return 0;
+ for (
+ ;
+ test_bit(mp, mx + xoff, my, b->w) &&
+ my >= 0 &&
+ my < map_tile_size;
+ my += d
+ );
+ if (d < 0) {
+ *y = (tr->y - (col->y + col->h));
+ *y += (my << fbits) + f1 * 2;
+ } else {
+ *y = (tr->y + tr->h) - col->y;
+ *y -= ((map_tile_size - my) << fbits);
+ }
+ *vy = 0;
+ *od = d;
+ return 1;
+}
+
+void update_body(
+ const struct Map* map,
+ int* x,
+ int* y,
+ int* vx,
+ int* vy,
+ int* grounded,
+ int* headbutted,
+ int* on_ramp,
+ const Rect* r
+) {
+ Rect rect = { 0 };
+ int ramp = 0, d;
+ rect.w = r->w;
+ rect.h = r->h;
+ if (grounded[0] < 3) {
+ *vx = (*vx << fbits) / (f1 + player_friction);
+ }
+ grounded[0]++;
+ headbutted[0] = 0;
+#define ittiles(expr) { \
+ int sx, sy, ex, ey, tx, ty; \
+ Rect tr; \
+ rect.x = *x + r->x; \
+ rect.y = *y + r->y; \
+ sx = (rect.x >> fbits) / map_tile_size; \
+ sy = (rect.y >> fbits) / map_tile_size; \
+ ex = ((rect.x + rect.w) >> fbits) / map_tile_size; \
+ ey = ((rect.y + rect.h) >> fbits) / map_tile_size; \
+ tr.w = map_tile_size << fbits; \
+ tr.h = map_tile_size << fbits; \
+ for (ty = sy; ty <= ey && ty < map_h; ty++) { \
+ for (tx = sx; tx <= ex && tx < map_w; tx++) { \
+ int tc = map->collision[tx + ty * map_w]; \
+ tr.x = (tx * map_tile_size) << fbits; \
+ tr.y = (ty * map_tile_size) << fbits; \
+ if (rects_overlap(&tr, &rect)) \
+ expr \
+ } \
+ }}
+ /* increase the gravity if i'm on a ramp, to
+ * smoothly slide down instead of skipping */
+ *vy += main_gravity + main_gravity_ramp * (*on_ramp && *grounded < 3);
+ if (*vy) {
+ *y += *vy;
+ ittiles({
+ if (tc > 1) {
+ int id = tc - 2;
+ if (solve_mask(
+ id,
+ x,
+ y,
+ vy,
+ r,
+ &rect,
+ &tr,
+ &d
+ )) {
+ if (d < 0)
+ *grounded = 0;
+ else {
+ *headbutted = 1;
+ }
+ ramp = 1;
+ }
+ }
+ });
+ if (ramp) goto do_x;
+ ittiles({
+ if (tc == 1) {
+ if (*vy > 0) {
+ *y = tr.y - (r->y + r->h);
+ *vy = 0;
+ *grounded = 0;
+ } else if (*vy < 0) {
+ *y = (tr.y + tr.h) - r->y;
+ *vy = 0;
+ *headbutted = 1;
+ }
+ }
+ });
+ }
+do_x:
+ *on_ramp = ramp;
+ if (*vx) {
+ *x += *vx;
+ if (ramp) {
+ /* Handle the case where the body is at the top
+ * of a ramp hitting into a block. */
+ ittiles({
+ if (tc == 1) {
+ int cy;
+ if (d < 0) cy = rect.y;
+ else cy = rect.y + rect.h;
+ if (*vx < 0 && point_rect_overlap(
+ &tr,
+ rect.x,
+ cy
+ )) *x = (tr.x + tr.w) - r->x;
+ if (*vx > 0 && point_rect_overlap(
+ &tr,
+ rect.x + rect.w,
+ cy
+ )) *x = tr.x - (r->x + r->w);
+ }
+ });
+ return;
+ }
+ ittiles({
+ if (tc == 1) {
+ if (*vx > 0) {
+ *x = tr.x - (r->x + r->w);
+ *vx = 0;
+ } else if (*vx < 0) {
+ *x = (tr.x + tr.w) - r->x;
+ *vx = 0;
+ }
+ }
+ });
+ }
+}
+
diff --git a/physics.h b/physics.h
new file mode 100644
index 0000000..7044996
--- /dev/null
+++ b/physics.h
@@ -0,0 +1,19 @@
+#ifndef physics_h
+#define physics_h
+
+struct Map;
+struct Rect;
+
+void update_body(
+ const struct Map* map,
+ int* x,
+ int* y,
+ int* vx,
+ int* vy,
+ int* grounded,
+ int* headbutted,
+ int* on_ramp,
+ const struct Rect* r
+);
+
+#endif
diff --git a/plat.c b/plat.c
index 7c5c9f8..cc351ff 100644
--- a/plat.c
+++ b/plat.c
@@ -3,6 +3,18 @@
#include "memory.h"
#include "plat.h"
+int btn_pressed(const App* a, Btn btn) {
+ return a->btn_states[btn] & btn_state_pressed;
+}
+
+int btn_just_pressed(const App* a, Btn btn) {
+ return a->btn_states[btn] & btn_state_just_pressed;
+}
+
+int btn_just_released(const App* a, Btn btn) {
+ return a->btn_states[btn] & btn_state_just_released;
+}
+
#ifdef plat_posix
#define _POSIX_SOURCE
#define _GNU_SOURCE
diff --git a/plat.h b/plat.h
index 3c91ef6..32f17f6 100644
--- a/plat.h
+++ b/plat.h
@@ -81,4 +81,8 @@ void init_audio(void);
void deinit_audio(void);
void play_sound(int len);
+int btn_pressed(const App* a, Btn btn);
+int btn_just_pressed(const App* a, Btn btn);
+int btn_just_released(const App* a, Btn btn);
+
#endif
diff --git a/player.c b/player.c
new file mode 100644
index 0000000..a99f40b
--- /dev/null
+++ b/player.c
@@ -0,0 +1,132 @@
+#include "animation.h"
+#include "asset.h"
+#include "map.h"
+#include "maths.h"
+#include "obj.h"
+#include "physics.h"
+#include "plat.h"
+#include "render.h"
+
+void init_player(Player* p) {
+ p->anim = asset_id_guy_run_right_anm;
+ p->x = 0;
+ p->y = 0;
+ p->vx = 0;
+ p->vy = 0;
+ p->frame = 0;
+ p->grounded = 0;
+ p->headbutted = 0;
+ p->on_ramp = 0;
+ p->jumping = 0;
+ p->face = face_right;
+}
+
+void update_player_anim(Player* p) {
+ const Animation* anim = get_animation(p->anim);
+ update_anim(anim, &p->frame, &p->rect);
+}
+
+void update_player_phys(Player* p, const Map* map) {
+ const Rect r = {
+ 5 << fbits,
+ 4 << fbits,
+ 5 << fbits,
+ 12 << fbits
+ };
+ update_body(
+ map,
+ &p->x,
+ &p->y,
+ &p->vx,
+ &p->vy,
+ &p->grounded,
+ &p->headbutted,
+ &p->on_ramp,
+ &r
+ );
+}
+
+void update_player_move(Player* p, const App* a) {
+ int grounded = p->grounded < 3;
+ int mf = player_move_force;
+ int nanim, moving, t;
+ int jumping = p->jumping;
+ Face* face = &p->face;
+ if (!grounded) {
+ mf = player_air_move_force;
+ }
+ if (btn_pressed(a, btn_left)) {
+ p->vx -= mf;
+ *face = face_left;
+ }
+ if (btn_pressed(a, btn_right)) {
+ p->vx += mf;
+ *face = face_right;
+ }
+ p->vx =
+ p->vx < -player_max_vel ? -player_max_vel:
+ p->vx > player_max_vel ? player_max_vel:
+ p->vx;
+ moving = absolute(p->vx);
+ moving = moving > player_stop_thresh;
+ if (p->grounded == 0 || p->headbutted) {
+ p->jumping = 0;
+ }
+ jumping = p->jumping;
+ if (jumping) {
+ if (p->grounded < 10 && btn_pressed(a, btn_jump)) {
+ t = absolute(p->vy + 1);
+ t = (f1 << fbits) / t;
+ p->vy -= (player_jump_power_air * t) >> fbits;
+ }
+ if (p->vy < 0 && btn_just_released(a, btn_jump)) {
+ p->vy += f1;
+ p->jumping = 0;
+ }
+ }
+ if (grounded && btn_just_pressed(a, btn_jump)) {
+ p->vy = -player_jump_power;
+ p->jumping = 1;
+ play_sound(500);
+ }
+ jumping = p->jumping;
+ nanim = p->anim;
+ switch (p->face) {
+ case face_left:
+ nanim =
+ grounded ?
+ moving ?
+ asset_id_guy_run_left_anm:
+ asset_id_guy_idle_left_anm:
+ p->vy < 0 ?
+ asset_id_guy_jump_left_anm:
+ asset_id_guy_fall_left_anm;
+ break;
+ case face_right:
+ nanim =
+ grounded ?
+ moving ?
+ asset_id_guy_run_right_anm:
+ asset_id_guy_idle_right_anm:
+ p->vy < 0 ?
+ asset_id_guy_jump_right_anm:
+ asset_id_guy_fall_right_anm;
+ break;
+ default: break;
+ }
+ if (nanim != p->anim) {
+ p->frame = 0;
+ p->anim = nanim;
+ }
+}
+
+void update_player(Player* p, App* app, const Map* map) {
+ update_player_move(p, app);
+ update_player_phys(p, map);
+ update_player_anim(p);
+}
+
+void ren_player(Player* p, struct Renderer* r) {
+ const Bitmap* b = get_bitmap(asset_id_guy_img);
+ ren_map(r, p->x >> fbits, p->y >> fbits, &p->rect, b);
+}
diff --git a/rect.c b/rect.c
index 69758c1..a35052d 100644
--- a/rect.c
+++ b/rect.c
@@ -1,5 +1,21 @@
#include "rect.h"
+int rects_overlap(const Rect* a, const Rect* b) {
+ return
+ a->x + a->w > b->x &&
+ a->y + a->h > b->y &&
+ a->x < b->x + b->w &&
+ a->y < b->y + b->h;
+}
+
+int point_rect_overlap(const Rect* r, int px, int py) {
+ return
+ px >= r->x &&
+ py >= r->y &&
+ px <= r->x + r->w &&
+ py <= r->y + r->h;
+}
+
Rect* rect_clip(Rect* r, const Rect* c) {
int n;
if ((n = c->x - r->x) > 0) {
diff --git a/rect.h b/rect.h
index 5c89aec..40f5e4e 100644
--- a/rect.h
+++ b/rect.h
@@ -5,6 +5,8 @@ typedef struct Rect {
int x, y, w, h;
} Rect;
+int rects_overlap(const Rect* a, const Rect* b);
+int point_rect_overlap(const Rect* r, int px, int py);
Rect* rect_clip(Rect* r, const Rect* c);
Rect* rect_clipr(Rect* r, const int* c);
Rect* rect_clips(Rect* r, Rect* s, const Rect* c);
diff --git a/render.h b/render.h
index 01391c5..2cb54b8 100644
--- a/render.h
+++ b/render.h
@@ -7,7 +7,7 @@ typedef struct Bitmap {
short w, h;
} Bitmap;
-typedef struct {
+typedef struct Renderer {
unsigned* t;
int w, h;
int clip[4];