From d2a1db6b310e5fb04a578a046a9a00bee5c24af7 Mon Sep 17 00:00:00 2001 From: Xilo Date: Wed, 13 Dec 2023 19:20:35 -0500 Subject: [PATCH] Update Link, Add Instance, Update Using Examples (#25) * Update Link, Add Instance, Update Using Examples Updated the link to Unity's NGO docs to be on version 1.5.2 since that's what the game uses; Added an Instance field to the ExampleNetworkHandler; Fixed Using Examples referencing NetworkHandler instead of ExampleNetworkHandler; Added Using Example for calling the EventClientRpc * Simplified Instance Variable --- .../user-guide/modding/advanced/networking.md | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/docs/user-guide/modding/advanced/networking.md b/docs/user-guide/modding/advanced/networking.md index 09564b7b8d8..d71f561d7bf 100644 --- a/docs/user-guide/modding/advanced/networking.md +++ b/docs/user-guide/modding/advanced/networking.md @@ -2,7 +2,7 @@ !> **Warning: This is an advanced article. While this introduces some C# concepts, it is highly recommended to understand C# and the basics of modding this game before reading this article.** -?>**Note:** This is not a tutorial on how to use Unity's [Netcode for GameObjects](https://docs-multiplayer.unity3d.com/netcode/current/tutorials/get-started-ngo/) RPCs and Network Variables. This is only meant to be used to understand how to implement custom networking into the game. +?>**Note:** This is not a tutorial on how to use Unity's [Netcode for GameObjects](https://docs-multiplayer.unity3d.com/netcode/1.5.2/about/) RPCs and Network Variables. This is only meant to be used to understand how to implement custom networking into the game. ## Preface @@ -89,10 +89,19 @@ namespace ExampleMod public class ExampleNetworkHandler : NetworkBehaviour { + public static ExampleNetworkHandler Instance { get; private set; } } } ``` +We also add the one line of code to allow scripts to easily access any methods or variables, since in the case of our ExampleMod, there is only one version of this class. While you can just use: + +```cs +public static ExampleNetworkHandler Instance; +``` + +Doing it with the aforementioned method will prevent any classes from overriding the Instance variable, ensuring it can always be referenced as long as the ExampleNetworkHandler exists. + ### ClientRpc We have our basic component! From here, we need to add the RPCs and a measure to avoid duplicate signals. Since the event info is only sent by the server, we do not have to deal with a ServerRpc and only need to set up the ClientRpc. This is what our example mod uses: @@ -136,7 +145,7 @@ if (LevelEvent != null) All this if statement checks is whether the event is not equal to null and calls the event if so. The event will be null *if there are no subscribers to the event.* -### Preventing Duplication of Events +### Preventing Duplication of Events and Instance Since we are using `static` when defining our C# event, an edge case can occur. What happens if the event is not unsubscribed from, and the player joins a new server? Any code that unknowingly subscribes to the event a second time will run twice! How do we make sure this does not occur? We set the C# event to equal null. The best time to do so is when the NetworkHandler gets spawned in: @@ -151,6 +160,21 @@ public override void OnNetworkSpawn() This removes any subscribers and continues to call the base OnNetworkSpawn method to allow any code that runs in that method to still occur. +But what about our Instance variable? If we don't set it to anything, any scripts attempting to use this handler won't be able to use .Instance! Here, we can assign the `Instance` variable to be the current object. We also need to remove any previously existing GameObject with our `ExampleNetworkHandler` class, which can only be done via the server. + +```cs +public override void OnNetworkSpawn() +{ + LevelEvent = null; + + if (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer) + Instance?.gameObject.GetComponent().Despawn(); + Instance = this; + + base.OnNetworkSpawn(); +} +``` + ### Finalized Network Handler We finished! All that's left is to throw it all together into one script: @@ -169,6 +193,10 @@ namespace ExampleMod { LevelEvent = null; + if (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer) + Instance?.gameObject.GetComponent().Despawn(); + Instance = this; + base.OnNetworkSpawn(); } @@ -179,13 +207,15 @@ namespace ExampleMod } public static event Action LevelEvent; + + public static ExampleNetworkHandler Instance { get; private set; } } } ``` ## Spawning the NetworkHandler -Before we can spawn the ExampleNetworkHandler, we must load it into the game. There are two ways we can do this: through LethalLib or loading it from an AssetBundle. In our case, we are going to load our handler from an AssetBundle. +Before we can spawn the ExampleNetworkHandler, we must load it into the game. To do so, we need to load our handler from an AssetBundle. The Game Object we spawn as an asset requires a network object. We will use this prefab for our Network Handler: @@ -343,19 +373,27 @@ Finally! The handler is in the game! Now we can utilize it. But how? Easy, we su [HarmonyPostfix, HarmonyPatch(typeof(RoundManager), nameof(RoundManager.GenerateNewLevelClientRpc))] static void SubscribeToHandler() { - NetworkHandler.LevelEvent += ReceivedEventFromServer; + ExampleNetworkHandler.LevelEvent += ReceivedEventFromServer; } [HarmonyPostfix, HarmonyPatch(typeof(RoundManager), nameof(RoundManager.DespawnPropsAtEndOfRound))] static void UnsubscribeFromHandler() { - NetworkHandler.LevelEvent -= ReceivedEventFromServer; + ExampleNetworkHandler.LevelEvent -= ReceivedEventFromServer; } static void ReceivedEventFromServer(string eventName) { // Event Code Here } + +static void SendEventToClients(string eventName) +{ + if (!(NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsHost)) + return; + + ExampleNetworkHandler.Instance.EventClientRpc(eventName); +} ``` What does this all do? Well, `NetworkHandler.LevelEvent += ReceivedEventFromServer` simply tells C# that we want `ReceivedEventFromServer(string eventName)` to run when the `LevelEvent` event is invoked. `NetworkHandler.LevelEvent -= Received` tells C# that we no longer want `ReceivedEventFromServer` to run when the event is invoked.