Confused About Networking

Started by Kairos · 11 months ago · 1 reply · 148 views

#1
Kairos
Member
JoinedSep 2021 Posts4 Score375

Maybe I’m overthinking this, but I can’t quite wrap my head around how networking works...

The docs mention “Serverside code only works when running local projects”  what exactly does that mean?

Context
Trying to learn c#'s syntax and s&box in general so I originally built a simple round controller as its own class all just backend with some print/logs and called it from GameManager.OnHostInitialize. It worked fine:
  • start round

  • countdown timer

  • end round

  • run intermission

  • start next round

Now I want to replicate the current round and remaining time to clients. I read that [Sync] is the right way (instead of firing an RPC every second), but it requires making it a Component. That feels odd is this actually the intended use for system-type logic? Should I just make an empty GameObject and attach a component script to handle it?

Where I’m stuck
When I test networking by joining from a second instance, the client seems to run its own copy of the script.
They’re not synced, and it looks like the logic is all client-side.
So:
  • Does every client always get its own instance of components?

  • How do I make one authoritative “server-side” instance that drives the state?

  • Are [Sync] variables only valid if the object was spawned with NetworkSpawn()?

  • Is the idea that everything exists on both sides but the host owns and replicates the authoritative version?
TL;DR

I’m confused about:
  • what “serverside code only works when running local projects” really means,

  • why my component script runs on every client,

  • and the correct way to have a single authoritative instance replicate data to all clients via [Sync].

Any clarification or examples would be appreciated, or if I'm just a retard let me know!

edited 11 months ago#2
Kairos
Member
JoinedSep 2021 Posts4 Score375
I am in fact a silly goose, I think I have a decent grasp on the networking now. It's pretty nice and simple seems the intent was met (: 

At first, I thought logic just “ran on both sides” and would magically stay in sync, but that’s not how it works. I guess the best way to think of it is that the host (server) is authoritative, and clients just get data from it via [Sync] and RPC

For anyone else struggling like I was here's a little rundown of my process

  • Made an empty GameObject and attached my RoundController component.
  • Set Network Mode → Network Object, Orphaned Mode → Host, and Owner Transfer → Fixed.
  • Let the host run all the logic, and used [Sync] to replicate round info to clients.
  • [Change] to print out the current round when updated on both client and host for debugging

At first, I made everything static, which doesn’t replicate; each client had its own copy.
The fix was removing static and using [Sync] fields on a networked object.

I also discovered Networking.IsHost and Networking.IsClient, which work like the old “server/client” checks.

and here's my very barebones code The host runs all the round logic and prints countdowns. Clients automatically receive synced [Sync] values and print their current round.

public sealed class RoundController : Component
{
    public bool Started { get; set; } = false;
    public int RoundState { get; set; } = 0;
    public float RoundDuration { get; set; } = 10f;
    public float IntermissionDuration { get; set; } = 5f;
    private float TimeRemaining { get; set; } = 0f;

    [Sync, Change ("NewRound")] public int CurrentRound { get; set; } = 1;

    // This is hooked into the variable above and will run when that value is changed.
    private void NewRound( int oldValue, int newValue )
    {
       if (Networking.IsHost)
          Log.Info($"[Host] Round {newValue}");
       else if (Networking.IsClient)
          Log.Info($"[Client] Round {newValue}");
    }

    public void Initialize()
    {
        if (Started) { Log.Info("Gamemode already started"); return; }
        Started = true;
        StartRound();
    }

    public async void StartRound()
    {
        if (!Started || RoundState == 1) return;

        RoundState = 1;
        TimeRemaining = RoundDuration;
        Log.Info($"Round Started: {CurrentRound}");

        while (RoundState == 1 && TimeRemaining > 0)
        {
            await GameTask.DelaySeconds(1);
            TimeRemaining -= 1;
            if (Networking.IsHost)
                Log.Info($"Time Remaining: {TimeRemaining}");
        }

        if (Started)
        {
            Log.Info("Out of time");
            EndRound();
        }
    }

    public async void EndRound()
    {
        if (!Started || RoundState == 2) return;

        RoundState = 0;
        CurrentRound++;

        RoundState = 2;
        TimeRemaining = IntermissionDuration;
       Log.Info( "Beginning intermission..." );
        while (RoundState == 2 && TimeRemaining > 0)
        {
            await GameTask.DelaySeconds(1);
            TimeRemaining -= 1;
        }

        StartRound();
    }

    protected override void OnStart()
    {
        if (Networking.IsHost) // Make sure the system is only being ran from the host
            Initialize();
    }

    protected override void OnDestroy()
    {
        if (Networking.IsHost)
        {
            Started = false;
            RoundState = 0;
        }
    }
    
    
}

people
Log in to reply
You can't reply if you're not logged in. That would be crazy.