3
\$\begingroup\$

I'm making a multiplayer 2d shooter platformer in unity. And after studying the different options to program an authoritarian server and with the great help that many of you have already provided me on this site. I have decided to create my server also with a unity in headless mode in order to reproduce or simulate the physics of the games on the server with the same engine (box2d) and that the server is the one that decides the movements of the clients.

As you have told me (Thanks @DMGregory), a good option for server development in unity is to use a single project to program both client and server and thus be able to reuse the scenes and prefabs without having to clone them in 2 different projects, but with This idea I find some main problems that I do not know how to solve.

  1. Debugging. How can I run the 2 editors of the same project in unity simultaneously to run server and client?

  2. Separate client and server. Is there a way to separate those elements necessary for the client from those necessary for the server in unity? If not, when compiling my project to use it as a server, it will have a lot of unnecessary material (scripts, sprites, prefabs, etc.) that will make the program more heavy without necessity.

Thank you very much and sorry for my level of English

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

Myself, I'd recommend keeping the server and client authored in a single Unity project. This minimizes the chance for mistakes where you change something in either the server or client and miss updating the other, resulting in divergent behaviour or major errors.

You could approach it in other ways, like bundling everything needed by both client and server into a shared library and asset pack referenced in each separate project, but that's extra hoops to jump through every time you change something.

Instead, we can keep everything in one project, and use preprocessor directives to control what gets built into the client versus server. Code wrapped in an #if SERVER_BUILD ... #endif pair will be compiled only if you've defined the SERVER_BUILD symbol in this compilation pass.

We can add our SERVER_BUILD symbol under Build > Player Settings... > Other Settings > Configuration> Scripting Define Symbols when we're exporting a server build, or leave it out when compiling for the client.

Defining a custom symbol

You can automate this with a custom build script or command line shortcut, so you can export either a client or server build on demand without manually twiddling the settings every time.

To avoid littering your code with #if directives all over the place, you can take advantage of a feature in C# called partial classes. This lets you split the definition of a single class between multiple files, and we'll use it to group together the common, client, and server-specific functionality we need.

First we'll define a partial class that has just the stuff that's always needed - no matter whether we're a server build or a client. Here's a very barebones example:

public partial class PlayerController : MonoBehaviour
{
    // Common variables needed on both server and client go here.
    public float movementSpeed;

    void Start() {
        // Common start-up code needed on both server and client
        // can be written here.

        // Call serve/client-specific start-up behaviour,
        // depending on which one we are.
        OnStartup();
    }

    void Move(Vector2 input) {
        // TODO: implement move logic.
        // Note that here, we don't know whether the input
        // came from a locally-connected gamepad/keyboard,
        // or a network packet. We can handle it agnostically.
    }
}

Note that this class has no notion of the source of input, or that the player has a model/sprite/animator at all, since only the client needs to worry about that stuff.

Then we can have a separate file, call it PlayerController_client.cs that expands on this with client-specific needs and implementations. We wrap the whole thing inside an #if !SERVER_BUILD directive, so when we're compiling a server build, the compiler sees this as an empty file and it has no effect on the code that's generated or the data that's packed.

#if !SERVER_BUILD
using UnityEngine;

public partial class PlayerController : MonoBehaviour
{
    public Animator visualPrefab;
    public string horizontalAxis;
    public string verticalAxis;

    Animator _visual;
    Camera _camera;

    void OnStartup() {
        _visual = Instantiate(visualPrefab, transform);
        _camera = Camera.main;
    }

    private void Update() {
        // Capture input from client's controller.
        Vector2 input = new Vector2(Input.GetAxis(horizontalAxis), 
                                    Input.GetAxis(verticalAxis));

        // TODO: SendInputToServer(input);        

        // Locally predict the movement based on this input.
        Move(input);

        // Update our animation states accordingly.
        _visual.SetFloat("horizontal", input.x);
        _visual.SetFloat("vertical", input.y);

        // TODO: Handle applying corrections from server in case of bad predictions.
    }
}
#endif

Here the visual representation of the player character isn't part of the character prefab. We spawn it from its own prefab in Start. (You could also do this in an editor callback if you need to position player avatars visually in the scene when setting up maps)

Because this reference to the visual prefab is only present in the client version, when we compile a server version the visual model is completely unreferenced, and Unity can optimize your build by excluding that model, animation data, materials, textures, etc. from the server version of the game.

Note that we still call into the same Move() function that's shared between the server and client functions. So any changes we make to that movement logic will take effect identically between server and client.

We can make another partial class for the server-specific functionality. In that version, our Update method will take its input from the network messages received from the client, instead of trying to read a local gamepad. But again we'll call into the same Move() function to get consistent simulation rules. And we'll skip the overhead of updating the character's animation state, since the server doesn't care about that detail (as long as you're not using animation events to affect gameplay beheviour - if you do that, then you'd want to move the animation logic into the shared file).

This basic strategy will let you very efficiently strip-out functionality or assets you don't need for one platform or another at compile time, and switch between implementations appropriate for each. Doing it in separate files through partial classes helps you keep the code organized, and clearly separate responsibilities - one member of your team could be updating the server logic while another one edits the client-side feedback, without versioning conflicts.

\$\endgroup\$
  • \$\begingroup\$ Thank you very much for your answer, I did not know the possibility of using different if SERVER_BUILD to make compilations of my project, I will certainly use it for my project. DMGregory I always thank you for your great interest in helping others \$\endgroup\$ – Antonio Agustin Oct 12 '20 at 19:55
  • \$\begingroup\$ I am currently using this approach on my project. However I am struggling with 2 issues: 1. When working in the unity editor, is there any fast way to switch between a server and client setup? (changing the player settings field takes really long and forces a recompile) 2. I am using Rider as my editor and it disables intellisense and refactoring on code that where the #if is not fulfilled. Do you know any way to avoid this? \$\endgroup\$ – Aides Jan 17 at 13:48
  • 1
    \$\begingroup\$ Those sound like good questions to post by clicking the "Ask Question" button. The one about intellisense and refactoring sounds like it would also impact non-game programmers, so asking it on StackOverflow might get you more answers, faster. \$\endgroup\$ – DMGregory Jan 17 at 13:54

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.