application separation
This commit is contained in:
		
							
								
								
									
										23
									
								
								apps/template/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								apps/template/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| cmake_minimum_required(VERSION 3.21) | ||||
|  | ||||
| project(template LANGUAGES C) | ||||
|  | ||||
| if(NOT CMAKE_BUILD_TYPE) | ||||
|         set(CMAKE_BUILD_TYPE Debug) | ||||
| endif() | ||||
|  | ||||
| # add root townengine cmake file | ||||
| if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) | ||||
|         add_subdirectory(../../ ../../../.build) | ||||
| endif() | ||||
|  | ||||
| set(SOURCE_FILES | ||||
|         game.c game.h | ||||
| ) | ||||
|  | ||||
| add_executable(${PROJECT_NAME} ${SOURCE_FILES}) | ||||
| use_townengine(${PROJECT_NAME}) | ||||
|  | ||||
| set_target_properties(${PROJECT_NAME} PROPERTIES | ||||
|         RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} | ||||
| ) | ||||
							
								
								
									
										3
									
								
								apps/template/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								apps/template/build.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| #!/bin/env sh | ||||
|  | ||||
| cmake -B .build "$@" && cmake --build .build | ||||
							
								
								
									
										26
									
								
								apps/template/game.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								apps/template/game.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| #include "townengine/game_api.h" | ||||
| #include "game.h" | ||||
| #include "state.h" | ||||
|  | ||||
| #include <malloc.h> | ||||
|  | ||||
|  | ||||
| void game_tick(void) { | ||||
|     /* do your initialization on first tick */ | ||||
|     if (ctx.tick_count == 0) { | ||||
|         /* application data could be stored in ctx.udata and retrieved anywhere */ | ||||
|         ctx.udata = ccalloc(1, sizeof (struct state)); | ||||
|     } | ||||
|  | ||||
|     /* a lot of data is accessible from `ctx`, look into `townengine/context.h` for more */ | ||||
|  | ||||
|     struct state *state = ctx.udata; | ||||
|     ++state->counter; | ||||
| } | ||||
|  | ||||
|  | ||||
| void game_end(void) { | ||||
|     /* do your deinitialization here */ | ||||
|     struct state *state = ctx.udata; | ||||
|     free(state); | ||||
| } | ||||
							
								
								
									
										13
									
								
								apps/template/game.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								apps/template/game.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| #ifndef GAME_H | ||||
| #define GAME_H | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
|  | ||||
| void game_tick(void); | ||||
| void game_end(void); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										12
									
								
								apps/template/state.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								apps/template/state.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #ifndef STATE_H | ||||
| #define STATE_H | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
| /* populate it with state information */ | ||||
| struct state { | ||||
|     uint64_t counter; | ||||
| }; | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										31
									
								
								apps/testgame/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								apps/testgame/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| cmake_minimum_required(VERSION 3.21) | ||||
|  | ||||
| project(testgame LANGUAGES C) | ||||
|  | ||||
| if(NOT CMAKE_BUILD_TYPE) | ||||
|         set(CMAKE_BUILD_TYPE Debug) | ||||
| endif() | ||||
|  | ||||
| # add root townengine cmake file | ||||
| if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) | ||||
|         add_subdirectory(../../ ../../../.build) | ||||
| endif() | ||||
|  | ||||
| set(SOURCE_FILES | ||||
|         game.c game.h | ||||
|         state.h | ||||
|  | ||||
|         player.c player.h | ||||
|         world.c world.h | ||||
|  | ||||
|         scenes/scene.c scenes/scene.h | ||||
|         scenes/title.c scenes/title.h | ||||
|         scenes/ingame.c scenes/ingame.h | ||||
| ) | ||||
|  | ||||
| add_executable(${PROJECT_NAME} ${SOURCE_FILES}) | ||||
| use_townengine(${PROJECT_NAME}) | ||||
|  | ||||
| set_target_properties(${PROJECT_NAME} PROPERTIES | ||||
|         RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} | ||||
| ) | ||||
							
								
								
									
										3
									
								
								apps/testgame/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								apps/testgame/build.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| #!/bin/env sh | ||||
|  | ||||
| cmake -B .build "$@" && cmake --build .build | ||||
							
								
								
									
										64
									
								
								apps/testgame/game.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								apps/testgame/game.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| #include "game.h" | ||||
