Entities system
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:
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
.
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:
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:
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:
And voila! We have a decent player system to work with now: time to move on to the most exciting part, multiplayer.