It’s fine to develop the basic gameplay as single-player and then convert to multiplayer after as long as you take a modular approach and use functions for actions like shooting instead of in-lining them into your input code.
For example:
void Update() {
if (this.IsLocalPlayer) {
if (Input.GetKeyDown( KeyCode.Space ))
InvokeServerRpc( CmdShoot );
and then elsewhere:
[ServerRPC]
void CmdShoot() {
GameObject bullet = Instantiate( bulletPrefab, this.transform.position, Quaternion.identity );
bullet.GetComponent<Rigidbody2D>().velocity = Vector2.up * bulletSpeed;
bullet.GetComponent<NetworkedObject>().Spawn();
Destroy( bullet, 1.0f );
}
The above is taken directly from a code example someone created for MLAPI here: Sample project inclusion · Issue #220 · Unity-Technologies/com.unity.netcode.gameobjects · GitHub (see my caveat comment below that one about changing the host IP if you intend to try it out).
So when you’re developing this as single-player, you would still have a CmdShoot function that gets called when the player presses the spacebar, it’s just that instead of calling it directly you use MLAPI’s InvokeServerRpc function and you add attributes like ServerRPC above the CmdShoot function, and then inside that there’s just one extra line to what you’d have in single-layer where you Spawn the bullet. And the only other network bit is the IsLocalPlayer check before you grab user input.
Here’s the docs in relation to the above: https://mlapi.network/wiki/messaging-system/
As for more advanced things like prediction/compensation, MLAPI also has a nice approach to this: https://mlapi.network/wiki/lag-compensation/
As you can tell, I like using MLAPI. It’s got great docs although the examples are lacking (that example linked above by a user is actually a pretty good starting point to get your head around it).
You can code your game as a single-player experience and add multiplayer later, as long as you don’t write spaghetti code and you separate gameplay actions into their own functions so that you can then add the decoration that says “only do this on the server” or “call this function on the client” etc.
The other thing to keep in mind is disabling components or functionality you don’t want other characters in the game to do. For example you want your input and controller and camera functionality to only be used on your character and not the others you see in your instance of the game world (because they need to be controlled by their respective owners). So I usually do something like this and attach it to the player prefab:
using MLAPI;
public class DisableNonLocal : NetworkedBehaviour
{
void Start()
{
if (!IsLocalPlayer)
{
GetComponent<CameraWalkerController>().enabled = false;
GetComponent<Mover>().enabled = false;
GetComponent<AnimationControl>().enabled = false;
Destroy(transform.Find("CameraRoot").gameObject);
In other words, for all players that are not me, i.e. all the copies of other players that I see in my instance of this shared world we’re each running separately, disable the controllers/movers and destroy their camera.
As for how they move or animate and how that’s synced, that’s where assets like Simple Network Sync come into play: Simple Network Sync | Network | Unity Asset Store – you attach its components to your player and it takes care of syncing the position/rotation/animation (and any extra variables you tell it to).
Anyway, what network library or multiplayer solution you choose is up to you. I’ve gone with MLAPI and SNS because they made sense to me and were well documented and supported via Discord chats with the developers.
So yeah, in summary, make your game single-player to start, then sprinkle in the multiplayer after – as long as you keep things modular!