Monday, October 12, 2015

Making a Real Time Multiplayer Online Game in NodeJS (Part 3)

In post 1 and post 2 of this series, I introduced and talked through the server side code for a project I'm working on called QuadPong: a real time game made in NodeJS for the purpose of practicing server side programming and synchronization.  You can view the source code on my GitHub.

Now we're going to go over the client side and talk about the code and libraries used to talk to the server and generate our client side rendering of the game.  I'll cover in this post:

  • index.html & main.css - the HTML page the game is on and the CSS used
  • socket_handler.js - the client side socket layer which handles talking to the server
  • keypress.js & canvas_helper.js - two open source libraries/sections of code I used and why I picked them
  • game.js - the main logic of the game


Main Page

First there is the index.html page which contains the structure for the game.  It's very basic: at the top I included a few important scripts such as JQuery, keypress.js and our CSS file.


The game div will contain the canvas once it is initialized.  It's where the game plays out.

The buttons div is something I added to not have to add much in the way of UI to the game.  Ideally these commands would be a part of the in game logic, but for this quick prototype they work as a hack.

The players table also shows a list of the in game players so you can see who is in the game.  It's not pretty, but it's functional: it shows the player number, whether or not they are human or a computer player, how much life they have remaining in the game, and which player is you.

In the CSS file there are only a few sections dealing mostly with centering and background base colors.  It's very basic and I didn't spend a lot of time on the design for this project.

Socket Handler

Socket handler -- as you can probably deduce -- handles the socket connections with the server.  At the top of the file is the initialization of socket.io.  I then pull in the player and game ids if they exist in localStorage as well as initialize a few other variables used in the game.  Some of these could probably be moved into the game.js file as an improvement.

Below the variables are a few events to set up the initial game state.  When the socket.io connection connects for the first time, it will emit the data saved in localStorage.  This allows the server to reply with any updated information.  The connect event also requests for the game list to show how many open games there are.  One potential improvement here would be to make the server return information about each game in the list to allow users to join a specific game or spectate games.  

Next is the update function.  When the client receives information from the server, it updates the game objects, sets the player data, updates the player list table and then calls update on the Canvas to render out the new data.  These variables and the updateCanvas function are declared in game.js which we'll look at in a bit.

There are also quite a few other basic game control events, which I'll list quickly out here:
  • on reconnect - sets the game state and sets the game data appropriately
  • on started_game - sets the game state to started and saves any adjusted game & player data
  • on game_settings - sets the client settings to mirror the server settings
  • on created_game - enter the waiting state for other players to join & save game id
  • on joined_game - enter the waiting state and update the game settings with all of the player data of players in that game
  • on added_computer - adds a computer player to the player list
  • on added_player - adds a human player to the player list
  • on game_list - updates the game list & updates the canvas - this is the reflection back from the server when the client emits request_game_list
  • on sync_players - synchronizes the client side player list to the server list
  • on no_games - an error saying that no available games can be joined when a player tries to join and can't
  • on game_over - the event setting the end of game state and winner of the game
In addition to this long list of events to handle the game logic, I also have some helper functions to create games, join games, start games, update the player list, restart the client game (leaving the current room), and extract player_data.  Below you can see some of these functions.

Here is the save_data function which stores the information received from the server and stored in global objects into localStorage, add_player which adds a human player to the player table, and add_computer, which adds a computer player to the player table.  Again, it would have been wise to maintain a consistent function naming convention and if I refactored this portion I would use camelcase instead of underscore.


Create game sets the player number to 1, emits an event to the server to create a new game and set the caller as the main player, and adds a human player to the players table.  Join game emits an event to join a random open game with the player's stored id.


Start game emits an event to the server notifying it to start the specified game id and restart game is a client side function that resets the state of the client side, and leaves the game on the server side, so that the player can join a different game.

And at the bottom of our socket handler, is clear_player_list, which clears out the player table and extract_player_data which pulls player data from an object passed in. 

