diff --git a/src/entity.h b/src/entity.h index 572028d2..1f6d7a3f 100644 --- a/src/entity.h +++ b/src/entity.h @@ -12,6 +12,11 @@ enum entity_prop { ENTITY_PROP_CAMERA, ENTITY_PROP_CAMERA_ACTIVE, + ENTITY_PROP_EQUIPPER, + ENTITY_PROP_EQUIPABLE, + + ENTITY_PROP_FIRING_EQUIPPED, + /* Test props */ ENTITY_PROP_TEST, @@ -58,17 +63,26 @@ struct entity { struct entity_handle last; /* ====================================================================== */ + /* Position */ + /* Access with xform getters/setters */ struct xform local_xform; /* Transform in relation to parent entity (or the world if entity has no parent) */ struct xform cached_global_xform; /* Calculated from entity tree */ b32 cached_global_xform_dirty; + /* ====================================================================== */ + /* Control */ + + struct { + struct v2 move; + struct v2 focus; + } control; + /* ====================================================================== */ /* Physics */ struct v2 acceleration; struct v2 velocity; - struct v2 focus; /* Focus is a vector relative to the entity */ /* ENTITY_PROP_PLAYER_CONTROLLED */ f32 player_max_speed; @@ -90,6 +104,11 @@ struct entity { f64 animation_time_in_frame; u32 animation_frame; + /* ====================================================================== */ + /* Equip */ + + struct entity_handle equipped; + /* ====================================================================== */ /* Testing */ diff --git a/src/game.c b/src/game.c index f3d03a67..348d68e6 100644 --- a/src/game.c +++ b/src/game.c @@ -121,9 +121,10 @@ INTERNAL void spawn_test_entities(void) entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED); e->player_max_speed = 4.f; e->player_acceleration = 20.0f; - e->focus = V2(0, -1); + e->control.focus = V2(0, -1); entity_enable_prop(e, ENTITY_PROP_ANIMATING); + entity_enable_prop(e, ENTITY_PROP_EQUIPPER); player_ent = e; @@ -132,41 +133,21 @@ INTERNAL void spawn_test_entities(void) /* Child 1 */ { - struct v2 pos = V2(0.15, -0.05); + struct v2 pos = V2(1, -1); struct v2 size = V2(1, 1); f32 r = 0; struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); - struct entity *e = entity_alloc_child(player_ent); + struct entity *e = entity_alloc_top(G.world.entity_store); entity_set_local_xform(e, xf); e->sprite = sprite_tag_from_path(STR("res/graphics/gun.ase")); - //e->sprite_tint = RGBA_32_F(0, 0, 0, 1); entity_enable_prop(e, ENTITY_PROP_ANIMATING); - entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED); - } - - /* Child 2 */ - { - struct v2 pos = V2(-0.15, -0.05); - struct v2 size = V2(-0.25, 0.5); - f32 r = 0; - - struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); - - struct entity *e = entity_alloc_child(player_ent); - entity_set_local_xform(e, xf); - - e->sprite = sprite_tag_from_path(STR("res/graphics/tim.ase")); - e->sprite_span_name = STR("idle.two_handed"); - e->sprite_tint = RGBA_32_F(0, 0, 0, 1); - - entity_enable_prop(e, ENTITY_PROP_ANIMATING); - - entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED); + entity_enable_prop(e, ENTITY_PROP_EQUIPABLE); + player_ent->equipped = e->handle; } /* Camera ent */ @@ -203,10 +184,8 @@ INTERNAL void game_update(void) struct temp_arena scratch = scratch_begin_no_conflict(); - struct entity_store *store = G.world.entity_store; - /* ========================== * - * Begin frame cache scopes + * Begin frame * ========================== */ struct sprite_scope *sprite_frame_scope = sprite_scope_begin(); @@ -225,23 +204,20 @@ INTERNAL void game_update(void) G.world.time += G.world.dt; /* ========================== * - * Process game cmds + * Pull cmds * ========================== */ + struct entity_store *store = G.world.entity_store; struct game_cmd_array game_cmds = pop_cmds(scratch.arena); + + /* ========================== * + * Pre-sim game cmds + * ========================== */ + for (u64 cmd_index = 0; cmd_index < game_cmds.count; ++cmd_index) { struct game_cmd cmd = game_cmds.cmds[cmd_index]; switch (cmd.kind) { - /* Movement */ - case GAME_CMD_KIND_PLAYER_MOVE: { - G.world.player_move_dir = cmd.move_dir; - } break; - - case GAME_CMD_KIND_PLAYER_AIM: { - G.world.camera_focus = cmd.aim; - } break; - /* Clear level */ case GAME_CMD_KIND_CLEAR_ALL: { logf_info("Clearing level"); @@ -263,11 +239,6 @@ INTERNAL void game_update(void) }; } - if (v2_len(G.world.player_move_dir) > 1.f) { - /* Clamp movement magnitude */ - G.world.player_move_dir = v2_norm(G.world.player_move_dir); - } - /* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */ @@ -284,7 +255,7 @@ INTERNAL void game_update(void) if (sprite_tag_is_nil(ent->sprite)) continue; /* ========================== * - * Update animation + * Update sprite animation * ========================== */ if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) { @@ -332,31 +303,15 @@ INTERNAL void game_update(void) /* ========================== * - * Update entities pre-physics + * Update entities pre-simulation * ========================== */ - /* Find active camera */ - struct entity *active_camera; - { - enum entity_prop props[] = { ENTITY_PROP_CAMERA, ENTITY_PROP_CAMERA_ACTIVE }; - active_camera = entity_find_first_match_all(store, (struct entity_prop_array) { .count = ARRAY_COUNT(props), .props = props }); - } - - /* Update camera focus */ - if (active_camera->valid) { - active_camera->focus = G.world.camera_focus; - } - - /* Calculate player aim pos from camera focus */ - struct v2 player_aim_pos = v2_add(entity_get_xform(active_camera).og, G.world.camera_focus); - (UNUSED)player_aim_pos; - for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) { struct entity *ent = &entities_array.entities[entity_index]; if (!ent->valid) continue; /* ========================== * - * Initialize + * Initialize test * ========================== */ /* ENTITY_PROP_TEST */ @@ -367,15 +322,50 @@ INTERNAL void game_update(void) } /* ========================== * - * Calculate player aim angle + * Update control from player cmds + * ========================== */ + + if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { + for (u64 i = 0; i < game_cmds.count; ++i) { + struct game_cmd cmd = game_cmds.cmds[i]; + b32 start = cmd.state == 1; + b32 stop = cmd.state == -1; + + /* TODO: Combine movement from multiple inputs? E.G. a sudden + * start and immediate stop cmd should still move the player a + * tad. */ + + switch (cmd.kind) { + case GAME_CMD_KIND_PLAYER_MOVE: { + struct v2 move = cmd.move_dir; + if (v2_len_squared(move) > 1) { + /* Cap movement vector magnitude at 1 */ + move = v2_norm(move); + } + ent->control.move = move; + ent->control.focus = v2_sub(cmd.aim_pos, entity_get_xform(ent).og); + } break; + + case GAME_CMD_KIND_PLAYER_FIRE: { + if (start) { + entity_enable_prop(ent, ENTITY_PROP_FIRING_EQUIPPED); + } else if (stop) { + entity_disable_prop(ent, ENTITY_PROP_FIRING_EQUIPPED); + } + } break; + + default: break; + } + } + } + + /* ========================== * + * Update player angle * ========================== */ if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { struct xform xf = entity_get_xform(ent); - /* Update focus */ - ent->focus = v2_sub(player_aim_pos, xf.og); - /* Solve for final angle using law of sines */ f32 final_xf_angle; { @@ -390,7 +380,7 @@ INTERNAL void game_update(void) } struct v2 ent_pos = xf.og; - struct v2 focus_pos = v2_add(ent_pos, ent->focus); + struct v2 focus_pos = v2_add(ent_pos, ent->control.focus); struct v2 hold_pos; { @@ -451,10 +441,22 @@ INTERNAL void game_update(void) xf= xform_with_scale(xf, s); entity_set_local_xform(ent, xf); } + + /* ENTITY_PROP_FIRING_EQUIPPED */ + { + struct entity *eq = entity_from_handle(store, ent->equipped); + if (eq->valid) { + if (entity_has_prop(ent, ENTITY_PROP_FIRING_EQUIPPED)) { + eq->sprite_tint = RGBA_32_F(1, 0, 0, 1); + } else { + eq->sprite_tint = RGBA_32_F(1, 1, 1, 1); + } + } + } } /* ========================== * - * Update entity physics + * Simulate entities * ========================== */ for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) { @@ -462,14 +464,14 @@ INTERNAL void game_update(void) if (!ent->valid) continue; /* ========================== * - * Player movement + * Player control * ========================== */ if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { f32 max_speed = ent->player_max_speed; f32 acceleration_rate = ent->player_acceleration; acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS); /* Can't integrate acceleration rate higher than FPS */ - struct v2 target_velocity = v2_mul(G.world.player_move_dir, max_speed); + struct v2 target_velocity = v2_mul(ent->control.move, max_speed); struct v2 target_acceleration = v2_sub(target_velocity, ent->velocity); ent->acceleration = v2_mul(target_acceleration, acceleration_rate); } @@ -493,7 +495,7 @@ INTERNAL void game_update(void) } /* ========================== * - * Update entities post-physics + * Update entities post-simulation * ========================== */ for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) { @@ -521,7 +523,7 @@ INTERNAL void game_update(void) } f32 ratio_y = 0.33f; f32 ratio_x = ratio_y / aspect_ratio; - struct v2 camera_focus_dir = v2_mul_v2(follow->focus, V2(ratio_x, ratio_y)); + struct v2 camera_focus_dir = v2_mul_v2(follow->control.focus, V2(ratio_x, ratio_y)); struct v2 camera_focus_pos = v2_add(entity_get_xform(follow).og, camera_focus_dir); ent->camera_xform_target = xf; ent->camera_xform_target.og = camera_focus_pos; diff --git a/src/game.h b/src/game.h index 4d9c1f85..2a6a1067 100644 --- a/src/game.h +++ b/src/game.h @@ -10,7 +10,7 @@ enum game_cmd_kind { GAME_CMD_KIND_NONE, GAME_CMD_KIND_PLAYER_MOVE, - GAME_CMD_KIND_PLAYER_AIM, + GAME_CMD_KIND_PLAYER_FIRE, /* Testing */ GAME_CMD_KIND_CLEAR_ALL, @@ -22,11 +22,12 @@ enum game_cmd_kind { struct game_cmd { enum game_cmd_kind kind; + /* 1 = start, -1 = stop */ + i32 state; + /* GAME_CMD_KIND_PLAYER_MOVE */ struct v2 move_dir; - - /* GAME_CMD_KIND_PLAYER_AIM */ - struct v2 aim; + struct v2 aim_pos; }; struct game_cmd_array { diff --git a/src/user.c b/src/user.c index e5fba856..cdb604ea 100644 --- a/src/user.c +++ b/src/user.c @@ -80,6 +80,7 @@ GLOBAL READONLY enum user_bind_kind g_binds[SYS_BTN_COUNT] = { [SYS_BTN_S] = USER_BIND_KIND_MOVE_DOWN, [SYS_BTN_A] = USER_BIND_KIND_MOVE_LEFT, [SYS_BTN_D] = USER_BIND_KIND_MOVE_RIGHT, + [SYS_BTN_M1] = USER_BIND_KIND_FIRE, /* Testing */ @@ -440,11 +441,13 @@ INTERNAL void user_update(void) tick_blend = clamp_f32(tick_blend, 0.0f, 1.0f); } + /* TODO: Should we actually be basing interpolated tick on t1 rather + * 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 */ G.world.time = math_lerp64(t0->time, t1->time, (f64)tick_blend); - G.world.camera_focus = v2_lerp(t0->camera_focus, t1->camera_focus, tick_blend); /* Blend entities */ struct entity_array t0_entities = entity_store_as_array(t0->entity_store); @@ -466,7 +469,9 @@ INTERNAL void user_update(void) e->acceleration = v2_lerp(e0->acceleration, e1->acceleration, tick_blend); e->velocity = v2_lerp(e0->velocity, e1->velocity, tick_blend); e->player_acceleration = math_lerp(e0->player_acceleration, e1->player_acceleration, tick_blend); - e->focus = v2_lerp(e0->focus, e1->focus, tick_blend); + + e->control.move = v2_lerp(e0->control.move, e1->control.move, tick_blend); + e->control.focus = v2_lerp(e0->control.focus, e1->control.focus, tick_blend); e->sprite_local_xform = xform_lerp(e0->sprite_local_xform, e1->sprite_local_xform, tick_blend); e->animation_time_in_frame = math_lerp64(e0->animation_time_in_frame, e1->animation_time_in_frame, (f64)tick_blend); @@ -848,13 +853,13 @@ INTERNAL void user_update(void) debug_draw_xform(xf); } - /* Draw aim arrow */ + /* Draw focus arrow */ if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite); struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("hold"), ent->animation_frame); struct v2 start = xform_mul_v2(sprite_xform, slice.center); start = xform_mul_v2(G.world_view, start); - struct v2 end = v2_add(xf.og, ent->focus); + struct v2 end = v2_add(xf.og, ent->control.focus); end = xform_mul_v2(G.world_view, end); draw_solid_arrow_line(G.viewport_canvas, start, end, 3, 10, RGBA_32_F(1, 1, 1, 0.5)); } @@ -946,58 +951,66 @@ INTERNAL void user_update(void) } /* ========================== * - * Construct movement input + * Construct player control cmd * ========================== */ /* Movement */ - struct v2 input_move_dir = { 0 }; { - for (enum user_bind_kind bind = 0; bind < (i32)ARRAY_COUNT(G.bind_states); ++bind) { - struct bind_state state = G.bind_states[bind]; + struct v2 input_move_dir = { 0 }; + { + for (enum user_bind_kind bind = 0; bind < (i32)ARRAY_COUNT(G.bind_states); ++bind) { + struct bind_state state = G.bind_states[bind]; - if (!state.is_held && state.num_presses <= 0) { - continue; + if (!state.is_held && state.num_presses <= 0) { + continue; + } + + switch (bind) { + /* Movement */ + case USER_BIND_KIND_MOVE_UP: { + input_move_dir.y -= 1; + } break; + + case USER_BIND_KIND_MOVE_DOWN: { + input_move_dir.y += 1; + } break; + + case USER_BIND_KIND_MOVE_LEFT: { + input_move_dir.x -= 1; + } break; + + case USER_BIND_KIND_MOVE_RIGHT: { + input_move_dir.x += 1; + } break; + + default: break; + } } - switch (bind) { - /* Movement */ - case USER_BIND_KIND_MOVE_UP: { - input_move_dir.y -= 1; - } break; - - case USER_BIND_KIND_MOVE_DOWN: { - input_move_dir.y += 1; - } break; - - case USER_BIND_KIND_MOVE_LEFT: { - input_move_dir.x -= 1; - } break; - - case USER_BIND_KIND_MOVE_RIGHT: { - input_move_dir.x += 1; - } break; - - default: break; - } + input_move_dir = xform_basis_invert_mul_v2(G.world_view, input_move_dir); /* Make move dir relative to world view */ + input_move_dir = v2_norm(input_move_dir); } - input_move_dir = xform_basis_invert_mul_v2(G.world_view, input_move_dir); /* Make move dir relative to world view */ - input_move_dir = v2_norm(input_move_dir); - } + struct v2 input_aim_pos = G.world_cursor; - /* Queue move cmd */ - queue_game_cmd(&cmd_list, (struct game_cmd) { - .kind = GAME_CMD_KIND_PLAYER_MOVE, - .move_dir = input_move_dir, - }); + i32 cmd_fire = (G.bind_states[USER_BIND_KIND_FIRE].num_presses > 0 || G.bind_states[USER_BIND_KIND_FIRE].is_held) ? 1 : -1; - /* Queue aim cmd */ - if (!G.debug_camera) { - struct v2 input_aim = v2_sub(G.world_cursor, entity_get_xform(active_camera).og); - queue_game_cmd(&cmd_list, (struct game_cmd) { - .kind = GAME_CMD_KIND_PLAYER_AIM, - .aim = input_aim, - }); + if (!G.debug_camera) { + /* Queue player move cmd */ + queue_game_cmd(&cmd_list, (struct game_cmd) { + .kind = GAME_CMD_KIND_PLAYER_MOVE, + .move_dir = input_move_dir, + .aim_pos = input_aim_pos + }); + + /* Queue player fire cmd */ + if (cmd_fire) { + queue_game_cmd(&cmd_list, (struct game_cmd) { + .kind = GAME_CMD_KIND_PLAYER_FIRE, + .state = cmd_fire + }); + } + } } /* ---------------------------------------------------------------------- */ diff --git a/src/user.h b/src/user.h index b2f25ce6..bfe94d37 100644 --- a/src/user.h +++ b/src/user.h @@ -18,6 +18,7 @@ enum user_bind_kind { USER_BIND_KIND_MOVE_DOWN, USER_BIND_KIND_MOVE_LEFT, USER_BIND_KIND_MOVE_RIGHT, + USER_BIND_KIND_FIRE, /* Testing */ diff --git a/src/world.h b/src/world.h index bbe24f7b..915d6810 100644 --- a/src/world.h +++ b/src/world.h @@ -12,9 +12,6 @@ struct world { f64 dt; f64 time; - struct v2 player_move_dir; - struct v2 camera_focus; - struct entity_store *entity_store; };