-
Notifications
You must be signed in to change notification settings - Fork 33
Architecture
An initial “handshake” is used when a client connects to the server, this is used to set up things that need to be ready for a user to start playing the game. After this initial handshake, the client's data is processed in the server's game loops.
- User opens up their web browser
- Web browser makes a WebSocket connection to server the server with Socket.io.
- Server creates object to store player data, keep track of the socket id to reference that user, and generates a spawn location
- Client sends an init event with their username
- Server responds to init event with welcome event, which sends the game width, game height.
- Client stores the game height and width from the server, and calculates its own screen height and width. The screen height and width is sent back to the server in a welcome_recieved event.
- Server stores the client’s screen height and screen width so that it knows which game objects are in the user’s view. Server stores the object for the client in the global client object array (which is iterated through in the game loop), and also inserts the user in the quadtree (more on this later).
The server has four loops that run in calls to setInterval. Two of these loops are for updating the client’s radar and scoreboard. These aren’t super important when it comes to the architecture of the game, and don’t have too much of an impact on performance. They won't be discussed. As we will see in the architecture discussion, these interval times are somewhat arbitrary and insignificant when many user's are connected.
This loop iterates through each connected client that was added in step 7 of the initial handshake. For each client, the server calculates the logic for that client. This is where the bulk of the code is, and it takes care of things like updating the user’s tank position, calculating how much ammo the user should have, updating the positions of all the user’s bullets, and handling collisions on the tank.
This loop iterates through each connected client that was added in step 7 of the initial handshake. For each client, it queries the quadtree using their position and their screen height and screen width. It then sends a game_objects_update to the client, which sends exactly enough information for the client to be able to draw the objects that should appear on their screen.
Obviously, the client has its own game loop too. This loop is used for drawing the objects that were received from the game_objects_update event.
Importantly, after each game_objects_update event the client sends a client_checkin event to the server, which consists of which of the w-a-s-d keys are pressed, if the spacebar is pressed, if the mouse is clicked, and the angle of the cursor from the center of the board.
Our game needed to efficiently process lots of objects in two dimensional space. Two data structures we used for this are a quadtree and a spatial hash. The quadtree was used to store walls, bullets, and tanks. The spatial hash was used for storing the tank tracks.
Uses a tree structure where each parent has four children. Game objects are stored in lists on tree nodes, and once the number of children on a node passes a threshold, the divides its area among 4 new children and disperses its objects to the appropriate child. This data structure helps to efficiently eliminate objects from consideration when doing two dimensional collision detection.
Read more about quadtrees here.
A spatial hash also efficiently eliminates objects from consideration when doing two dimensional collision detection. Spatial hashes divide the area of consideration into different buckets, and hash objects into a particular bucket (or buckets). The buckets can quickly be queried to find objects that could be colliding. We used a spatial hash for storing tank tracks because we read they were faster than quadtrees if all the objects stored were the same size.
Read more about spatial hashes here