Fixed timestep for gameplay and physics

main
Cameron Murphy Reikes 2 years ago
parent 11b1c0964e
commit d028d96786

186
main.c

@ -735,6 +735,8 @@ typedef struct GameState {
Entity entities[MAX_ENTITIES]; Entity entities[MAX_ENTITIES];
} GameState; } GameState;
GameState gs = {0}; GameState gs = {0};
double unprocessed_fixed_timestep_time = 0.0;
#define FIXED_TIMESTEP (1.0/60.0)
EntityRef frome(Entity *e) EntityRef frome(Entity *e)
{ {
@ -2569,44 +2571,20 @@ void frame(void)
assert(player != NULL); assert(player != NULL);
#ifdef DEVTOOLS // fixed timestep loop
dbgsquare(screen_to_world(mouse_pos)); Entity *interacting_with = 0; // used by rendering to figure out who to draw dialog box on
int num_timestep_loops = 0;
// tile coord
if(show_devtools)
{ {
TileCoord hovering = world_to_tilecoord(screen_to_world(mouse_pos)); unprocessed_fixed_timestep_time += dt;
Vec2 points[4] ={0}; while(unprocessed_fixed_timestep_time > FIXED_TIMESTEP)
AABB q = tile_aabb(hovering);
dbgrect(q);
draw_text((TextParams){false, false, tprint("%d", get_tile(&level_level0, hovering).kind), world_to_screen(tilecoord_to_world(hovering)), BLACK, 1.0f});
}
// debug draw font image
{ {
draw_quad((DrawParams){true, quad_centered(V2(0.0, 0.0), V2(250.0, 250.0)), image_font,full_region(image_font), WHITE}); num_timestep_loops++;
} unprocessed_fixed_timestep_time -= FIXED_TIMESTEP;
float dt = (float)FIXED_TIMESTEP;
// statistics
if(show_devtools)
PROFILE_SCOPE("statistics")
{
Vec2 pos = V2(0.0, screen_size().Y);
int num_entities = 0;
ENTITIES_ITER(gs.entities) num_entities++;
char *stats = tprint("Frametime: %.1f ms\nProcessing: %.1f ms\nEntities: %d\nDraw calls: %d\nProfiling: %s\n", dt*1000.0, last_frame_processing_time*1000.0, num_entities, num_draw_calls, profiling ? "yes" : "no");
AABB bounds = draw_text((TextParams){false, true, stats, pos, BLACK, 1.0f});
pos.Y -= bounds.upper_left.Y - screen_size().Y;
bounds = draw_text((TextParams){false, true, stats, pos, BLACK, 1.0f});
// background panel
colorquad(false, quad_aabb(bounds), (Color){1.0, 1.0, 1.0, 0.3f});
draw_text((TextParams){false, false, stats, pos, BLACK, 1.0f});
num_draw_calls = 0;
}
#endif // devtools
// process gs.entities // process gs.entities
PROFILE_SCOPE("entity processing") PROFILE_SCOPE("entity processing")
{
ENTITIES_ITER(gs.entities) ENTITIES_ITER(gs.entities)
{ {
assert(!(it->exists && it->generation == 0)); assert(!(it->exists && it->generation == 0));
@ -2865,6 +2843,7 @@ void frame(void)
assert(false); assert(false);
} }
} }
}
PROFILE_SCOPE("Destroy gs.entities") PROFILE_SCOPE("Destroy gs.entities")
{ {
@ -2879,13 +2858,10 @@ void frame(void)
} }
} }
PROFILE_SCOPE("process player and render player character") PROFILE_SCOPE("process player")
{ {
Vec2 character_sprite_pos = AddV2(player->pos, V2(0.0, 20.0f));
// do dialog // do dialog
Entity *closest_interact_with = NULL; Entity *closest_interact_with = 0;
{ {
// find closest to talk to // find closest to talk to
{ {
@ -2919,23 +2895,13 @@ void frame(void)
} }
Entity *interacting_with = closest_interact_with; interacting_with = closest_interact_with;
if(player->state == CHARACTER_TALKING) if(player->state == CHARACTER_TALKING)
{ {
interacting_with = gete(player->talking_to); interacting_with = gete(player->talking_to);
assert(interacting_with); assert(interacting_with);
} }
// if somebody, show their dialog panel
if(interacting_with)
{
// interaction circle
draw_quad((DrawParams){true, quad_centered(interacting_with->pos, V2(TILE_SIZE, TILE_SIZE)), image_dialog_circle, full_region(image_dialog_circle), WHITE});
if(interacting_with->is_npc)
{
draw_dialog_panel(interacting_with);
}
}
// process dialog and display dialog box when talking to NPC // process dialog and display dialog box when talking to NPC
if(player->state == CHARACTER_TALKING) if(player->state == CHARACTER_TALKING)
@ -2948,6 +2914,7 @@ void frame(void)
} }
} }
} }
// roll input management, sometimes means talk to the npc // roll input management, sometimes means talk to the npc
if(player->state != CHARACTER_TALKING && roll_just_pressed && closest_interact_with) if(player->state != CHARACTER_TALKING && roll_just_pressed && closest_interact_with)
{ {
@ -2994,8 +2961,6 @@ void frame(void)
BUFF_CLEAR(&player->done_damage_to_this_swing); BUFF_CLEAR(&player->done_damage_to_this_swing);
player->swing_progress = 0.0; player->swing_progress = 0.0;
} }
// roll processing // roll processing
{ {
if(player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING) if(player->state != CHARACTER_IDLE && player->state != CHARACTER_WALKING)
@ -3015,7 +2980,6 @@ void frame(void)
if(!player->is_rolling) player->time_not_rolling += dt; if(!player->is_rolling) player->time_not_rolling += dt;
} }
Vec2 target_vel = {0}; Vec2 target_vel = {0};
float speed = 0.0f; float speed = 0.0f;
@ -3033,17 +2997,6 @@ void frame(void)
{ {
speed *= 1.0f + ((float)player->boots_modifier * 0.1f); speed *= 1.0f + ((float)player->boots_modifier * 0.1f);
} }
if(player->is_rolling)
{
draw_animated_sprite(&knight_rolling, player->roll_progress, player->facing_left, character_sprite_pos, WHITE);
}
else
{
draw_animated_sprite(&knight_running, elapsed_time, player->facing_left, character_sprite_pos, WHITE);
}
if(LenV2(movement) == 0.0) if(LenV2(movement) == 0.0)
{ {
player->state = CHARACTER_IDLE; player->state = CHARACTER_IDLE;
@ -3054,14 +3007,6 @@ void frame(void)
} }
else if(player->state == CHARACTER_IDLE) else if(player->state == CHARACTER_IDLE)
{ {
if(player->is_rolling)
{
draw_animated_sprite(&knight_rolling, player->roll_progress, player->facing_left, character_sprite_pos, WHITE);
}
else
{
draw_animated_sprite(&knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE);
}
if(LenV2(movement) > 0.01) player->state = CHARACTER_WALKING; if(LenV2(movement) > 0.01) player->state = CHARACTER_WALKING;
} }
else if(player->state == CHARACTER_ATTACK) else if(player->state == CHARACTER_ATTACK)
@ -3073,9 +3018,7 @@ void frame(void)
{ {
request_do_damage(*it, player->pos, 0.2f); request_do_damage(*it, player->pos, 0.2f);
} }
player->swing_progress += dt; player->swing_progress += dt;
draw_animated_sprite(&knight_attack, player->swing_progress, player->facing_left, character_sprite_pos, WHITE);
if(player->swing_progress > anim_sprite_duration(&knight_attack)) if(player->swing_progress > anim_sprite_duration(&knight_attack))
{ {
player->state = CHARACTER_IDLE; player->state = CHARACTER_IDLE;
@ -3083,11 +3026,10 @@ void frame(void)
} }
else if(player->state == CHARACTER_TALKING) else if(player->state == CHARACTER_TALKING)
{ {
draw_animated_sprite(&knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE);
} }
else else
{ {
assert(false); // unknown character state? not defined how to draw assert(false); // unknown character state? not defined how to process
} }
// velocity processing // velocity processing
@ -3096,14 +3038,66 @@ void frame(void)
player->vel = LerpV2(player->vel, dt * 15.0f, target_vel); player->vel = LerpV2(player->vel, dt * 15.0f, target_vel);
player->pos = move_and_slide((MoveSlideParams){player, player->pos, player->vel}); player->pos = move_and_slide((MoveSlideParams){player, player->pos, player->vel});
} }
// health // health
if(player->damage >= 1.0) if(player->damage >= 1.0)
{ {
reset_level(); reset_level();
} }
}
} // while loop
}
PROFILE_SCOPE("render player")
{
Vec2 character_sprite_pos = AddV2(player->pos, V2(0.0, 20.0f));
// if somebody, show their dialog panel
if(interacting_with)
{
// interaction circle
draw_quad((DrawParams){true, quad_centered(interacting_with->pos, V2(TILE_SIZE, TILE_SIZE)), image_dialog_circle, full_region(image_dialog_circle), WHITE});
if(interacting_with->is_npc)
{
draw_dialog_panel(interacting_with);
}
}
if(player->state == CHARACTER_WALKING)
{
if(player->is_rolling)
{
draw_animated_sprite(&knight_rolling, player->roll_progress, player->facing_left, character_sprite_pos, WHITE);
}
else else
{
draw_animated_sprite(&knight_running, elapsed_time, player->facing_left, character_sprite_pos, WHITE);
}
}
else if(player->state == CHARACTER_IDLE)
{
if(player->is_rolling)
{
draw_animated_sprite(&knight_rolling, player->roll_progress, player->facing_left, character_sprite_pos, WHITE);
}
else
{
draw_animated_sprite(&knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE);
}
}
else if(player->state == CHARACTER_ATTACK)
{
draw_animated_sprite(&knight_attack, player->swing_progress, player->facing_left, character_sprite_pos, WHITE);
}
else if(player->state == CHARACTER_TALKING)
{
draw_animated_sprite(&knight_idle, elapsed_time, player->facing_left, character_sprite_pos, WHITE);
}
else
{
assert(false); // unknown character state? not defined how to draw
}
// hurt vignette
if(player->damage > 0.0)
{ {
draw_quad((DrawParams){false, (Quad){.ul=V2(0.0f, screen_size().Y), .ur = screen_size(), .lr = V2(screen_size().X, 0.0f)}, image_hurt_vignette, full_region(image_hurt_vignette), (Color){1.0f, 1.0f, 1.0f, player->damage}}); draw_quad((DrawParams){false, (Quad){.ul=V2(0.0f, screen_size().Y), .ur = screen_size(), .lr = V2(screen_size().X, 0.0f)}, image_hurt_vignette, full_region(image_hurt_vignette), (Color){1.0f, 1.0f, 1.0f, player->damage}});
} }
@ -3287,7 +3281,6 @@ void frame(void)
draw_all_translucent(); draw_all_translucent();
// ui // ui
#define HELPER_SIZE 250.0f #define HELPER_SIZE 250.0f
if(!mobile_controls) if(!mobile_controls)
{ {
@ -3312,6 +3305,43 @@ void frame(void)
draw_quad((DrawParams){false, quad_centered(attack_button_pos(), V2(mobile_button_size(), mobile_button_size())), IMG(image_mobile_button), WHITE, .y_coord_sorting = 1.0f}); draw_quad((DrawParams){false, quad_centered(attack_button_pos(), V2(mobile_button_size(), mobile_button_size())), IMG(image_mobile_button), WHITE, .y_coord_sorting = 1.0f});
} }
#ifdef DEVTOOLS
dbgsquare(screen_to_world(mouse_pos));
// tile coord
if(show_devtools)
{
TileCoord hovering = world_to_tilecoord(screen_to_world(mouse_pos));
Vec2 points[4] ={0};
AABB q = tile_aabb(hovering);
dbgrect(q);
draw_text((TextParams){false, false, tprint("%d", get_tile(&level_level0, hovering).kind), world_to_screen(tilecoord_to_world(hovering)), BLACK, 1.0f});
}
// debug draw font image
{
draw_quad((DrawParams){true, quad_centered(V2(0.0, 0.0), V2(250.0, 250.0)), image_font,full_region(image_font), WHITE});
}
// statistics
if(show_devtools)
PROFILE_SCOPE("statistics")
{
Vec2 pos = V2(0.0, screen_size().Y);
int num_entities = 0;
ENTITIES_ITER(gs.entities) num_entities++;
char *stats = tprint("Frametime: %.1f ms\nProcessing: %.1f ms\nEntities: %d\nDraw calls: %d\nProfiling: %s\nNumber fixed timestep loops: %d\n", dt*1000.0, last_frame_processing_time*1000.0, num_entities, num_draw_calls, profiling ? "yes" : "no", num_timestep_loops);
AABB bounds = draw_text((TextParams){false, true, stats, pos, BLACK, 1.0f});
pos.Y -= bounds.upper_left.Y - screen_size().Y;
bounds = draw_text((TextParams){false, true, stats, pos, BLACK, 1.0f});
// background panel
colorquad(false, quad_aabb(bounds), (Color){1.0, 1.0, 1.0, 0.3f});
draw_text((TextParams){false, false, stats, pos, BLACK, 1.0f});
num_draw_calls = 0;
}
#endif // devtools
// update camera position // update camera position
{ {
Vec2 target = MulV2F(player->pos, -1.0f * cam.scale); Vec2 target = MulV2F(player->pos, -1.0f * cam.scale);

@ -1,7 +1,6 @@
Happening by END OF STREAM: Happening by END OF STREAM:
- Payment working DONE - Payment working
- Respond to cancel with stripe, redirect to ?cancelled=true that clears day pass ticket cookie and ui value DONE - Fixed timesep the gameplay (which means separate player rendering)
- Fixed timesep the gameplay (which means separate player rendering)
- Make action between stars much more technical sounding so AI doesn't hallucinate responses - Make action between stars much more technical sounding so AI doesn't hallucinate responses
- Help you fight and fight you actions - Help you fight and fight you actions
- New characters/items from fate - New characters/items from fate
@ -21,6 +20,7 @@ SHIP BETA
- Make ANOTHER tiktok if you can. Por favor - Make ANOTHER tiktok if you can. Por favor
Later: Later:
- Respond to cancel with stripe, redirect to ?cancelled=true that clears day pass ticket cookie and ui value
- Put playgpt.io in twitch title - Put playgpt.io in twitch title
- Portal at end, exit level - Portal at end, exit level
- Keep people away from skeletons, make aggroed/chase state where you can't talk to people - Keep people away from skeletons, make aggroed/chase state where you can't talk to people

Loading…
Cancel
Save