Hello everyone,
I am working on an online game using Netcode for GameObjects and Relay. I have gotten the basics working, such as spawning the player, having it sync up with the server, and using those keys to connect to others via relay.
In my game, I would need a game manager; which has a class called money. This class is responsible for holding the amount of money available to everyone in the game, as in everyone would have access to the same balance (shared). However, It is not working as expected.
using System;
using Unity.Netcode;
using UnityEngine;
namespace SimGame.Systems.Mechanics {
public class Money : NetworkBehaviour
{
#region Singleton
public static Money Instance { get; private set; }
#endregion
#region Members and Datatypes
private NetworkVariable<int> _currentBalance = new(0);
private struct Loan : IEquatable<Loan>
{
public int Amount;
public int DailyInterest;
public bool Equals(Loan other)
{
return Amount == other.Amount && DailyInterest == other.DailyInterest;
}
}
private NetworkList<Loan> _loans;
#endregion
#region Events
public event Action<int> OnBalanceChanged;
#endregion
#region Functions
public override void OnNetworkSpawn()
{
SetInstance();
}
protected override void OnNetworkPostSpawn()
{
OnBalanceChanged?.Invoke(_currentBalance.Value);
}
private void SetInstance()
{
if (Instance != null && Instance != this)
{
Destroy(this);
}
else
{
Instance = this;
}
}
public void ChangeBalance(int amount)
{
_currentBalance.Value += amount;
OnBalanceChanged?.Invoke(_currentBalance.Value);
}
}
#endregion
}
In the screen shot, you will see that the object is not owned by the server, which I think a game manager should be owned by the server.
The issue is that it is not being owned by the server, and the singleton isn’t getting initialized fast enough. I should mention that I am new to working with online. Any help, feedback, advice, etc. would be greatly appreciated. If any clarification is needed, I’ll gladly provide them to the best of my ability.
No, you don’t.
There’s nothing about a game that needs “managing”. It is woefully undefined what such a class’ responsibilities are supposed to be.
See, this is the class you need. Although I would call it Wallet, since that’s what “holds money”. Either way, this class has a much clearer purpose and meaning, but also no relation whatsoever to a supposed “Game Manager”.
Then you spawned it with the wrong ownership. You can call “SpawnWithOwner” but by default, any spawned network object is owned by the server.
In principle, but specifically when it comes to networking, avoid singletons altogether. If you must, add all singleton instances up-front in the very first scene that loads and put it in DontDestroyOnLoad.
This means the class isn’t networked, which isn’t an issue however. The “Wallet” can still interact with networked objects. For instance, if a player object spawns, have it invoke a public static event Action<NetworkObject> OnPlayerSpawned
as such: OnPlayerSpawned?.Invoke(GetComponent<NetworkObject>());
. Same for despawning of course.
The Wallet registers to these events up front. Other classes can do so too because they also may have an interest in knowing about players spawning and despawning. Then classes can interact with the given players. You can still use NetworkManager in non-networked classes to check if IsServer
or whatever you may need.
Alright, I saw your article on writing better netcode, and this is my first plunge into using netcode. I also know that you’re a guidance counselor. Is this the best place to ask you questions on writing better netcode then? If so, how would you make a class that manages the wallet of the session? The idea for the game is that every player can make purchases and get loans using the same shared wallet on the server. I want an easy way to communicate with this wallet from any class, so I chose a singleton, especially since netcode uses singletons.
P.S. When I said Game Manager, I meant the object that holds the different components that need to be available session-wide.
I’d likely have the server load an additive scene that contains all the server-only objects and scripts, including the Wallet.
Wallet hooks into NetworkManager events OnServerStarted and Stopped in order to spawn a WalletNetwork object for everyone. It also assigns its Wallet reference to WalletNetwork.
The network interface is where you implement any wallet driven RPCs and events. Only on the server-side it has a non-null Wallet reference. It uses that to communicate with the wallet.
To make any networked object a temporary (!) singleton, the general pattern is as follows:
private static Singleton {get; private set;}
public static event Action<NetworkWallet> OnNetworkWalletSpawned;
public static event Action<NetworkWallet> OnNetworkWalletDespawning;
void OnNetworkSpawn()
{
if (Singleton != null)
throw new Exception("duplicate singleton object spawned!");
Singleton = this;
OnNetworkWalletSpawned?.Invoke(this);
}
void OnNetworkDespawn()
{
OnNetworkWalletDespawning?.Invoke(this);
Singleton = null;
}
You must never use the singleton Instance until after the “Spawned” event was raised. Implementing event driven notifications of objects getting spawned or despawned is one of the most helpful patterns for network development since there’s an unknown delay before any object gets spawned.
The spawn event also sends the reference as a parameter for convenience. This is unnecessary for a singleton but I added it here because you can use the same pattern for getting notified of, say, enemies being spawned. This would allow any script to add or remove spawned objects that it is interested in “managing”.
I also always throw an exception if a singleton object gets instantiated a second time because this indicates an error. In singleplayer, you can “hack it away” by just destroying the second object - with potentially disastrous results if the object contains other scripts. But in networking, you cannot simply despawn a second singleton instance, and instantiating a second one is clearly a mistake that needs to be fixed.
Interesting. This additive scene will be the container of all network/server-related functionality, and instead of relying on Unity’s starting logic, we will make our own to subscribe to. Also, destroying a second instance online is bad practice, and thanks for the help in general.