more bullet testing

This commit is contained in:
jacob 2024-08-13 16:34:31 -05:00
parent bcbf9c9588
commit 45c6d94009
7 changed files with 154 additions and 116 deletions

View File

@ -30,7 +30,7 @@
#define PIXELS_PER_UNIT 256.0 #define PIXELS_PER_UNIT 256.0
#define GAME_FPS 50.0 #define GAME_FPS 50.0
#define GAME_TIMESCALE 1 #define GAME_TIMESCALE 1.0
/* How many ticks back in time should the user blend between? /* How many ticks back in time should the user blend between?
* <Delay ms> = <USER_INTERP_OFFSET_TICK_RATIO> * <Game tick rate> * <Delay ms> = <USER_INTERP_OFFSET_TICK_RATIO> * <Game tick rate>

View File

@ -72,7 +72,8 @@ void entity_store_copy_replace(struct entity_store *dest, struct entity_store *s
void entity_store_reset(struct entity_store *store) void entity_store_reset(struct entity_store *store)
{ {
store->count = 0; store->allocated = 0;
store->reserved = 0;
store->first_free = entity_nil_handle(); store->first_free = entity_nil_handle();
arena_pop_to(&store->arena, STORE_ENTITIES_OFFSET); arena_pop_to(&store->arena, STORE_ENTITIES_OFFSET);
store_make_root(store); store_make_root(store);
@ -87,7 +88,7 @@ INTERNAL struct entity *entity_alloc_internal(struct entity_store *store)
struct entity *entity = NULL; struct entity *entity = NULL;
struct entity_handle handle = { 0 }; struct entity_handle handle = { 0 };
if (store->first_free.gen) { if (store->first_free.gen) {
/* Reuse from free list */ /* Reuse from free list */;
entity = entity_from_handle(store, store->first_free); entity = entity_from_handle(store, store->first_free);
handle = entity->handle; handle = entity->handle;
++handle.gen; ++handle.gen;
@ -95,10 +96,11 @@ INTERNAL struct entity *entity_alloc_internal(struct entity_store *store)
} else { } else {
/* Make new */ /* Make new */
entity = arena_push(&store->arena, struct entity); entity = arena_push(&store->arena, struct entity);
handle = (struct entity_handle) { .gen = 1, .idx = store->count++ }; handle = (struct entity_handle) { .gen = 1, .idx = store->reserved++ };
} }
*entity = g_entity_default; *entity = g_entity_default;
entity->handle = handle; entity->handle = handle;
++store->allocated;
return entity; return entity;
} }
@ -125,6 +127,7 @@ INTERNAL void entity_release_internal(struct entity_store *store, struct entity
ent->valid = false; ent->valid = false;
ent->next_free = store->first_free; ent->next_free = store->first_free;
store->first_free = ent->handle; store->first_free = ent->handle;
--store->allocated;
} }
void entity_release(struct entity_store *store, struct entity *ent) void entity_release(struct entity_store *store, struct entity *ent)
@ -152,7 +155,7 @@ struct entity_store *entity_get_store(struct entity *ent)
/* Returns a valid entity or read-only nil entity. Always safe to read result, need to check `valid` to write. */ /* Returns a valid entity or read-only nil entity. Always safe to read result, need to check `valid` to write. */
struct entity *entity_from_handle(struct entity_store *store, struct entity_handle handle) struct entity *entity_from_handle(struct entity_store *store, struct entity_handle handle)
{ {
if (handle.gen != 0 && handle.idx < store->count) { if (handle.gen != 0 && handle.idx < store->reserved) {
struct entity *entity = &store->entities[handle.idx]; struct entity *entity = &store->entities[handle.idx];
if (entity->handle.gen == handle.gen) { if (entity->handle.gen == handle.gen) {
return entity; return entity;
@ -163,7 +166,7 @@ struct entity *entity_from_handle(struct entity_store *store, struct entity_hand
struct entity *entity_find_first_match_one(struct entity_store *store, enum entity_prop prop) struct entity *entity_find_first_match_one(struct entity_store *store, enum entity_prop prop)
{ {
u64 count = store->count; u64 count = store->reserved;
struct entity *entities = store->entities; struct entity *entities = store->entities;
for (u64 entity_index = 0; entity_index < count; ++entity_index) { for (u64 entity_index = 0; entity_index < count; ++entity_index) {
struct entity *ent = &entities[entity_index]; struct entity *ent = &entities[entity_index];
@ -176,7 +179,7 @@ struct entity *entity_find_first_match_one(struct entity_store *store, enum enti
struct entity *entity_find_first_match_all(struct entity_store *store, struct entity_prop_array props) struct entity *entity_find_first_match_all(struct entity_store *store, struct entity_prop_array props)
{ {
u64 count = store->count; u64 count = store->reserved;
struct entity *entities = store->entities; struct entity *entities = store->entities;
for (u64 entity_index = 0; entity_index < count; ++entity_index) { for (u64 entity_index = 0; entity_index < count; ++entity_index) {
struct entity *ent = &entities[entity_index]; struct entity *ent = &entities[entity_index];

View File

@ -34,7 +34,8 @@ struct entity_handle {
struct entity_store { struct entity_store {
struct arena arena; struct arena arena;
u64 count; u64 allocated;
u64 reserved;
struct entity_handle first_free; struct entity_handle first_free;
struct entity_handle root; struct entity_handle root;
struct entity *entities; struct entity *entities;

View File

@ -247,10 +247,40 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
} }
/* ========================== * /* ========================== *
* Update sprite from animation * Activate entities
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!ent->valid) continue;
if (!entity_has_prop(ent, ENTITY_PROP_ACTIVE)) {
entity_enable_prop(ent, ENTITY_PROP_ACTIVE);
++ent->continuity_gen;
}
}
/* ========================== *
* Reset triggered entities
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
if (entity_has_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK)) {
entity_disable_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK);
entity_enable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK);
} else if (entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) {
entity_disable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK);
}
}
/* ========================== *
* Update animations from sprite
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
if (sprite_tag_is_nil(ent->sprite)) continue; if (sprite_tag_is_nil(ent->sprite)) continue;
@ -302,7 +332,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
* Update control from player cmds * Update control from player cmds
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
@ -363,7 +393,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
* Test * Test
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
@ -394,67 +424,11 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
} }
} }
/* ========================== *
* Activate triggers
* ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
/* Trigger equipped */
if (entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED)) {
struct entity *eq = entity_from_handle(store, ent->equipped);
if (entity_has_prop(eq, ENTITY_PROP_ACTIVE)) {
entity_enable_prop(eq, ENTITY_PROP_TRIGGERED_THIS_TICK);
}
}
}
/* ========================== *
* Fire triggers
* ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
if (!entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) continue;
if ((time - ent->last_triggered < ent->trigger_delay) && ent->last_triggered != 0) continue;
ent->last_triggered = time;
/* Fire weapon */
if (entity_has_prop(ent, ENTITY_PROP_WEAPON)) {
struct xform xf = entity_get_xform(ent);
struct sprite_tag sprite = ent->sprite;
u32 animation_frame = ent->animation_frame;
struct xform sprite_xform = xform_mul(xf, ent->sprite_local_xform);
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite);
struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, STR("out"), animation_frame);
struct v2 out_pos = xform_mul_v2(sprite_xform, out_slice.center);
struct v2 out_vec = xform_basis_mul_v2(sprite_xform, out_slice.dir);
out_vec = v2_norm(out_vec);
out_vec = v2_mul(out_vec, 2);
{
/* Spawn bullet */
struct entity *bullet = entity_alloc(root);
bullet->sprite = sprite_tag_from_path(STR("res/graphics/bullet.ase"));
struct xform bullet_xf = XFORM_POS(out_pos);
entity_set_xform(bullet, bullet_xf);
bullet->velocity = out_vec;
}
}
}
/* ========================== * /* ========================== *
* Simulate entity physics * Simulate entity physics
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
@ -540,11 +514,67 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
} }
} }
/* ========================== *
* Activate triggers
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
/* Trigger equipped */
if (entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED)) {
struct entity *eq = entity_from_handle(store, ent->equipped);
if (entity_has_prop(eq, ENTITY_PROP_ACTIVE)) {
entity_enable_prop(eq, ENTITY_PROP_TRIGGERED_THIS_TICK);
}
}
}
/* ========================== *
* Fire triggers
* ========================== */
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
if (!entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) continue;
if ((time - ent->last_triggered < ent->trigger_delay) && ent->last_triggered != 0) continue;
ent->last_triggered = time;
/* Fire weapon */
if (entity_has_prop(ent, ENTITY_PROP_WEAPON)) {
struct xform xf = entity_get_xform(ent);
struct sprite_tag sprite = ent->sprite;
u32 animation_frame = ent->animation_frame;
struct xform sprite_xform = xform_mul(xf, ent->sprite_local_xform);
struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite);
struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, STR("out"), animation_frame);
struct v2 out_pos = xform_mul_v2(sprite_xform, out_slice.center);
struct v2 out_vec = xform_basis_mul_v2(sprite_xform, out_slice.dir);
out_vec = v2_norm(out_vec);
out_vec = v2_mul(out_vec, 5);
{
/* Spawn bullet */
struct entity *bullet = entity_alloc(root);
bullet->sprite = sprite_tag_from_path(STR("res/graphics/bullet.ase"));
struct xform bullet_xf = XFORM_POS(out_pos);
entity_set_xform(bullet, bullet_xf);
bullet->velocity = out_vec;
}
}
}
/* ========================== * /* ========================== *
* Update camera position * Update camera position
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
@ -589,7 +619,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
* Update sound emitters * Update sound emitters
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
@ -622,7 +652,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
struct entity **ents_to_release = arena_dry_push(temp.arena, struct entity *); struct entity **ents_to_release = arena_dry_push(temp.arena, struct entity *);
u64 ents_to_release_count = 0; u64 ents_to_release_count = 0;
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!ent->valid) continue; if (!ent->valid) continue;
@ -689,36 +719,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
publish_game_tick(); publish_game_tick();
__profframe("Game"); __profframe("Game");
/* ========================== *
* Reset triggered entities
* ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
if (entity_has_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK)) {
entity_disable_prop(ent, ENTITY_PROP_TRIGGER_NEXT_TICK);
entity_enable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK);
} else if (entity_has_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK)) {
entity_disable_prop(ent, ENTITY_PROP_TRIGGERED_THIS_TICK);
}
}
/* ========================== *
* Activate entities
* ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!ent->valid) continue;
if (!entity_has_prop(ent, ENTITY_PROP_ACTIVE)) {
entity_enable_prop(ent, ENTITY_PROP_ACTIVE);
++ent->continuity_gen;
}
}
/* ========================== * /* ========================== *
* End frame cache scopes * End frame cache scopes
* ========================== */ * ========================== */
@ -749,10 +749,23 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg)
if (!G.paused) { if (!G.paused) {
game_update(game_cmds); game_update(game_cmds);
} }
/* Check for pause cmd */ /* Check for pause / next frame cmds */
for (u64 i = 0; i < game_cmds.count; ++i) { for (u64 i = 0; i < game_cmds.count; ++i) {
if (game_cmds.cmds[i].kind == GAME_CMD_KIND_PAUSE) { struct game_cmd cmd = game_cmds.cmds[i];
G.paused = !G.paused; switch (cmd.kind) {
case GAME_CMD_KIND_PAUSE: {
G.paused = !G.paused;
} break;
case GAME_CMD_KIND_STEP: {
if (G.paused) {
G.paused = false;
game_update(game_cmds);
G.paused = true;
}
} break;
default: break;
} }
} }
} }

View File

@ -22,6 +22,7 @@ enum game_cmd_kind {
GAME_CMD_KIND_CLEAR_ALL, GAME_CMD_KIND_CLEAR_ALL,
GAME_CMD_KIND_SPAWN_TEST, GAME_CMD_KIND_SPAWN_TEST,
GAME_CMD_KIND_PAUSE, GAME_CMD_KIND_PAUSE,
GAME_CMD_KIND_STEP,
GAME_CMD_KIND_COUNT GAME_CMD_KIND_COUNT
}; };

View File

@ -86,6 +86,7 @@ GLOBAL READONLY enum user_bind_kind g_binds[SYS_BTN_COUNT] = {
[SYS_BTN_C] = USER_BIND_KIND_DEBUG_CLEAR, [SYS_BTN_C] = USER_BIND_KIND_DEBUG_CLEAR,
[SYS_BTN_V] = USER_BIND_KIND_DEBUG_SPAWN, [SYS_BTN_V] = USER_BIND_KIND_DEBUG_SPAWN,
[SYS_BTN_N] = USER_BIND_KIND_DEBUG_STEP,
[SYS_BTN_F1] = USER_BIND_KIND_DEBUG_PAUSE, [SYS_BTN_F1] = USER_BIND_KIND_DEBUG_PAUSE,
[SYS_BTN_F2] = USER_BIND_KIND_DEBUG_CAMERA, [SYS_BTN_F2] = USER_BIND_KIND_DEBUG_CAMERA,
[SYS_BTN_F3] = USER_BIND_KIND_DEBUG_DRAW, [SYS_BTN_F3] = USER_BIND_KIND_DEBUG_DRAW,
@ -440,24 +441,29 @@ INTERNAL void user_update(void)
tick_blend = clamp_f32(tick_blend, 0.0f, 1.0f); tick_blend = clamp_f32(tick_blend, 0.0f, 1.0f);
} }
/* TODO: Should we actually be basing interpolated tick on t1 rather world_copy_replace(&G.world, t0);
* than t0? This means un-interpolated values will use the newer frame's
* value, while still interpolating from the older frame. */
world_copy_replace(&G.world, t1);
/* Blend world globals */ /* Blend world globals */
G.world.time = math_lerp64(t0->time, t1->time, (f64)tick_blend); G.world.time = math_lerp64(t0->time, t1->time, (f64)tick_blend);
/* Blend entities */ /* Blend entities */
u64 num_entities = min_u64(t0->entity_store->count, t1->entity_store->count); u64 num_entities = min_u64(t0->entity_store->reserved, t1->entity_store->reserved);
{ {
__profscope(tick_blending); __profscope(tick_blending);
for (u64 i = 0; i < num_entities; ++i) { for (u64 i = 0; i < num_entities; ++i) {
struct entity *e = &store->entities[i];
struct entity *e0 = &t0->entity_store->entities[i]; struct entity *e0 = &t0->entity_store->entities[i];
struct entity *e1 = &t1->entity_store->entities[i]; struct entity *e1 = &t1->entity_store->entities[i];
struct entity *e = &store->entities[i]; ASSERT(e->cached_global_xform_dirty == false); /* Game thread should have cached all global xforms before publishing */
ASSERT(!e->valid || e->cached_global_xform_dirty == false); /* Game thread should have cached all global xforms before publishing */
if (e0->handle.gen == e1->handle.gen && e0->continuity_gen == e1->continuity_gen) { if (entity_has_prop(e, ENTITY_PROP_TEST)) {
DEBUGBREAKABLE;
}
if (e0->valid && e1->valid
&& entity_has_prop(e0, ENTITY_PROP_ACTIVE) && entity_has_prop(e1, ENTITY_PROP_ACTIVE)
&& e0->handle.gen == e1->handle.gen
&& e0->continuity_gen == e1->continuity_gen) {
e->local_xform = xform_lerp(e0->local_xform, e1->local_xform, tick_blend); e->local_xform = xform_lerp(e0->local_xform, e1->local_xform, tick_blend);
e->cached_global_xform = xform_lerp(e0->cached_global_xform, e1->cached_global_xform, tick_blend); e->cached_global_xform = xform_lerp(e0->cached_global_xform, e1->cached_global_xform, tick_blend);
@ -586,6 +592,16 @@ INTERNAL void user_update(void)
} }
} }
/* Test step */
{
struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_STEP];
if (state.num_presses) {
queue_game_cmd(&cmd_list, (struct game_cmd) {
.kind = GAME_CMD_KIND_STEP
});
}
}
/* Test spawn */ /* Test spawn */
{ {
struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_SPAWN]; struct bind_state state = G.bind_states[USER_BIND_KIND_DEBUG_SPAWN];
@ -770,16 +786,16 @@ INTERNAL void user_update(void)
* Draw entities * Draw entities
* ========================== */ * ========================== */
for (u64 entity_index = 0; entity_index < store->count; ++entity_index) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
__profscope(user_entity_iter); __profscope(user_entity_iter);
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
if (ent->is_root) continue;
struct sprite_tag sprite = ent->sprite; struct sprite_tag sprite = ent->sprite;
/* Skip undrawable entities */ /* Skip undrawable entities */
if (sprite_tag_is_nil(sprite) && !entity_has_prop(ent, ENTITY_PROP_CAMERA)) { if (ent->is_root
|| (sprite_tag_is_nil(sprite) && !entity_has_prop(ent, ENTITY_PROP_CAMERA))) {
continue; continue;
} }
@ -1030,6 +1046,9 @@ INTERNAL void user_update(void)
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world time: %F"), FMT_FLOAT((f64)G.world.time))); draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("world time: %F"), FMT_FLOAT((f64)G.world.time)));
pos.y += spacing; pos.y += spacing;
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("entities: %F/%F"), FMT_UINT(G.world.entity_store->allocated), FMT_UINT(G.world.entity_store->reserved)));
pos.y += spacing;
draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_size: (%F, %F)"), FMT_FLOAT((f64)G.screen_size.x), FMT_FLOAT((f64)G.screen_size.y))); draw_text(G.viewport_canvas, font, pos, string_format(temp.arena, STR("screen_size: (%F, %F)"), FMT_FLOAT((f64)G.screen_size.x), FMT_FLOAT((f64)G.screen_size.y)));
pos.y += spacing; pos.y += spacing;

View File

@ -27,6 +27,7 @@ enum user_bind_kind {
USER_BIND_KIND_DEBUG_DRAW, USER_BIND_KIND_DEBUG_DRAW,
USER_BIND_KIND_DEBUG_CAMERA, USER_BIND_KIND_DEBUG_CAMERA,
USER_BIND_KIND_DEBUG_PAUSE, USER_BIND_KIND_DEBUG_PAUSE,
USER_BIND_KIND_DEBUG_STEP,
USER_BIND_KIND_FULLSCREEN, USER_BIND_KIND_FULLSCREEN,
USER_BIND_KIND_ZOOM_IN, USER_BIND_KIND_ZOOM_IN,
USER_BIND_KIND_ZOOM_OUT, USER_BIND_KIND_ZOOM_OUT,