4
\$\begingroup\$

Lots of classic games that I've read the source code to do things like

if (clientside)
    // ...

or the same but with serverside. The code is littered with this and it becomes an extreme nightmare to test. This is especially so when the games I've looked at all happen to use global variables for the above so testing goes out the window, and I am fortunate enough to be able to write tests for such things (hobby projects with no time constraints!)

I'm at a point where I need to care about this now (especially with having to deal with prediction and actions, scripts, etc, in a level). As such, I've gone searching for how people architected solutions to this problem such that there was the ability to have clientside prediction of some stuff, while preventing the code from becoming a gigantic series of if statements strewn about everywhere.

What is the best way to approach this? I am willing to trade a bit speed for clean code with good OOP. Is there a good example that I can learn from? Or am I stuck with littering if (client/serverside) everywhere?

Ideally I want to write the least code possible, write it in the cleanest way possible, and have something that I can easily reason about without having a ton of branching thinking "ok what if condition A, not B, C and D, not E" when trying to figure something out.

I've been trying to see if there's a way to make some kind of common base class which shares all the code between them, and then making a bunch of abstract methods that either the Client implementation or Server implementation would do their own stuff on top of it, but part of me is worried that because I've neither done anything like this before nor seen anyone ever do it this way, I'm scared of wasting a significant amount of time on something that may be architecturally bad. This is primarily why I'm posting this.

In short: What is the cleanest way to architect the code? Are there any examples or articles I can learn from?

Edit: The code should all be in one binary, so people can host a multiplayer server from an offline experience.

Note: This question is completely different from how to do prediction, unlagged, etc, and as such is not about that (and I already know exactly what I need to do). It's about the implementation of such things such that I can write a client that supports predicting and server code that reduces the amount of duplication as much as possible.

\$\endgroup\$
  • 2
    \$\begingroup\$ The last time I wrote a multiplayer game I had no shared code between client and server. In fact, the client and server were written in different programming languages (Browser-based JavaScript and Java respectively). \$\endgroup\$ – Philipp Sep 17 at 8:10
1
\$\begingroup\$

Edit: The code does not need to be in one binary to allow people to host a multiplayer server from an offline experience. A player is never a server A player might be a host this means the server is hosted on their machine. (or a machine they control) They still connect as a client just they have admin credentials!!!!

This is a fundamental OOP concept known as Relationsip: "Is a" versus "Has a". A player might have a server, but they are objectively not a server.

There are very few games which hot-swap between SP and MP and the ones that do, simply run a SP server while the player is playing SP, then open up to MP connections if requested.

A possible exception might be a game which relies heavily on a database, but even then the SP instance is still treated like a client. Upon switching to MP, the single player game ends, loads into a server, and the player re-joins as a client.

You would only use this exception if there wast the database reliance, and if the server code was only relaying the absolute minimum amount of information between players. Positions, events, etc. Things the client would have to know to send to the server in the first place.