Another improvement I could make here is to pull out some of these helper functions into a separate library to keep this section of code dedicated to the socket handling only.  The player table stuff could really go in it's own file for maintenance as the code base grows.


Added Libraries and Code

Nearly all of the code in this project was written by me, but on the client side I used a few libraries I want to quickly note.  First I used JQuery, using it mostly for selectors in the socket handler script, but I also used two other more interesting sections of code.

KeyPress.JS
KeyPress is a library by dmauro that you can check out here:  https://dmauro.github.io/Keypress/.  I went with this library because it gave me a lot of flexibility with key events and let me bind and chain them in various ways.
In the section of code to the right, you can see a portion of the key registration.  KeyPress lets you register combinations for on key up and on key down events.  Since the player can be any one of the four players in the game, I wanted to map each direction in a specific way.  For instance, if the player is player number 4, on the left side of the game, pressing would move them up, but would translate to no action for players in slots 1 and 3.  

On the server, paddles only have 2 movement options, positive motion "R" (to the right facing the game) and negative motion "L".  So for player 4 would translate to a negative motion, whereas for player 2, translates to a positive motion.  I decided to handle this logic on the client side, for simplicities sake on the server, so when a key press event is generated, it calls out to a separate function, playerActionGenerator with a character representing the key press event.   corresponds to a "none" event or setting the player action to none.   

The playerActionGenerator function below handles these various directions.  First it checks if the state is in an active state, or a menu state which handles these events differently (for selecting menu items), and if it is in a game, it checks the player number and generates the appropriate action based on the direction.

The function playerAction then takes the direction passed in and builds the package to emit to the server to update the action on the server's copy of the player.

KeyPress ended up being pretty easy to use and it's pretty flexible on what it's capable of so I highly recommend using it.  I haven't even used most of the chainability of buttons and combinations that are possible.

In addition to KeyPress and JQuery, I used a snippet of code from a Stack Overflow post.  It sets the canvas DPI correctly so that text and objects render crisply instead of blurry and then creates and returns the Hi DPI canvas.  It works very well and I haven't had to modify it at all.


Game.JS

Finally let's look at the last portion of the client side game code in game.js.  This file also contains the KeyPress handling as well but as we've already gone over that I'll skip over those sections.

At the top of the file are a bunch of variables and declarations to set up the game state.  Most of what we'll look at here can be summed up as:  game.js draws on the canvas based on which state the game is in.  If the game is playing or in a "GAME" state, it will use the game objects which get updated by pushes from the server to draw the game objects, otherwise it will draw various menus and text.

First we have the onload function for the window, where we create the Hi DPI canvas, append it to the game div and register our KeyPress handler.


The entry point for most actions in this script is updateCanvas, seen below.  Any time actions from the server need to update the UI, updateCanvas is called.  Depending on the state of the game, one of the many draw functions will be called.  


Many of these functions simply draw text in various places in the game, but we'll look specifically at drawPlayers which is called in drawGame on line 109.  Now, the way I built the server coordinate system was a traditional Cartesian Coordinate System, with up being the positive Y direction and right being the positive X axis, with (0,0) at the center of the game.  Canvas, however uses the top left corner as (0,0) with down being the positive Y direction, so I made the convert function to simply convert the server system to the client one, including a size multiplier to scale the graphics.

The drawPlayers function uses convert to iterate through all four players and update the rectangle based on the bounding box coordinates received from the server.



Drawing the ball works very similarly and the rest of the draw functions simply are used for menus and text, which should be easy to understand and read through.  That's pretty much the entirety of the client side, it's fairly simple as it's mostly just getting positional updates from the server, sending player action updates to the server, and rendering the game out for the player to see.  Plus all of the objects in the game are basic geometric shapes.

So feel free to pull down the source code and play with it and extend the game or use it to make your own game.  You can view it all on my GitHub here: https://github.com/WakeskaterX/QuadPong

Thanks for reading and that's all I have!  The three part series on my little NodeJS side project is concluded.

No comments:

Post a Comment