### Install via Git URL Source: https://purrnet.dev/docs/tools/conversion-tool Use this URL in the Unity Package Manager to install the conversion tool. ```text https://github.com/PurrNet/PurrNet-Conversion-Tool.git?path=/Assets/PurrNet-Conversion ``` -------------------------------- ### SyncVar Initialization Options Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/syncvar Examples of different ways to instantiate a SyncVar. ```csharp new(); ``` ```csharp new(5); ``` -------------------------------- ### Install Purrnity Transport via Git URL Source: https://purrnet.dev/docs/community-guides/unity-multiplayer-services-with-purrnet Use this URL in the Unity Package Manager to install the transport adapter. ```text https://github.com/youngwoocho02/PurrnityTransport.git ``` -------------------------------- ### Configure RPC Parameters Source: https://purrnet.dev/docs/systems-and-modules/remote-procedure-call-rpc Example of applying RPC attributes with specific configuration parameters. ```C# [ServerRpc(RequireOwnership:false, RunLocally:true)] ``` -------------------------------- ### Install PurrNet Multiplayer Services Handler via Git URL Source: https://purrnet.dev/docs/community-guides/unity-multiplayer-services-with-purrnet Use this URL in the Unity Package Manager to install the session management layer. ```text https://github.com/youngwoocho02/PurrNetMultiplayerServicesHandler.git ``` -------------------------------- ### Minimal Awaitable RPC Implementation Source: https://purrnet.dev/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc A basic example showing the minimum requirements for defining and calling an awaitable RPC. ```csharp private async void MyLocalMethod() { var myBool = await TestBool(); if (!myBool) return; Debug.Log("Yay we made it!"); } [ServerRpc(requireOwnership:false)] public async Task TestBool() { return true; } ``` -------------------------------- ### Configure Basic UDP Connection Source: https://purrnet.dev/docs/community-guides/unity-multiplayer-services-with-purrnet Standard direct UDP connection setup for server and client. ```csharp // Server transport.Listen(7777); // Client transport.Connect("127.0.0.1", 7777); ``` -------------------------------- ### Install PurrDiction via Git URL Source: https://purrnet.dev/docs/client-side-prediction/installation Use these URLs in the Unity Package Manager to install the stable or development versions of PurrDiction. ```text https://github.com/PurrNet/PurrDiction.git?path=/Assets/PurrDiction#release ``` ```text https://github.com/PurrNet/PurrDiction.git?path=/Assets/PurrDiction#dev ``` -------------------------------- ### Implementing Input Synchronization for Physics Source: https://purrnet.dev/docs/guides/easy-multiplayer-physics-input-sync This example demonstrates how to set up a server-authoritative physics movement script using PurrNet, where clients send input data to the server for processing. ```csharp [SerializeField] private float moveForce = 10f; [SerializeField] private float jumpForce = 10f; [SerializeField] private float bounceForce = 10f; [SerializeField] private Rigidbody rigidbody; private bool _willJump; protected override void OnSpawned(bool asServer) { base.OnSpawned(asServer); if (asServer) return; //All clients set it to kinematic, so only the server runs physics! rigidbody.isKinematic = !isServer; //Only the owner has it enabled, as to run Update() enabled = isOwner; //Only the owner runs OnTick to send input to the server if (isOwner) networkManager.onTick += OnTick; } protected override void OnDestroy() { base.OnDestroy(); //Unsubcribing again for cleanup networkManager.onTick -= OnTick; } private void Update() { //We have to store the input to be used during the next tick if (Input.GetKeyDown(KeyCode.Space)) _willJump = true; } private void OnTick(bool asServer) { //In case of a host setup, we don't want this to run twice. if (asServer) return; //We generate the input struct that will be sent to the server var input = new InputData() { movement = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")), jump = _willJump }; //Restting the jump bool back after we've now used it in a tick _willJump = false; //We send the input to the server Move(input); } //Server RPC with Unreliable channel to send the data more efficiently [ServerRpc(Channel.Unreliable)] private void Move(InputData inputData) { //This is where you can also handle cheat detection on the inputData //You could for example normalize it, if the magnitude is above 1 //From here the code is basically "single-player" code from the //perspective of the server //We generate the movement vector from the given input. var movement = new Vector3(inputData.movement.x, 0, inputData.movement.y) * moveForce; rigidbody.AddForce(movement); if(inputData.jump) rigidbody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); } private void OnCollisionEnter(Collision other) { //Other than the if-statement here, this is single-player code from the //perspective of the server if (!isServer) return; if (!other.gameObject.TryGetComponent(out PlayerPhysicsMovement otherPlayer)) return; var direction = (transform.position - other.transform.position).normalized; rigidbody.AddForce(direction * bounceForce, ForceMode.Impulse); } //Struct in which we hold input data. This isn't necessary, just a clean approach private struct InputData { public Vector2 movement; public bool jump; } ``` -------------------------------- ### Implement a Server-Authorized SyncTimer Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/synctimer Demonstrates initializing a SyncTimer with default settings, handling ticks, and managing timer state like starting, pausing, and resuming. ```csharp public TMP_Text timerText; //false = OwnerAuth //3 = Reoncile interval //The reconcile interval deciphers how often it will force align all clients private SyncTimer timer = new(); private void Awake() { //onTimerSecondTick is called every 1 second timer.onTimerSecondTick += OnTimerSecondTick; } protected override void OnSpawned(bool asServer) { //This starts the timer with 30 seconds countdown if(isOwner) timer.StartTimer(30f); } private void OnTimerSecondTick() { //You can also get .remaining to get the precise float value //For displaying timers, the remainingInt makes it easy timerText.text = timer.remainingInt.ToString(); } private void PauseGameTimer() { //Pauses the timer and will sync the remaining time because it's set to true timer.PauseTimer(true); } private void ResumeGameTimer() { //Will resume the timer from where it was paused timer.ResumeTimer(); } ``` -------------------------------- ### Install EOS Transport via Git URL Source: https://purrnet.dev/docs/systems-and-modules/transports/eos-transport Use these URLs in the Unity Package Manager to install the stable or development versions of the EOS Transport. ```text https://github.com/PurrNet/PurrNetEOSTransport.git?path=/Assets/EOSTransport#release ``` ```text https://github.com/PurrNet/PurrNetEOSTransport.git?path=/Assets/EOSTransport#dev ``` -------------------------------- ### Generic RPC Usage Example Source: https://purrnet.dev/docs/systems-and-modules/remote-procedure-call-rpc/generic-rpc Demonstrates calling an RPC with different data types and defining a generic ServerRpc method. ```csharp private void SendFirst() { TestRpc("Purrfect!", true); } private void SendSecond() { TestRpc(69, 4.20f); } [ServerRpc] private void TestRpc(T myData1, P myData2) { Debug.Log($"Received {myData1} with type of: {myData1.GetType()}"); Debug.Log($"Received {myData2} with type of: {myData2.GetType()}"); } ``` -------------------------------- ### Install PurrDiction via OpenUPM CLI Source: https://purrnet.dev/docs/client-side-prediction/installation Use the OpenUPM command-line interface to add the PurrDiction package to your project. ```bash openupm add dev.purrnet.purrdiction ``` -------------------------------- ### Install PlayEveryWare EOS Plugin Source: https://purrnet.dev/docs/systems-and-modules/transports/eos-transport Required dependency for the EOS Transport to provide the EOS SDK and EOSManager. ```text https://github.com/PlayEveryWare/eos_plugin_for_unity_upm.git ``` -------------------------------- ### Sending a message to the server Source: https://purrnet.dev/docs/community-guides/broadcast-chat-system Example of triggering a message broadcast to the server using the NetworkManager. ```csharp void Update() { if (Keyboard.current.enterKey.wasPressedThisFrame) { ChatMessage message = new ChatMessage { name = InstanceHandler.NetworkManager.localPlayer.ToString(), message = "Hello World!" }; InstanceHandler.NetworkManager.SendToServer(message); } } ``` -------------------------------- ### Configure Unity Relay Connection Source: https://purrnet.dev/docs/community-guides/unity-multiplayer-services-with-purrnet Setup for NAT traversal using Unity Relay server and join data. ```csharp // Host - set relay server data before listening transport.SetRelayServerData(relayServerData); transport.Listen(0); // Client - set relay join data before connecting transport.SetRelayServerData(relayJoinData); transport.Connect("0.0.0.0", 0); ``` -------------------------------- ### Start a game session via Gameye API Source: https://purrnet.dev/docs/game-server-hosting/gameye Sends a POST request to the Gameye Session API to initiate a server instance. The response provides the host and port details required for client connections. ```bash curl -X POST https://api.gameye.io/session \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "location": "eu-west", "image": "your-purrnet-server", "args": ["-batchmode", "-nographics"], "labels": { "matchId": "abc123" }, "ttl": 3600 }' ``` ```json { "id": "session-abc123", "host": "185.x.x.x", "ports": [ { "type": "udp", "container": 5000, "host": 34521 } ] } ``` -------------------------------- ### Spawn Addressable via GUID Source: https://purrnet.dev/docs/systems-and-modules/addressables/addressable-spawning-and-despawning Uses the static Addressables API with a GUID to instantiate a networked object. ```csharp async void SpawnByGuid() { var handle = Addressables.InstantiateAsync(prefabReference.AssetGUID, position, rotation); await handle.Task; } ``` -------------------------------- ### Initializing Modules Source: https://purrnet.dev/docs/systems-and-modules/network-modules/common-pitfalls Use the OnInitializeModules callback to set up modules before networking data is processed. ```csharp protected override void OnInitializeModules() { health = new SyncVar(100); // Custom initialization here } ``` -------------------------------- ### Initialize and Use SyncQueue Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/synclist-1-1 Demonstrates creating a SyncQueue instance, subscribing to change events, and performing standard queue operations like Enqueue, Dequeue, and Clear. ```csharp //Creates a new instance of the queue - `true` means it is owner auth. [SerializeField] private SyncQueue myQueue = new(true); protected override void OnSpawned() { //Subscribing to changes made to the list myQueue .onChanged += OnQueueChanged; } private void OnQueueChanged(SyncQueueChange change) { //This is called for everyone when the queue changes. //It will log out the value and operation Debug.Log($"Queue updated: {change}"); } private void ChangeMyQueue() { //This will enqueue into the queue myQueue.Enqueue(69); //This will dequeue the first element in the queue myQueue.Dequeue(); //This will clear the queue myQueue.Clear(); //This will peek at the first element var myVal = myQueue.Peek(); } ``` -------------------------------- ### Initialize and Modify SyncDictionary Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/syncdictionary Demonstrates how to instantiate a SyncDictionary, subscribe to change events, and perform common operations like adding, removing, and clearing entries. ```csharp //Creates a new instance of the dictionary - `true` means it is owner auth. [SerializeField] private SyncDictionary myDictionary = new(true); protected override void OnSpawned() { //Subscribing to changes made to the dictionary myDictionary.onChanged += OnDictionaryChanged; } private void OnDictionaryChanged(SyncDictionaryChange change) { //This is called for everyone when the dictionary changes. //It will log out the Key, Value and operation Debug.Log($"Dictionary updated: {change}"); } private void ChangeMyDictionary() { //This will change or add a value to the dictionary myDictionary[123] = 0.69f; //This will remove the value from the dictionary myDictionary.Remove(123); //This will clear the dictionary myDictionary.Clear(); //This will mark the key as dirty myDictionary.SetDirty(123); } ``` -------------------------------- ### Register and Retrieve Instances with Instance Handler Source: https://purrnet.dev/docs/systems-and-modules/instance-handler Demonstrates registering an instance on Awake, unregistering on OnDestroy, and retrieving instances using both GetInstance and TryGetInstance methods. ```csharp public class GameManager : NetworkBehaviour { private void Awake() { //We register the GameManager instance InstanceHandler.RegisterInstance(this); } private void OnDestroy() { //Upon being destroyed, we unregister the game manager instance InstanceHandler.UnregisterInstance(); } private static void GetInstanceExample() { //This will fail if the manager isn't registered InstanceHandler.GetInstance().Success(); } private static void TryGetInstanceExample() { //This will only run if we get the manager. Potentially invert the if statement and log and error if you don't get it if(InstanceHandler.TryGetInstance(out GameManager manager)) manager.Success(); } private void Success() { Debug.Log("Now we're logging from the GameManager instance!", this); } } ``` -------------------------------- ### Initialize and Subscribe to SyncHashSet Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/synchashset Demonstrates creating a SyncHashSet instance with owner authority and subscribing to its change events. ```csharp //Creates a new instance of the Hashset - `true` means it is owner auth. [SerializeField] private SyncHashSet myHashSet = new(true); protected override void OnSpawned() { //Subscribing to changes made to the hash set myHashSet.onChanged += OnHashSetChanged; } private void OnHashSetChanged(SyncHashSetChange change) { //This is called for everyone when the hash set changes. //It will log out the Value and operation Debug.Log($"HashSet updated: {change}"); } ``` -------------------------------- ### Initialize Unity Services Source: https://purrnet.dev/docs/community-guides/unity-multiplayer-services-with-purrnet Required initialization steps before using Multiplayer Services. ```csharp using PurrNet.MultiplayerServices; // Initialize Unity Services first await UnityServices.InitializeAsync(); await AuthenticationService.Instance.SignInAnonymouslyAsync(); ``` -------------------------------- ### Vector3 Delta Serialization Example Source: https://purrnet.dev/docs/systems-and-modules/bitpacker-serialization/delta-packers Demonstrates the usage of DeltaPacker to write and read changes for a Vector3 type. ```csharp // Writing DeltaPacker.Write(packer, oldPosition, newPosition); // Reading Vector3 oldValue = ..; Vector3 result = default; var currentPosition = DeltaPacker.Read(packer, oldValue, ref result); ``` -------------------------------- ### Projectile Knockback Implementation Source: https://purrnet.dev/docs/client-side-prediction/multi-identity-interactions Example of a projectile identity resolving a target, applying force to its state, and self-destructing. ```csharp public struct ProjectileState : IPredictedData { public PredictedObjectID target; public Vector3 force; public void Dispose() {} } public class Projectile : PredictedIdentity { protected override void Simulate(ref ProjectileState s, float dt) { if (!s.target.HasValue) return; var mover = predictionManager.hierarchy.GetComponent(s.target); if (mover == null) return; ref var ms = ref mover.currentState; ms.velocity += s.force; // deterministic state mutation predictionManager.hierarchy.Delete(id.objectId); // self destroy after applying } } ``` -------------------------------- ### Initialize and Manage a SyncList Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/synclist Demonstrates how to declare a SyncList, subscribe to change events, and perform common list operations like adding, removing, and clearing items. ```csharp //Creates a new instance of the list - `true` means it is owner auth. [SerializeField] private SyncList myList = new(true); protected override void OnSpawned() { //Subscribing to changes made to the list myList.onChanged += OnListChanged; } private void OnListChanged(SyncListChange change) { //This is called for everyone when the list changes. //It will log out the value, index and operation Debug.Log($"List updated: {change}"); } private void ChangeMyList() { //This will change or add a value myList[0] = 69; //This will remove the value myList.Remove(69); //This will remove the entry at the given index myList.RemoveAt(0); //This will clear the list myList.Clear(); //This will insert a value at the given index myList.Insert(1, 420f); //This will mark the index as dirty myList.SetDirty(0); } ``` -------------------------------- ### Implementing a PredictedStateNode Source: https://purrnet.dev/docs/client-side-prediction/predicted-state-machine/predicted-state-node Example of inheriting from PredictedStateNode to define an attack state with simulation and view logic overrides. ```csharp public class AttackState : PredictedStateNode { public override void Enter() { /* start attack windup */ } protected override void StateSimulate(in MyInput i, ref MyState s, float dt) { // simulate attack timing using only s and i s.attackTimer = Mathf.Max(0, s.attackTimer - dt); if (s.attackTimer == 0) machine.SetState(nextState); } public override void ViewEnter(bool verified) { /* play VFX/SFX, camera shake, etc */ } } ``` -------------------------------- ### Implement Basic SyncInput Usage Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/syncinput Demonstrates subscribing to input changes and sending input values from the owner to the server. ```csharp [SerializeField] private SyncInput _input = new(); private void Awake() { //Subscribe to the input change event _input.onChanged += OnInputChanged; } private void OnDestroy() { //Unsubscribe again _input.onChanged -= OnInputChanged; } private void OnInputChanged(Vector2 newInput) { //Only the server will receive this event //The event will be called whenever input changes Debug.Log($"New input: {newInput}"); } private void Update() { if (!isOwner) return; //The owner will consistently send input - Only the necessary input changes will be sent //Rest is automatically filtered by the SyncInput var input = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")); _input.value = input; } ``` -------------------------------- ### Implement Basic Broadcasting Source: https://purrnet.dev/docs/systems-and-modules/broadcast Demonstrates subscribing to data, sending data from server to all clients or from client to server, and defining the data structure. ```csharp private void Start() { //We subscribe to receiving this type of data InstanceHandler.NetworkManager.Subscribe(OnReceiveData); //Below is an example of how you can subscribe only as the server. False for client //This can potentially give you some more control //InstanceHandler.NetworkManager.Subscribe(OnReceiveData, true); } private void OnDestroy() { //Remember to unsubscribe again to avoid possible errors and issues InstanceHandler.NetworkManager.Unsubscribe(OnReceiveData); } private void Update() { //Press the 1 key to send data if (Input.GetKeyDown(KeyCode.Alpha1)) { if (InstanceHandler.NetworkManager.isServer) { //If we are the server, we build the data and send it to every player var data = new DataToSend() { intValue = 69, boolValue = false }; InstanceHandler.NetworkManager.SendToAll(data); } else { //If we are not the server, we send some data to the server var data = new DataToSend() { intValue = 420, boolValue = true }; InstanceHandler.NetworkManager.SendToServer(data); } } } private void OnReceiveData(PlayerID player, DataToSend data, bool asserver) { //We've now received data Debug.Log($"Received data from player {player}: {data.intValue}, {data.boolValue}"); } //We need to know what data type we are sending around private struct DataToSend : IPackedAuto { public int intValue; public bool boolValue; } ``` -------------------------------- ### Build and push the Docker image Source: https://purrnet.dev/docs/game-server-hosting/gameye Commands to build the container image and upload it to a registry for Gameye to access. ```bash docker build -t your-registry/purrnet-server:1.0.0 . docker push your-registry/purrnet-server:1.0.0 ``` -------------------------------- ### Implement a Deterministic Oscillator Source: https://purrnet.dev/docs/client-side-prediction/deterministic-identity Example of defining a state struct and an oscillator class inheriting from DeterministicIdentity to perform deterministic simulation. ```csharp using PurrNet.Prediction; public struct OscState : IPredictedData { public sfloat phase; public sfloat speed; public sfloat amplitude; public void Dispose() {} } public class Oscillator : DeterministicIdentity { protected override OscState GetInitialState() => new OscState { speed = (sfloat)1.5f, amplitude = (sfloat)2f, phase = 0 }; protected override void GetUnityState(ref OscState s) { /* read if needed */ } protected override void SetUnityState(OscState s) { /* apply on rollback if needed */ } protected override void Simulate(ref OscState s, sfloat dt) { s.phase += s.speed * dt; var y = sfloat.Sin(s.phase) * s.amplitude; // use y for downstream deterministic effects; drive visuals in UpdateView } protected override void UpdateView(OscState view, OscState? verified) { // Convert to float for rendering only } } ``` -------------------------------- ### Unload Addressable Scene Source: https://purrnet.dev/docs/systems-and-modules/addressables/addressable-scene-handling Unload scenes using either the asset GUID to remove all instances or a specific SceneID for targeted unloading. ```csharp scenes.UnloadAddressableSceneByGuid(sceneReference.AssetGUID); ``` ```csharp if (scenes.TryGetSceneIdByAddressableGuid(sceneReference.AssetGUID, out var sceneId)) scenes.UnloadSceneAsync(sceneId); ``` -------------------------------- ### Spawning an object with ownership in FishNet Source: https://purrnet.dev/docs/getting-started/converting-to-purrnet/converting-from-fishnet Demonstrates the FishNet approach of instantiating a prefab and explicitly calling the ServerManager to spawn it with a specific owner connection. ```csharp GameObject go = Instantiate(_yourPrefab); InstanceFinder.ServerManager.Spawn(go, ownerConnection); ``` -------------------------------- ### Implement Stateful PredictedIdentity with Input Source: https://purrnet.dev/docs/client-side-prediction/predicted-identities A complete implementation example for a stateful object using input, state structs, and lifecycle overrides. ```csharp public struct MyInput : IPredictedData { public Vector2? input; public void Dispose() {} } public struct MyState : IPredictedData { public Vector3 pos; public Quaternion rot; public void Dispose() {} } public class MyPredicted : PredictedIdentity { protected override MyState GetInitialState() => new MyState { pos = transform.position, rot = transform.rotation }; protected override void GetUnityState(ref MyState s) { s.pos = transform.position; s.rot = transform.rotation; } protected override void SetUnityState(MyState s) { transform.SetPositionAndRotation(s.pos, s.rot); } protected override void GetFinalInput(ref MyInput i) { i.input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); } protected override void SanitizeInput(ref MyInput i) { if (!i.input.HasValue) return; var v = Vector2.ClampMagnitude(new Vector2(i.input.Value.x, i.input.Value.y), 1f); i.input = v; } protected override void Simulate(MyInput i, ref MyState s, float dt) { if (!i.input.HasValue) return; transform.position += new Vector3(i.input.Value.x, 0, i.input.Value.y) * dt * 5f; } protected override void UpdateView(MyState view, MyState? verified) { transform.SetPositionAndRotation(view.pos, view.rot); } } ``` -------------------------------- ### SyncArray Initialization and Usage Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/synclist-1 Demonstrates how to initialize a SyncArray, subscribe to change events, and modify array elements or length. ```APIDOC ## SyncArray Initialization ### Description Creates a new instance of a synchronized array that automatically aligns contents between players. ### Constructor `public SyncArray(int initialLength, bool ownerAuth)` - **initialLength** (int) - The initial size of the array. - **ownerAuth** (bool) - If true, the array uses owner authority. ### Usage Example ```csharp public SyncArray syncArray = new(20, true); protected override void OnSpawned() { syncArray.onChanged += OnArrayChange; } private void ChangeMyArray() { syncArray[0] = 69; syncArray.Length = 15; } ``` ``` -------------------------------- ### Persist Network Identity in Awake Source: https://purrnet.dev/docs/systems-and-modules/network-identity/dont-destroy-on-load Call DontDestroyOnLoad in the Awake method to ensure the object persists across scene changes. This must be done before the network starts. ```csharp private void Awake() { DontDestroyOnLoad(this); } ``` -------------------------------- ### Initialize and Update PredictedProjectile3D Source: https://purrnet.dev/docs/client-side-prediction/built-in-components/predicted-projectile-3d Demonstrates setting initial velocity during simulation startup and applying impulses during the simulation tick. ```csharp protected override void SimulationStart() { projectile.velocity = transform.forward * launchSpeed; } protected override void Simulate(ref ShotState state, float delta) { if (state.boostThisTick) projectile.AddImpulse(transform.forward * boostImpulse); } ``` -------------------------------- ### Implementing SyncEvent in Unity Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/syncevent Demonstrates initializing a SyncEvent with owner authority, subscribing to it, and invoking it from the owner. ```csharp //Creates an instance of the event - True means that it is owner authority [SerializeField] private SyncEvent syncEvent = new(true); protected override void OnSpawned() { //Listening to the event syncEvent.AddListener(SyncEventTest); } private void SyncEventTest(int myValue) { //Everyone subscribed to the syncEvent will receive this value when the owner invokes it Debug.Log($"Received value: {myValue} from SyncEvent"); } public void InvokeSyncEvent() { //Because the event is owner auth, only the owner can call the event. if (!isOwner) return; //Invoking the event as the owner with the value 10 syncEvent.Invoke(10); } ``` -------------------------------- ### Implementing and Accessing PlayerIdentity Source: https://purrnet.dev/docs/systems-and-modules/network-identity/playeridentity Demonstrates how to inherit from PlayerIdentity to enable player tracking and use static methods like TryGetPlayer, TryGetLocal, and the allPlayers dictionary. ```csharp public class PlayerMovement : PlayerIdentity { public void Teleport(Vector3 destination) { //Do whatever to teleport here } } public class GameManager : NetworkIdentity { [SerializeField] private Transform teleportDestination; public void TeleportPlayer(PlayerID targetPlayer) { //Returns true if it finds the player if(!PlayerMovement.TryGetPlayer(targetPlayer, out var player) return; player.Teleport(teleportDestination.position); } public void TeleportLocalPlayer() { //Returns true if it finds the local player if(!PlayerMovement.TryGetLocal(out var player) return; player.Teleport(teleportDestination.position); } public void TeleportAllPlayers() { //allPlayers gives you a dictionary of all the players registered foreach(var player in PlayerMovement.allPlayers) { player.value.Teleport(teleportDestination.position); } } } ``` -------------------------------- ### Configure a Manually Updated SyncTimer Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/synctimer Shows how to initialize a SyncTimer with manual updates enabled and advance it using custom delta time. ```csharp private SyncTimer _timer = new(manualUpdate:true); private void Update() { _timer.Advance(Time.deltaTime); } ``` -------------------------------- ### Teleporting with NetworkRigidbody Source: https://purrnet.dev/docs/plug-n-play-components/network-rigidbody Use TeleportTo for instant repositioning to ensure velocity is cleared and the change is synced reliably. ```csharp // Teleport to a new position and rotation (clears buffer, syncs instantly) networkRb.TeleportTo(spawnPoint.position, spawnPoint.rotation); // Teleport to just a new position (keeps current rotation) networkRb.TeleportTo(respawnPosition); ``` -------------------------------- ### Create Dynamic Modules Source: https://purrnet.dev/docs/client-side-prediction/predicted-modules Construct modules during simulation to allow automatic replication and reconciliation. ```csharp protected override void Simulate(MyInput input, ref MyState state, float delta) { if (input.applyBurn && !TryGetModule(out _)) { var burn = new BurnEffectModule(this); burn.StartFor(3f); } } ``` -------------------------------- ### Spawning and Despawning Objects Source: https://purrnet.dev/docs/getting-started/converting-to-purrnet/converting-from-mirror Comparison of object lifecycle management between Mirror and PurrNet. ```csharp GameObject go = Instantiate(myPrefab); NetworkServer.Spawn(go, connectionToClient); NetworkServer.Destroy(go); ``` ```csharp var identity = Instantiate(myIdentityPrefab); identity.GiveOwnership(ownerPlayer); Destroy(identity.gameObject); ``` -------------------------------- ### Implement Steam Host and Client Connection Logic Source: https://purrnet.dev/docs/guides/steam-setup/connect-with-steam Methods for configuring SteamTransport and managing network states for hosting and joining sessions. ```csharp steamTransport.peerToPeer = true; steamTransport.dedicatedServer = false; steamTransport.address = user.id.ToString(); NetworkManager.main.StartHost(); } /// /// This method is responsible for calling PurrNet's StartClient() method. It also gets and configures the /// SteamTransport. It also leverages a UserData struct from Heathens to store the Steam account ID of the host. /// /// /// Feel free to make this method public or private depending on your need. Steam must be initialized before /// this method is called. /// public void StartClient(string steamCode) { var steamTransport = NetworkManager.main.transport as SteamTransport; if (steamTransport == null) { Debug.LogError("SteamTransport missing on NetworkManager", this); return; } if (string.IsNullOrEmpty(steamCode)) { Debug.LogError("Connection address is empty.", this); return; } // This is a Heathens API call. var hostAddress = UserData.Get(steamCode); if (!hostAddress.IsValid) { Debug.LogError($"{hostAddress} is an invalid connection address.", this); return; } steamTransport.peerToPeer = true; steamTransport.dedicatedServer = false; steamTransport.address = hostAddress.id.ToString(); NetworkManager.main.StartClient(); } /// /// This method is responsible for calling StartHost() and changing the UI visuals. /// private void HandleHostClicked() { if (hostButton == null || hostTextField == null) { Debug.LogError("Host button or host text field is null. " + "Please drag and drop it into the ConnectionManager.", this); return; } StartHost(); if (NetworkManager.main.isOffline) { hostTextField.text = "Server is Offline"; hostButton.image.color = Color.red; return; } hostTextField.text = UserData.Get().HexId; hostButton.image.color = Color.green; hostButton?.onClick.RemoveListener(HandleHostClicked); clientButton?.onClick.RemoveListener(HandleClientClicked); } /// /// This method is responsible for calling StartClient() and changing the UI visuals. /// private void HandleClientClicked() { if (string.IsNullOrEmpty(clientInputField.text)) { clientInputField.text = "Please enter the Steam Hex ID of the host."; clientButton.image.color = Color.red; return; } StartClient(clientInputField.text); clientInputField.text = $"Connected to {clientInputField.text}"; clientButton.image.color = Color.green; clientButton?.onClick.RemoveListener(HandleHostClicked); clientButton?.onClick.RemoveListener(HandleClientClicked); } } } ``` -------------------------------- ### Create a Direct P2P Session Source: https://purrnet.dev/docs/community-guides/unity-multiplayer-services-with-purrnet Create a session configured for direct peer-to-peer connectivity. ```csharp var options = new SessionOptions() .WithPurrDirect() // Use direct P2P connection .WithMaxPlayers(4); var session = await MultiplayerService.Instance.CreateSessionAsync(options); ``` -------------------------------- ### Initialize and Modify SyncArray Source: https://purrnet.dev/docs/systems-and-modules/network-modules/sync-types/synclist-1 Demonstrates creating a SyncArray instance, subscribing to change events, and performing modifications like setting values or resizing. ```csharp //Creates a new instance of the array //20 sets the initial length of the array //`true` means it is owner auth. public SyncArray syncArray = new(20, true); protected override void OnSpawned() { //Subscribing to changes made to the array syncArray.onChanged += OnArrayChange; } private void OnArrayChange(SyncArrayChange change) { //This is now called for everyone when the array changes. //It will log out the value, index and operation Debug.Log(change); } private void ChangeMyArray() { //This will change or add a value syncArray [0] = 69; //Resized the array to 15 elements syncArray.Length = 15; } ``` -------------------------------- ### Create a Dockerfile for the Linux server Source: https://purrnet.dev/docs/game-server-hosting/gameye Defines a minimal Ubuntu-based container image for the server build. Ensure the EXPOSE port matches the port configured in the PurrNet UDP transport. ```dockerfile FROM ubuntu:22.04 RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY Build/ /app/ RUN chmod +x /app/MyGameServer.x86_64 # Expose the port your PurrNet UDP transport is configured to listen on. EXPOSE 5000/udp ENTRYPOINT ["/app/MyGameServer.x86_64", "-batchmode", "-nographics"] ``` -------------------------------- ### Listen to Scene Loading Events Source: https://purrnet.dev/docs/systems-and-modules/addressables/addressable-scene-handling Subscribe to onAddressableSceneStartLoading and onAddressableSceneLoaded to track the lifecycle of Addressable scenes. ```csharp scenes.onAddressableSceneStartLoading += (sceneId, guid, asServer) => { Debug.Log($"Loading addressable scene: {guid}"); }; scenes.onAddressableSceneLoaded += (sceneId, guid, asServer) => { Debug.Log($"Addressable scene loaded: {guid}"); }; ``` -------------------------------- ### Implementing IAsyncPackable for Network Serialization Source: https://purrnet.dev/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing Demonstrates how to use IAsyncPackable to resolve non-serializable references like Renderer components by packing identifying strings instead. ```csharp private struct NetRenderer : IAsyncPackable { public string goName; [DontPack] public Renderer renderer; public async Task PrepareForPackAsync() { if (!renderer) { Debug.LogWarning("PrepareForPackAsync: renderer is null"); return this; } await Task.Delay(1000); goName = renderer.gameObject.name; return this; } public async Task PrepareAfterUnpackAsync() { await Task.Delay(1000); var go = string.IsNullOrEmpty(goName) ? null : GameObject.Find(goName); if (!go) { Debug.LogError($"No GO found by name: '{goName}'"); return this; } renderer = go.GetComponent(); return this; } } ``` -------------------------------- ### Implementing a Static ServerRpc Source: https://purrnet.dev/docs/systems-and-modules/remote-procedure-call-rpc/static-rpc Demonstrates defining a static RPC method that can be invoked without a NetworkIdentity. Note that this approach lacks ownership checks and should be used carefully for gameplay logic. ```csharp private void SendData() { TestRpc(123); } [ServerRpc] private static void TestRpc(int myNumber) { Debug.Log($"Received {myNumber}"); } ``` -------------------------------- ### Handle Host Logic with asServer Source: https://purrnet.dev/docs/terminology/host Demonstrates using the asServer boolean to differentiate between server and client execution contexts within a callback. ```csharp protected override void OnSpawned(bool asServer) { base.OnSpawned(asServer); if (asServer) return; } ``` ```csharp protected override void OnSpawned(bool asServer) { base.OnSpawned(asServer); if (asServer) SetupServerSide(); else SetupClientSide(); } ``` -------------------------------- ### Safe Module Lookup Source: https://purrnet.dev/docs/client-side-prediction/predicted-modules Use TryGetModule to retrieve a module from the identity's live list to ensure compatibility with rollback and replay systems. ```csharp if (TryGetModule(out var burn)) burn.Refresh(); ``` -------------------------------- ### Configure NetworkTransformCompositeStrategy Source: https://purrnet.dev/docs/plug-n-play-components/network-transform/adaptive-sync Initializes a composite strategy with a list of movement strategies and sets the interval for the entire chain. ```csharp var strategy = new NetworkTransformCompositeStrategy { maxSendInterval = 0.2f, strategies = new NetworkTransformSyncStrategy[] { new RailMovementStrategy(), new NetworkTransformDefaultStrategy() } }; networkTransform.SetSyncStrategy(strategy); ``` -------------------------------- ### Spawning an object with ownership in PurrNet Source: https://purrnet.dev/docs/getting-started/converting-to-purrnet/converting-from-fishnet Demonstrates the PurrNet approach of instantiating a prefab and assigning ownership directly to the identity component. ```csharp var identity = Instantiate(myIdentityPrefab); identity.GiveOwnership(ownerPlayer); ``` -------------------------------- ### Configure Network Simulation via Code Source: https://purrnet.dev/docs/systems-and-modules/transports/simulating-latency Programmatically enable and configure network simulation settings, such as latency and packet loss, for automated testing. ```csharp udpTransport.networkSimulation = new NetworkSimulation { includeInBuild = true, simulateLatency = true, minLatency = 40, maxLatency = 80, simulatePacketLoss = true, packetLossChance = 5 }; ``` -------------------------------- ### Implement Simulate Method Source: https://purrnet.dev/docs/community-guides/character-controller-knockback-client-side-prediction Applies knockback velocity to the character controller and reduces the velocity over time using MoveTowards. ```csharp protected override void Simulate(ref PredictedKnockbackState state, float delta) { state.CurrentKnockbackVelocity = Vector3.MoveTowards(state.CurrentKnockbackVelocity, Vector3.zero, drag * delta); controller.Move(state.CurrentKnockbackVelocity * delta); } ``` -------------------------------- ### Compare ServerRpc syntax between FishNet and PurrNet Source: https://purrnet.dev/docs/getting-started/converting-to-purrnet/converting-from-fishnet Shows the slight difference in attribute parameter naming conventions when defining a ServerRpc. ```csharp [ServerRpc(RequireOwnership = false)] private void MyRpc() { } ``` ```csharp [ServerRpc(requireOwnership:false)] private void MyRpc() { } ```