A game engine for top-down 2D RPG games.
rpg game-engine raylib c99
1#include "keraforge/fs.h" 2#include <keraforge.h> 3#include <stdlib.h> 4#include <raymath.h> 5#include <string.h> 6 7 8struct kf_actorregistry kf_actorregistry = {0}; 9struct kf_actor *kf_actors = NULL, *kf_actors_last = NULL; 10u32 kf_actor_count = 0; 11 12 13struct kf_actor *kf_actor_new(char *id, struct kf_spritesheet sprites, f32 width, f32 height, bool collides) 14{ 15 struct kf_actor *actor = calloc(1, sizeof(struct kf_actor)); 16 kf_actor_count++; 17 18 if (!kf_actors) 19 { 20 kf_actors = kf_actors_last = actor; 21 } 22 else 23 { 24 actor->prev = kf_actors_last; 25 kf_actors_last->next = actor; 26 kf_actors_last = actor; 27 } 28 29 actor->id = id; 30 actor->sprites = sprites; 31 actor->size.x = width; 32 actor->size.y = height; 33 actor->collide = collides; 34 35 actor->speed = 25; 36 actor->speedmod = 1; 37 actor->friction = 1.5f; 38 actor->pointing = kf_north; 39 40 return actor; 41} 42 43struct kf_spritesheet kf_actor_loadspritesheet(char *filename) 44{ 45 return kf_loadspritesheet(filename, 20, 20); 46} 47 48int kf_actor_canmovetowards(struct kf_world *world, struct kf_actor *actor, struct kf_vec2(f32) dir) 49{ 50 Rectangle r = { 51 actor->pos.x + (actor->size.x / 2) + actor->sizeoffset.x, 52 actor->pos.y + (actor->size.y / 2) + actor->sizeoffset.y, 53 actor->size.x, 54 actor->size.y 55 }; 56 r.x += dir.x; 57 r.y += dir.y; 58 59 /* get a range of tiles to check */ 60 const u32 sx = fmax(0, floorf(r.x / KF_TILE_SIZE_PX) - 1); 61 const u32 sy = fmax(0, floorf(r.y / KF_TILE_SIZE_PX) - 1); 62 const u32 ex = fmin(world->width, ceilf(r.width / KF_TILE_SIZE_PX) + sx + 2); 63 const u32 ey = fmin(world->height, ceilf(r.height / KF_TILE_SIZE_PX) + sy + 2); 64 const size_t down = world->width - ex + sx - 1; /* number of indexes to add to reach the next tile down */ 65 66 /* check if any tiles will collide with the actor's rect */ 67 const f32 trx = sx * KF_TILE_SIZE_PX; 68 Rectangle tr = { 69 trx, 70 sy * KF_TILE_SIZE_PX, 71 /* TODO: 72 Subtracting 1 as a bandaid fix to high velocities causing the player to be stopped early in collisions. 73 This is a very notorious problem in 3D collision and fwik there are plenty of 2D solutions. 74 I'll research and implement one eventually:tm: */ 75 KF_TILE_SIZE_PX - 1, 76 KF_TILE_SIZE_PX - 1, 77 }; /* tile rect */ 78 u32 x; 79 struct kf_tile *tile = kf_world_gettile(world, sx, sy); 80 81 for (u32 y = sy ; y <= ey ; y++) 82 { 83 for (x = sx ; x <= ex ; x++) 84 { 85 if (kf_tiles.collide[tile->id] && CheckCollisionRecs(r, tr)) 86 return 0; 87 tile++; /* shift tile pointer to the right */ 88 tr.x += KF_TILE_SIZE_PX; 89 } 90 tile += down; /* shift tile pointer down */ 91 tr.x = trx; 92 tr.y += KF_TILE_SIZE_PX; 93 } 94 95 return 1; 96} 97 98void kf_actor_addforce(struct kf_actor *self, struct kf_vec2(f32) force) 99{ 100 self->vel.x += force.x; 101 self->vel.y += force.y; 102} 103 104void kf_actor_move(struct kf_world *world, struct kf_actor *self, f32 dt) 105{ 106 struct kf_vec2(f32) delta = kf_mulval_vec2(f32)(self->vel, (self->speed * self->speedmod) * dt); 107 108 if (self->collide) 109 { 110 if (delta.x && !kf_actor_canmovetowards(world, self, kf_vec2_x(f32)(delta))) 111 delta.x = 0; 112 if (delta.y && !kf_actor_canmovetowards(world, self, kf_vec2_y(f32)(delta))) 113 delta.y = 0; 114 } 115 116 if (!self->controlled) 117 { 118 if (delta.y > 0) 119 self->pointing = kf_south; 120 else if (delta.y < 0) 121 self->pointing = kf_north; 122 else if (delta.x < 0) 123 self->pointing = kf_west; 124 else if (delta.x > 0) 125 self->pointing = kf_east; 126 } 127 128 self->pos = kf_add_vec2(f32)(self->pos, delta); 129 130 static const f32 speed_deadzone = 0.1f; 131 132 if (self->vel.x > -speed_deadzone && self->vel.x < speed_deadzone) 133 self->vel.x = 0; 134 else if (self->vel.x) 135 self->vel.x /= self->friction; 136 137 if (self->vel.y > -speed_deadzone && self->vel.y < speed_deadzone) 138 self->vel.y = 0; 139 else if (self->vel.y) 140 self->vel.y /= self->friction; 141 142 // if (self->speedmod > -(1+speed_deadzone) && self->speedmod < 1+speed_deadzone) 143 // self->speedmod = 1; 144 // else if (self->speedmod) 145 // self->speedmod -= self->friction * self->friction * dt; 146} 147 148void kf_actor_draw(struct kf_actor *actor) 149{ 150 int frames = 4; 151 int x = 0, y = 0; 152 153 switch (actor->pointing) 154 { 155 case kf_south: y = 0; break; 156 case kf_east: y = 1; break; 157 case kf_west: y = 2; break; 158 case kf_north: y = 3; break; 159 } 160 161 bool moving = actor->vel.x != 0 || actor->vel.y != 0; 162 163 if (actor->running && moving) 164 { 165 y += 5; /* run sprites */ 166 frames = 6; 167 } 168 else if (moving) 169 { 170 x += 7; /* walk sprites */ 171 } 172 173 /* todo: jump */ 174 175 x += (int)(kf_s * (frames+1)) % frames; 176 177 kf_drawsprite(&actor->sprites, actor->pos.x, actor->pos.y, x, y); 178} 179 180int kf_actor_getregistryid(char *id) 181{ 182 size_t l = strlen(id); 183 for (int i = 0 ; i < kf_actorregistry.count ; i++) 184 { 185 char *c = kf_actorregistry.id[i]; 186 size_t cl = strlen(c); 187 if (strncmp(c, id, l>cl?l:cl) == 0) 188 { 189 return i; 190 } 191 } 192 return -1; 193} 194 195#define _KF_ACTORFILE_TMP "data/tmp/actors.bin" 196#define _KF_ACTORFILE_XZ "data/actors.bin.xz" 197#define _KF_ACTORFILE "data/actors.bin" 198 199int kf_saveactors(void) 200{ 201 // char *outfile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE; 202 char *outfile = _KF_ACTORFILE; 203 204 FILE *fp = fopen(outfile, "wb"); 205 if (!fp) 206 KF_THROW("failed to open %s", outfile); 207 208 size_t nactors = 0; 209 size_t total_bytes = 0; 210 211 for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next) 212 { 213 if (!actor->id) 214 continue; 215 int id = kf_actor_getregistryid(actor->id); 216 if (id == -1) 217 continue; 218 size_t n = strlen(actor->id); 219 if (fwrite(actor->id, 1, n, fp) != n) 220 { 221 KF_THROWSOFTER("failed to write actor ID: %s", actor->id); 222 fclose(fp); 223 return 0; 224 } 225 n = 0; 226 u8 *data = kf_actorregistry.serialize[id](actor, &n); 227 if (!data) 228 { 229 KF_THROWSOFTER("failed to serialize actor of type %s", actor->id); 230 fclose(fp); 231 return 0; 232 } 233 if (fwrite(data, 1, n, fp) != n) 234 { 235 KF_THROWSOFTER("failed to write serialized actor of type %s", actor->id); 236 fclose(fp); 237 return 0; 238 } 239 nactors++; 240 total_bytes += n; 241 } 242 243 kf_logdbg("serialized %d actors (%lu bytes)", nactors, total_bytes); 244 fclose(fp); 245 246 return 1; 247}