move platformes and scenery to /apps/demos/
This commit is contained in:
		
							
								
								
									
										23
									
								
								apps/demos/platformer/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								apps/demos/platformer/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| cmake_minimum_required(VERSION 3.21) | ||||
|  | ||||
| project(platformer LANGUAGES C) | ||||
|  | ||||
| if(NOT CMAKE_BUILD_TYPE) | ||||
|         set(CMAKE_BUILD_TYPE Debug) | ||||
| endif() | ||||
|  | ||||
| add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR}) | ||||
|  | ||||
| set(SOURCE_FILES | ||||
|         game.c | ||||
|         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 | ||||
| ) | ||||
|  | ||||
| use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR}) | ||||
							
								
								
									
										3
									
								
								apps/demos/platformer/data/packs/data.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								apps/demos/platformer/data/packs/data.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| [[deps]] | ||||
| source = "../../common-data" # where does it come from, might be an url | ||||
| name = "common-data"         # should be globally unique | ||||
							
								
								
									
										11
									
								
								apps/demos/platformer/data/twn.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								apps/demos/platformer/data/twn.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| [about] | ||||
| title = "Platfomer Demo" | ||||
| developer = "Townengine Team" | ||||
| app_id = "platformer-demo" | ||||
| dev_id = "townengine-team" | ||||
|  | ||||
| [game] | ||||
| base_render_width = 640 | ||||
| base_render_height = 360 | ||||
|  | ||||
| [engine] | ||||
							
								
								
									
										79
									
								
								apps/demos/platformer/game.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								apps/demos/platformer/game.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| #include "state.h" | ||||
| #include "scenes/scene.h" | ||||
| #include "scenes/title.h" | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <malloc.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
|  | ||||
| void game_tick(void) { | ||||
|     if (ctx.initialization_needed) { | ||||
|         if (!ctx.udata) { | ||||
|             ctx.udata = ccalloc(1, sizeof (State)); | ||||
|  | ||||
|             State *state = ctx.udata; | ||||
|             state->ctx = &ctx; | ||||
|             state->scene = title_scene(state); | ||||
|         } | ||||
|  | ||||
|         input_add_action("debug_toggle"); | ||||
|         input_bind_action_control("debug_toggle", CONTROL_BACKSPACE); | ||||
|  | ||||
|         input_add_action("debug_dump_atlases"); | ||||
|         input_bind_action_control("debug_dump_atlases", CONTROL_HOME); | ||||
|  | ||||
|         input_add_action("player_left"); | ||||
|         input_bind_action_control("player_left", CONTROL_A); | ||||
|  | ||||
|         input_add_action("player_right"); | ||||
|         input_bind_action_control("player_right", CONTROL_D); | ||||
|  | ||||
|         input_add_action("player_forward"); | ||||
|         input_bind_action_control("player_forward", CONTROL_W); | ||||
|  | ||||
|         input_add_action("player_backward"); | ||||
|         input_bind_action_control("player_backward", CONTROL_S); | ||||
|  | ||||
|         input_add_action("player_jump"); | ||||
|         input_bind_action_control("player_jump", CONTROL_SPACE); | ||||
|  | ||||
|         input_add_action("player_run"); | ||||
|         input_bind_action_control("player_run", CONTROL_LSHIFT); | ||||
|  | ||||
|         input_add_action("ui_accept"); | ||||
|         input_bind_action_control("ui_accept", CONTROL_RETURN); | ||||
|  | ||||
|         input_add_action("mouse_capture_toggle"); | ||||
|         input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE); | ||||
|     } | ||||
|  | ||||
|     State *state = ctx.udata; | ||||
|  | ||||
|     if (input_is_action_just_pressed("debug_toggle")) { | ||||
|         ctx.debug = !ctx.debug; | ||||
|     } | ||||
|  | ||||
|     if (input_is_action_just_pressed("debug_dump_atlases")) { | ||||
|         textures_dump_atlases(); | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|     State *state = ctx.udata; | ||||
|     state->scene->end(state); | ||||
|     free(state); | ||||
| } | ||||
							
								
								
									
										304
									
								
								apps/demos/platformer/player.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								apps/demos/platformer/player.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,304 @@ | ||||
