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