Multiple local inputs. You can now play with your 3 friends locally, fighting over the same keyboard.
This commit is contained in:
@@ -2,20 +2,27 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#define INPUT_QUEUE_CAPACITY 8
|
|
||||||
|
|
||||||
void game_instance_init(Game_Instance *instance) {
|
void game_instance_init(Game_Instance *instance) {
|
||||||
instance->local_player_id = 0xFFFF;
|
instance->amount_of_local_players = 0;
|
||||||
// TODO: SS - Init input-queue.
|
|
||||||
instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context.
|
instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context.
|
||||||
instance->game_session = NULL;
|
instance->game_session = NULL;
|
||||||
memset(&instance->game_networking, 0, sizeof(Game_Networking));
|
memset(&instance->game_networking, 0, sizeof(Game_Networking));
|
||||||
|
|
||||||
squeue_init(&instance->local_input_queue, 4, sizeof(Simulation_Game_Input));
|
for(uint8_t i = 0; i < MAX_LOCAL_PLAYERS; i++) {
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[i];
|
||||||
|
squeue_init(
|
||||||
|
&locally_controlled_player->input_queue,
|
||||||
|
MAX_INPUTS_IN_QUEUE_PER_LOCAL_PLAYER,
|
||||||
|
sizeof(Simulation_Game_Input)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_instance_dispose(Game_Instance *instance) {
|
void game_instance_dispose(Game_Instance *instance) {
|
||||||
squeue_free(&instance->local_input_queue);
|
for(uint8_t i = 0; i < MAX_LOCAL_PLAYERS; i++) {
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[i];
|
||||||
|
squeue_free(&locally_controlled_player->input_queue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings) {
|
bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings) {
|
||||||
@@ -33,7 +40,7 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session)); // LEAK
|
instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session));
|
||||||
|
|
||||||
if(settings.online) {
|
if(settings.online) {
|
||||||
if(instance->game_networking.api != NULL) {
|
if(instance->game_networking.api != NULL) {
|
||||||
@@ -52,14 +59,25 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance->local_player_id = host_player_id;
|
game_instance_clear_local_players(instance);
|
||||||
|
assert(game_instance_setup_local_player(instance, host_player_id));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if(settings.max_players > MAX_LOCAL_PLAYERS) {
|
||||||
|
printf("Failed. Max amount of players (when playing locally) is %u. Max amount of players in the settings is %u.\n", MAX_LOCAL_PLAYERS, settings.max_players);
|
||||||
|
free(instance->game_session);
|
||||||
|
instance->game_session = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
game_session_init(instance->game_session, settings);
|
game_session_init(instance->game_session, settings);
|
||||||
|
|
||||||
|
// When playing locally/offline, create the player(s) here.
|
||||||
|
game_instance_clear_local_players(instance);
|
||||||
for(uint32_t i = 0; i < settings.max_players; i++) {
|
for(uint32_t i = 0; i < settings.max_players; i++) {
|
||||||
// When playing locally/offline, create the player(s) here.
|
uint16_t player_id = 0xFFFF;
|
||||||
assert(game_session_create_player(instance->game_session, &instance->local_player_id)); // TODO: SS - Fix me! Always assigning the newest player to be the active 'local_player_id' which isn't correct.
|
assert(game_session_create_player(instance->game_session, &player_id));
|
||||||
|
assert(game_instance_setup_local_player(instance, player_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Created %u local players.\n", settings.max_players);
|
printf("Created %u local players.\n", settings.max_players);
|
||||||
@@ -116,13 +134,44 @@ void game_instance_stop_session(Game_Instance *instance) {
|
|||||||
instance->game_session = NULL;
|
instance->game_session = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool game_instance_push_local_input(Game_Instance *instance, Simulation_Game_Input input) {
|
void game_instance_clear_local_players(Game_Instance *instance) {
|
||||||
return squeue_push(&instance->local_input_queue, (void *)&input);
|
instance->amount_of_local_players = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool game_instance_pop_local_input(Game_Instance *instance, Simulation_Game_Input *out_input) {
|
bool game_instance_setup_local_player(Game_Instance *instance, uint16_t player_id) {
|
||||||
|
if(instance->amount_of_local_players >= MAX_LOCAL_PLAYERS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[instance->amount_of_local_players];
|
||||||
|
assert(locally_controlled_player != NULL);
|
||||||
|
instance->amount_of_local_players += 1;
|
||||||
|
|
||||||
|
locally_controlled_player->player_id = player_id;
|
||||||
|
squeue_clear(&locally_controlled_player->input_queue);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game_instance_push_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input input) {
|
||||||
|
assert(local_player_index < MAX_LOCAL_PLAYERS);
|
||||||
|
assert(local_player_index < instance->amount_of_local_players);
|
||||||
|
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[local_player_index];
|
||||||
|
assert(locally_controlled_player != NULL);
|
||||||
|
|
||||||
|
return squeue_push(&locally_controlled_player->input_queue, (void *)&input);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game_instance_pop_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input *out_input) {
|
||||||
|
assert(local_player_index < MAX_LOCAL_PLAYERS);
|
||||||
|
assert(local_player_index < instance->amount_of_local_players);
|
||||||
|
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[local_player_index];
|
||||||
|
assert(locally_controlled_player != NULL);
|
||||||
|
|
||||||
Simulation_Game_Input input = {0};
|
Simulation_Game_Input input = {0};
|
||||||
if(!squeue_pop(&instance->local_input_queue, (void *)&input)) {
|
if(!squeue_pop(&locally_controlled_player->input_queue, (void *)&input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,18 @@
|
|||||||
|
|
||||||
#include "shared/squeue.h"
|
#include "shared/squeue.h"
|
||||||
|
|
||||||
|
#define MAX_LOCAL_PLAYERS 4
|
||||||
|
#define MAX_INPUTS_IN_QUEUE_PER_LOCAL_PLAYER 4
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t local_player_id; // TODO: SS - We need to modify this to support local multiplayer.
|
uint16_t player_id;
|
||||||
SQueue local_input_queue;
|
SQueue input_queue;
|
||||||
|
Simulation_Game_Input prev_input; // TODO: SS - Remove this and just peek the queue?
|
||||||
|
} Locally_Controlled_Player;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Locally_Controlled_Player locally_controlled_players[MAX_LOCAL_PLAYERS];
|
||||||
|
uint8_t amount_of_local_players;
|
||||||
|
|
||||||
double simulation_accumulator;
|
double simulation_accumulator;
|
||||||
|
|
||||||
@@ -23,10 +32,12 @@ void game_instance_dispose(Game_Instance *instance);
|
|||||||
|
|
||||||
bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings);
|
bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings);
|
||||||
bool game_instance_join_session(Game_Instance *instance, const char *session_id);
|
bool game_instance_join_session(Game_Instance *instance, const char *session_id);
|
||||||
|
|
||||||
void game_instance_stop_session(Game_Instance *instance);
|
void game_instance_stop_session(Game_Instance *instance);
|
||||||
|
|
||||||
bool game_instance_push_local_input(Game_Instance *instance, Simulation_Game_Input input);
|
void game_instance_clear_local_players(Game_Instance *instance);
|
||||||
bool game_instance_pop_local_input(Game_Instance *instance, Simulation_Game_Input *out_input);
|
bool game_instance_setup_local_player(Game_Instance *instance, uint16_t player_id);
|
||||||
|
|
||||||
|
bool game_instance_push_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input input);
|
||||||
|
bool game_instance_pop_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input *out_input);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -26,7 +26,7 @@ static void state_enter(Presentation_State *state) {
|
|||||||
.offset = (Vector2) { 0, 0 },
|
.offset = (Vector2) { 0, 0 },
|
||||||
.target = (Vector2) { 0, 0 },
|
.target = (Vector2) { 0, 0 },
|
||||||
.rotation = 0.0f,
|
.rotation = 0.0f,
|
||||||
.zoom = 3.0f
|
.zoom = 2.0f
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: SS - Maybe put the textures in an array and index them using an enum?
|
// TODO: SS - Maybe put the textures in an array and index them using an enum?
|
||||||
@@ -43,6 +43,47 @@ static void state_enter(Presentation_State *state) {
|
|||||||
assert(IsTextureValid(ctx->texture_snake_body));
|
assert(IsTextureValid(ctx->texture_snake_body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Simulation_Game_Input gather_input_for_local_player(uint8_t local_player_index) {
|
||||||
|
switch(local_player_index) {
|
||||||
|
case 0: {
|
||||||
|
return (Simulation_Game_Input) {
|
||||||
|
.up = IsKeyDown(KEY_W),
|
||||||
|
.down = IsKeyDown(KEY_S),
|
||||||
|
.right = IsKeyDown(KEY_D),
|
||||||
|
.left = IsKeyDown(KEY_A)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
return (Simulation_Game_Input) {
|
||||||
|
.up = IsKeyDown(KEY_UP),
|
||||||
|
.down = IsKeyDown(KEY_DOWN),
|
||||||
|
.right = IsKeyDown(KEY_RIGHT),
|
||||||
|
.left = IsKeyDown(KEY_LEFT)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
return (Simulation_Game_Input) {
|
||||||
|
.up = IsKeyDown(KEY_I),
|
||||||
|
.down = IsKeyDown(KEY_K),
|
||||||
|
.right = IsKeyDown(KEY_L),
|
||||||
|
.left = IsKeyDown(KEY_J)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
return (Simulation_Game_Input) {
|
||||||
|
.up = IsKeyDown(KEY_T),
|
||||||
|
.down = IsKeyDown(KEY_G),
|
||||||
|
.right = IsKeyDown(KEY_H),
|
||||||
|
.left = IsKeyDown(KEY_F)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
assert(false);
|
||||||
|
return (Simulation_Game_Input) {0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void state_tick(Presentation_State *state) {
|
static void state_tick(Presentation_State *state) {
|
||||||
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
|
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
@@ -53,16 +94,31 @@ static void state_tick(Presentation_State *state) {
|
|||||||
assert(session != NULL);
|
assert(session != NULL);
|
||||||
|
|
||||||
{ // Push local input to queue.
|
{ // Push local input to queue.
|
||||||
Simulation_Game_Input input = (Simulation_Game_Input) { // NOTE: SS - This needs to be slightly modified to support N local players.
|
if(instance->amount_of_local_players == 1) { // If playing alone (either singleplayer or online-multiplayer), more inputs are allowed.
|
||||||
.up = IsKeyDown(KEY_UP) || IsKeyDown(KEY_W),
|
Simulation_Game_Input input = (Simulation_Game_Input) {
|
||||||
.down = IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S),
|
.up = IsKeyDown(KEY_UP) || IsKeyDown(KEY_W),
|
||||||
.right = IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D),
|
.down = IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S),
|
||||||
.left = IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)
|
.right = IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D),
|
||||||
};
|
.left = IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)
|
||||||
|
};
|
||||||
|
|
||||||
if(!simulation_input_equal(input, ctx->prev_local_input)) {
|
Locally_Controlled_Player *player = &instance->locally_controlled_players[0];
|
||||||
game_instance_push_local_input(instance, input);
|
|
||||||
ctx->prev_local_input = input;
|
if(!simulation_input_equal(input, player->prev_input)) {
|
||||||
|
game_instance_push_local_input(instance, 0, input);
|
||||||
|
player->prev_input = input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(instance->amount_of_local_players > 1) {
|
||||||
|
for(uint8_t i = 0; i < instance->amount_of_local_players; i++) {
|
||||||
|
Simulation_Game_Input input = gather_input_for_local_player(i);
|
||||||
|
Locally_Controlled_Player *player = &instance->locally_controlled_players[i];
|
||||||
|
|
||||||
|
if(!simulation_input_equal(input, player->prev_input)) {
|
||||||
|
game_instance_push_local_input(instance, i, input);
|
||||||
|
player->prev_input = input;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +128,14 @@ static void state_tick(Presentation_State *state) {
|
|||||||
const double sim_dt = 1.0f / session->settings.tickrate;
|
const double sim_dt = 1.0f / session->settings.tickrate;
|
||||||
|
|
||||||
while (ctx->simulation_accumulator >= sim_dt) {
|
while (ctx->simulation_accumulator >= sim_dt) {
|
||||||
// Pop input from instance's local_input_queue and set the local player's input to it, if we have one.
|
for(uint16_t i = 0; i < instance->amount_of_local_players; i++) {
|
||||||
Simulation_Game_Input input = {0};
|
// Pop input from local-player's input_queue and set the player's input to it, if we have one.
|
||||||
if(game_instance_pop_local_input(instance, &input)) {
|
Simulation_Game_Input input = {0};
|
||||||
// TODO: SS - We should probably check if this input is invalid for the snake. (pressing "go right" when going left, for example.)
|
if(game_instance_pop_local_input(instance, i, &input)) {
|
||||||
// If it is invalid, the next input in the queue should be tried.
|
// TODO: SS - We should probably check if this input is invalid for the snake. (pressing "go right" when going left, for example.)
|
||||||
game_session_set_player_input(session, instance->local_player_id, input);
|
// If it is invalid, the next input in the queue should be tried.
|
||||||
|
game_session_set_player_input(session, i, input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
game_session_tick(session);
|
game_session_tick(session);
|
||||||
@@ -325,6 +383,9 @@ static void state_render(Presentation_State *state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: SS - Let the main-camera follow the entity you're controlling?
|
||||||
|
|
||||||
|
|
||||||
// TODO: SS - Switch on 'sim_world->match_state'.
|
// TODO: SS - Switch on 'sim_world->match_state'.
|
||||||
|
|
||||||
if(ctx->debug_draw_session_details) {
|
if(ctx->debug_draw_session_details) {
|
||||||
@@ -362,7 +423,7 @@ static void state_render(Presentation_State *state) {
|
|||||||
networking->is_host ? "true" : "false",
|
networking->is_host ? "true" : "false",
|
||||||
networking->session_id,
|
networking->session_id,
|
||||||
networking->local_client_id,
|
networking->local_client_id,
|
||||||
instance->local_player_id,
|
instance->locally_controlled_players[0].player_id,
|
||||||
game_session_get_amount_of_active_players(session), sim_world->max_players
|
game_session_get_amount_of_active_players(session), sim_world->max_players
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
Game_Instance *game_instance;
|
Game_Instance *game_instance;
|
||||||
double simulation_accumulator;
|
double simulation_accumulator;
|
||||||
Simulation_Game_Input prev_local_input;
|
|
||||||
|
|
||||||
Camera2D main_camera;
|
Camera2D main_camera;
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ void game_session_init_default_settings(bool online, Game_Session_Settings *out_
|
|||||||
out_settings->seed = 1337; // TODO: SS - Randomize.
|
out_settings->seed = 1337; // TODO: SS - Randomize.
|
||||||
out_settings->online = online;
|
out_settings->online = online;
|
||||||
out_settings->tickrate = 10.0;
|
out_settings->tickrate = 10.0;
|
||||||
out_settings->level_width = 64;
|
out_settings->level_width = 128;
|
||||||
out_settings->level_height = 32;
|
out_settings->level_height = 64;
|
||||||
out_settings->max_players = online ? 8 : 2;
|
out_settings->max_players = online ? 8 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,4 +54,9 @@ bool squeue_peek(const SQueue *q, void *out) {
|
|||||||
void *src = (uint8_t*)q->buffer + q->head * q->element_size;
|
void *src = (uint8_t*)q->buffer + q->head * q->element_size;
|
||||||
memcpy(out, src, q->element_size);
|
memcpy(out, src, q->element_size);
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void squeue_clear(SQueue *q) {
|
||||||
|
void *t = NULL;
|
||||||
|
while(squeue_pop(q, t)) {}
|
||||||
}
|
}
|
||||||
@@ -24,4 +24,6 @@ bool squeue_push(SQueue *q, const void *elem);
|
|||||||
bool squeue_pop(SQueue *q, void *out);
|
bool squeue_pop(SQueue *q, void *out);
|
||||||
bool squeue_peek(const SQueue *q, void *out);
|
bool squeue_peek(const SQueue *q, void *out);
|
||||||
|
|
||||||
|
void squeue_clear(SQueue *q);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -46,9 +46,9 @@ int main() {
|
|||||||
presentation_state_machine.current->tick(presentation_state_machine.current);
|
presentation_state_machine.current->tick(presentation_state_machine.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(IsKeyPressed(KEY_F)) {
|
// if(IsKeyPressed(KEY_F)) { // TODO: SS - Add this back, but not 'F'.
|
||||||
ToggleFullscreen();
|
// ToggleFullscreen();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Render.
|
// Render.
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
|
|||||||
Reference in New Issue
Block a user