| #include "player.h" | ||||
| #include "world.h" | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include <SDL2/SDL.h> | ||||
|  | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| #include <tgmath.h> | ||||
|  | ||||
|  | ||||
| static void update_timers(Player *player) { | ||||
|      tick_timer(&player->jump_air_timer); | ||||
|      tick_timer(&player->jump_coyote_timer); | ||||
|      tick_timer(&player->jump_buffer_timer); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void input_move(Player *player) { | ||||
|     /* apply horizontal damping when the player stops moving */ | ||||
|     /* in other words, make it decelerate to a standstill */ | ||||
|     if (!input_is_action_pressed("player_left") && | ||||
|         !input_is_action_pressed("player_right")) | ||||
|     { | ||||
|         player->dx *= player->horizontal_damping; | ||||
|     } | ||||
|  | ||||
|     int input_dir = 0; | ||||
|     if (input_is_action_pressed("player_left")) | ||||
|         input_dir = -1; | ||||
|     if (input_is_action_pressed("player_right")) | ||||
|         input_dir = 1; | ||||
|     if (input_is_action_pressed("player_left") && | ||||
|         input_is_action_pressed("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(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(Player *player) { | ||||
|     player->current_gravity_multiplier = player->jump_default_multiplier; | ||||
|  | ||||
|     if (input_is_action_just_pressed("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("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(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(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(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(Player *player, Rect 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(Player *player) { | ||||
|     Rect 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);  | ||||
|  | ||||
|     typedef enum CollisionDirection { COLLISION_LEFT = -1, COLLISION_RIGHT = 1 } CollisionDirection; | ||||
|     CollisionDirection 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(Player *player) { | ||||
|     Rect 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);  | ||||
|  | ||||
|     typedef enum CollisionDirection { COLLISION_ABOVE = -1, COLLISION_BELOW = 1 } CollisionDirection; | ||||
|     CollisionDirection 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; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| Player *player_create(World *world) { | ||||
|     Player *player = cmalloc(sizeof *player); | ||||
|  | ||||
|     *player = (Player) { | ||||
|         .world = world, | ||||
|  | ||||
|         .sprite_w = 48, | ||||
|         .sprite_h = 48, | ||||
|  | ||||
|         .rect = (Rect) { | ||||
|             .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(Player *player) { | ||||
|     m_sprite("/assets/player/baron-walk.png", | ||||
|             (Rect) { | ||||
|                 .x = player->rect.x + ((player->rect.w - player->sprite_w) / 2), | ||||
|                 .y = player->rect.y - 8, | ||||
|                 .w = player->sprite_w, | ||||
|                 .h = player->sprite_h, | ||||
|             }); | ||||
|  | ||||
|     draw_circle((Vec2) { 256, 128 }, | ||||
|                 24, | ||||
|                 (Color) { 255, 0, 0, 255 }); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void drawdef_debug(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, */ | ||||
|     /* }; */ | ||||
|  | ||||
|     draw_rectangle(player->collider_x, | ||||
|                    (Color){ 0, 0, 255, 128 }); | ||||
|  | ||||
|     draw_rectangle(player->collider_y, | ||||
|                    (Color){ 0, 0, 255, 128 }); | ||||
| } | ||||
|  | ||||
|  | ||||
| void player_destroy(Player *player) { | ||||
|     free(player); | ||||
| } | ||||
|  | ||||
|  | ||||
| void player_calc(Player *player) { | ||||
|     update_timers(player); | ||||
|  | ||||
|     input_move(player); | ||||
|     input_jump(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); | ||||
| } | ||||
							
								
								
									
										65
									
								
								apps/demos/platformer/player.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								apps/demos/platformer/player.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| #ifndef PLAYER_H | ||||
| #define PLAYER_H | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
|  | ||||
| typedef struct World World; | ||||
|  | ||||
|  | ||||
| typedef enum PlayerAction { | ||||
|     PLAYER_ACTION_GROUND, | ||||
|     PLAYER_ACTION_FALL, | ||||
|     PLAYER_ACTION_JUMP, | ||||
| } PlayerAction; | ||||
|  | ||||
|  | ||||
| typedef struct Player { | ||||
|     World *world; | ||||
|  | ||||
|     /* visual */ | ||||
|     float sprite_w; | ||||
|     float sprite_h; | ||||
|  | ||||
|     /* body */ | ||||
|     Rect rect; | ||||
|  | ||||
|     /* state */ | ||||
|     PlayerAction action; | ||||
|  | ||||
|     /* physics */ | ||||
|     Rect collider_x; | ||||
|     Rect 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 */ | ||||
| } Player; | ||||
|  | ||||
|  | ||||
| Player *player_create(World *world); | ||||
| void player_destroy(Player *player); | ||||
| void player_calc(Player *player); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										60
									
								
								apps/demos/platformer/scenes/ingame.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								apps/demos/platformer/scenes/ingame.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| #include "ingame.h" | ||||
| #include "title.h" | ||||
| #include "scene.h" | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
| #include "twn_vec.h" | ||||
|  | ||||
| #include <SDL2/SDL.h> | ||||
|  | ||||
|  | ||||
| static void ingame_tick(State *state) { | ||||
|     SceneIngame *scn = (SceneIngame *)state->scene; | ||||
|  | ||||
|     world_drawdef(scn->world); | ||||
|     player_calc(scn->player); | ||||
|  | ||||
|     const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up)); | ||||
|     const float speed = 0.04f; /* TODO: put this in a better place */ | ||||
|     if (input_is_action_pressed("player_left")) | ||||
|         scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed)); | ||||
|  | ||||
|     if (input_is_action_pressed("player_right")) | ||||
|         scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed)); | ||||
|  | ||||
|     if (input_is_action_pressed("player_forward")) | ||||
|         scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed)); | ||||
|  | ||||
|     if (input_is_action_pressed("player_backward")) | ||||
|         scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed)); | ||||
|  | ||||
|     if (input_is_action_pressed("player_jump")) | ||||
|         scn->cam.pos.y += speed; | ||||
|  | ||||
|     if (input_is_action_pressed("player_run")) | ||||
|         scn->cam.pos.y -= speed; | ||||
| } | ||||
|  | ||||
|  | ||||
| static void ingame_end(State *state) { | ||||
|     SceneIngame *scn = (SceneIngame *)state->scene; | ||||
|     player_destroy(scn->player); | ||||
|     world_destroy(scn->world); | ||||
|     free(state->scene); | ||||
| } | ||||
|  | ||||
|  | ||||
| Scene *ingame_scene(State *state) { | ||||
|     (void)state; | ||||
|  | ||||
|     SceneIngame *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); | ||||
|  | ||||
|     new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 }; | ||||
|  | ||||
|     return (Scene *)new_scene; | ||||
| } | ||||
							
								
								
									
										30
									
								
								apps/demos/platformer/scenes/ingame.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								apps/demos/platformer/scenes/ingame.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| #ifndef INGAME_H | ||||
| #define INGAME_H | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include "../state.h" | ||||
| #include "scene.h" | ||||
| #include "../player.h" | ||||
| #include "../world.h" | ||||
|  | ||||
|  | ||||
| typedef struct SceneIngame { | ||||
|     Scene base; | ||||
|  | ||||
|     World *world; | ||||
|     Player *player; | ||||
|  | ||||
|     Camera cam; | ||||
|  | ||||
|     /* TODO: put this in a better place */ | ||||
|     float yaw; | ||||
|     float pitch; | ||||
|     float roll; | ||||
| } SceneIngame; | ||||
|  | ||||
|  | ||||
| Scene *ingame_scene(State *state); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										8
									
								
								apps/demos/platformer/scenes/scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								apps/demos/platformer/scenes/scene.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #include "scene.h" | ||||
| #include "../state.h" | ||||
|  | ||||
|  | ||||
| void switch_to(State *state, Scene *(*scene_func)(State *)) { | ||||
|     state->next_scene = scene_func(state); | ||||
|     state->is_scene_switching = true; | ||||
| } | ||||
							
								
								
									
										16
									
								
								apps/demos/platformer/scenes/scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								apps/demos/platformer/scenes/scene.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef SCENE_H | ||||
| #define SCENE_H | ||||
|  | ||||
|  | ||||
| typedef struct State State; | ||||
| typedef struct Scene { | ||||
|     char *id; | ||||
|     void (*tick)(State *); | ||||
|     void (*end)(State *); | ||||
| } Scene; | ||||
|  | ||||
|  | ||||
| void switch_to(State *state, Scene *(*scene_func)(State *)); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										75
									
								
								apps/demos/platformer/scenes/title.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								apps/demos/platformer/scenes/title.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| #include "title.h" | ||||
| #include "ingame.h" | ||||
| #include "../world.h" | ||||
| #include "../player.h" | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include <SDL2/SDL.h> | ||||
|  | ||||
| #include <stdio.h> | ||||
|  | ||||
|  | ||||
| static void title_tick(State *state) { | ||||
|     SceneTitle *scn = (SceneTitle *)state->scene; | ||||
|     (void)scn; | ||||
|  | ||||
|     if (input_is_action_just_pressed("ui_accept")) { | ||||
|         switch_to(state, ingame_scene); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     m_sprite("/assets/title.png", ((Rect) { | ||||
|             ((float)ctx.base_draw_w / 2) - ((float)320 / 2), 64, 320, 128 })); | ||||
|      | ||||
|      | ||||
|     /* draw the tick count as an example of dynamic text */ | ||||
|     size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->tick_count) + 1; | ||||
|     char *text_str = cmalloc(text_str_len); | ||||
|     snprintf(text_str, text_str_len, "%lu", state->ctx->tick_count); | ||||
|  | ||||
|     const char *font = "fonts/kenney-pixel.ttf"; | ||||
|     int text_h = 32; | ||||
|     int text_w = draw_text_width(text_str, text_h, font); | ||||
|  | ||||
|     draw_rectangle( | ||||
|         (Rect) { | ||||
|             .x = 0, | ||||
|             .y = 0, | ||||
|             .w = (float)text_w, | ||||
|             .h = (float)text_h, | ||||
|         }, | ||||
|         (Color) { 0, 0, 0, 255 } | ||||
|     ); | ||||
|     draw_text( | ||||
|         text_str, | ||||
|         (Vec2){ 0, 0 }, | ||||
|         text_h, | ||||
|         (Color) { 255, 255, 255, 255 }, | ||||
|         font | ||||
|     ); | ||||
|     free(text_str); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void title_end(State *state) { | ||||
|     SceneTitle *scn = (SceneTitle *)state->scene; | ||||
|     player_destroy(scn->player); | ||||
|     world_destroy(scn->world); | ||||
|     free(state->scene); | ||||
| } | ||||
|  | ||||
|  | ||||
| Scene *title_scene(State *state) { | ||||
|     (void)state; | ||||
|  | ||||
|     SceneTitle *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 (Scene *)new_scene; | ||||
| } | ||||
							
								
								
									
										21
									
								
								apps/demos/platformer/scenes/title.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								apps/demos/platformer/scenes/title.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| #ifndef TITLE_H | ||||
| #define TITLE_H | ||||
|  | ||||
| #include "../state.h" | ||||
| #include "scene.h" | ||||
| #include "../player.h" | ||||
| #include "../world.h" | ||||
|  | ||||
|  | ||||
| typedef struct SceneTitle { | ||||
|     Scene base; | ||||
|  | ||||
|     World *world; | ||||
|     Player *player; | ||||
| } SceneTitle; | ||||
|  | ||||
|  | ||||
| Scene *title_scene(State *state); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										20
									
								
								apps/demos/platformer/state.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								apps/demos/platformer/state.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| #ifndef STATE_H | ||||
| #define STATE_H | ||||
|  | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include <stdbool.h> | ||||
|  | ||||
|  | ||||
| typedef struct Scene Scene; | ||||
|  | ||||
| typedef struct State { | ||||
|     Context *ctx; | ||||
|     Scene *scene; | ||||
|     Scene *next_scene; | ||||
|     bool is_scene_switching; | ||||
| } State; | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										188
									
								
								apps/demos/platformer/world.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								apps/demos/platformer/world.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include "world.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 = (Recti) { | ||||
|                     .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 Vec2i to_grid_location(struct World *world, float x, float y) { | ||||
|     return (Vec2i) { | ||||
|         .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; | ||||
|  | ||||
|         draw_rectangle(to_frect(world->tiles[i].rect), | ||||
|                         (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; | ||||
|  | ||||
|         m_sprite("/assets/white.png", to_frect(world->tiles[i].rect)); | ||||
|     } | ||||
|  | ||||
|     drawdef_debug(world); | ||||
| } | ||||
|  | ||||
|  | ||||
| bool world_find_intersect_frect(struct World *world, Rect rect, 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; | ||||
|  | ||||
|         Rect 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) { | ||||
|             Rect temp; | ||||
|             is_intersecting = overlap_frect(&rect, &tile_frect, &temp); | ||||
|         } else { | ||||
|             is_intersecting = overlap_frect(&rect, &tile_frect, intersection); | ||||
|         } | ||||
|  | ||||
|         if (is_intersecting) | ||||
|             break;  | ||||
|     } | ||||
|  | ||||
|     return is_intersecting; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool world_find_intersect_rect(struct World *world, Recti rect, Recti *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; | ||||
|  | ||||
|         Recti *tile_rect = &world->tiles[i].rect; | ||||
|  | ||||
|         if (intersection == NULL) { | ||||
|             Recti temp; | ||||
|             is_intersecting = overlap_rect(&rect, tile_rect, &temp); | ||||
|         } else { | ||||
|             is_intersecting = overlap_rect(&rect, tile_rect, intersection); | ||||
|         } | ||||
|  | ||||
|         if (is_intersecting) | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     return is_intersecting; | ||||
| } | ||||
|  | ||||
|  | ||||
| bool world_is_tile_at(struct World *world, float x, float y) { | ||||
|     Vec2i 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) { | ||||
|     Vec2i 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) { | ||||
|     Vec2i 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/demos/platformer/world.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								apps/demos/platformer/world.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| #ifndef WORLD_H | ||||
| #define WORLD_H | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
|  | ||||
|  | ||||
| typedef enum TileType { | ||||
|     TILE_TYPE_VOID, | ||||
|     TILE_TYPE_SOLID, | ||||
| } TileType; | ||||
|  | ||||
|  | ||||
| typedef struct Tile { | ||||
|     Recti rect; | ||||
|     TileType type; | ||||
| } Tile; | ||||
|  | ||||
|  | ||||
| typedef struct World { | ||||
|     TileType **tilemap; | ||||
|     Tile *tiles; | ||||
|      | ||||
|     int tile_size; | ||||
|     unsigned int tilemap_width; | ||||
|     unsigned int tilemap_height; | ||||
|     size_t tile_nonvoid_count; | ||||
|     float gravity; | ||||
| } World; | ||||
|  | ||||
|  | ||||
| World *world_create(void); | ||||
| void world_destroy(World *world); | ||||
| void world_drawdef(World *world); | ||||
| bool world_find_intersect_frect(World *world, Rect rect, Rect *intersection); | ||||
| bool world_find_intersect_rect(World *world, Recti rect, Recti *intersection); | ||||
| bool world_is_tile_at(World *world, float x, float y); | ||||
| void world_place_tile(World *world, float x, float y); | ||||
| void world_remove_tile(World *world, float x, float y); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										20
									
								
								apps/demos/scenery/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								apps/demos/scenery/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| cmake_minimum_required(VERSION 3.21) | ||||
|  | ||||
| project(scenery LANGUAGES C) | ||||
|  | ||||
| if(NOT CMAKE_BUILD_TYPE) | ||||
|         set(CMAKE_BUILD_TYPE Debug) | ||||
| endif() | ||||
|  | ||||
| add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR}) | ||||
|  | ||||
| set(SOURCE_FILES | ||||
|         game.c | ||||
|         state.h | ||||
|  | ||||
|         scenes/scene.c scenes/scene.h | ||||
|         scenes/title.c scenes/title.h | ||||
|         scenes/ingame.c scenes/ingame.h | ||||
| ) | ||||
|  | ||||
| use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR}) | ||||
							
								
								
									
										3
									
								
								apps/demos/scenery/data/packs/data.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								apps/demos/scenery/data/packs/data.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| [[deps]] | ||||
| source = "../../common-data" # where does it come from, might be an url | ||||
| name = "common-data"         # should be globally unique | ||||
							
								
								
									
										11
									
								
								apps/demos/scenery/data/twn.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								apps/demos/scenery/data/twn.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| [about] | ||||
| title = "Serene Scenery" | ||||
| developer = "Townengine Team" | ||||
| app_id = "platformer-demo" | ||||
| dev_id = "townengine-team" | ||||
|  | ||||
| [game] | ||||
| base_render_width = 640 | ||||
| base_render_height = 360 | ||||
|  | ||||
| [engine] | ||||
							
								
								
									
										80
									
								
								apps/demos/scenery/game.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								apps/demos/scenery/game.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| #include "state.h" | ||||
| #include "scenes/scene.h" | ||||
| #include "scenes/title.h" | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <malloc.h> | ||||
| #include <stdint.h> | ||||
| #include <stdlib.h> | ||||
|  | ||||
|  | ||||
| void game_tick(void) { | ||||
|     if (ctx.initialization_needed) { | ||||
|         if (!ctx.udata) { | ||||
|             ctx.udata = ccalloc(1, sizeof (State)); | ||||
|  | ||||
|             State *state = ctx.udata; | ||||
|             state->ctx = &ctx; | ||||
|             state->scene = title_scene(state); | ||||
|         } | ||||
|  | ||||
|         input_add_action("debug_toggle"); | ||||
|         input_bind_action_control("debug_toggle", CONTROL_BACKSPACE); | ||||
|  | ||||
|         input_add_action("debug_dump_atlases"); | ||||
|         input_bind_action_control("debug_dump_atlases", CONTROL_HOME); | ||||
|  | ||||
|         input_add_action("player_left"); | ||||
|         input_bind_action_control("player_left", CONTROL_A); | ||||
|  | ||||
|         input_add_action("player_right"); | ||||
|         input_bind_action_control("player_right", CONTROL_D); | ||||
|  | ||||
|         input_add_action("player_forward"); | ||||
|         input_bind_action_control("player_forward", CONTROL_W); | ||||
|  | ||||
|         input_add_action("player_backward"); | ||||
|         input_bind_action_control("player_backward", CONTROL_S); | ||||
|  | ||||
|         input_add_action("player_jump"); | ||||
|         input_bind_action_control("player_jump", CONTROL_SPACE); | ||||
|  | ||||
|         input_add_action("player_run"); | ||||
|         input_bind_action_control("player_run", CONTROL_LSHIFT); | ||||
|  | ||||
|         input_add_action("ui_accept"); | ||||
|         input_bind_action_control("ui_accept", CONTROL_RETURN); | ||||
|  | ||||
|         input_add_action("mouse_capture_toggle"); | ||||
|         input_bind_action_control("mouse_capture_toggle", CONTROL_ESCAPE); | ||||
|     } | ||||
|  | ||||
|     State *state = ctx.udata; | ||||
|  | ||||
|     if (input_is_action_just_pressed("debug_toggle")) { | ||||
|         ctx.debug = !ctx.debug; | ||||
|     } | ||||
|  | ||||
|     if (input_is_action_just_pressed("debug_dump_atlases")) { | ||||
|         textures_dump_atlases(); | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|     State *state = ctx.udata; | ||||
|     state->scene->end(state); | ||||
|     free(state); | ||||
| } | ||||
							
								
								
									
										117
									
								
								apps/demos/scenery/scenes/ingame.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								apps/demos/scenery/scenes/ingame.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| #include "ingame.h" | ||||
| #include "title.h" | ||||
| #include "scene.h" | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
| #include "twn_vec.h" | ||||
|  | ||||
| #define STB_PERLIN_IMPLEMENTATION | ||||
| #include <stb_perlin.h> | ||||
| #include <SDL2/SDL.h> | ||||
|  | ||||
| #include <stdlib.h> | ||||
|  | ||||
|  | ||||
| static void ingame_tick(State *state) { | ||||
|     SceneIngame *scn = (SceneIngame *)state->scene; | ||||
|  | ||||
|     if (input_is_mouse_captured()) { | ||||
|         const float sensitivity = 0.6f; /* TODO: put this in a better place */ | ||||
|         scn->yaw += (float)ctx.mouse_relative_position.x * sensitivity; | ||||
|         scn->pitch -= (float)ctx.mouse_relative_position.y * sensitivity; | ||||
|         scn->pitch = clampf(scn->pitch, -89.0f, 89.0f); | ||||
|  | ||||
|         const float yaw_rad = scn->yaw * (float)DEG2RAD; | ||||
|         const float pitch_rad = scn->pitch * (float)DEG2RAD; | ||||
|  | ||||
|         scn->cam.target = m_vec_norm(((Vec3){ | ||||
|             cosf(yaw_rad) * cosf(pitch_rad), | ||||
|             sinf(pitch_rad), | ||||
|             sinf(yaw_rad) * cosf(pitch_rad) | ||||
|         })); | ||||
|     } | ||||
|  | ||||
|     const Vec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up)); | ||||
|     const float speed = 0.04f; /* TODO: put this in a better place */ | ||||
|     if (input_is_action_pressed("player_left")) | ||||
|         scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(right, speed)); | ||||
|  | ||||
|     if (input_is_action_pressed("player_right")) | ||||
|         scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(right, speed)); | ||||
|  | ||||
|     if (input_is_action_pressed("player_forward")) | ||||
|         scn->cam.pos = vec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed)); | ||||
|  | ||||
|     if (input_is_action_pressed("player_backward")) | ||||
|         scn->cam.pos = vec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed)); | ||||
|  | ||||
|     if (input_is_action_pressed("player_jump")) | ||||
|         scn->cam.pos.y += speed; | ||||
|  | ||||
|     if (input_is_action_pressed("player_run")) | ||||
|         scn->cam.pos.y -= speed; | ||||
|  | ||||
|     /* toggle mouse capture with end key */ | ||||
|     if (input_is_action_just_pressed("mouse_capture_toggle")) { | ||||
|         input_set_mouse_captured(!input_is_mouse_captured()); | ||||
|     } | ||||
|  | ||||
|     draw_camera(&scn->cam); | ||||
|  | ||||
|     #define TERRAIN_FREQUENCY 0.1f | ||||
|  | ||||
|     for (int ly = 64; ly--;) { | ||||
|         for (int lx = 64; lx--;) { | ||||
|             float x = SDL_truncf(scn->cam.pos.x + 32 - lx); | ||||
|             float y = SDL_truncf(scn->cam.pos.z + 32 - ly); | ||||
|  | ||||
|             float d0 = stb_perlin_noise3((float)x       * TERRAIN_FREQUENCY, (float)y       * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; | ||||
|             float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y       * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; | ||||
|             float d2 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; | ||||
|             float d3 = stb_perlin_noise3((float)x       * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; | ||||
|  | ||||
|             draw_triangle("/assets/grass.png", | ||||
|                             (Vec3){ (float)x,     d0, (float)y }, | ||||
|                             (Vec3){ (float)x + 1, d1, (float)y }, | ||||
|                             (Vec3){ (float)x,     d3, (float)y - 1 }, | ||||
|                             (Vec2){ 128, 128 }, | ||||
|                             (Vec2){ 128, 0 }, | ||||
|                             (Vec2){ 0, 128 }); | ||||
|  | ||||
|             draw_triangle("/assets/grass.png", | ||||
|                             (Vec3){ (float)x + 1, d1, (float)y }, | ||||
|                             (Vec3){ (float)x + 1, d2, (float)y - 1 }, | ||||
|                             (Vec3){ (float)x,     d3, (float)y - 1 }, | ||||
|                             (Vec2){ 128, 0 }, | ||||
|                             (Vec2){ 0, 0 }, | ||||
|                             (Vec2){ 0, 128 }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     draw_skybox("/assets/miramar/miramar_*.tga"); | ||||
|     draw_fog(0.9, 1.0, 0.05, (Color){ 140, 147, 160, 255 }); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void ingame_end(State *state) { | ||||
|     free(state->scene); | ||||
| } | ||||
|  | ||||
|  | ||||
| Scene *ingame_scene(State *state) { | ||||
|     (void)state; | ||||
|  | ||||
|     SceneIngame *new_scene = ccalloc(1, sizeof *new_scene); | ||||
|     new_scene->base.tick = ingame_tick; | ||||
|     new_scene->base.end = ingame_end; | ||||
|  | ||||
|     new_scene->cam = (Camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 }; | ||||
|  | ||||
|     m_audio(m_set(path, "music/mod65.xm"), | ||||
|             m_opt(channel, "soundtrack"), | ||||
|             m_opt(repeat, true)); | ||||
|  | ||||
|     input_set_mouse_captured(true); | ||||
|  | ||||
|     return (Scene *)new_scene; | ||||
| } | ||||
							
								
								
									
										25
									
								
								apps/demos/scenery/scenes/ingame.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								apps/demos/scenery/scenes/ingame.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| #ifndef INGAME_H | ||||
| #define INGAME_H | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include "../state.h" | ||||
| #include "scene.h" | ||||
|  | ||||
|  | ||||
| typedef struct SceneIngame { | ||||
|     Scene base; | ||||
|  | ||||
|     Camera cam; | ||||
|  | ||||
|     /* TODO: put this in a better place */ | ||||
|     float yaw; | ||||
|     float pitch; | ||||
|     float roll; | ||||
| } SceneIngame; | ||||
|  | ||||
|  | ||||
| Scene *ingame_scene(State *state); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										8
									
								
								apps/demos/scenery/scenes/scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								apps/demos/scenery/scenes/scene.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #include "scene.h" | ||||
| #include "../state.h" | ||||
|  | ||||
|  | ||||
| void switch_to(State *state, Scene *(*scene_func)(State *)) { | ||||
|     state->next_scene = scene_func(state); | ||||
|     state->is_scene_switching = true; | ||||
| } | ||||
							
								
								
									
										16
									
								
								apps/demos/scenery/scenes/scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								apps/demos/scenery/scenes/scene.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef SCENE_H | ||||
| #define SCENE_H | ||||
|  | ||||
|  | ||||
| typedef struct State State; | ||||
| typedef struct Scene { | ||||
|     char *id; | ||||
|     void (*tick)(State *); | ||||
|     void (*end)(State *); | ||||
| } Scene; | ||||
|  | ||||
|  | ||||
| void switch_to(State *state, Scene *(*scene_func)(State *)); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										61
									
								
								apps/demos/scenery/scenes/title.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								apps/demos/scenery/scenes/title.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| #include "title.h" | ||||
| #include "ingame.h" | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
|  | ||||
|  | ||||
| static void title_tick(State *state) { | ||||
|     SceneTitle *scn = (SceneTitle *)state->scene; | ||||
|     (void)scn; | ||||
|  | ||||
|     if (input_is_action_just_pressed("ui_accept")) { | ||||
|         switch_to(state, ingame_scene); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     m_sprite("/assets/title.png", ((Rect) { | ||||
|             ((float)ctx.base_draw_w / 2) - ((float)320 / 2), 64, 320, 128 })); | ||||
|      | ||||
|      | ||||
|     /* draw the tick count as an example of dynamic text */ | ||||
|     size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->tick_count) + 1; | ||||
|     char *text_str = cmalloc(text_str_len); | ||||
|     snprintf(text_str, text_str_len, "%lu", state->ctx->tick_count); | ||||
|  | ||||
|     const char *font = "/fonts/kenney-pixel.ttf"; | ||||
|     int text_h = 32; | ||||
|     int text_w = draw_text_width(text_str, text_h, font); | ||||
|  | ||||
|     draw_rectangle( | ||||
|         (Rect) { | ||||
|             .x = 0, | ||||
|             .y = 0, | ||||
|             .w = (float)text_w, | ||||
|             .h = (float)text_h, | ||||
|         }, | ||||
|         (Color) { 0, 0, 0, 255 } | ||||
|     ); | ||||
|     draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font); | ||||
|  | ||||
|     free(text_str); | ||||
| } | ||||
|  | ||||
|  | ||||
| static void title_end(State *state) { | ||||
|     free(state->scene); | ||||
| } | ||||
|  | ||||
|  | ||||
| Scene *title_scene(State *state) { | ||||
|     (void)state; | ||||
|  | ||||
|     SceneTitle *new_scene = ccalloc(1, sizeof *new_scene); | ||||
|     new_scene->base.tick = title_tick; | ||||
|     new_scene->base.end = title_end; | ||||
|  | ||||
|     return (Scene *)new_scene; | ||||
| } | ||||
							
								
								
									
										16
									
								
								apps/demos/scenery/scenes/title.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								apps/demos/scenery/scenes/title.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef TITLE_H | ||||
| #define TITLE_H | ||||
|  | ||||
| #include "../state.h" | ||||
| #include "scene.h" | ||||
|  | ||||
|  | ||||
| typedef struct SceneTitle { | ||||
|     Scene base; | ||||
| } SceneTitle; | ||||
|  | ||||
|  | ||||
| Scene *title_scene(State *state); | ||||
|  | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										20
									
								
								apps/demos/scenery/state.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								apps/demos/scenery/state.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| #ifndef STATE_H | ||||
| #define STATE_H | ||||
|  | ||||
|  | ||||
| #include "twn_game_api.h" | ||||
|  | ||||
| #include <stdbool.h> | ||||
|  | ||||
|  | ||||
| typedef struct Scene Scene; | ||||
|  | ||||
| typedef struct State { | ||||
|     Context *ctx; | ||||
|     Scene *scene; | ||||
|     Scene *next_scene; | ||||
|     bool is_scene_switching; | ||||
| } State; | ||||
|  | ||||
|  | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user