The last step to making a decent foundation for a multiplayer game is a system for handling players, or entities. There are ton of different ways of organising your entities in a game, such as all the different variations of component systems, etc… In this game, we won’t be using a component system for now, as there won’t be that many different types of entities: only players, bullets, and some props.

For each of these entity types, I just have a separate structure which has the data needed for them. This is the player structure:

struct player_t {
    // Character name of player
    const char *name;
    // When accessing client information which holds stuff like IP address, etc...
    uint16_t client_id;
    // When accessing local player information
    uint32_t local_id;

    vector3_t ws_position;
    vector3_t ws_view_direction;
    vector3_t ws_up_vector;
    float default_speed;

    player_render_t *render;

    // Maximum player actions
    uint32_t player_action_count;
    player_actions_t player_actions[MAX_PLAYER_ACTIONS];
    
    // ...
};

There is just a simple player array in the world structure, which is where all the players will be. Each player has a local_id which corresponds to the index of the player in this array. Everything in the engine will reference that player with this handle. The problem arises when players get removed. If a player gets removed in the middle of this array, there is an issue: do we bring the player at the end of the array to fill in this new gap? The problem with doing this, is that the index of the player will change! All the components of the engine which referenced this player with that ID, will now be pointing to some random space in memory which doesn’t currently hold a player, or it may hold a different player, if we add a new one!

Therefore, I made this custom container which makes sure, that when entities get removed, the indices of all the other entities stay the same (implementation is in source/common/containers.hpp, in struct stack_container_t<>).

Updating the entities is very simple, just loop through and apply physics and other things…

The more interesting part is how input is handled. We must remember that this is a networked game, meaning that on the server side, players will still be receiving input, but from remote computers (we will get into how this works in the multiplayer articles). I therefore needed a pretty flexible input system.

What I came up with, was that each player has a stack of struct player_actions_t in the array player_actions.

struct player_actions_t {
    union {
        struct {
            uint16_t move_forward: 1;
            uint16_t move_left: 1;
            uint16_t move_back: 1;
            uint16_t move_right: 1;
            uint16_t jump: 1;
            uint16_t crouch: 1;
            uint16_t trigger_left: 1;
            uint16_t trigger_right: 1;
        };

        uint16_t bytes;
    };
    
    float dmouse_x;
    float dmouse_y;
    float dt;
};

It just contains a bitfield of flags, mouse movement and the delta time of the frame which added these input actions.

Every frame, when updating the players (in the w_tick_players() function), the loop will also execute all the player actions which are pending in the array of player_actions_t. Once those have been executed, there will be no more actions to execute in this frame, so set player_action_count to 0.

Every frame, when polling for input from the players submodule, we create a player_actions_t structure which is filled in with data from the input structures, and these player actions will get pushed onto the array of actions of the local player (which is bound to mouse and keyboard - this is an index into the array of players).

If we are playing without connecting to the server, the only player which will actually have player actions is the one that we decided to bind to mouse and keyboard.

Here is a quick demo:

When you start the game, there are no entities, and the camera (and mouse and keyboard) are just bound to some spectator player which, cannot really do anything but move around:

photo

In the ImGUI window, there are fields which I can tweak (position, view direction , up vector, …). After tweaking, I press Add player which adds the player. If I tick Local, the player I add will be bound to mouse and keyboard:

photo

Here the input is bound to a player, and I can terraform, etc…

Now, I add another player, which is not local, at a certain position:

photo

And voila! We have a decent player system to work with now: time to move on to the most exciting part, multiplayer.