Doomstein is a player-vs-player first-person shooting game. Players can connect to each other using a wireless local area network and fight against each other. Each player has 100 hit points and, each bullet causes 10 damage; This is a project built using my own engine, written in C++ and Direct3D 11.
The map in the game is loaded as three different XML files:
MapMaterialTypes.xml
This file defines all the materials we used on the map. The MaterialSheet defines the name, layout, and image we used for materials. The MaterialTypes define the name, material sheet, and sprite coordinate of this material on the sheet.
MapRegionTypes.xml
This file defines the map regions, which are tiles that form the map. Each RegionType can be solid or not solid. Each region has one or two materials defined in MapMaterialTypes.xml. The non-solid regions, like InvalidRegion, need two materials because players can go into these regions and see the floor and the ceiling. The solid regions like CobblestoneWall only need one material because players can only see the four sides of them.
TestRoom.xml
Here is how the map is defined. First, I define legend to connect glyph with regions defined in MapRegionTypes.xml. Then I can "draw" the map using the glyph and put entities at the end of the file.
When rendering the map, I have to render it differently for solid and non-solid regions. To increase the efficiency of rendering, I combined all the regions into a "mesh" and draw that mesh. Here is how that is done:
For solid regions(regions are called "tiles" in the code), I checked each of their adjacent regions because if two solid regions are right next to each other, there is no need to draw the wall in between them. So I only add walls into the mesh when the wall is between a solid region and a non-solid region.
For the non-solid regions it's much easier, just add the floor and ceiling for every one of them.
After the mesh is finished, I can draw the whole map all at once.
The physics used in this game is a simple 2D physics that handles discs vs AABBs and discs vs discs. Each wall is processed as an AABB and each player is processed as a disc, so players cannot go into walls and can push each other.
Here are functions used implementing physics.
The PushDiscOutOfAABB2D function took in the center and radius of a disc and an AABB, find the nearest point from the disc center to the AABB, and try to push itself away from that point.
The PushDiscsOutOfEachOther2D took in two discs, calculate how much they overlap, and move both discs away from each other if they are overlapping.
The network system used in Doomstein runs all the updates on the server-side, which has the game world and its own player. A client starts the connection with the server using TCP to ensure success.
After the connection is established, a client does two things: sending player inputs towards the server and receiving data from the server required to update rendering. At the same time, the server receives inputs from clients, updating entities including players and bullets, and send transforms of all entities back to clients for updating rendering. All the data transmission mentioned here are done with unreliable UDP because these data requires fast transmission. Also, even some of these data get lost during transmission, it's not going to cause serious problems.
There is data we cannot afford to lose, like players' healths. All players can see each other's health and if any data get lost when we update health, one player might have different health values for different players, and that causes problems. Therefore, when I transmit health values, I use reliable UDP. The client will send an acknowledged packet back to the server once it receives health updates. And the server will keep sending health updates to a client before it receives the acknowledged packet from the client. That way, we can make sure all health updates are received by all clients.