3
\$\begingroup\$

CONTEXT:
I am currently developing the front-end/ client of a simple browser game using HTML5 Canvas/JavaScript. In the future, I would like to add a multi-player dimension to it, most likely using node.js/mongoDB. At this point, however, I have no experience whatsoever with the required back-end technologies.

DESIRED OUTCOME:
The game involves some simple interactions (e.g. collect a key; once you've collected it, you can open the door, etc.). In the multi-player mode, the outcomes of said interactions carried out by any player are supposed to affect the game state for all players (e.g. if any player has opened the door, it stays open for all players). This will likely require setting up a database.

QUESTION:
At what point do I need to start writing the code in a way which involves referencing a database?
Does it make any sense at all to first develop the "game logic" in JavaScript only (as if it was only meant to be played as a single player) and only afterwards proceed to rewriting the code referencing a database?

Apologies if my question sounds idiotic. Newbie here!

\$\endgroup\$
0

5 Answers 5

1
\$\begingroup\$

Turning a game developed for singleplayer into a multiplayer game when it was not designed for that from the start can be extremely difficult, both regarding software architecture and regarding game design.

But there are some things you can do which make it much easier to add multiplayer later while not hampering your development progress too much while you are still single-player.

Software Architecture

The most important is having a clear separation of concerns from the start. Strongly encapsulate the input, game mechanics, game state, graphic engine and UI of your game. Have them communicate with each other using clearly defined, loosely coupled functions which assume nothing about the inner workings of the other modules.

This allows you to later replace these local modules with remote modules which then communicate with the remaining local modules using the same functions you already have.

  • When a second local player joins the game, you simply create a new instance of the input module which listens to different keys and assign it to a new instance of the player entity.
  • When a new remote player joins the game, then a network input module takes the role of the local input module.
  • When you move game mechanics to a server, then the server networking module takes the role of a local gameplay module.
  • To send local events to the server, you could either add a second consumer to your input module or use the same functions which are used by the UI module to monitor changes in the game state.

You might also go a step further and develop your singleplayer game as if it were a multiplayer game. Assume every game entity is remote-controlled, but "fake" that remote control with a local controller. That way most of your game architecture won't know and won't care if a game entity is an enemy controlled by a local AI controller or another player controlled by a network controller.

And besides, proper encapsulation and separation of concerns is always a good guideline for a clear and easily maintainable and extensible software architecture.

Game Mechanics

But besides the architectural concerns, there are also game design concerns. Multiplayer games are often far harder to design than single player games for a couple reasons:

  • Game balance is more important. When you accidentally have a feature in your game which is so overpowered that it breaks the game, then single-players will often be able to show at least some restraint from abusing it until it stops the game from being fun. You might also see players trying to finish the game without using that overpowered feature as a self-imposed challenge. In multiplayer, however, players will have far less quarrels. Competitive multiplayers will usually do everything it takes to win, even if it makes the game boring to play for everyone including themselves.
  • Counter play becomes an important consideration. When you give the player a new ability in a single-player game, you just have to wonder "How does this make the game more fun for the user?". But in a multiplayer game you also have to wonder "How does this make the game more fun for the one it is used against?". For example, freezing an enemy in a block of ice for a couple minutes might be fun in single-player, but in multi-player it is extremely annoying for the one being frozen and unable to play. Extra Credits made a good video about this topic.
  • Time skipping becomes impossible. In a single player game you can skip boring sequences by either speeding up time (common in strategy and puzzle games) or by simply omitting them (fast-travel systems are a common example). However, when multiple players share the same game world, they also share the same game time. You can usually not speed up the game for some players who are currently in a boring phase but not for those who are currently in a more complex game phase.

Again, the best way to address these problems early is to design your single-player game as if it were a multiplayer game. To address balance and counter-play, consider to follow a general rule that whatever the player can do can also be done by the AI opponents. And if you notice that parts of your game are boring and tedious, don't take the easy way out by simply using time-skipping. Try to find a proper solution to make those phases as interesting as the rest of the game or to speed them up in a way which is plausible within the fiction of the game and does not break game balance in multiplayer.

Narration

There are also narrative differences between single player and multiplayer. In single player, the player-character (PC) can be someone special. There is no issue with making the PC the chosen Mary Sue character the whole world turns around. But the more players you add, the more limited you become regarding the role of the PC in your game's narration. In a multiplayer game, the PCs have to share the spotlight with each other. Not everyone can be "the chosen one".

In a massive multiplayer game where you have thousands of PCs, it becomes almost impossible to add any convincing narrative which makes the PC and their experience seem unique. Some MMO games try by having those quests of the game designed to be played alone make the PC somehow special, hoping that the player ignores the fact that all the people around them currently experience the exact same story they do. But the illusion falls apart as soon as you enter the cooperative content.

But in exchange for that, you get a completely different narrative angle. You can give the player the feeling of being part of something greater and making a useful contribution to a coordinated group effort. This is challenging (but not impossible) to pull off in a single-player game due to the lack of empathy for the NPC characters, but it can be a very satisfying experience in a massive multiplayer game where the player knows that all those people they fight alongside are in fact real humans.

\$\endgroup\$
0
\$\begingroup\$

Knowing the technologies you are going to use for the network layer is usually a big advantage so you don't write your whole game in a certain way, and find out 40% needs to be rewritten to adjust to the network technology you will be working with.

For a multiplayer you are surely going to need to work with Sockets, for real time communication.

\$\endgroup\$
0
\$\begingroup\$

I think Philipp has covered the main architectural concerns.

Just one more point I'd like to add is to plan for your game actions to resolve asynchronously.

Imagine we write the local version of the "spend key to unlock door" interaction something like this:

function UseKey(door) {
   if(door.isLocked == false)
       return false;

   door.Unlock();
   inventory.remove(key);
   return true;
}

If we port that to a multiplayer form naively, we could have two players each try to unlock the door at the same time, before either one has gotten the message that the door is already unlocked. So they both consume their keys for just one door unlocking — or you need more complicated reconciliation logic to detect this conflict and roll back the consequences to give back one of the keys after the fact.

Instead you'll want your game actions to take some form of "request & confirm" form, where using the key just sends a signal to the [local or server] door authority that you intend to unlock it, but doesn't immediately alter your game state. Then you have some kind of event or callback that can happen after the initial use interaction has returned, which actually confirms whether the interaction succeeded and applies the consequences to your game state.

Setting up even your local/single-player version of the game to work this way will make it much easier when you later want to replace the local gameplay logic with a server authority, without massive structural changes to every action that could result in a conflict with other players, or cheating with a hacked client.

\$\endgroup\$
0
\$\begingroup\$

In this case (as I did) you need to move all you game mechanic to backend. Not a big deal with NodeJS. And develop your wrapper for WebSocket to provide auth and way you will communicate between clients and server (as your client is browser - "you can't trust anyone).

Client in this case only do what server say.

UPD: Think careful about communication protocol. Constant parsing JSON is not good idea. For example you can separate variables in message with non-print character.

\$\endgroup\$
-2
\$\begingroup\$

I would say there's no real need to think about it right now, just implement the game client-side but make sure to keep the core 'rules' decoupled from the rest of the code.

Reason 1 would be from a purely architectural standpoint, you don't want your client-side code to be too coupled to your rendering code and you won't want the server code too coupled to the database, so it should be written to be flexible anyway.

Reason 2 - if you end up going with Node for the backend (which I recommend), the game logic can be very similar -- you could even share the 'rules' module across the client and the server -- because the environments (Node and the browser) are both event-driven and Node allows you to have variables that persist between requests (as opposed to, for example, PHP where everything would have to be loaded from the database on each request).

See the code behind meteorchess.com for an example of this being done: lightsquare (client), lightsquared (server) and chess (chess rules module used unaltered by both). In this app, the database (Mongo) is only used for archiving the games once finished. While in-play, the games are just in memory on the server.

\$\endgroup\$

You must log in to answer this question.