diff --git a/assets.mdesk b/assets.mdesk index 9d6c6f9..602908b 100644 --- a/assets.mdesk +++ b/assets.mdesk @@ -14,6 +14,10 @@ { filepath: "_Attack.png", } +@image knight_roll: +{ + filepath: "_Roll.png", +} @image old_man: { filepath: "small_old_man.png", diff --git a/assets/_Roll.png b/assets/_Roll.png new file mode 100644 index 0000000..e85dcff Binary files /dev/null and b/assets/_Roll.png differ diff --git a/assets/level0.json b/assets/level0.json index 74f13df..0258194 100644 --- a/assets/level0.json +++ b/assets/level0.json @@ -1,11 +1,4 @@ { "compressionlevel":-1, - "editorsettings": - { - "export": - { - "format":"json" - } - }, "height":60, "infinite":false, "layers":[ @@ -32,11 +25,11 @@ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 212, 213, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 158, 210, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, - 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, - 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, - 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, - 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, - 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 366, 314, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 212, 159, 159, 159, 159, 159, 159, 159, 213, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 316, 367, 314, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 261, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 366, 314, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 313, 367, 367, 367, 367, 367, 368, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 261, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 263, 265, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, @@ -89,12 +82,45 @@ "class":"", "height":32, "id":1, - "name":"spawn", + "name":"PLAYER", "rotation":0, "visible":true, "width":32, "x":960, "y":928 + }, + { + "class":"", + "height":32, + "id":2, + "name":"OLD_MAN", + "rotation":0, + "visible":true, + "width":32, + "x":804.666333333333, + "y":770.666333333333 + }, + { + "class":"", + "height":32, + "id":3, + "name":"OLD_MAN", + "rotation":0, + "visible":true, + "width":32, + "x":834, + "y":916 + }, + { + "class":"", + "height":32, + "id":4, + "name":"OLD_MAN", + "rotation":0, + "visible":true, + "width":32, + "x":970, + "y":761.333333333333 }], "opacity":1, "type":"objectgroup", @@ -103,7 +129,7 @@ "y":0 }], "nextlayerid":6, - "nextobjectid":2, + "nextobjectid":5, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.9.2", diff --git a/assets/testsmalllevel.json b/assets/testsmalllevel.json index 1033133..a7f322a 100644 --- a/assets/testsmalllevel.json +++ b/assets/testsmalllevel.json @@ -4,13 +4,13 @@ "layers":[ { "data":[53, 53, 53, 53, 53, 53, 53, 53, - 53, 209, 159, 159, 159, 160, 209, 160, - 53, 261, 263, 263, 263, 265, 261, 265, - 53, 261, 263, 263, 263, 265, 261, 265, - 53, 261, 263, 263, 263, 212, 210, 265, - 53, 366, 367, 367, 314, 263, 316, 368, - 53, 53, 53, 53, 366, 367, 368, 53, - 53, 53, 53, 53, 53, 53, 53, 53], + 53, 209, 159, 159, 159, 160, 209, 160, + 53, 261, 263, 263, 263, 265, 261, 265, + 53, 261, 263, 263, 263, 265, 261, 265, + 53, 261, 263, 263, 263, 212, 210, 265, + 53, 366, 367, 367, 314, 263, 316, 368, + 53, 53, 53, 53, 366, 367, 368, 53, + 53, 53, 53, 53, 53, 53, 53, 53], "height":8, "id":1, "name":"Tile Layer 1", @@ -30,7 +30,7 @@ "class":"", "height":31.604938271605, "id":1, - "name":"spawn", + "name":"PLAYER", "rotation":0, "visible":true, "width":29.9415204678363, @@ -62,4 +62,4 @@ "type":"map", "version":"1.9", "width":8 -} +} \ No newline at end of file diff --git a/codegen.c b/codegen.c index ea207cb..c81ef74 100644 --- a/codegen.c +++ b/codegen.c @@ -48,6 +48,15 @@ void dump_root(MD_Node* from) { } } +bool has_decimal(MD_String8 s) +{ + for(int i = 0; i < s.size; i++) + { + if(s.str[i] == '.') return true; + } + return false; +} + MD_Arena *cg_arena = NULL; MD_String8 ChildValue(MD_Node *n, MD_String8 name) { @@ -114,7 +123,6 @@ int main(int argc, char **argv) { MD_String8List load_list = {0}; MD_String8List level_decl_list = {0}; MD_String8List tileset_decls = {0}; - MD_String8List object_decls_list = {0}; for(MD_EachNode(node, parse.node->first_child)) { if(MD_S8Match(node->first_tag->string, MD_S8Lit("image"), 0)) { MD_String8 variable_name = MD_S8Fmt(cg_arena, "image_%.*s", MD_S8VArg(node->string)); @@ -172,20 +180,28 @@ int main(int argc, char **argv) { log("New level variable %.*s\n", MD_S8VArg(variable_name)); MD_String8 filepath = asset_file_path(ChildValue(node, MD_S8Lit("filepath"))); MD_ParseResult level_parse = MD_ParseWholeFile(cg_arena, filepath); - assert(level_parse.node != 0, MD_S8Lit("Failed to load level file")); + assert(!MD_NodeIsNil(level_parse.node->first_child), MD_S8Lit("Failed to load level file")); MD_Node *layers = MD_ChildFromString(level_parse.node->first_child, MD_S8Lit("layers"), 0); fprintf(output, "Level %.*s = {\n", MD_S8VArg(variable_name)); for(MD_EachNode(lay, layers->first_child)) { MD_String8 type = MD_ChildFromString(lay, MD_S8Lit("type"), 0)->first_child->string; if(MD_S8Match(type, MD_S8Lit("objectgroup"), 0)) { + fprintf(output, ".initial_entities = {\n"); for(MD_EachNode(object, MD_ChildFromString(lay, MD_S8Lit("objects"), 0)->first_child)) { dump(object); + // negative numbers for object position aren't supported here MD_String8 name = MD_ChildFromString(object, MD_S8Lit("name"), 0)->first_child->string; MD_String8 x_string = MD_ChildFromString(object, MD_S8Lit("x"), 0)->first_child->string; MD_String8 y_string = MD_ChildFromString(object, MD_S8Lit("y"), 0)->first_child->string; - list_printf(&object_decls_list, "Vec2 %.*s_tilepoint = { %.*s, %.*s };\n", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string)); + y_string = MD_S8Fmt(cg_arena, "-%.*s", MD_S8VArg(y_string)); + + if(has_decimal(x_string)) x_string = MD_S8Fmt(cg_arena, "%.*sf", MD_S8VArg(x_string)); + if(has_decimal(y_string)) y_string = MD_S8Fmt(cg_arena, "%.*sf", MD_S8VArg(y_string)); + + fprintf(output, "{ .exists = true, .kind = ENTITY_%.*s, .pos = { .X=%.*s, .Y=%.*s }, }, ", MD_S8VArg(name), MD_S8VArg(x_string), MD_S8VArg(y_string)); } + fprintf(output, "\n}, // entities\n"); } if(MD_S8Match(type, MD_S8Lit("tilelayer"), 0)) { int width = atoi(nullterm(MD_ChildFromString(layers->first_child, MD_S8Lit("width"), 0)->first_child->string)); @@ -215,10 +231,9 @@ int main(int argc, char **argv) { MD_StringJoin join = MD_ZERO_STRUCT; MD_String8 declarations = MD_S8ListJoin(cg_arena, declarations_list, &join); - MD_String8 object_declarations = MD_S8ListJoin(cg_arena, object_decls_list, &join); MD_String8 loads = MD_S8ListJoin(cg_arena, load_list, &join); fprintf(output, "%.*s\nvoid load_assets() {\n%.*s\n}\n", MD_S8VArg(declarations), MD_S8VArg(loads)); - fprintf(output, "%.*s\n%.*s\n", MD_S8VArg(MD_S8ListJoin(cg_arena, tileset_decls, &join)), MD_S8VArg(object_declarations)); + fprintf(output, "%.*s\n", MD_S8VArg(MD_S8ListJoin(cg_arena, tileset_decls, &join))); fclose(output); return 0; diff --git a/main.c b/main.c index 9cadb91..5574c1e 100644 --- a/main.c +++ b/main.c @@ -68,13 +68,49 @@ typedef struct AnimatedSprite bool no_wrap; // does not wrap when playing } AnimatedSprite; +typedef enum CharacterState +{ + CHARACTER_WALKING, + CHARACTER_IDLE, + CHARACTER_ATTACK, +} CharacterState; + +typedef enum EntityKind +{ + ENTITY_INVALID, // zero initialized is invalid entity + + ENTITY_PLAYER, + ENTITY_OLD_MAN, +} EntityKind; + + +typedef struct Entity +{ + bool exists; + EntityKind kind; + + // fields for all entities + Vec2 pos; + bool facing_left; + + // character + CharacterState state; + bool is_rolling; // can only roll in idle or walk states + float speed; // for lerping to the speed, so that roll gives speed boost which fades + double roll_progress; + double swing_progress; +} Entity; + #define LEVEL_TILES 60 #define TILE_SIZE 32 // in pixels +#define MAX_ENTITIES 128 +#define PLAYER_SPEED 3.0f // in meters per second +#define PLAYER_ROLL_SPEED 7.0f typedef struct Level { TileInstance tiles[LEVEL_TILES][LEVEL_TILES]; - Vec2 spawnpoint; + Entity initial_entities[MAX_ENTITIES]; // shouldn't be directly modified, only used to initialize entities on loading of level } Level; typedef struct TileCoord @@ -134,6 +170,7 @@ char *tprint(const char *format, ...) return to_return; } +// tilecoord is integer tile position, not like tile coord Vec2 tilecoord_to_world(TileCoord t) { return V2( (float)t.x * (float)TILE_SIZE * 1.0f, -(float)t.y * (float)TILE_SIZE * 1.0f ); @@ -232,11 +269,9 @@ AnimatedSprite knight_idle = .img = &image_knight_idle, .time_per_frame = 0.3, .num_frames = 10, - .start = - {16.0f, 0.0f}, + .start = {16.0f, 0.0f}, .horizontal_diff_btwn_frames = 120.0, - .region_size = - {80.0f, 80.0f}, + .region_size = {80.0f, 80.0f}, }; AnimatedSprite knight_running = @@ -249,6 +284,18 @@ AnimatedSprite knight_running = .region_size = {80.0f, 80.0f}, }; +AnimatedSprite knight_rolling = +{ + .img = &image_knight_roll, + .time_per_frame = 0.05, + .num_frames = 12, + .start = {19.0f, 0.0f}, + .horizontal_diff_btwn_frames = 120.0, + .region_size = {80.0f, 80.0f}, + .no_wrap = true, +}; + + AnimatedSprite knight_attack = { .img = &image_knight_attack, @@ -284,34 +331,8 @@ static struct sg_bindings bind; } state; -typedef enum CharacterState -{ - CHARACTER_WALKING, - CHARACTER_IDLE, - CHARACTER_ATTACK, -} CharacterState; - -typedef enum EntityKind -{ - Player, - OldMan, -} EntityKind; - -typedef struct Entity -{ - bool exists; - EntityKind kind; - - Vec2 pos; - bool facing_left; - - // character - CharacterState state; - double swing_progress; -} Entity; - -Entity entities[128] = {0}; +Entity entities[MAX_ENTITIES] = {0}; Entity *player = NULL; Entity *new_entity() @@ -340,8 +361,23 @@ void init(void) load_assets(); - player = new_entity(); - player->pos = tilepoint_to_world(spawn_tilepoint); + // load level + Level *to_load = &level_level0; + { + assert(ARRLEN(to_load->initial_entities) == ARRLEN(entities)); + memcpy(entities, to_load->initial_entities, sizeof(Entity) * MAX_ENTITIES); + + player = NULL; + for(int i = 0; i < MAX_ENTITIES; i++) + { + if(entities[i].exists && entities[i].kind == ENTITY_PLAYER) + { + assert(player == NULL); + player = &entities[i]; + } + } + assert(player != NULL); // level initial config must have player entity + } // load font { @@ -459,8 +495,7 @@ typedef struct Camera // everything is in pixels in world space, 43 pixels is approx 1 meter measured from // merchant sprite being 5'6" const float pixels_per_meter = 43.0f; -Camera cam = -{.scale = 2.0f }; +Camera cam = {.scale = 2.0f }; Vec2 cam_offset() { @@ -859,7 +894,7 @@ Vec2 move_and_slide(Vec2 position, Vec2 movement_this_frame, Vec2 collision_aabb TileCoord to_check = world_to_tilecoord(*it); uint16_t tile_id = get_tile(&level_level0, to_check).kind; - if(tile_id == 53 || tile_id == 0 || tile_id == 367 || tile_id == 317 || tile_id == 313 || tile_id == 366 ) + if(tile_id == 53 || tile_id == 0 || tile_id == 367 || tile_id == 317 || tile_id == 313 || tile_id == 366 || tile_id == 368) { dbgsquare(tilecoord_to_world(to_check)); AABB to_depenetrate_from = tile_aabb(to_check); @@ -929,6 +964,7 @@ void frame(void) (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S] ); bool attack = keydown[SAPP_KEYCODE_J]; + bool roll = keydown[SAPP_KEYCODE_K]; if(LenV2(movement) > 1.0) { movement = NormV2(movement); @@ -1033,7 +1069,14 @@ void frame(void) #endif // devtools - draw_animated_sprite(&old_man_idle, time, false, V2(884.788635f, -928.000000f), WHITE); + for(int i = 0; i < ARRLEN(entities); i++) if(entities[i].exists) + { + if(entities[i].kind == ENTITY_OLD_MAN) + { + draw_animated_sprite(&old_man_idle, time, false, entities[i].pos, WHITE); + } + } + // player character { @@ -1045,11 +1088,41 @@ void frame(void) player->swing_progress = 0.0; } + // rolling + if(roll && !player->is_rolling && (player->state == CHARACTER_IDLE || player->state == CHARACTER_WALKING)) + { + player->is_rolling = true; + player->roll_progress = 0.0; + player->speed = PLAYER_ROLL_SPEED; + } + if(player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING) + { + player->roll_progress = 0.0; + player->is_rolling = false; + } + if(player->is_rolling) + { + player->roll_progress += dt; + if(player->roll_progress > anim_sprite_duration(&knight_rolling)) + { + player->is_rolling = false; + } + } + cam.pos = LerpV2(cam.pos, dt*8.0f, MulV2F(player->pos, -1.0f * cam.scale)); if(player->state == CHARACTER_WALKING) { - player->pos = move_and_slide(player->pos, MulV2F(movement, dt * pixels_per_meter * 4.0f), V2(TILE_SIZE, TILE_SIZE)); - draw_animated_sprite(&knight_running, time, player->facing_left, character_sprite_pos, WHITE); + if(player->speed <= 0.01f) player->speed = PLAYER_SPEED; + player->speed = Lerp(player->speed, dt * 3.0f, PLAYER_SPEED); + player->pos = move_and_slide(player->pos, MulV2F(movement, dt * pixels_per_meter * player->speed), V2(TILE_SIZE, TILE_SIZE)); + if(player->is_rolling) + { + draw_animated_sprite(&knight_rolling, player->roll_progress, player->facing_left, character_sprite_pos, WHITE); + } + else + { + draw_animated_sprite(&knight_running, time, player->facing_left, character_sprite_pos, WHITE); + } if(LenV2(movement) == 0.0) { player->state = CHARACTER_IDLE; @@ -1061,7 +1134,14 @@ void frame(void) } else if(player->state == CHARACTER_IDLE) { - draw_animated_sprite(&knight_idle, time, player->facing_left, character_sprite_pos, WHITE); + if(player->is_rolling) + { + draw_animated_sprite(&knight_rolling, player->roll_progress, player->facing_left, character_sprite_pos, WHITE); + } + else + { + draw_animated_sprite(&knight_idle, time, player->facing_left, character_sprite_pos, WHITE); + } if(LenV2(movement) > 0.01) player->state = CHARACTER_WALKING; } else if(player->state == CHARACTER_ATTACK)