| #include "townengine/game_api.h" | ||||
| #include "state.h" | ||||
| #include "scenes/scene.h" | ||||
| #include "scenes/title.h" | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <malloc.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
|  | ||||
| void game_tick(void) { | ||||
|     if (ctx.tick_count == 0) { | ||||
|         ctx.udata = ccalloc(1, sizeof (struct state)); | ||||
|         struct state *state = ctx.udata; | ||||
|         state->ctx = &ctx; | ||||
|         state->scene = title_scene(state); | ||||
|  | ||||
|         input_add_action(&ctx.input, "debug_dump_atlases"); | ||||
|         input_bind_action_scancode(&ctx.input, "debug_dump_atlases", SDL_SCANCODE_HOME); | ||||
|  | ||||
|         input_add_action(&ctx.input, "debug_toggle"); | ||||
|         input_bind_action_scancode(&ctx.input, "debug_toggle", SDL_SCANCODE_BACKSPACE); | ||||
|  | ||||
|         input_add_action(&ctx.input, "player_left"); | ||||
|         input_bind_action_scancode(&ctx.input, "player_left", SDL_SCANCODE_LEFT); | ||||
|  | ||||
|         input_add_action(&ctx.input, "player_right"); | ||||
|         input_bind_action_scancode(&ctx.input, "player_right", SDL_SCANCODE_RIGHT); | ||||
|  | ||||
|         input_add_action(&ctx.input, "player_jump"); | ||||
|         input_bind_action_scancode(&ctx.input, "player_jump", SDL_SCANCODE_X); | ||||
|  | ||||
|         input_add_action(&ctx.input, "ui_accept"); | ||||
|         input_bind_action_scancode(&ctx.input, "ui_accept", SDL_SCANCODE_RETURN); | ||||
|     } | ||||
|  | ||||
|     struct state *state = ctx.udata; | ||||
|  | ||||
|     if (input_is_action_just_pressed(&ctx.input, "debug_dump_atlases")) { | ||||
|         textures_dump_atlases(&ctx.texture_cache); | ||||
|     } | ||||
|  | ||||
|     if (input_is_action_just_pressed(&ctx.input, "debug_toggle")) { | ||||
|         ctx.debug = !ctx.debug; | ||||
|     } | ||||
|  | ||||
|     state->scene->tick(state); | ||||
|  | ||||
|     /* there's a scene switch pending, we can do it now that the tick is done */ | ||||
|     if (state->next_scene != NULL) { | ||||
|         state->scene->end(state); | ||||
|         state->scene = state->next_scene; | ||||
|         state->is_scene_switching = false; | ||||
|         state->next_scene = NULL; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| void game_end(void) { | ||||
|     struct state *state = ctx.udata; | ||||
|     state->scene->end(state); | ||||
|     free(state); | ||||
| } | ||||
							
								
								
									
										14
									
								
								apps/testgame/game.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								apps/testgame/game.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #ifndef GAME_H | ||||
| #define GAME_H | ||||
|  | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
| #include <stdint.h> | ||||
|  | ||||
|  | ||||
| void game_tick(void); | ||||
| void game_end(void); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										303
									
								
								apps/testgame/player.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								apps/testgame/player.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,303 @@ | ||||
| #include "player.h" | ||||
| #include "world.h" | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
| #include <SDL2/SDL.h> | ||||
|  | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| #include <tgmath.h> | ||||
|  | ||||
|  | ||||
| static void update_timers(struct player *player) { | ||||
|      tick_timer(&player->jump_air_timer); | ||||
|      tick_timer(&player->jump_coyote_timer); | ||||
|      tick_timer(&player->jump_buffer_timer); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void input_move(struct input_state *input, struct player *player) { | ||||
|     /* apply horizontal damping when the player stops moving */ | ||||
|     /* in other words, make it decelerate to a standstill */ | ||||
|     if (!input_is_action_pressed(input, "player_left") && | ||||
|         !input_is_action_pressed(input, "player_right")) | ||||
|     { | ||||
|         player->dx *= player->horizontal_damping; | ||||
|     } | ||||
|  | ||||
|     int input_dir = 0; | ||||
|     if (input_is_action_pressed(input, "player_left")) | ||||
|         input_dir = -1; | ||||
|     if (input_is_action_pressed(input, "player_right")) | ||||
|         input_dir = 1; | ||||
|     if (input_is_action_pressed(input, "player_left") && | ||||
|         input_is_action_pressed(input, "player_right")) | ||||
|         input_dir = 0; | ||||
|  | ||||
|     player->dx += (float)input_dir * player->run_horizontal_speed; | ||||
|     player->dx = SDL_clamp(player->dx, -player->max_dx, player->max_dx); | ||||
|  | ||||
|     if (fabs(player->dx) < player->run_horizontal_speed) { | ||||
|         player->dx = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| static void jump(struct player *player) { | ||||
|     player->jump_coyote_timer = 0; | ||||
|     player->jump_buffer_timer = 0; | ||||
|     player->dy = player->jump_force_initial; | ||||
|     player->action = PLAYER_ACTION_JUMP; | ||||
|     player->jump_air_timer = player->jump_air_ticks; | ||||
| } | ||||
|  | ||||
|  | ||||
| static void input_jump(struct input_state *input, struct player *player) { | ||||
|     player->current_gravity_multiplier = player->jump_default_multiplier; | ||||
|  | ||||
|     if (input_is_action_just_pressed(input, "player_jump")) { | ||||
|         player->jump_air_timer = 0; | ||||
|         player->jump_buffer_timer = player->jump_buffer_ticks; | ||||
|  | ||||
|         if (player->action == PLAYER_ACTION_GROUND || player->jump_coyote_timer > 0) { | ||||
|             jump(player); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (input_is_action_pressed(input, "player_jump")) { | ||||
|         if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) { | ||||
|             player->current_gravity_multiplier = player->jump_boosted_multiplier; | ||||
|             player->dy += player->jump_force_increase; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| static void update_collider_x(struct player *player) { | ||||
|     player->collider_x.w = player->rect.w; | ||||
|     player->collider_x.h = player->rect.h - 8; | ||||
|     player->collider_x.x = player->rect.x; | ||||
|     player->collider_x.y = player->rect.y + ((player->rect.h - player->collider_x.h) / 2); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void update_collider_y(struct player *player) { | ||||
|     player->collider_y.w = player->rect.w; | ||||
|     player->collider_y.h = player->rect.h; | ||||
|     player->collider_y.x = player->rect.x + ((player->rect.w - player->collider_y.w) / 2); | ||||
|     player->collider_y.y = player->rect.y; | ||||
| } | ||||
|  | ||||
|  | ||||
| static void apply_gravity(struct player *player, float gravity) { | ||||
|     player->dy -= gravity * player->current_gravity_multiplier; | ||||
|     player->dy = fmax(player->dy, -player->terminal_velocity); | ||||
|  | ||||
|     if (player->dy < 0) { | ||||
|         /* just started falling */ | ||||
|         if (player->action == PLAYER_ACTION_GROUND) { | ||||
|             player->jump_coyote_timer = player->jump_coyote_ticks; | ||||
|         } | ||||
|  | ||||
|         player->action = PLAYER_ACTION_FALL; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* returns whether or not a correction was applied */ | ||||
| static bool corner_correct(struct player *player, t_frect collision) { | ||||
|     /* | ||||
|      * somewhat of a hack here. we only want to do corner correction | ||||
|      * if the corner in question really is the corner of a "platform," | ||||
|      * and not simply the edge of a single tile in a row of many | ||||
|      * tiles, as that would briefly clip the player into the ceiling, | ||||
|      * halting movement.  the dumbest way i could think of to fix this | ||||
|      * is to simply ensure that there's no tile to possibly clip into | ||||
|      * before correcting, by checking if there's a tile right above | ||||
|      * the center of the player | ||||
|      */ | ||||
|     if (world_is_tile_at(player->world, | ||||
|                          player->rect.x + (player->rect.w / 2), | ||||
|                          player->rect.y - 1)) | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     float player_center_x = player->collider_x.x + (player->collider_x.w / 2); | ||||
|     float collision_center_x = collision.x + (collision.w / 2);  | ||||
|     float abs_difference = fabs(player_center_x - collision_center_x); | ||||
|  | ||||
|     /* we're good, no correction needed */ | ||||
|     if (abs_difference < player->jump_corner_correction_offset) | ||||
|         return false; | ||||
|  | ||||
|     /* collision was on player's right side */ | ||||
|     if (player_center_x < collision_center_x) { | ||||
|         player->rect.x -= collision.w / 2; | ||||
|     } | ||||
|     /* collision was on player's left side */ | ||||
|     else if (player_center_x > collision_center_x) { | ||||
|         player->rect.x += collision.w / 2; | ||||
|     } | ||||
|      | ||||
|     return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| static void calc_collisions_x(struct player *player) { | ||||
|     t_frect collision; | ||||
|     bool is_colliding = world_find_intersect_frect(player->world, player->collider_x, &collision); | ||||
|     if (!is_colliding) return; | ||||
|  | ||||
|     float player_center_x = player->collider_x.x + (player->collider_x.w / 2); | ||||
|     float collision_center_x = collision.x + (collision.w / 2);  | ||||
|  | ||||
|     enum collision_direction { COLLISION_LEFT = -1, COLLISION_RIGHT = 1 }; | ||||
|     enum collision_direction dir_x = | ||||
|         player_center_x > collision_center_x ? COLLISION_LEFT : COLLISION_RIGHT; | ||||
|  | ||||
|     player->rect.x -= collision.w * (float)dir_x; | ||||
|     player->dx = 0; | ||||
| } | ||||
|  | ||||
|  | ||||
| static void calc_collisions_y(struct player *player) { | ||||
|     t_frect collision; | ||||
|     bool is_colliding = world_find_intersect_frect(player->world, player->collider_y, &collision); | ||||
|     if (!is_colliding) return; | ||||
|  | ||||
|     float player_center_y = player->collider_y.y + (player->collider_y.h / 2); | ||||
|     float collision_center_y = collision.y + (collision.h / 2);  | ||||
|  | ||||
|     enum collision_direction { COLLISION_ABOVE = -1, COLLISION_BELOW = 1 }; | ||||
|     enum collision_direction dir_y = | ||||
|         player_center_y > collision_center_y ? COLLISION_ABOVE : COLLISION_BELOW; | ||||
|  | ||||
|     /* before the resolution */ | ||||
|     if (dir_y == COLLISION_ABOVE) { | ||||
|         if (corner_correct(player, collision)) { | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* resolution */ | ||||
|     player->rect.y -= collision.h * (float)dir_y; | ||||
|     player->dy = 0; | ||||
|  | ||||
|     /* after the resolution */ | ||||
|     if (dir_y == COLLISION_BELOW) { | ||||
|         /* bandaid fix for float precision-related jittering */ | ||||
|         player->rect.y = ceilf(player->rect.y); | ||||
|  | ||||
|         player->action = PLAYER_ACTION_GROUND; | ||||
|  | ||||
|         if (player->jump_buffer_timer > 0) { | ||||
|             jump(player); | ||||
|             apply_gravity(player, player->world->gravity); | ||||
|         } | ||||
|     } else if (dir_y == COLLISION_ABOVE) { | ||||
|         player->jump_air_timer = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| struct player *player_create(struct world *world) { | ||||
|     struct player *player = cmalloc(sizeof *player); | ||||
|  | ||||
|     *player = (struct player) { | ||||
|         .world = world, | ||||
|  | ||||
|         .sprite_w = 48, | ||||
|         .sprite_h = 48, | ||||
|  | ||||
|         .rect = (t_frect) { | ||||
|             .x = 92, | ||||
|             .y = 200, | ||||
|             .w = 16, | ||||
|             .h = 32, | ||||
|         }, | ||||
|  | ||||
|         .action = PLAYER_ACTION_GROUND, | ||||
|  | ||||
|         .collider_thickness = 42, | ||||
|         .max_dx = 8, | ||||
|         .run_horizontal_speed = 0.5f, | ||||
|         .horizontal_damping = 0.9f, | ||||
|         .current_gravity_multiplier = 1, | ||||
|         .terminal_velocity = 30, | ||||
|  | ||||
|         .jump_force_initial = 10, | ||||
|         .jump_force_increase = 1, | ||||
|         .jump_air_ticks = 18, | ||||
|         .jump_coyote_ticks = 8, | ||||
|         .jump_buffer_ticks = 8, | ||||
|  | ||||
|         .jump_default_multiplier = 1.4f, | ||||
|         .jump_boosted_multiplier = 1, | ||||
|  | ||||
|         .jump_corner_correction_offset = 16.0f, | ||||
|     }; | ||||
|  | ||||
|     return player; | ||||
| } | ||||
|  | ||||
|  | ||||
| static void drawdef(struct player *player) { | ||||
|     push_sprite("/assets/player/baron-walk.png", | ||||
|                 (t_frect) { | ||||
|                     .x = player->rect.x + ((player->rect.w - player->sprite_w) / 2), | ||||
|                     .y = player->rect.y - 8, | ||||
|                     .w = player->sprite_w, | ||||
|                     .h = player->sprite_h, | ||||
|                 }); | ||||
|  | ||||
|     push_circle((t_fvec2) { 256, 128 }, | ||||
|                 24, | ||||
|                 (t_color) { 255, 0, 0, 255 }); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void drawdef_debug(struct player *player) { | ||||
|     if (!ctx.debug) | ||||
|         return; | ||||
|  | ||||
|     /* const int info_separation = 24; */ | ||||
|     /* const struct RectPrimitive info_theme = { */ | ||||
|     /*  .x = 8, */ | ||||
|     /*  .r = 0, .g = 0, .b = 0, .a = 255, */ | ||||
|     /* }; */ | ||||
|  | ||||
|     push_rectangle(player->collider_x, | ||||
|                    (t_color){ 0, 0, 255, 128 }); | ||||
|  | ||||
|     push_rectangle(player->collider_y, | ||||
|                    (t_color){ 0, 0, 255, 128 }); | ||||
| } | ||||
|  | ||||
|  | ||||
| void player_destroy(struct player *player) { | ||||
|     free(player); | ||||
| } | ||||
|  | ||||
|  | ||||
| void player_calc(struct player *player) { | ||||
|     update_timers(player); | ||||
|  | ||||
|     input_move(&ctx.input, player); | ||||
|     input_jump(&ctx.input, player); | ||||
|  | ||||
|     player->rect.x += player->dx; | ||||
|     update_collider_x(player); | ||||
|     calc_collisions_x(player); | ||||
|  | ||||
|     apply_gravity(player, player->world->gravity); | ||||
|     player->rect.y -= player->dy; | ||||
|     update_collider_y(player); | ||||
|     calc_collisions_y(player); | ||||
|  | ||||
|     update_collider_x(player); | ||||
|     update_collider_y(player); | ||||
|  | ||||
|     drawdef(player); | ||||
|     drawdef_debug(player); | ||||
| } | ||||
							
								
								
									
										66
									
								
								apps/testgame/player.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								apps/testgame/player.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #ifndef PLAYER_H | ||||
| #define PLAYER_H | ||||
|  | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
|  | ||||
| struct world; | ||||
|  | ||||
|  | ||||
| enum player_action { | ||||
|     PLAYER_ACTION_GROUND, | ||||
|     PLAYER_ACTION_FALL, | ||||
|     PLAYER_ACTION_JUMP, | ||||
| }; | ||||
|  | ||||
|  | ||||
| struct player { | ||||
|     struct world *world; | ||||
|  | ||||
|     /* visual */ | ||||
|     float sprite_w; | ||||
|     float sprite_h; | ||||
|  | ||||
|     /* body */ | ||||
|     t_frect rect; | ||||
|  | ||||
|     /* state */ | ||||
|     enum player_action action; | ||||
|  | ||||
|     /* physics */ | ||||
|     t_frect collider_x; | ||||
|     t_frect collider_y; | ||||
|     int collider_thickness; | ||||
|     float dx; | ||||
|     float dy; | ||||
|     float max_dx; | ||||
|     float run_horizontal_speed; | ||||
|     float horizontal_damping; | ||||
|     float current_gravity_multiplier; | ||||
|     float terminal_velocity; | ||||
|  | ||||
|     /* jumping */ | ||||
|     float jump_force_initial; | ||||
|     float jump_force_increase; /* aka "jump power", increment of dy per tick */ | ||||
|     int jump_air_ticks; | ||||
|     int jump_air_timer; | ||||
|     int jump_coyote_ticks; | ||||
|     int jump_coyote_timer; | ||||
|     int jump_buffer_ticks; | ||||
|     int jump_buffer_timer; | ||||
|  | ||||
|     /* gravity multipliers */ | ||||
|     float jump_default_multiplier; | ||||
|     float jump_boosted_multiplier; | ||||
|  | ||||
|     float jump_corner_correction_offset; /* from center */ | ||||
| }; | ||||
|  | ||||
|  | ||||
| struct player *player_create(struct world *world); | ||||
| void player_destroy(struct player *player); | ||||
| void player_calc(struct player *player); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										99
									
								
								apps/testgame/scenes/ingame.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								apps/testgame/scenes/ingame.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| #include "ingame.h" | ||||
| #include "title.h" | ||||
| #include "scene.h" | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
|  | ||||
| static void ingame_tick(struct state *state) { | ||||
|     struct scene_ingame *scn = (struct scene_ingame *)state->scene; | ||||
|  | ||||
|     world_drawdef(scn->world); | ||||
|     player_calc(scn->player); | ||||
|  | ||||
|     static t_camera cam = { .pos = { 0 }, .target = { 0, 0, -1 }, .up = { 0, -1, 0 }, .fov = (float)M_PI_2 }; | ||||
|  | ||||
|     if (input_is_action_pressed(&ctx.input, "player_left")) | ||||
|         cam.pos.x -= 0.01f; | ||||
|     if (input_is_action_pressed(&ctx.input, "player_right")) | ||||
|         cam.pos.x += 0.01f; | ||||
|     if (input_is_action_pressed(&ctx.input, "player_jump")) | ||||
|         cam.pos.z -= 0.01f; | ||||
|  | ||||
|     push_camera(&cam); | ||||
|  | ||||
|     push_sprite_ex((t_frect){ .x = 32, .y = 64, .w = 64, .h = 64 }, (t_push_sprite_args){ | ||||
|         .path = "/assets/light.png", | ||||
|         .color = (t_color){255, 0, 0, 255}, }); | ||||
|  | ||||
|     push_sprite_ex((t_frect){ .x = 48, .y = 64, .w = 64, .h = 64 }, (t_push_sprite_args){ | ||||
|         .path = "/assets/light.png", | ||||
|         .color = (t_color){0, 255, 0, 255}, }); | ||||
|  | ||||
|     push_sprite_ex((t_frect){ .x = 64, .y = 64, .w = 64, .h = 64 }, (t_push_sprite_args){ | ||||
|         .path = "/assets/light.png", | ||||
|         .color = (t_color){0, 0, 255, 255}, }); | ||||
|  | ||||
|     push_sprite_ex((t_frect){ .x = 32, .y = 32, .w = 64, .h = 64 }, (t_push_sprite_args){ | ||||
|         .path = "/assets/player/baron-walk.png", | ||||
|         .color = (t_color){255, 255, 255, 255}, | ||||
|         .rotation = (float)M_PI * 2 * (float)(ctx.tick_count % 64) / 64, }); | ||||
|  | ||||
|     push_sprite_ex((t_frect){ .x = 64, .y = 32, .w = 64, .h = 64 }, (t_push_sprite_args){ | ||||
|         .path = "/assets/player/baron-walk.png", | ||||
|         .color = (t_color){255, 255, 255, 255}, | ||||
|         .rotation = (float)M_PI / 64, }); | ||||
|  | ||||
|     push_sprite_ex((t_frect){ .x = 96, .y = 32, .w = 64, .h = 64 }, (t_push_sprite_args){ | ||||
|         .path = "/assets/player/baron-walk.png", | ||||
|         .color = (t_color){255, 255, 255, 255}, }); | ||||
|  | ||||
|     push_sprite_ex((t_frect){ .x = 128, .y = 32, .w = 128, .h = 64 }, (t_push_sprite_args){ | ||||
|         .path = "/assets/player/baron-walk.png", | ||||
|         .color = (t_color){255, 255, 255, 255}, | ||||
|         .rotation = (float)M_PI * 2 * (float)(ctx.tick_count % 64) / 64, }); | ||||
|  | ||||
|     unfurl_triangle("/assets/big-violet.png", | ||||
|                     (t_fvec3){ -1, -1, 0 }, | ||||
|                     (t_fvec3){ 1, -1, 0 }, | ||||
|                     (t_fvec3){ 1, 1, 0 }, | ||||
|                     (t_shvec2){ 0, 2048 }, | ||||
|                     (t_shvec2){ 2048, 2048 }, | ||||
|                     (t_shvec2){ 2048, 0 }); | ||||
|  | ||||
|     unfurl_triangle("/assets/big-violet.png", | ||||
|                     (t_fvec3){ 1, 1, 0 }, | ||||
|                     (t_fvec3){ -1, 1, 0 }, | ||||
|                     (t_fvec3){ -1, -1, 0 }, | ||||
|                     (t_shvec2){ 2048, 0 }, | ||||
|                     (t_shvec2){ 0, 0 }, | ||||
|                     (t_shvec2){ 0, 2048 }); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void ingame_end(struct state *state) { | ||||
|     struct scene_ingame *scn = (struct scene_ingame *)state->scene; | ||||
|     player_destroy(scn->player); | ||||
|     world_destroy(scn->world); | ||||
|     free(state->scene); | ||||
| } | ||||
|  | ||||
|  | ||||
| struct scene *ingame_scene(struct state *state) { | ||||
|     (void)state; | ||||
|  | ||||
|     struct scene_ingame *new_scene = ccalloc(1, sizeof *new_scene); | ||||
|     new_scene->base.tick = ingame_tick; | ||||
|     new_scene->base.end = ingame_end; | ||||
|  | ||||
|     new_scene->world = world_create(); | ||||
|     new_scene->player = player_create(new_scene->world); | ||||
|  | ||||
|     play_audio_ex("music/repeat-test.xm", "soundtrack", (t_play_audio_args){ | ||||
|         .volume = 0.8f, | ||||
|         .panning = -0.5f, | ||||
|         .repeat = true, | ||||
|     }); | ||||
|  | ||||
|     return (struct scene *)new_scene; | ||||
| } | ||||
							
								
								
									
										23
									
								
								apps/testgame/scenes/ingame.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								apps/testgame/scenes/ingame.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| #ifndef INGAME_H | ||||
| #define INGAME_H | ||||
|  | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
| #include "../state.h" | ||||
| #include "scene.h" | ||||
| #include "../player.h" | ||||
| #include "../world.h" | ||||
|  | ||||
|  | ||||
| struct scene_ingame { | ||||
|     struct scene base; | ||||
|  | ||||
|     struct world *world; | ||||
|     struct player *player; | ||||
| }; | ||||
|  | ||||
|  | ||||
| struct scene *ingame_scene(struct state *state); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										8
									
								
								apps/testgame/scenes/scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								apps/testgame/scenes/scene.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #include "scene.h" | ||||
| #include "../state.h" | ||||
|  | ||||
|  | ||||
| void switch_to(struct state *state, struct scene *(*scene_func)(struct state *)) { | ||||
|     state->next_scene = scene_func(state); | ||||
|     state->is_scene_switching = true; | ||||
| } | ||||
							
								
								
									
										16
									
								
								apps/testgame/scenes/scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								apps/testgame/scenes/scene.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef SCENE_H | ||||
| #define SCENE_H | ||||
|  | ||||
|  | ||||
| struct state; | ||||
| struct scene { | ||||
|     char *id; | ||||
|     void (*tick)(struct state *); | ||||
|     void (*end)(struct state *); | ||||
| }; | ||||
|  | ||||
|  | ||||
| void switch_to(struct state *state, struct scene *(*scene_func)(struct state *)); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										42
									
								
								apps/testgame/scenes/title.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								apps/testgame/scenes/title.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| #include "title.h" | ||||
| #include "ingame.h" | ||||
| #include "../world.h" | ||||
| #include "../player.h" | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
|  | ||||
| static void title_tick(struct state *state) { | ||||
|     struct scene_title *scn = (struct scene_title *)state->scene; | ||||
|     (void)scn; | ||||
|  | ||||
|     if (input_is_action_just_pressed(&state->ctx->input, "ui_accept")) { | ||||
|         switch_to(state, ingame_scene); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     push_sprite("/assets/title.png", (t_frect) { | ||||
|         (RENDER_BASE_WIDTH / 2) - (320 / 2), 64, 320, 128 | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void title_end(struct state *state) { | ||||
|     struct scene_title *scn = (struct scene_title *)state->scene; | ||||
|     player_destroy(scn->player); | ||||
|     world_destroy(scn->world); | ||||
|     free(state->scene); | ||||
| } | ||||
|  | ||||
|  | ||||
| struct scene *title_scene(struct state *state) { | ||||
|     (void)state; | ||||
|  | ||||
|     struct scene_title *new_scene = ccalloc(1, sizeof *new_scene); | ||||
|     new_scene->base.tick = title_tick; | ||||
|     new_scene->base.end = title_end; | ||||
|  | ||||
|     new_scene->world = world_create(); | ||||
|     new_scene->player = player_create(new_scene->world); | ||||
|  | ||||
|     return (struct scene *)new_scene; | ||||
| } | ||||
							
								
								
									
										23
									
								
								apps/testgame/scenes/title.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								apps/testgame/scenes/title.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| #ifndef TITLE_H | ||||
| #define TITLE_H | ||||
|  | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
| #include "../state.h" | ||||
| #include "scene.h" | ||||
| #include "../player.h" | ||||
| #include "../world.h" | ||||
|  | ||||
|  | ||||
| struct scene_title { | ||||
|     struct scene base; | ||||
|  | ||||
|     struct world *world; | ||||
|     struct player *player; | ||||
| }; | ||||
|  | ||||
|  | ||||
| struct scene *title_scene(struct state *state); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										16
									
								
								apps/testgame/state.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								apps/testgame/state.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef STATE_H | ||||
| #define STATE_H | ||||
|  | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
|  | ||||
| struct state { | ||||
|     t_ctx *ctx; | ||||
|     struct scene *scene; | ||||
|     struct scene *next_scene; | ||||
|     bool is_scene_switching; | ||||
| }; | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										187
									
								
								apps/testgame/world.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								apps/testgame/world.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| #include "world.h" | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdint.h> | ||||
| #include <tgmath.h> | ||||
|  | ||||
|  | ||||
| static void update_tiles(struct world *world) { | ||||
|     for (size_t row = 0; row < world->tilemap_height; ++row) { | ||||
|         for (size_t col = 0; col < world->tilemap_width; ++col) { | ||||
|             world->tiles[(row * world->tilemap_width) + col] = (struct tile) { | ||||
|                 .rect = (t_rect) { | ||||
|                     .x = (int)col * world->tile_size, | ||||
|                     .y = (int)row * world->tile_size, | ||||
|                     .w = world->tile_size, | ||||
|                     .h = world->tile_size, | ||||
|                 }, | ||||
|                 .type = world->tilemap[row][col], | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| static t_vec2 to_grid_location(struct world *world, float x, float y) { | ||||
|     return (t_vec2) { | ||||
|         .x = (int)floor(x / (float)world->tile_size), | ||||
|         .y = (int)floor(y / (float)world->tile_size), | ||||
|     }; | ||||
| } | ||||
|  | ||||
|  | ||||
| static void drawdef_debug(struct world *world) { | ||||
|     if (!ctx.debug) return; | ||||
|  | ||||
|     for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) { | ||||
|         if (world->tiles[i].type == TILE_TYPE_VOID) continue; | ||||
|  | ||||
|         push_rectangle(to_frect(world->tiles[i].rect), | ||||
|                         (t_color) { 255, 0, 255, 128 }); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| struct world *world_create(void) { | ||||
|     struct world *world = cmalloc(sizeof *world); | ||||
|  | ||||
|     *world = (struct world) { | ||||
|         .tiles = NULL, | ||||
|         .tile_size = 42, | ||||
|         .tilemap_width = 20, | ||||
|         .tilemap_height = 12, | ||||
|         .gravity = 1, | ||||
|     }; | ||||
|  | ||||
|     /* create the tilemap */ | ||||
|     /* it simply stores what's in each tile as a 2d array */ | ||||
|     /* on its own, it's entirely unrelated to drawing or logic */ | ||||
|     world->tilemap = cmalloc(sizeof *world->tilemap * world->tilemap_height); | ||||
|     for (size_t i = 0; i < world->tilemap_height; ++i) { | ||||
|         world->tilemap[i] = cmalloc(sizeof **world->tilemap * world->tilemap_width); | ||||
|  | ||||
|         for (size_t j = 0; j < world->tilemap_width; ++j) { | ||||
|             world->tilemap[i][j] = TILE_TYPE_VOID; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     for (size_t i = 0; i < 12; ++i) { | ||||
|         world->tilemap[world->tilemap_height-2][2+i] = TILE_TYPE_SOLID; | ||||
|     } | ||||
|     world->tilemap[world->tilemap_height-3][8] = TILE_TYPE_SOLID; | ||||
|     world->tilemap[world->tilemap_height-4][8] = TILE_TYPE_SOLID; | ||||
|     world->tilemap[world->tilemap_height-5][10] = TILE_TYPE_SOLID; | ||||
|     world->tilemap[world->tilemap_height-6][10] = TILE_TYPE_SOLID; | ||||
|     for (size_t i = 0; i < 7; ++i) { | ||||
|         world->tilemap[world->tilemap_height-6][12+i] = TILE_TYPE_SOLID; | ||||
|     } | ||||
|  | ||||
|     /* the tiles array contains data meant to be used by other logic */ | ||||
|     /* most importantly, it is used to draw the tiles */ | ||||
|     const size_t tile_count = world->tilemap_height * world->tilemap_width; | ||||
|     world->tiles = cmalloc(sizeof *world->tiles * tile_count); | ||||
|     update_tiles(world); | ||||
|  | ||||
|     return world; | ||||
| } | ||||
|  | ||||
|  | ||||
| void world_destroy(struct world *world) { | ||||
|     free(world->tiles); | ||||
|  | ||||
|     for (size_t i = 0; i < world->tilemap_height; ++i) { | ||||
|         free(world->tilemap[i]); | ||||
|     } | ||||
|     free(world->tilemap); | ||||
|  | ||||
|     free(world); | ||||
| } | ||||
|  | ||||
|  | ||||
| void world_drawdef(struct world *world) { | ||||
|     for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) { | ||||
|         if (world->tiles[i].type == TILE_TYPE_VOID) | ||||
|             continue; | ||||
|  | ||||
|         push_sprite("/assets/white.png", to_frect(world->tiles[i].rect)); | ||||
|     } | ||||
|  | ||||
|     drawdef_debug(world); | ||||
| } | ||||
|  | ||||
|  | ||||
| bool world_find_intersect_frect(struct world *world, t_frect rect, t_frect *intersection) { | ||||
|     bool is_intersecting = false; | ||||
|  | ||||
|     const size_t tile_count = world->tilemap_height * world->tilemap_width; | ||||
|     for (size_t i = 0; i < tile_count; ++i) { | ||||
|         if (world->tiles[i].type == TILE_TYPE_VOID) | ||||
|             continue; | ||||
|  | ||||
|         t_frect tile_frect = { | ||||
|             .x = (float)(world->tiles[i].rect.x), | ||||
|             .y = (float)(world->tiles[i].rect.y), | ||||
|             .w = (float)(world->tiles[i].rect.w), | ||||
|             .h = (float)(world->tiles[i].rect.h), | ||||
|         }; | ||||
|  | ||||
|         if (intersection == NULL) { | ||||
|             t_frect temp; | ||||
|             is_intersecting = intersect_frect(&rect, &tile_frect, &temp); | ||||
|         } else { | ||||
|             is_intersecting = intersect_frect(&rect, &tile_frect, intersection); | ||||
|         } | ||||
|  | ||||
|         if (is_intersecting) | ||||
|             break;  | ||||
|     } | ||||
|  | ||||
|     return is_intersecting; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool world_find_intersect_rect(struct world *world, t_rect rect, t_rect *intersection) { | ||||
|     bool is_intersecting = false; | ||||
|  | ||||
|     const size_t tile_count = world->tilemap_height * world->tilemap_width; | ||||
|     for (size_t i = 0; i < tile_count; ++i) { | ||||
|         if (world->tiles[i].type == TILE_TYPE_VOID) | ||||
|             continue; | ||||
|  | ||||
|         t_rect *tile_rect = &world->tiles[i].rect; | ||||
|  | ||||
|         if (intersection == NULL) { | ||||
|             t_rect temp; | ||||
|             is_intersecting = intersect_rect(&rect, tile_rect, &temp); | ||||
|         } else { | ||||
|             is_intersecting = intersect_rect(&rect, tile_rect, intersection); | ||||
|         } | ||||
|  | ||||
|         if (is_intersecting) | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     return is_intersecting; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool world_is_tile_at(struct world *world, float x, float y) { | ||||
|     t_vec2 position_in_grid = to_grid_location(world, x, y); | ||||
|     return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID; | ||||
| } | ||||
|  | ||||
|  | ||||
| void world_place_tile(struct world *world, float x, float y) { | ||||
|     t_vec2 position_in_grid = to_grid_location(world, x, y); | ||||
|     world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID; | ||||
|     update_tiles(world); | ||||
| } | ||||
|  | ||||
|  | ||||
| void world_remove_tile(struct world *world, float x, float y) { | ||||
|     t_vec2 position_in_grid = to_grid_location(world, x, y); | ||||
|     world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID; | ||||
|     update_tiles(world); | ||||
| } | ||||
							
								
								
									
										44
									
								
								apps/testgame/world.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								apps/testgame/world.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| #ifndef WORLD_H | ||||
| #define WORLD_H | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
|  | ||||
|  | ||||
| enum tile_type { | ||||
|     TILE_TYPE_VOID, | ||||
|     TILE_TYPE_SOLID, | ||||
| }; | ||||
|  | ||||
|  | ||||
| struct tile { | ||||
|     t_rect rect; | ||||
|     enum tile_type type; | ||||
| }; | ||||
|  | ||||
|  | ||||
| struct world { | ||||
|     enum tile_type **tilemap; | ||||
|     struct tile *tiles; | ||||
|      | ||||
|     int tile_size; | ||||
|     unsigned int tilemap_width; | ||||
|     unsigned int tilemap_height; | ||||
|     size_t tile_nonvoid_count; | ||||
|     float gravity; | ||||
| }; | ||||
|  | ||||
|  | ||||
| struct world *world_create(void); | ||||
| void world_destroy(struct world *world); | ||||
| void world_drawdef(struct world *world); | ||||
| bool world_find_intersect_frect(struct world *world, t_frect rect, t_frect *intersection); | ||||
| bool world_find_intersect_rect(struct world *world, t_rect rect, t_rect *intersection); | ||||
| bool world_is_tile_at(struct world *world, float x, float y); | ||||
| void world_place_tile(struct world *world, float x, float y); | ||||
| void world_remove_tile(struct world *world, float x, float y); | ||||
|  | ||||
|  | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user