There are a number of ways to divide responsibilities between server and client (such as a "lightweight" server, which lets both clients handle their own logic and does it's best to sort things out (which opens a big cheating / hacking risk)) Absolutely ZERO designs involve treating client and server as the same entity however.

Yes. You can brute force client and server code into the same binary. No, you should never actually do that. It would not be "clean" (or proper) server architecture. It would be demonstrably wrong architecture.

The entire premise of a server is inherently based on client & server being separate things. If these things are not separate you are not building a client / server, you are building peer-to-peer, and this requires a completely different approach, cautions, considerations & designs.

Just Use a .dll if your program is C++/C# or .jar if it's Java based. This is one of the exact purposes these things exist to be used for. Any code that contains anything that even remotely looks like if(client){//do stuff} else if (server){//do other stuff} is a massive red flag that something has gone horribly wrong in the design. (Even if you just stuff your server code into it's own class(es) it still has to be conceptually treated as an entirely different program for the sake of properly dividing responsibilities)

When using a .dll or .jar there is absolutely no practical difference in how you write the code. Code hints, function calls, etc all work the same as they would if the .dll content were a class directly in the project source. What a .dll does do is encapsulate code to keep it more modular, and organized.

There should never be an instance of if (client/server) present in code because it should never be ambiguous or subject to a variable / property. if (instanceof server) shouldn't even be present, since no correct code should ever need to ask this in the first place.

Clients and Servers communicate through a Port / Connection. A correctly designed client knows it's talking to the server because it's talking through a port or a connection.

A server on the other hand might ask something like: if(player.getCredentials()==server.AdminKey())

Examples:

Minecraft (mobile editions): While playing single player, another player on the same wifi can join your game. When this happens your screen will freeze with a "please wait" dialog. In the background, you are leaving single-player instance, and joining a multi-player server as a client. One player being the "host". (Leightweight Server. Hacking not a big issue)

99% of games with single player campaign / multiplayer modes. You will notice these are separate menu options. The single player campaign is a standalone program. The multiplayer mode is basically a separate "client" program. (Code redundancy here is between the Client and an instance of Singleplayer which isn't even a Client, since it's not connecting to the server. Redundancy only exists specifically to encapsulate the server in the first place)

FarCry 5 / Darksouls upon a player joining single player client ends, and you join a server as a client. (Basically the same as above but hidden to create the illusion of seamless transition) OR the player is just already connected to a server to begin with.

Ark / Empyrion The single player experience takes place on a server set to single-player mode, that can be switched to multi-player if desired.

//end edit

The question of how to architect any code comes largely down to three factors:

  1. Best Practices
  2. Design Patterns
  3. Opinion

The answer to: "What is the cleanest way to architect (server) code" really depends on what the server is doing.

However, there is one really important thing to keep in mind: Encapsulation.

It doesn't matter if you are writing a video game or bank software, "Server" and "Client" are best treated as two different pieces of software which communicate with each other.

Speaking from a C++ / C# background:

I would put the server code in a .dll and create a UI in the client app to interact with this library. (And if necessary a standalone UI for server management). All server code would be run entirely standalone.

(If you are using Java .jar is a close equivalent to .dll)

If you want reference material a good place to start would be looking at a simple client-server chat program tutorial.

From there you also want to look into multi-threaded design.

Finally do some research into systems analysis (specifically diagramming a system in a flow-chart). The secret to making ANY code "clean" is to design it as a flow chart first.

Ultimately, it will come down to standard design patterns and OOP principles. But it will be a good head start to: A. Treat "client" & "server" as separate programs entirely. and B. view "server" as any other type of software, as in code that executes instructions. (A lot of people starting out with server-side coding view it as this unique special type of program. It's not, it's just a program.)

Some examples:

Runescape is a great example of encapsulation.

The client is almost entirely UI. It displays visuals to the player, and takes input (text, mouse clicks). This input is sent to the server. The server then sends instructions back to the client as what to display.

Another example would be games like Empyrion, or Jedi Academy. These games run the server weather or not players are actually connected. While there are in-game ui features to control the server, the server itself is running as an independent program. (Often you can see a console window). When it comes time to exit, the client application simply sends a command to the server to shut down.

\$\endgroup\$
  • \$\begingroup\$ Mostly i agree, but in most cases, the server does exactly what a client would do, except rendering and sending / recieving different events to update. So treating it as two different pieces of software would be quite... difficult to say. \$\endgroup\$ – PSquall Sep 17 at 9:03
  • \$\begingroup\$ That's a fair counterpoint, and part of why imo this question falls into "opinion land"... but I would argue that if the server and client are doing the same functions that both have been designed incorrectly. In the indie era I'm sure there are loads of games where this is present, but it shouldn't be the case. If server and client have redundant functionality this is probably a sign both have a design flaw. I struggle to imagine a case where a developer would need redundant functionality like this. Also, using a .dll architecture the encapsulated server can still seem client based. \$\endgroup\$ – Batman Sep 17 at 9:22
  • 1
    \$\begingroup\$ I wouldnt call it "redundant", as some systems on a server can be for security reasons like anti-cheat. If a server receives the same input as the client, but the server calculates another position of a player, than that may be because of cheating. But of course this is a just a generalization and highly dependent of the game and what systems are really needed server / clientside. \$\endgroup\$ – PSquall Sep 17 at 10:37
  • \$\begingroup\$ I get where your coming from but I think you might be operating on bad information. AntiCheat systems for example are usually a third standalone program running alongside the Client and Server. Upon detecting abnormal client behavior, it reports to both Client and Server. Your example of mismatched positions would be bad anti-cheat software since lag could cause this. Yes, you can make Client, Server and AC all in one executable. Heck you could put it all in one class. should you? No. It makes your code error prone and harder to debug. And the question is about the cleanest (correct) way. \$\endgroup\$ – Batman Sep 17 at 22:19
  • 2
    \$\begingroup\$ @Batman the anti cheat program will be mostly used to prevent bots. It will not protect against hackers who can manipulate network traffic. There is the saying never trust the client. The code duplication between the client and the server is for that. \$\endgroup\$ – Vaillancourt Sep 18 at 0:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.