# Table of Contents - [API Documentation | PurrNet](#api-documentation-purrnet) - [Simple Guides | PurrNet](#simple-guides-purrnet) - [Addons | PurrNet](#addons-purrnet) - [Survival Game | PurrNet](#survival-game-purrnet) - [Getting Started | PurrNet](#getting-started-purrnet) - [Dissonance | PurrNet](#dissonance-purrnet) - [Integrations | PurrNet](#integrations-purrnet) - [Unity Lobby Provider | PurrNet](#unity-lobby-provider-purrnet) - [Full game guides | PurrNet](#full-game-guides-purrnet) - [First Person Shooter | PurrNet](#first-person-shooter-purrnet) - [Making your own provider | PurrNet](#making-your-own-provider-purrnet) - [Steam Provider | PurrNet](#steam-provider-purrnet) - [Installation | PurrNet](#installation-purrnet) - [Minimal Setup | PurrNet](#minimal-setup-purrnet) - [Tools | PurrNet](#tools-purrnet) - [Cozy Weather | PurrNet](#cozy-weather-purrnet) - [Converting to PurrNet | PurrNet](#converting-to-purrnet-purrnet) - [Lobby System | PurrNet](#lobby-system-purrnet) - [Conversion Tool | PurrNet](#conversion-tool-purrnet) - [Community Guides | PurrNet](#community-guides-purrnet) - [Host | PurrNet](#host-purrnet) - [Network Transform | PurrNet](#network-transform-purrnet) - [Edgegap | PurrNet](#edgegap-purrnet) - [Pricing | PurrNet](#pricing-purrnet) - [Addon library | PurrNet](#addon-library-purrnet) - [Plug n' play components | PurrNet](#plug-n-play-components-purrnet) - [Network Reflection | PurrNet](#network-reflection-purrnet) - [Network Animator | PurrNet](#network-animator-purrnet) - [Client Side Prediction | PurrNet](#client-side-prediction-purrnet) - [PurrChat (AI assistant) | PurrNet](#purrchat-ai-assistant-purrnet) - [PlayerID | PurrNet](#playerid-purrnet) - [Network Ownership Toggle | PurrNet](#network-ownership-toggle-purrnet) - [Game Server Hosting | PurrNet](#game-server-hosting-purrnet) - [Easy Multiplayer Physics (input Sync) | PurrNet](#easy-multiplayer-physics-input-sync-purrnet) - [Terminology | PurrNet](#terminology-purrnet) - [PurrVoice (Voice chat) | PurrNet](#purrvoice-voice-chat-purrnet) - [Muting | PurrNet](#muting-purrnet) - [Early Access | PurrNet](#early-access-purrnet) - [Microphone Input | PurrNet](#microphone-input-purrnet) - [Physics in Multiplayer | PurrNet](#physics-in-multiplayer-purrnet) - [Compatibility | PurrNet](#compatibility-purrnet) - [Controller | PurrNet](#controller-purrnet) - [Performance | PurrNet](#performance-purrnet) - [Creating your own converter | PurrNet](#creating-your-own-converter-purrnet) - [Client Auth/Everyone (Unsafe) | PurrNet](#client-auth-everyone-unsafe-purrnet) - [Lip Sync | PurrNet](#lip-sync-purrnet) - [For Studios | PurrNet](#for-studios-purrnet) - [Channels | PurrNet](#channels-purrnet) - [Providers | PurrNet](#providers-purrnet) - [Output Providers | PurrNet](#output-providers-purrnet) - [Input Providers | PurrNet](#input-providers-purrnet) - [Support PurrNet | PurrNet](#support-purrnet-purrnet) - [Network Rigidbody | PurrNet](#network-rigidbody-purrnet) - [Server Auth (Safe) | PurrNet](#server-auth-safe-purrnet) - [Unity Multiplayer Services with PurrNet | PurrNet](#unity-multiplayer-services-with-purrnet-purrnet) - [Filters | PurrNet](#filters-purrnet) - [Network State Machine | PurrNet](#network-state-machine-purrnet) - [Playomoji | PurrNet](#playomoji-purrnet) - [Converting from Mirror | PurrNet](#converting-from-mirror-purrnet) - [Bandwidth Profiler | PurrNet](#bandwidth-profiler-purrnet) - [A Smithing Game | PurrNet](#a-smithing-game-purrnet) - [Chat system with broadcasts | PurrNet](#chat-system-with-broadcasts-purrnet) - [Code stripping | PurrNet](#code-stripping-purrnet) - [Made with PurrNet | PurrNet](#made-with-purrnet-purrnet) - [Glorb | PurrNet](#glorb-purrnet) - [Bao Bao's™ Cozy Cleaning Services | PurrNet](#bao-bao-s-cozy-cleaning-services-purrnet) - [Five Heads | PurrNet](#five-heads-purrnet) - [BitPacker (Serialization) | PurrNet](#bitpacker-serialization-purrnet) - [GeckoShop | PurrNet](#geckoshop-purrnet) - [On-Together | PurrNet](#on-together-purrnet) - [Tower Arena Survive | PurrNet](#tower-arena-survive-purrnet) - [Purrdicted Character Controller Knockback | PurrNet](#purrdicted-character-controller-knockback-purrnet) - [Network Audio Source | PurrNet](#network-audio-source-purrnet) - [Sport Animals | PurrNet](#sport-animals-purrnet) - [RPC Benchmarks | PurrNet](#rpc-benchmarks-purrnet) - [Introduction | PurrNet](#introduction-purrnet) - [Systems and modules | PurrNet](#systems-and-modules-purrnet) - [Pantless | PurrNet](#pantless-purrnet) - [Symbiosis | PurrNet](#symbiosis-purrnet) - [Network Transform benchmarks | PurrNet](#network-transform-benchmarks-purrnet) - [Broadcasts | PurrNet](#broadcasts-purrnet) - [Collider Rollback (Lag compensation) | PurrNet](#collider-rollback-lag-compensation-purrnet) - [Addressables | PurrNet](#addressables-purrnet) - [Scene Management | PurrNet](#scene-management-purrnet) - [Owner Auth | PurrNet](#owner-auth-purrnet) - [Purrdicted Character Controller | PurrNet](#purrdicted-character-controller-purrnet) - [Security Model | PurrNet](#security-model-purrnet) - [CRACKED | PurrNet](#cracked-purrnet) - [SyncBigData | PurrNet](#syncbigdata-purrnet) - [SyncTextureFile | PurrNet](#synctexturefile-purrnet) - [PurrDiction | PurrNet](#purrdiction-purrnet) - [Incremental Game Sample | PurrNet](#incremental-game-sample-purrnet) - [Network Prefabs | PurrNet](#network-prefabs-purrnet) - [Network Assets | PurrNet](#network-assets-purrnet) - [Addressable RPC's | PurrNet](#addressable-rpc-s-purrnet) - [Instance Handler | PurrNet](#instance-handler-purrnet) - [Don't destroy on load | PurrNet](#don-t-destroy-on-load-purrnet) - [Static RPC | PurrNet](#static-rpc-purrnet) - [Network Visibility | PurrNet](#network-visibility-purrnet) - [Network Rules | PurrNet](#network-rules-purrnet) - [Development Shortcuts | PurrNet](#development-shortcuts-purrnet) - [UniTask | PurrNet](#unitask-purrnet) - [Network Manager | PurrNet](#network-manager-purrnet) - [Generic RPC | PurrNet](#generic-rpc-purrnet) - [Views and Interpolation | PurrNet](#views-and-interpolation-purrnet) - [State Handling | PurrNet](#state-handling-purrnet) - [Using the visibility | PurrNet](#using-the-visibility-purrnet) - [Pooling | PurrNet](#pooling-purrnet) - [Awaitable RPC | PurrNet](#awaitable-rpc-purrnet) - [Pre/Post Processors | PurrNet](#pre-post-processors-purrnet) - [Overview | PurrNet](#overview-purrnet) - [Deterministic Identity | PurrNet](#deterministic-identity-purrnet) - [Installation | PurrNet](#installation-purrnet) - [Predicted Identities | PurrNet](#predicted-identities-purrnet) - [Networking custom classes, structs & types | PurrNet](#networking-custom-classes-structs-types-purrnet) - [Predicted Projectile (3D) | PurrNet](#predicted-projectile-3d-purrnet) - [EOS Transport (Community) | PurrNet](#eos-transport-community-purrnet) - [Built-in Components | PurrNet](#built-in-components-purrnet) - [Predicted State Machine | PurrNet](#predicted-state-machine-purrnet) - [Interacting With Multiple Identities | PurrNet](#interacting-with-multiple-identities-purrnet) - [Delta Packers | PurrNet](#delta-packers-purrnet) - [Addressable Scene handling | PurrNet](#addressable-scene-handling-purrnet) - [Purrnity Transport (Community) | PurrNet](#purrnity-transport-community-purrnet) - [Predicted Rigidbody (2D & 3D) | PurrNet](#predicted-rigidbody-2d-3d-purrnet) - [Spawning & Despawning | PurrNet](#spawning-despawning-purrnet) - [Predicted Identity Spawner | PurrNet](#predicted-identity-spawner-purrnet) - [Predicted State Node | PurrNet](#predicted-state-node-purrnet) - [Cirque de Slay | PurrNet](#cirque-de-slay-purrnet) - [NetworkBehaviour | PurrNet](#networkbehaviour-purrnet) - [Disposable Collections | PurrNet](#disposable-collections-purrnet) - [Common Pitfalls | PurrNet](#common-pitfalls-purrnet) - [Addressable Spawning & Despawning | PurrNet](#addressable-spawning-despawning-purrnet) - [Async Serialization (packing) | PurrNet](#async-serialization-packing-purrnet) - [Composite Transport | PurrNet](#composite-transport-purrnet) - [UDP Transport | PurrNet](#udp-transport-purrnet) - [Input Handling | PurrNet](#input-handling-purrnet) - [Direct Local Execution of RPCs | PurrNet](#direct-local-execution-of-rpcs-purrnet) - [Network Modules | PurrNet](#network-modules-purrnet) - [PlayerIdentity | PurrNet](#playeridentity-purrnet) - [Local Transport | PurrNet](#local-transport-purrnet) - [Predicted Timer Module | PurrNet](#predicted-timer-module-purrnet) - [Predicted Hierarchy | PurrNet](#predicted-hierarchy-purrnet) - [Web Transport | PurrNet](#web-transport-purrnet) - [Network Identity | PurrNet](#network-identity-purrnet) - [Predicted Transform | PurrNet](#predicted-transform-purrnet) - [Predicted Modules | PurrNet](#predicted-modules-purrnet) - [Steam transport setup | PurrNet](#steam-transport-setup-purrnet) - [Transports | PurrNet](#transports-purrnet) - [Converting from FishNet | PurrNet](#converting-from-fishnet-purrnet) - [Best Practices | PurrNet](#best-practices-purrnet) - [Simulating latency | PurrNet](#simulating-latency-purrnet) - [Authentication | PurrNet](#authentication-purrnet) - [Steam Transport | PurrNet](#steam-transport-purrnet) - [Unique to PurrNet | PurrNet](#unique-to-purrnet-purrnet) - [Purr Transport | PurrNet](#purr-transport-purrnet) - [Ownership | PurrNet](#ownership-purrnet) - [Distance condition | PurrNet](#distance-condition-purrnet) - [Client Spawning Validation | PurrNet](#client-spawning-validation-purrnet) - [Sync Types | PurrNet](#sync-types-purrnet) - [Execution Flow | PurrNet](#execution-flow-purrnet) - [Remote Procedure Calls | PurrNet](#remote-procedure-calls-purrnet) - [SyncArray | PurrNet](#syncarray-purrnet) - [SyncTimer | PurrNet](#synctimer-purrnet) - [SyncEvent | PurrNet](#syncevent-purrnet) - [SyncVar | PurrNet](#syncvar-purrnet) - [SyncQueue | PurrNet](#syncqueue-purrnet) - [SyncHashset | PurrNet](#synchashset-purrnet) - [SyncInput | PurrNet](#syncinput-purrnet) - [SyncList | PurrNet](#synclist-purrnet) - [Validated SyncVar | PurrNet](#validated-syncvar-purrnet) - [Roadmap | PurrNet](#roadmap-purrnet) - [SyncDictionary | PurrNet](#syncdictionary-purrnet) --- # API Documentation | PurrNet You can find our API documentation below. Release: [https://purrnet.github.io/PurrNet/release/arrow-up-right](https://purrnet.github.io/PurrNet/release) Dev: [https://purrnet.github.io/PurrNet/dev/arrow-up-right](https://purrnet.github.io/PurrNet/dev/) [PreviousGlorbchevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/glorb) Last updated 2 months ago --- # Simple Guides | PurrNet [Physics in Multiplayerchevron-right](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer) [Easy Multiplayer Physics (input Sync)chevron-right](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync) [PreviousConverting from FishNetchevron-left](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet) [NextPhysics in Multiplayerchevron-right](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer) --- # Addons | PurrNet [Lobby Systemchevron-right](https://purrnet.gitbook.io/docs/addons/lobby-system) [PreviousEdgegapchevron-left](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap) [NextLobby Systemchevron-right](https://purrnet.gitbook.io/docs/addons/lobby-system) --- # Survival Game | PurrNet The whole series is out as videos, and more videos can be added in the future, so keep an eye on the playlist. In the series, we go over various topics so it all works in multiplayer with Unity and PurrNet! Some of the topics we go over: * Setting up the character w. animations * Drag and drop inventory system * Equip, drop or consume items * Hit resources with loot drops * Dynamically spawn resources * Use a storage chest that everyone can access * Health & Hunger * And more So get started with making your own survival multiplayer game from scratch completely for free! [PreviousFirst Person Shooterchevron-left](https://purrnet.gitbook.io/docs/full-game-guides/first-person-shooter) [NextCommunity Guideschevron-right](https://purrnet.gitbook.io/docs/community-guides) Last updated 1 year ago --- # Getting Started | PurrNet [unityInstallationchevron-right](https://purrnet.gitbook.io/docs/getting-started/installation-setup) [sign-posts-wrenchMinimal Setupchevron-right](https://purrnet.gitbook.io/docs/getting-started/getting-started) [arrow-down-arrow-upConverting to PurrNetchevron-right](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet) [PreviousFor Studioschevron-left](https://purrnet.gitbook.io/docs/readme/for-studios) [NextInstallationchevron-right](https://purrnet.gitbook.io/docs/getting-started/installation-setup) --- # Dissonance | PurrNet The PurrNet team has made a simple integration to the Dissonance voice chat system. Setup on the PurrNet end is as simple as adding the PurrNetCommsNetwork component to the manager which holds the DissonanceComms component already. And the rest of the setup is on Dissonance. Get the integration with installation instructions here on GitHub: [https://github.com/BobsiUnity/PurrNet-VoiceChatarrow-up-right](https://github.com/BobsiUnity/PurrNet-VoiceChat) [PreviousUniTaskchevron-left](https://purrnet.gitbook.io/docs/integrations/unitask) [NextCozy Weatherchevron-right](https://purrnet.gitbook.io/docs/integrations/cozy-weather) Last updated 1 year ago --- # Integrations | PurrNet This will be the docs for integrations both made by the PurrNet team or individuals sharing their works. This will be integrations for external tools. [PreviousControllerchevron-left](https://purrnet.gitbook.io/docs/terminology/controller) [NextUniTaskchevron-right](https://purrnet.gitbook.io/docs/integrations/unitask) Last updated 1 year ago --- # Unity Lobby Provider | PurrNet This is a community made provider, so use it at your own. It should plug seamlessly into the Lobby system as a provider, and make your life as a developer easy when working with Unity's lobby setup! [PreviousSteam Providerchevron-left](https://purrnet.gitbook.io/docs/addons/lobby-system/steam-provider) [NextTerminologychevron-right](https://purrnet.gitbook.io/docs/terminology) Last updated 9 months ago --- # Full game guides | PurrNet [📈Incremental Game Samplechevron-right](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample) [🔫First Person Shooterchevron-right](https://purrnet.gitbook.io/docs/full-game-guides/first-person-shooter) [🪓Survival Gamechevron-right](https://purrnet.gitbook.io/docs/full-game-guides/survival-game) [PreviousNetwork Audio Sourcechevron-left](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source) [NextIncremental Game Samplechevron-right](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample) Last updated 1 month ago --- # First Person Shooter | PurrNet The whole series is out as videos, and more videos can be added in the future, so keep an eye on the playlist. The series takes you through a bunch of steps: * Setting up PurrNet * Creating your character networked with inverse kinematics/rigging * Using the networked state-machine to control game states & weapon switching * Setting up a lobby and integrating Steam * And much more! So get started by watching the videos, following along and remember to ask any questions in the comments or in our [Discord serverarrow-up-right](https://discord.gg/HnNKdkq9ta) [PreviousIncremental Game Samplechevron-left](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample) [NextSurvival Gamechevron-right](https://purrnet.gitbook.io/docs/full-game-guides/survival-game) Last updated 1 year ago --- # Making your own provider | PurrNet Making your own provider is a simple process, but the "difficulty" of the task will be mostly determined by the provider you work with. All you essentially have to do is have a `MonoBehaviour` script that also inherits from the interface: `ILobbyProvider`. You can look into the ILobbyProvider or more easily, have your IDE auto fill out everything in the interface. From here you essentially have to fill out the functionality of the given tasks, as well as call the UnityActions where relevant. Naming should generally be self-explanatory. Feel free to always ask in the PurrNet Discord if you need any help. [PreviousLobby Systemchevron-left](https://purrnet.gitbook.io/docs/addons/lobby-system) [NextSteam Providerchevron-right](https://purrnet.gitbook.io/docs/addons/lobby-system/steam-provider) Last updated 9 months ago --- # Steam Provider | PurrNet This provider is built by the PurrNet team, and is a simple yet effective implementation for Steamworks.net, allowing you to easily create and close lobbies, invite friends or join by code. In order to work with the Steam provider, you need Steamworks.NET installed. If you don't have that already, don't worry! We got your back. Simply hit the "Add Steamworks.NET to Package Manager" button which is found on the **Steam Lobby Provider** component. Wait a few seconds and the import will start. Once done, you're ready to go! Start up the Steam client on your computer and hit play in Unity. It's as easy as that. [PreviousMaking your own providerchevron-left](https://purrnet.gitbook.io/docs/addons/lobby-system/making-your-own-provider) [NextUnity Lobby Providerchevron-right](https://purrnet.gitbook.io/docs/addons/lobby-system/unity-lobby-provider) Last updated 9 months ago --- # Installation | PurrNet Getting set up with PurrNet is very easy, and takes only a few steps to get you started making multiplayer games! [hashtag](https://purrnet.gitbook.io/docs/getting-started/installation-setup#adding-it-to-your-project) Adding it to your project -------------------------------------------------------------------------------------------------------------------------------------- You can add PurrNet to your project in 3 ways: ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/installation-setup#git-through-package-manager) **Git through Package Manager** Through the package manager you can add a package using a git URL. Just add the following URL, and whenever you need PurrNet updated, you can update it directly from the package manager: `https://github.com/PurrNet/PurrNet.git?path=/Assets/PurrNet#release` You can also use the `dev` branch if you're looking to access the latest features, though it may come at the cost of stability. You can switch at any time by using either URLs: `https://github.com/PurrNet/PurrNet.git?path=/Assets/PurrNet#dev` #### [hashtag](https://purrnet.gitbook.io/docs/getting-started/installation-setup#requirements) ⚠️ Requirements * **You must have Git installed** on your system for Unity to fetch packages via git URLs. * If you just installed Git, **restart Unity and Unity Hub** before trying again. * If it still doesn't work after restarting Unity, **restart your computer**. ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/installation-setup#unity-asset-store) **Unity Asset store** It's as easy as going to the asset store using this link, and adding it like a normal package: [https://assetstore.unity.com/packages/slug/297320arrow-up-right](https://assetstore.unity.com/packages/slug/297320) ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/installation-setup#package-import) **Package Import** You can download the latest release from [this pagearrow-up-right](https://github.com/PurrNet/PurrNet/releases) and simply double click it to import it into your project You can now continue on to the [Getting Started](https://purrnet.gitbook.io/docs/getting-started/getting-started) page [PreviousGetting Startedchevron-left](https://purrnet.gitbook.io/docs/getting-started) [NextMinimal Setupchevron-right](https://purrnet.gitbook.io/docs/getting-started/getting-started) Last updated 10 months ago * [Adding it to your project](https://purrnet.gitbook.io/docs/getting-started/installation-setup#adding-it-to-your-project) * [Git through Package Manager](https://purrnet.gitbook.io/docs/getting-started/installation-setup#git-through-package-manager) * [Unity Asset store](https://purrnet.gitbook.io/docs/getting-started/installation-setup#unity-asset-store) * [Package Import](https://purrnet.gitbook.io/docs/getting-started/installation-setup#package-import) --- # Minimal Setup | PurrNet In order to get started using PurrNet, it doesn't require a lot of effort. You simply need to set up a [network manager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) , and you're ready to go. So first, make sure to [get PurrNet installed](https://purrnet.gitbook.io/docs/getting-started/installation-setup) . 1. After you have PurrNet in your project, simply create a new Object in your scene and call it something like "Network" or "Network Manager". 2. To this object, you first add the "[Network Manager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) " component of PurrNet. 3. Populate the Network Rules field (Recommended: Unsafe) This will cause the Network Manager to fully unfold. 4. Populate the Network Prefabs field (Recommended: Just click the "New" button) And that's it! Try it now! Start your Unity editor and watch the network manager light up with both the server and client. This means that you are now running as [Host](https://purrnet.gitbook.io/docs/terminology/host) . [PreviousInstallationchevron-left](https://purrnet.gitbook.io/docs/getting-started/installation-setup) [NextConverting to PurrNetchevron-right](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet) Last updated 1 year ago --- # Tools | PurrNet [wand-magic-sparklesConversion Toolchevron-right](https://purrnet.gitbook.io/docs/tools/conversion-tool) [commentsPurrChat (AI assistant)chevron-right](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot) [microphone-linesPurrVoice (Voice chat)chevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat) [PreviousDevelopment Shortcutschevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/development-shortcuts) [NextConversion Toolchevron-right](https://purrnet.gitbook.io/docs/tools/conversion-tool) --- # Cozy Weather | PurrNet The PurrNet team together with Distant Lands have setup the integration of Cozy Link with PurrNet. Cozy Link: [https://assetstore.unity.com/packages/package/238669arrow-up-right](https://assetstore.unity.com/packages/package/238669) This allows you to super easily network the Cozy Weather system with PurrNet with no effort at all! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e0047641bad5dcea49df24d6ddc67a1f20fcebf8%252F0ec0a642-7e46-4eb1-87ad-f15fd1d9cb63.webp%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=4acf15dd&sv=2) [PreviousDissonancechevron-left](https://purrnet.gitbook.io/docs/integrations/dissonance) [NextMade with PurrNetchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet) Last updated 1 year ago --- # Converting to PurrNet | PurrNet If you're already familiar with other networking systems, check out these articles to more easily become accustomed to PurrNet given your existing experience! If you want to automate most of the conversion process, have a look at the [Conversion Tool](https://purrnet.gitbook.io/docs/tools/conversion-tool) . It handles code, prefabs, and scene conversion for you. [PreviousMinimal Setupchevron-left](https://purrnet.gitbook.io/docs/getting-started/getting-started) [NextConverting from Mirrorchevron-right](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror) Last updated 3 days ago --- # Lobby System | PurrNet With PurrNet, it's exceptionally easy to get started with a lobby in your game. We've built a plug n' play lobby setup for you to use with any provider (or self host) that you'd like. It comes with a Steam provider ready to use. ### [hashtag](https://purrnet.gitbook.io/docs/addons/lobby-system#importing) Importing PurrNet has it's own Addon Library window, which you can use to import tools, systems, examples and more with just one click. Start by going to: **Tools -> PurrNet -> Addon Library** Here you'll be able to find the **PurrNet Lobby** setup. Go ahead and hit install and import it using the Unity import window that will pop up. ### [hashtag](https://purrnet.gitbook.io/docs/addons/lobby-system#setup) Setup Open up your build settings, and add the two scenes found in the setup to your build settings. These scenes are found in the **PurrLobby -> LobbyScenes** folder. After doing this, open up the **LobbySample** scene, and if you haven't already, import the TMP essentials from the newly shown window. ### [hashtag](https://purrnet.gitbook.io/docs/addons/lobby-system#choosing-a-provider) Choosing a provider At the time of writing this guide, the only providers available is the [**Steam Provider**](https://purrnet.gitbook.io/docs/addons/lobby-system/steam-provider) and the [**Unity Lobby Provider**](https://purrnet.gitbook.io/docs/addons/lobby-system/unity-lobby-provider) **.** The steam provider is already in the scene by default. If you want to use a different provider, you can go to the [Making your own provider](https://purrnet.gitbook.io/docs/addons/lobby-system/making-your-own-provider) section to read more. Alternatively, more providers might be available already, or built by the community. In order to choose a provider, all you need to do is open the **LobbyManager** gameobject in the hierarchy. Here you'll see the **Lobby Manager** component, which acts as the brain of the whole system. Then you have "providers" which are responsible for the communication between the brain and the database of your choice (ex. Steam, Unity Lobbies, Epic Games, Your own database, etc.) You can change providers by having them on the **LobbyManager** gameobject as components. A dropdown will be at the top of the **Lobby Manager** component, allowing you to choose which provider to use. ### [hashtag](https://purrnet.gitbook.io/docs/addons/lobby-system#lobby-manager) Lobby Manager As mentioned in the [Choosing a provider](https://purrnet.gitbook.io/docs/addons/lobby-system#choosing-a-provider) section, the **Lobby Manager** acts as the brain of the system. It's very simple to use, yet very powerful for both debugging and an easy setup. A few foldout groups can be found on this: * Create Room Arguments These are the arguments used when creating a new room. These can be overwritten through code, so what you see in the inspector, are simply defaults. The Room Properties act as meta information stored in the lobbies (if the provider allows for it). This is also what will be used as parameters for the searching with the browser tool. * Search Room Arguments These are responsible for the "filters" used when browsing lobbies. If these don't align with anything that is in the properties when the room is created, it won't be able to find the room. Some default values are setup for you already. * Lobby Room Status This status view is very powerful, in order for you to see what the **Lobby Manager** sees of information. You can get a full overview of the lobby, including it's members and information of those. This information will work regardless of which provider you choose to use! [PreviousAddonschevron-left](https://purrnet.gitbook.io/docs/addons) [NextMaking your own providerchevron-right](https://purrnet.gitbook.io/docs/addons/lobby-system/making-your-own-provider) Last updated 9 months ago * [Importing](https://purrnet.gitbook.io/docs/addons/lobby-system#importing) * [Setup](https://purrnet.gitbook.io/docs/addons/lobby-system#setup) * [Choosing a provider](https://purrnet.gitbook.io/docs/addons/lobby-system#choosing-a-provider) * [Lobby Manager](https://purrnet.gitbook.io/docs/addons/lobby-system#lobby-manager) --- # Conversion Tool | PurrNet Switching networking solutions mid-project is painful. The PurrNet Conversion Tool takes care of the heavy lifting for you, converting your code, prefabs, and scenes from other networking systems to PurrNet. It works through a recipe system where each source networking library has its own set of mappings and conversion logic. Right now, **FishNet** is supported out of the box, with more converters coming. [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool#what-it-does) What it does ----------------------------------------------------------------------------------------------- The tool handles three layers of conversion: **Code** uses Roslyn to parse your C# files and apply type, namespace, method, property, and attribute mappings. It also handles trickier cases like merging FishNet's separate `OnStartServer`/`OnStartClient` into PurrNet's unified `OnSpawned(bool asServer)`. **Prefabs** finds networking components on your prefabs (like `NetworkTransform`, `NetworkAnimator`, `NetworkObject`) and swaps them out for the PurrNet equivalents, copying over relevant settings. **Scenes** does the same for scene objects, including full `NetworkManager` conversion with transport setup and tick rate transfer. Each step runs independently so you stay in control of the process. [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool#installation) Installation ----------------------------------------------------------------------------------------------- ### [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool#via-purrnet-package-manager) Via PurrNet Package Manager The easiest way. If you already have PurrNet installed, open the **PurrNet Package Manager** in Unity and install the Conversion Tool with a single click. ### [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool#via-git-url) Via git URL You can also add the package through the Unity Package Manager using this git URL: Copy https://github.com/PurrNet/PurrNet-Conversion-Tool.git?path=/Assets/PurrNet-Conversion Or add it directly to your `Packages/manifest.json`: ### [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool#requirements) Requirements You need [PurrNetarrow-up-right](https://github.com/PurrNet/PurrNet) installed in your project. The source networking library you're converting from (e.g. FishNet) also needs to be present, since the tool reads its components during prefab and scene conversion. [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool#how-to-use-it) How to use it ------------------------------------------------------------------------------------------------- Open the tool from **Tools > PurrNet > Conversion Tool**. 1. Select the networking system you're converting from in the dropdown 2. Set which folders to include for scripts, prefabs, and scenes (defaults to the entire Assets folder) 3. Run each conversion step in order: **Convert Code**, then **Convert Prefabs**, then **Convert Scenes** The conversion log at the bottom of the window shows you what changed. A `ConversionChangelog.txt` file is also written to your project root with timestamped details of every modification. After conversion, you can remove the old networking library from your project. circle-exclamation Make sure your project is backed up prior to beginning any conversion! circle-info The tool also supports creating custom converters for other networking libraries. See [Creating your own converter](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter) for more details. [PreviousToolschevron-left](https://purrnet.gitbook.io/docs/tools) [NextCreating your own converterchevron-right](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter) Last updated 3 days ago * [What it does](https://purrnet.gitbook.io/docs/tools/conversion-tool#what-it-does) * [Installation](https://purrnet.gitbook.io/docs/tools/conversion-tool#installation) * [Via PurrNet Package Manager](https://purrnet.gitbook.io/docs/tools/conversion-tool#via-purrnet-package-manager) * [Via git URL](https://purrnet.gitbook.io/docs/tools/conversion-tool#via-git-url) * [Requirements](https://purrnet.gitbook.io/docs/tools/conversion-tool#requirements) * [How to use it](https://purrnet.gitbook.io/docs/tools/conversion-tool#how-to-use-it) Copy "dev.purrnet.conversion": "https://github.com/PurrNet/PurrNet-Conversion-Tool.git?path=/Assets/PurrNet-Conversion" --- # Community Guides | PurrNet Want to share your knowledge or unique use-cases with PurrNet? This section is for community-written guides, tips, and tutorials—almost like a blog. * **Anyone can contribute:** Submit a PR to add your guide or update existing ones. * **Get credit:** Your name (and links) will be shown as the author. * **Be part of PurrNet:** Help others, show off your work, and grow the community. ### [hashtag](https://purrnet.gitbook.io/docs/community-guides#how-to-contribute) How to Contribute 1. Fork the repo [https://github.com/PurrNet/PurrDocsarrow-up-right](https://github.com/PurrNet/PurrDocs) 2. Add your guide as a new markdown file in the `CommunityGuides` folder. 3. Include your name and (optionally) a link to your profile or website. 4. Submit a pull request. We review all submissions for quality and relevance. Let’s build the best networking docs together! [PreviousSurvival Gamechevron-left](https://purrnet.gitbook.io/docs/full-game-guides/survival-game) [NextSteam transport setupchevron-right](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam) Last updated 10 months ago --- # Host | PurrNet Host means that you are running as both the server and the client. [PreviousClient Auth/Everyone (Unsafe)chevron-left](https://purrnet.gitbook.io/docs/terminology/client-auth-everyone-unsafe) [NextOwner Authchevron-right](https://purrnet.gitbook.io/docs/terminology/owner-auth) Last updated 1 year ago --- # Network Transform | PurrNet This component allows you to automatically network the Position, Rotation, Scale and parent of the corresponding transform to which it's attached. **Settings:** **Sync Settings:** These allow you to easily change what is being synchronized. **Interpolate Settings:** These allow you to modify which values gets interpolated (smoothed) **Owner Auth:** If false, the server will have authority **Sync Parent:** If you change the parent of the transform, will that also be synced. **Send interval ticks:** How often data should be sent. **Tolerances:** This allows you to adjust how much values should change before it syncs. Simply to avoid unnecessary synchronizing for numbers so small that they aren't noticeable. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-75ffa32301135018119f1d4e983ef2e0a19b1c13%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=7f7c4f21&sv=2) The Network Transform component with the default values [PreviousPlug n' play componentschevron-left](https://purrnet.gitbook.io/docs/plug-n-play-components) [NextNetwork Rigidbodychevron-right](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody) Last updated 2 months ago --- # Edgegap | PurrNet In this simple guide, we'll go through how you get started hosting your dedicated server(s) with Edgegap in PurrNet. Luckily, this is very easy! ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#preparation) Preparation circle-check You will need to have [an Edegap accountarrow-up-right](https://app.edgegap.com/auth/register) to properly follow this guide circle-check Using your account make sure to have [your tokenarrow-up-right](https://app.edgegap.com/user-settings?tab=tokens) at hand ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#getting-started) Getting Started First, start by getting setup and logged in to [Edgegaparrow-up-right](https://app.edgegap.com/) so you can access the dashboard. And after this, have a brief look at [their own documentationarrow-up-right](https://docs.edgegap.com/) , to get setup with their Unity editor tool. ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#installing-edgegap-in-unity) Installing Edgegap in Unity Go your your top toolbar: `Tools > PurrNet > Addon Library` and click `Install` on the `Edgegap` addon. This will automatically import the tool into your project! circle-exclamation **You must have** [**Git installed**arrow-up-right](https://git-scm.com/downloads) on your system for Unity to fetch packages via git URLs. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e8146d9d0ff3a81cc9f84cc112bc641029cc1188%252FScreenshot%25202025-10-14%2520213647.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=ee29b740&sv=2) To help you get started we have setup a menu item that allows you to verify all the required dependencies and also help guide you to properly install them or remind you to open required tools. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-70668af7bca010aa2f7eec835d1896bba72b6dd6%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=71d153c3&sv=2) But to give you a summary you need these tools: * [Docker arrow-up-right](https://www.docker.com/products/docker-desktop/) (Desktop or CLI) * Even after installed you need to make sure it is open when you want to upload a new server build * Install Unity Linux Build Support through UnityHub For more details checkout [Edegap's documentationenvelope](mailto:undefined) . ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#setting-up-purrnet) Setting up PurrNet When Edgegap is properly installed a little box will show up for supported transport that let's you toggle on or off the auto configuration of these transports. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-f5619bd6b5fb5e20113cb306ca39672283915aa6%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=b5bff7e7&sv=2) The auto configuration will detect when inside an Edgegap environment automatically and use environment variables to automatically setup the correct ports and even SSL settings on your behalf. The client still needs to properly configure these but you can rest assured that the server is handled. In case you have multiple of the same protocol for one server you might want to disable this and manually handle your setup accordingly. ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#deploying-the-server) Deploying the server There are a few steps required to reach from nothing to a deployed server. 1. Unity Linux Build 2. Containerize The Build Files 1. This is why you need docker installed, if you are not familiar with docker you can think of this step almost like zipping your project. I'm making a huge disservice to the technology behind the scenes but you don't need those details and can always learn more for yourself. 3. Upload container to Edgegap 4. Deploy your application! Feel free to checkout [Edgegap's documentationarrow-up-right](https://docs.edgegap.com/learn/unity-games/getting-started-with-servers) for proper detailed step by step on this but we will give you a quick getting started guide here too. #### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#speed-running-first-server) Speed running first server First, you want to open Edgegap's editor window. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-3711bfbd57c2af3d23af24ee7aacbbc0932da77e%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=fa496f19&sv=2) Then fill in your token: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-5fcf854538c3af216c76d16231f9586b62c962f9%252Fimage%2520%287%29.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=176f7065&sv=2) Once your token is setup and validated we can proceed to step 2 (building your server). All you have to do is press the `Build Server` button. This is just like a normal Unity build so make sure you have your scene list properly setup. The build target will be a Linux dedicated server which is why you had to install the extra modules. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-b55b9d70bb8bf408ab1f1174a775b814a4f6d57c%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=e677c489&sv=2) With the build done we need to containerize it. This is where docker comes into play, it is essentially packaging your build in a way that Edgegap can work with it. Just press `Containerize with Docker`. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-6392c6eeeb46d0b6bcfd73fc201ca7f4ca54e5b8%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=50f0474&sv=2) We will skip step 4, you can use it to locally run the container you just created to test the server locally (even if your operating system isn't Linux). In step 5 you need to choose your target application, a server image name (this can be whatever you want) and finally just press the `Upload image and Create app version` button. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-290a18c6bf17ae48e5f2eb691a94f0c499ff1077%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=7bb82978&sv=2) Once it finishes you can either `Deploy to Cloud` from the website dashboard or from step 6. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-94e907e5284424f4921f7cbd09356d96b3af7561%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=e216283a&sv=2) Every container you upload will have a version, by default it's the date that it was built at which you can use to select the latest. Once you have an application that is deployed you can try to connect from your editor or a client build. On the website's dashboard you can manually find the information required to connect: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-0609fed0a0d48fd3d637dca799e313cc6dfbe7de%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5ea4cf99&sv=2) You would use the \`Host\` as your address and \`External\` port as your client port. Here is an example of a correct setup inside Unity (notice `Server Port` and `Address`): ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-c30da498065c34591b249c587f4906f44f093c01%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=732ff0a4&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#easy-as-that) Easy as that! Now you know how to get a server up and running and connect to it! You can expand on this by using their [matchmaking servicearrow-up-right](https://docs.edgegap.com/learn/matchmaking/getting-started) to automatically create matches for your users. [PreviousGame Server Hostingchevron-left](https://purrnet.gitbook.io/docs/game-server-hosting) [NextAddonschevron-right](https://purrnet.gitbook.io/docs/addons) Last updated 5 months ago * [Preparation](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#preparation) * [Getting Started](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#getting-started) * [Installing Edgegap in Unity](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#installing-edgegap-in-unity) * [Setting up PurrNet](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#setting-up-purrnet) * [Deploying the server](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#deploying-the-server) * [Easy as that!](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap#easy-as-that) --- # Pricing | PurrNet **PurrNet is 100% free** and will stay that way. No core features behind a paywall, no premium version, no catches. * No revenue sharing * No up-front cost * No credits required (but appreciated!) We also welcome tool creators to be part of our [addon library](https://purrnet.gitbook.io/docs/readme/addon-library) at no cost or revenue sharing. If anything is paid, it is external services or tools that are not core to PurrNet. Examples of these could be: * Full setups and integrations with other tools * Extensive templates or starter projects * Plug n' play tools For studio-level support (hands-on project access, migration planning, custom feature development), see our [studio support page](https://purrnet.gitbook.io/docs/readme/for-studios) . [PreviousUnique to PurrNetchevron-left](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet) [NextCompatibilitychevron-right](https://purrnet.gitbook.io/docs/readme/compatibility) Last updated 11 days ago --- # Addon library | PurrNet The Asset Library is a custom Unity window found under **Window -> PurrNet Addon Library.** This window can be used to pull most things "optional" for PurrNet, such as new transports, examples, tools and more. [PreviousCompatibilitychevron-left](https://purrnet.gitbook.io/docs/readme/compatibility) [NextRoadmapchevron-right](https://purrnet.gitbook.io/docs/readme/roadmap) Last updated 1 year ago --- # Plug n' play components | PurrNet Some networking tasks come up in almost every multiplayer game: syncing an object's position, keeping animations in sync, or replicating physics. Writing custom sync code for each of these every time would be tedious and error-prone. Plug n' play components let you skip that entirely. Simply add a component to your object, and it automatically handles the networking for you. Normal examples of this are the [Network Transform](https://purrnet.gitbook.io/docs/plug-n-play-components/network-transform) allowing you to automatically network the position, rotation and scale of an object, or the [Network Animator](https://purrnet.gitbook.io/docs/plug-n-play-components/network-animator) allowing you to automatically sync animations. [PreviousLip Syncchevron-left](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/lip-sync) [NextNetwork Transformchevron-right](https://purrnet.gitbook.io/docs/plug-n-play-components/network-transform) Last updated 11 days ago --- # Network Reflection | PurrNet The Network Reflection component will take another component as input, and will expose any values within, in order for you to toggle which should be automatically synchronized across the network. This makes it super easy for you to convert your scripts from single player to multiplayer. This can be done with any component, even Unity default ones. It also holds the "Owner Auth" setting, in order to change who is responsible for this synchronizing, whether that'd be the owner or server. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-29ae67df37cc7f7a33819be9c01b6c18b87b9ff4%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=7cd05a64&sv=2) Example of the component used on a trail renderer to auto sync the emitting property [PreviousNetwork Ownership Togglechevron-left](https://purrnet.gitbook.io/docs/plug-n-play-components/network-ownership-toggle) [NextNetwork State Machinechevron-right](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked) Last updated 1 year ago --- # Network Animator | PurrNet This allows you to automatically network any parameter value within the Animator. This component needs to be on the same object as an Animator, otherwise it won't work. It will expose the values within the animator, of which you can toggle on and off in order to choose which to sync and which not to sync. **Beware:** This does not automatically sync "triggers". A trigger would have to be called through the NetworkAnimator component, similar to how it is called on a regular animator. This is how you'd call a trigger: The Network Animator also allows for you to control the animations directly through the Network Animator, so you don't need a reference to both the normal Animator as well as the Network Animator. So you can easily handle parameters as well: [PreviousNetwork Rigidbodychevron-left](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody) [NextNetwork Ownership Togglechevron-right](https://purrnet.gitbook.io/docs/plug-n-play-components/network-ownership-toggle) Last updated 2 months ago Copy private NetworkAnimator _netAnimator; private void DoPunch() { _netAnimator.SetTrigger("Punch"); } Copy private NetworkAnimator _netAnimator; private RigidBody _playerRigidbody; private void DoPunch() { _netAnimator.SetFloat("Speed", _playerRigidbody.Velocity.magnitude); _netAnimator.SetBool("IsMoving", _playerRigidbody.Velocity.magnitude > 0.1f); //This can be done for all actions of the Animator. Above is just two examples. } --- # Client Side Prediction | PurrNet Neotime has created a range of great posts about the what & why of prediction as well as going through how you should work with it, with real examples from his own game and implementation. Start reading more about how to work with PurrNets own PurrDiction on the blog post below. [![Logo](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fzayedcharef.io%2Fwp-content%2Fuploads%2F2025%2F08%2Fsquare-1-fix-1-edited-300x300.jpg&width=20&dpr=3&quality=100&sign=5e4f380b&sv=2)Part 1: The “What & Why” of Client-Side Prediction - Zayed Charef - blogZayed Charef - blog -chevron-right](https://zayedcharef.io/part-1-the-what-why-of-client-side-prediction/) [PreviousChat system with broadcastschevron-left](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system) [NextPurrdicted Character Controllerchevron-right](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction) Last updated 7 months ago --- # PurrChat (AI assistant) | PurrNet ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fwww.purrchat.app%2Fpurrchat_og.png&width=768&dpr=3&quality=100&sign=9ecac97c&sv=2) PurrChat Banner Netcode is the hardest part of game dev. PurrChat makes it simpler. You’ve been there: stuck in the docs, posting in the discord, experimenting for hours just to figure out one detail. Or maybe you’re deep into PurrDiction, fighting jittery movement, rollback bugs, and trying to understand what belongs in state and what doesn’t. What if instead you could just ask? Always available. Always grounded in the official docs and real source code. With PurrChat, those hours of guesswork become minutes of clear, source-backed answers. 👉 Built on the official PurrNet documentation and real PurrNet/PurrDiction source code. * * * [hashtag](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#why-developers-choose-purrchat) Why developers choose PurrChat --------------------------------------------------------------------------------------------------------------------------------------- * Really understands PurrNet and what you're trying to achieve: connected to the official docs and source code. * Answers like the PurrNet creators would give you: connected to verified & professional source code. * Aligned with best practices shared by PurrNet purrgrammers. * No rabbit holes: precise answers only from reliable sources — no guesswork. * * * [hashtag](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#how-purrchat-helps) How PurrChat helps --------------------------------------------------------------------------------------------------------------- * "Netcode is hard." PurrChat turns vague theory into clear, working patterns. * Server vs client authority? Understand trade-offs and when to use each. * Prediction headaches? Get structured controllers that reduce input latency the right way. * Smooth owners, jittery others? Fix interpolation and prediction mismatches in minutes. * Rollback nightmares? Separate visuals from deterministic state with confidence. * Practical flows. From creating lobbies to scene switching, guided setups with pitfalls explained. * * * [hashtag](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#focus-on-what-really-matters) Focus on what really matters ----------------------------------------------------------------------------------------------------------------------------------- PurrNet is only a tool. What you really want is to craft and release your game. Don’t waste sprints on rewrites, debugging, or hunting through docs. PurrChat gives you the clarity to move fast and build the multiplayer game you imagined. * * * [hashtag](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#get-started) Get started ------------------------------------------------------------------------------------------------- 1. [Open PurrChatarrow-up-right](https://purrchat.app/) and sign in. 2. Ask your PurrNet question (or paste a C# snippet). 3. Get your answer — clear, cited, and ready to use. Don't lose more hours to searching, debugging, or experimenting. Focus on what matters most: crafting and releasing your game. [Start todayarrow-up-right](https://purrchat.app/) . Questions or support? Join our Discord: https://discord.gg/NP9tP9Qx9R * * * [hashtag](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#ready-to-ask-prompts) Ready-to-ask prompts ------------------------------------------------------------------------------------------------------------------- * "Explain server authority vs client authority in PurrNet. Which should I use for \[my mechanic\], and why?" * "Create a minimal client-side prediction controller for \[movement\], with Simulate() logic and UpdateView() visuals." * "Owned player is smooth; non-owned players jitter. Show me the correct view/interpolation setup and where I likely went wrong." * "Observed players rollback when I use prediction. Audit my state vs view responsibilities and fix the rollback cause." * "What belongs in my predicted state for a melee combo system? What must stay out (and where do I put it)?" * "How do I build a lobby with the Lobby System and then move players into a game scene?" * "Why is my async RPC not returning on the client? Show the correct async RPC pattern with return values." * * * [PreviousCreating your own converterchevron-left](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter) [NextPurrVoice (Voice chat)chevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat) Last updated 4 months ago * [Why developers choose PurrChat](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#why-developers-choose-purrchat) * [How PurrChat helps](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#how-purrchat-helps) * [Focus on what really matters](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#focus-on-what-really-matters) * [Get started](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#get-started) * [Ready-to-ask prompts](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot#ready-to-ask-prompts) --- # PlayerID | PurrNet PlayerID is the struct which PurrNet uses to keep track of all clients that is currently or previously was connected to the server. This data can remain persistent, depending on your settings on the Network Manager. If a client disconnect, and later reconnects, they will keep the same data such as player ID and meta information. This makes it easy to identify players, implement saving/loading and much more. [PreviousServer Auth (Safe)chevron-left](https://purrnet.gitbook.io/docs/terminology/server-auth-safe) [NextControllerchevron-right](https://purrnet.gitbook.io/docs/terminology/controller) Last updated 1 year ago --- # Network Ownership Toggle | PurrNet This component allows you to enable/activate components/gameobjects based on [ownership](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/ownership) . Notice that there is a toggle on the far right of every entry, this toggle reads as `Activate if I'm the owner, otherwise deactivate`. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-d30f7e55c5deb43886e39ec81bfbb081b4685d6e%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=6749eb51&sv=2) In the example above, `PlayerMovement` would be enabled **only** for the owner and disabled for others while the `Text` GameObject would be disabled for the owner but active for others. This is quite handy to quickly setup a networked prefab without having to write too much code. [PreviousNetwork Animatorchevron-left](https://purrnet.gitbook.io/docs/plug-n-play-components/network-animator) [NextNetwork Reflectionchevron-right](https://purrnet.gitbook.io/docs/plug-n-play-components/network-reflection-auto-sync) Last updated 10 months ago --- # Game Server Hosting | PurrNet A dedicated server is a separate application that runs your game's server logic without any client-side components like rendering, input handling, or UI. This approach provides better performance, security, and scalability for multiplayer games. The cost? Well you need the server hosted somewhere. Whether that is self-hosting or utilizing a platform like [Edgegap](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap) to more easily do so. Unlike a [Host](https://purrnet.gitbook.io/docs/terminology/host) setup where one player acts as both server and client, a dedicated server: * Runs independently without any player directly connected to the machine * Handles only server-side game logic and networking * Doesn't render graphics or process player input locally * Can run on specialized server hardware or cloud platforms * Provides consistent performance regardless of player connections ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting#benefits-of-dedicated-servers) Benefits of Dedicated Servers #### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting#performance) Performance * Server performance isn't affected by client-side rendering or input * Can run on optimized server hardware * Better tick rate consistency #### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting#security) Security * Server authority is maintained on separate, controlled hardware * Reduced risk of cheating through client manipulation * Game state validation happens on trusted infrastructure #### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting#scalability) Scalability * Multiple server instances can run simultaneously * Players can connect from anywhere without relying on peer-to-peer connections * Easier handling of player disconnections and reconnections ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting#downsides) Downsides? Well it's more setup, and typically higher running-costs than something like a relay or direct LAN connection. Also, if you have game logic that for some reason relies on visuals, this could be more complicated to handle on a headless server, given that rendering isn't happening. ### [hashtag](https://purrnet.gitbook.io/docs/game-server-hosting#setting-up-a-dedicated-server-with-purrnet) Setting up a dedicated server with PurrNet In general the logic is pretty straight forward. Choose your transport of choice, typically this would be the [UDP transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/udp-transport) or the [Web Transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/web-transport) depending on your game's target platform. You spin up a dedicated server, and provide the transport layer with the IP and port. From here, you can make a headless build and deploy that to your server, and clients should now be able to connect. PurrNet already has the auto start flags in the [network manager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) . It's generally recommended setting this to start with a server build, to make sure the connection is being spun up automatically. If you have scene handling in your game, like starting in a main menu, you'll also need to redirect your server to move to the game scene for it to be able to run the connection. [PreviousUnity Multiplayer Services with PurrNetchevron-left](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet) [NextEdgegapchevron-right](https://purrnet.gitbook.io/docs/game-server-hosting/edgegap) Last updated 2 days ago * [Benefits of Dedicated Servers](https://purrnet.gitbook.io/docs/game-server-hosting#benefits-of-dedicated-servers) * [Downsides?](https://purrnet.gitbook.io/docs/game-server-hosting#downsides) * [Setting up a dedicated server with PurrNet](https://purrnet.gitbook.io/docs/game-server-hosting#setting-up-a-dedicated-server-with-purrnet) --- # Easy Multiplayer Physics (input Sync) | PurrNet The easiest way (in my humble opinion) to add physics interactions in multiplayer, is to use Input Synchronizing. The most popular alternative is Client Side Prediction (CSP). Both have their pros and cons. circle-info If you need physics in multiplayer, you might want to read the [Physics In Multiplayer](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer) page to see your options. circle-exclamation \[Old\] This can be done much easier now by utilizing the [SyncInput](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput) network module! Guide going over implementing Input Synchronization with PurrNet ### [hashtag](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#input-sync-vs-client-side-prediction) Input Sync VS Client Side Prediction #### [hashtag](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#client-side-prediction) Client Side Prediction ✔️ Instant response for players ✔️ Easy to cheat proof ❌ Difficult logic and hard to work with #### [hashtag](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#input-sync) Input Sync ✔️ Easy workflow (nearly as easy as single player code) ✔️ Easy to cheat proof ❌ Sensitive to ping Keep in mind, that even though it won't actually be as instant as something running locally, you can still [smoke and mirrorarrow-up-right](https://dictionary.cambridge.org/dictionary/english/smoke-and-mirrors) it to make it feel instant locally, by polishing to your game. This can be done with animations handled locally for example. ### [hashtag](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#why-cant-we-just-do-physics-for-each-client) Why can't we just do physics for each client? If the physics in your game does not interact between players, then you can do that just fine. However, if you want interactions between players, there has to be a single point of truth. This is why various techniques has to be used in order to properly network physics interactions. They all come with their pros and cons, and in the end, it's all about you picking something suitable for your game! ### [hashtag](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#how-does-it-work) How does it work The idea and execution is very simple. Essentially it's fully a server auth simulation, that is conveyed to clients using the [Network Transform](https://purrnet.gitbook.io/docs/plug-n-play-components/network-transform) . Essentially: The client sends only it's input and intentions to the server, and the server does the actual action locally, and thus the server becomes the single point of truth for the whole game. This is what makes it cheat proof, and also able to handle physics interactions (because they are all simulated in one place) ### [hashtag](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#simple-example) Simple Example This is essentially the example code shown from the video, with comments added to explain what is going on. [PreviousPhysics in Multiplayerchevron-left](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer) [NextSystems and moduleschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules) Last updated 2 months ago * [Input Sync VS Client Side Prediction](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#input-sync-vs-client-side-prediction) * [Why can't we just do physics for each client?](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#why-cant-we-just-do-physics-for-each-client) * [How does it work](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#how-does-it-work) * [Simple Example](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync#simple-example) Copy [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; } --- # Terminology | PurrNet [Early Accesschevron-right](https://purrnet.gitbook.io/docs/terminology/early-access) [Channelschevron-right](https://purrnet.gitbook.io/docs/terminology/channels) [Client Auth/Everyone (Unsafe)chevron-right](https://purrnet.gitbook.io/docs/terminology/client-auth-everyone-unsafe) [Hostchevron-right](https://purrnet.gitbook.io/docs/terminology/host) [Owner Authchevron-right](https://purrnet.gitbook.io/docs/terminology/owner-auth) [Server Auth (Safe)chevron-right](https://purrnet.gitbook.io/docs/terminology/server-auth-safe) [PlayerIDchevron-right](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) [Controllerchevron-right](https://purrnet.gitbook.io/docs/terminology/controller) [PreviousUnity Lobby Providerchevron-left](https://purrnet.gitbook.io/docs/addons/lobby-system/unity-lobby-provider) [NextEarly Accesschevron-right](https://purrnet.gitbook.io/docs/terminology/early-access) Last updated 1 year ago --- # PurrVoice (Voice chat) | PurrNet circle-exclamation PurrVoice is currently in [**Early Access**](https://purrnet.gitbook.io/docs/terminology/early-access) **,** meaning that only paid support members in the Discord has access to it. **PurrVoice** makes adding voice chat to your game effortless. Built for PurrNet, it’s a plug-and-play system that supports dynamic, fully networked audio filters—handled on the sender, server, or receiver side. Filters can be added or removed at runtime and will automatically sync across the network. Each filter can have a custom strength on the instance that applies it, without wasting bandwidth syncing values unnecessarily. Whether you’re building casual co-op or competitive multiplayer, PurrVoice gives you a flexible, and powerful voice integration—out of the box. [PreviousPurrChat (AI assistant)chevron-left](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot) [NextProviderschevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers) Last updated 4 months ago --- # Muting | PurrNet Handling muting using the PurrVoicePlayer is simple as ever. First of all, there is an exposed "muted" bool in the inspector, toggling this will reveal it's effects. **Locally Muted** - If you mute your own player, no audio data will be sent over the network, and no processing will happen. **Remove muted** - If you mute a remote player (another client) you'll locally not playback their audio. You can easily handle this through script like so:\\ Copy [SerializeField] private PurrVoicePlayer _voicePlayer; private void SetMute(bool mute) { _voicePlayer.muted = mute; } [PreviousFilterschevron-left](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/filters) [NextMicrophone Inputchevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/microphone-input) Last updated 9 months ago --- # Early Access | PurrNet Early Access for PurrNet means that only paying members have access to these features **for now**. Full free access will come, but for now it's a closed forum. This is typically because the feature is still being thoroughly tested. We'll do our best to avoid completely unusable and non-production viable products to enter early access. Early access allows us to test new features in a more controlled environment, and avoid having to spend too much time publicly supporting something that we know isn't fully ready. This is to avoid us having to lose time, so we can focus on creating the best possible features for you! [PreviousTerminologychevron-left](https://purrnet.gitbook.io/docs/terminology) [NextChannelschevron-right](https://purrnet.gitbook.io/docs/terminology/channels) Last updated 1 year ago --- # Microphone Input | PurrNet You can easily see all microphone devices and change device as you want, allowing for an easy setup of settings and good runtime flexibility. You can also easily see your current device in the PurrVoicePlayer: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-d18be63bdabb9a2e3687800d65442c76f8dd7203%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=2a5968ad&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/microphone-input#getting-microphone-devices) Getting microphone devices Getting the devices is easy, as we've already setup the class you need for this. You can easily call the AudioDevices from any script, which holds a "read only" list of all devices that you can easily access like so: ### [hashtag](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/microphone-input#changing-microphone) Changing microphone Changing microphone is just a single call to your local PurrVoicePlayer. This simple example below will set your microphone to the first device in the devices list. [PreviousMutingchevron-left](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/muting) [NextLip Syncchevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/lip-sync) Last updated 9 months ago * [Getting microphone devices](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/microphone-input#getting-microphone-devices) * [Changing microphone](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/microphone-input#changing-microphone) Copy private IReadOnlyList GetDevices() { return AudioDevices.devices; } Copy [SerializeField] private PurrVoicePlayer _voicePlayer; // This should be our local player private void MicChangeTest() { _voicePlayer.ChangeMicrophone(AudioDevices.devices[0]); } --- # Physics in Multiplayer | PurrNet Networking physics is a tricky thing. Things need to align, collisions should be possible from all players and misalignment or lag are bound to happen with high ping. There are essentially 3 main ways you can get physics to work with PurrNet. Which one is best, entirely depends on your use case and experience as a developer! Method Network Rigidbody Input Sync PurrDiction (CSP) Workflow Plug n play Very easy Hard Responsive ✅ ❌ ✅ Competitive ❌ ✅ ✅ Simulation quality 2.5/5 5/5 5/5 Visual quality 4/5 5/5 4.5/5 As you can see from the matrix above, there isn't one solution that has everything. And of course there are more depth to the answers, which you can read below. ### [hashtag](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer#network-rigidbody) [Network Rigidbody](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody) The [NetworkRigidbody](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody) component is entirely plug-n-play. It'll do it's very best to try and synchronize physics forces over the network, so locally everything should feel responsive to all players. The issue is mis-alignments. Given the unpredictive nature of non-deterministic physics together with players with ping and varying input, the results are not highly precise. This means that it's a great option for games where high precision doesn't matter, as it'll feel good locally, and especially interactions with world objects will be responsive. However, if precision in your physics matter a lot, it'll not be the right option for you. ### [hashtag](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer#input-sync) [Input Sync](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput) This is the absolute easiest way to get physics over the network that'll look perfect and be 100% precise as well. On top of that, PurrNet has a [SyncInput](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput) module which handles most for you, and even allows you to emulate ping on the host for maximum fairness even in a peer to peer scenario. This option won't fail you. Where it does fall flat is on the responsive side. The nature of input sync is simple. Essentially the entire physics side of your game will be handled by the server, and players are essentially just watching the replay live. This means that it's very prone to input lag. But results will be as good as a local game! This is generally the best option if you have a heavy controller type, like a rolling ball or a character slipping on ice for example. Because it gives you an easy way to mitigate the input lag using polish such as animations that can be played locally to give the illusion of instant input, whilst the actual simulation is ran on the server. ### [hashtag](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer#purrdiction-client-side-prediction) [PurrDiction](https://purrnet.gitbook.io/docs/client-side-prediction) (Client Side Prediction) With PurrNet, we have our own implementation of the technique called client-side-prediction. An implementation we're very proud of. In short: CSP is fully server auth similar to Input Sync, with the difference being that the clients are attempting to predict what's going to happen on the server. This utilizes input extrapolation with visual interpolation to yield the best possible results. This will not be a 100% perfect output visually, but it will be perfect most of the time, and even when imperfections happen, it'll smoothly solve it. It yields instant input response, which makes it feel great as well, and given it's a server authorized simulation, it's also safe for competitive play. It can almost be seen as the mix between the Network Rigidbody and Input Sync, attempting to get the best of both worlds. It is very important to note that PurrDiction is essentially it's own networking solution that does work with PurrNet, but it's a different way of writing logic and working with multiplayer. And mixing "normal" networking and prediction, can be a sticky affair if you don't know how the two systems interact. [PreviousSimple Guideschevron-left](https://purrnet.gitbook.io/docs/guides) [NextEasy Multiplayer Physics (input Sync)chevron-right](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync) Last updated 11 days ago * [Network Rigidbody](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer#network-rigidbody) * [Input Sync](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer#input-sync) * [PurrDiction (Client Side Prediction)](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer#purrdiction-client-side-prediction) --- # Compatibility | PurrNet PurrNet supports every major platform, and the modular transport layer makes it easy to plug in the right transport for your target platform. The [Composite Transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/composite-transport) even allows you to use multiple transports at once for cross-platform networking. ✅ **PC** ✅ **Linux** ✅ **Mac** ✅ **Android** ✅ **iOS** ✅ **WebGL** ✅ **Nintendo Switch** ✅ **PlayStation consoles** ✅ **Xbox consoles** ✅ **Dedicated server** ✅ **Local server** [PreviousPricingchevron-left](https://purrnet.gitbook.io/docs/readme/pricing) [NextAddon librarychevron-right](https://purrnet.gitbook.io/docs/readme/addon-library) Last updated 11 days ago --- # Controller | PurrNet The controller of an identity is essentially the one that is most authorized over this object. isController returns true if: - You are the owner or: - There is no owner and you are the server However, there might also be scenarios where something can be toggled as to whether it is ownerAuthed or not. If it is not owner authed, only the server will ever be the controller. The above logic still applies for owner authed logic. [PreviousPlayerIDchevron-left](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) [NextIntegrationschevron-right](https://purrnet.gitbook.io/docs/integrations) Last updated 9 months ago --- # Performance | PurrNet circle-exclamation PurrNet has yet to do much active performance optimization. All results seen below are prior to most attempts at bettering performance, and it should only improve for future updates. We do our best to run non-biased tests, also meaning that we don't pick tests from what PurrNet will excel at, but rather tests that are relevant to users of network solutions. We do our best at sharing the method used for testing as well as the tool used for tracking the bandwidth usage. [hashtag](https://purrnet.gitbook.io/docs/readme/performance#average-of-all-tests) Average of all tests ------------------------------------------------------------------------------------------------------------ This currently includes data from the [RPC Benchmarks](https://purrnet.gitbook.io/docs/readme/performance/performance) and [Network Transform Benchmarks](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks) . This is purely an average of how PurrNet compares to other systems, so see the individual articles for more accurate representations. * **PurrNet vs. FishNet** → **49.00% lower usage** * **PurrNet vs. Mirror** → **67.80% lower usage** * **PurrNet vs. NetCode** → **72.56% lower usage** [PreviousRoadmapchevron-left](https://purrnet.gitbook.io/docs/readme/roadmap) [NextRPC Benchmarkschevron-right](https://purrnet.gitbook.io/docs/readme/performance/performance) Last updated 1 year ago --- # Creating your own converter | PurrNet The Conversion Tool uses a recipe system that makes it straightforward to add support for other networking libraries. Each converter lives in its own folder and consists of three classes. [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#id-1.-mappings) 1\. Mappings ----------------------------------------------------------------------------------------------------------------------------- Extend `NetworkSystemMappings` and define your mappings in the constructor: Copy using PurrNet.ConversionTool; public class MySystemMappings : NetworkSystemMappings { public MySystemMappings() { SystemName = "MyNetworkLib"; SystemIdentifiers = new List { "MyNetworkLib" }; NamespaceMappings = new Dictionary { { "MyNetworkLib", "PurrNet" } }; TypeMappings = new Dictionary { { "NetConnection", "PlayerID" }, { "NetObject", "NetworkIdentity" } }; PropertyMappings = new Dictionary { { "IsOwner", "isOwner" }, { "IsServer", "isServer" } }; MethodMappings = new Dictionary { { "OnClientStart", "OnSpawned" } }; // ... and so on for MemberMappings, MethodCallMappings, // AttributeMappings, AttributeParameterMappings, etc. } } For cases that don't fit into simple mappings, override `SpecialCaseHandler` to do custom Roslyn syntax tree transformations. [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#id-2.-prefab-handling) 2\. Prefab handling ------------------------------------------------------------------------------------------------------------------------------------------- Extend `NetworkPrefabHandling` and override `ConvertPrefab`: [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#id-3.-scene-handling) 3\. Scene handling ----------------------------------------------------------------------------------------------------------------------------------------- Extend `NetworkSceneHandling` and override `ConvertSceneObject`: [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#assembly-setup) Assembly setup ------------------------------------------------------------------------------------------------------------------------------- Your converter folder needs its own `.asmdef` that references `PurrNet.ConversionTool` and the source networking library. Set it to **Editor only** and add a `defineConstraints` entry so it only compiles when the source library is present. Take a look at the [FishNet converter on GitHubarrow-up-right](https://github.com/PurrNet/PurrNet-Conversion-Tool/tree/dev/Assets/PurrNet-Conversion/Converters/FishNet%20Converter) for a full working example. [hashtag](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#discovery) Discovery --------------------------------------------------------------------------------------------------------------------- The tool automatically discovers converters through reflection. As long as your three classes are in the same folder and extend the right base classes, they will show up in the dropdown. No registration needed. [PreviousConversion Toolchevron-left](https://purrnet.gitbook.io/docs/tools/conversion-tool) [NextPurrChat (AI assistant)chevron-right](https://purrnet.gitbook.io/docs/tools/purrchat-ai-copilot) Last updated 3 days ago * [1\. Mappings](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#id-1.-mappings) * [2\. Prefab handling](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#id-2.-prefab-handling) * [3\. Scene handling](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#id-3.-scene-handling) * [Assembly setup](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#assembly-setup) * [Discovery](https://purrnet.gitbook.io/docs/tools/conversion-tool/creating-your-own-converter#discovery) Copy using PurrNet.ConversionTool; using UnityEngine; public class MySystemPrefabHandling : NetworkPrefabHandling { public override bool ConvertPrefab(GameObject prefab) { // Find old components, add PurrNet equivalents, copy settings // Return true if anything was changed return false; } } Copy using PurrNet.ConversionTool; using UnityEngine; public class MySystemSceneHandling : NetworkSceneHandling { public override bool ConvertSceneObject(GameObject sceneObject) { // Convert scene-specific objects like NetworkManager // Return true if anything was changed return false; } } --- # Client Auth/Everyone (Unsafe) | PurrNet Client Auth, "Everyone" and Unsafe, are words used over and over again throughout PurrNet. It is important to understand that this isn't inherently "bad", just because the word "unsafe" is involved. It will simply mean that the code isn't cheat proof, and for most games such as Co-op, friendly games or casual games, this isn't relevant either. Allowing for unsafe networking in your [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) will make life for you as a developer even easier with PurrNet, allowing you to cut corners at every step of the way! If the rules are not set to unsafe, you'd have to ensure that the calls are happening through the server, and ensure a safe code structure yourself. [PreviousChannelschevron-left](https://purrnet.gitbook.io/docs/terminology/channels) [NextHostchevron-right](https://purrnet.gitbook.io/docs/terminology/host) Last updated 1 year ago --- # Lip Sync | PurrNet Adding lip syncing to your multiplayer game is now easier than ever. We have some pre-built components to make your developer experience easy as ever. ### [hashtag](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/lip-sync#sprite) Sprite Simply add the **PurrLipSyncSprite** component to your player prefab and give it a reference to the **PurrVoicePlayer** component. From here it needs a **PhonemeSpritePreset**, which essentially holds the different sprites for various mouth shapes (A, E, O, U, etc.) Lastly you feed it your sprite renderer and the rest should be handled from there! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-af25d4a1922657ea07ae8eb5125a7a082d0f8f3f%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=7949962d&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/lip-sync#other) Other Simply add the **PurrLipSync** component to your player prefab and give it a reference to the **PurrVoicePlayer** component. There is a public value called **result** which holds LipSyncInfo. You can utilize this info to handle the various lip syncing setups. #### [hashtag](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/lip-sync#lipsyncinfo) LipSyncInfo: * phoneme * volume * rawVolume * phonemeRatios [PreviousMicrophone Inputchevron-left](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/microphone-input) [NextPlug n' play componentschevron-right](https://purrnet.gitbook.io/docs/plug-n-play-components) Last updated 9 months ago * [Sprite](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/lip-sync#sprite) * [Other](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/lip-sync#other) --- # For Studios | PurrNet PurrNet is free for everyone, but we know that studios shipping multiplayer games often need more than documentation and a Discord channel. That's why we offer dedicated studio support, tailored to your team and your project. Whether you're building a new multiplayer game from scratch, migrating from another networking solution, or running into production-level issues that need expert eyes, we can help. [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#what-we-offer) What we offer ---------------------------------------------------------------------------------------------- ### [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#hands-on-project-access) Hands-on project access We get direct access to your project and work alongside your team to solve networking issues. No back-and-forth trying to reproduce bugs from screenshots. We're in your codebase, fixing things with you. ### [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#architecture-review-and-performance-optimization) Architecture review and performance optimization We audit your networking architecture and help you identify bottlenecks before they become problems at scale. This is especially important as you grow your player count or prepare for launch. ### [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#migration-support) Migration support If you're moving from another networking solution, we provide migration planning and dedicated engineering support. We can pair-program with your team to minimize downtime and get you up and running on PurrNet as smoothly as possible. ### [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#custom-feature-development) Custom feature development If your project needs something that PurrNet doesn't currently offer, we can build it. Your requirements directly shape our roadmap, and you get early access to features built specifically for your use case. ### [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#custom-tooling-for-studio-workflows) Custom tooling for studio workflows Beyond the core library, we're actively building tooling designed to improve multiplayer workflows at scale. Things like better debugging, visualization, and profiling tools that make it easier to manage complex networked projects. Studio partners get exclusive access to these tools, and your feedback helps shape what we build next. ### [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#studio-exclusive-packages) Studio-exclusive packages Studio members get access to advanced networking tools and packages built specifically for production-scale games, not available to the public. ### [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#priority-support-with-private-dev-channel) Priority support with private dev channel You get a private channel with direct access to the PurrNet team. No waiting in a public queue. Your questions and issues get handled first. [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#pricing) Pricing ---------------------------------------------------------------------------------- Studio support starts at **$500/month** and is tailored to your team's needs. The exact scope depends on what kind of support you're looking for, whether that's occasional architecture reviews or full hands-on engineering time. [hashtag](https://purrnet.gitbook.io/docs/readme/for-studios#get-in-touch) Get in touch -------------------------------------------------------------------------------------------- The best way to get started is to reach out to us directly. We'll figure out together what level of support makes sense for your project. [**Contact us on our website**arrow-up-right](https://purrnet.dev/studios) [**Or reach out directly**arrow-up-right](https://purrnet.dev/contact) You can also find us on Discord: [https://discord.gg/NP9tP9Qx9Rarrow-up-right](https://discord.gg/NP9tP9Qx9R) [PreviousSupport PurrNetchevron-left](https://purrnet.gitbook.io/docs/readme/support-purrnet) [NextGetting Startedchevron-right](https://purrnet.gitbook.io/docs/getting-started) Last updated 11 days ago * [What we offer](https://purrnet.gitbook.io/docs/readme/for-studios#what-we-offer) * [Hands-on project access](https://purrnet.gitbook.io/docs/readme/for-studios#hands-on-project-access) * [Architecture review and performance optimization](https://purrnet.gitbook.io/docs/readme/for-studios#architecture-review-and-performance-optimization) * [Migration support](https://purrnet.gitbook.io/docs/readme/for-studios#migration-support) * [Custom feature development](https://purrnet.gitbook.io/docs/readme/for-studios#custom-feature-development) * [Custom tooling for studio workflows](https://purrnet.gitbook.io/docs/readme/for-studios#custom-tooling-for-studio-workflows) * [Studio-exclusive packages](https://purrnet.gitbook.io/docs/readme/for-studios#studio-exclusive-packages) * [Priority support with private dev channel](https://purrnet.gitbook.io/docs/readme/for-studios#priority-support-with-private-dev-channel) * [Pricing](https://purrnet.gitbook.io/docs/readme/for-studios#pricing) * [Get in touch](https://purrnet.gitbook.io/docs/readme/for-studios#get-in-touch) --- # Channels | PurrNet Channels are for the developer to decide how the data is sent. Typically the reliable channels are the most commonly used, but comes at a higher data cost. So if you are sending something all the time and you don't necessarily care if you lose a packet or two, the **Unreliable** channel might be a good option for you. Copy public enum Channel : byte { /// /// It ensures that the data is received but the order is not guaranteed. /// ReliableUnordered, /// /// Packets are guaranteed to be in order but not guaranteed to be received. /// UnreliableSequenced, /// /// Packets are guaranteed to be received in order. -- Default channel /// ReliableOrdered, /// /// Only the last sent packet is guaranteed to be received. /// ReliableSequenced, /// /// Packets are not guaranteed to be received nor in order. /// Unreliable } [PreviousEarly Accesschevron-left](https://purrnet.gitbook.io/docs/terminology/early-access) [NextClient Auth/Everyone (Unsafe)chevron-right](https://purrnet.gitbook.io/docs/terminology/client-auth-everyone-unsafe) Last updated 1 year ago --- # Providers | PurrNet The provider pattern is utilized for this voice chat setup, in order to make it very easy to use and highly customizable to use. It comes with some default providers, but it should also be pretty straight forward making your own! The providers are required for both input and output. If you just wish to use the Unity audio system, you should likely go with [Default Input Provider](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/input-providers/default-input-provider) and [Audio Source Voice Provider](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/output-providers/audio-source-voice-provider) . These utilize the default Unity audio source setup and Unity input handling for devices as well. Keep in mind that you need 2 individual output providers if you want local playback (typically used for testing). This is because the audio is handled on an instance basis, meaning that the handler for local and non local playback shouldn't be mixed! The PurrVoicePlayer should warn you in case of this mistake either way, but it's good to know why. [PreviousPurrVoice (Voice chat)chevron-left](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat) [NextInput Providerschevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/input-providers) Last updated 8 months ago --- # Output Providers | PurrNet [Audio Source Voice Providerchevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/output-providers/audio-source-voice-provider) [PreviousDefault Input Providerchevron-left](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/input-providers/default-input-provider) [NextAudio Source Voice Providerchevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/output-providers/audio-source-voice-provider) --- # Input Providers | PurrNet [Default Input Providerchevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/input-providers/default-input-provider) [PreviousProviderschevron-left](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers) [NextDefault Input Providerchevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/input-providers/default-input-provider) --- # Support PurrNet | PurrNet Using PurrNet is 100% free, but supporting us by letting us support you is the best way to go about it. We have a few different support tiers depending on what you need. To support us and claim your Discord role and early access features, head to the [support pagearrow-up-right](https://purrnet.dev/support) and choose a tier that works for you. Benefits Stray Cat (free) House Cat Royal British Custom Discord role check check [Early Access](https://purrnet.gitbook.io/docs/terminology/early-access) check check 1st level priority support check check 2nd level priority support check Hands-on project support check If you want to support us beyond these tiers, that is of course also much appreciated. In that case, please [contact us directlyarrow-up-right](https://purrnet.dev/contact) . [hashtag](https://purrnet.gitbook.io/docs/readme/support-purrnet#for-studios) For Studios ---------------------------------------------------------------------------------------------- If you're a studio shipping a multiplayer game, we offer a dedicated level of support that goes well beyond the tiers above. This includes things like: * **Hands-on project access** where we work directly in your project alongside your team * **Migration planning** with dedicated engineering to help you move from another networking solution * **Architecture reviews** and performance optimization * **Custom feature development** where your needs directly shape our roadmap * **Custom tooling** for studio workflows, with early access to tools designed to improve multiplayer development at scale * **Studio-exclusive packages** built for production-scale games * **Private dev channel** with direct access to the PurrNet team Studio support starts at $500/month and is tailored to what your team actually needs. circle-info Want to learn more? Check out our [dedicated studios page](https://purrnet.gitbook.io/docs/readme/for-studios) or [get in touch with us directlyarrow-up-right](https://purrnet.dev/studios) . [PreviousNetwork Transform benchmarkschevron-left](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks) [NextFor Studioschevron-right](https://purrnet.gitbook.io/docs/readme/for-studios) Last updated 11 days ago --- # Network Rigidbody | PurrNet This component allows you to very easily utilize the Rigidbody over the network. It helps sync the actual forces of the Rigidbody, and utilizes physics to smoothly align it across the network. circle-info If you need physics in multiplayer, you might want to read the [Physics In Multiplayer](https://purrnet.gitbook.io/docs/guides/physics-in-multiplayer) page ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody#settings) Settings It's a highly dynamic component, so although the default settings should work well for a lot of cases, it's recommended to familiarize yourself with what the various settings do. A lot of values behind the scenes are modified dynamically, which is why so many settings are exposed. Things like a flat acceptable error threshold or hard correction threshold, yielded bad results during collision and faster movement, which is why the acceleration settings help to dynamically expand the acceptable ranges during big acceleration changes. Setting Effect Owner Auth Decides who holds the truth of the simulation. If true, the owning client calculates physics (Client Auth). If false, the server does (Server Auth). Interpolation Delay How far behind real-time (in seconds) the interpolation target sits. Higher values absorb more network jitter but add visual latency. Default: 0.05s. Prediction Factor Pushes the target position forward using velocity to compensate for interpolation delay. 0 = no prediction, 1 = compensate for the full interpolation delay, >1 = predict further ahead. The offset is deterministic and identical on all machines. Position Strength How aggressively the rigidbody chases the target position. Acts as the natural frequency of a critically-damped spring. Higher values = tighter tracking but stiffer feel. The steady-state tracking lag is approximately velocity / strength. Correction Range The distance over which position correction ramps from zero to full strength. Larger values give softer correction, letting local collisions play out naturally before being pulled back. Smaller values give tighter snapping. Rotation Strength How aggressively the rigidbody corrects rotation errors. Works the same way as position strength but for angular correction. Hard Snap Distance If the position error exceeds this distance, the rigidbody teleports to the target instead of using forces. Acts as a safety net for large desync. Hard Snap Angle If the rotation error (degrees) exceeds this threshold, the rotation snaps instantly instead of using torque. Set to a negative value to disable rotation snapping entirely. Acceptable Rotation Error Rotation error (degrees) below which rotation correction stops, preventing micro-jitter at rest. Set to a negative value to disable rotation correction entirely. Position Change Threshold Minimum distance the rigidbody must move before triggering a network update. Prevents unnecessary updates while stationary. Rotation Change Threshold Minimum angle the rigidbody must rotate before triggering a network update. Velocity Stop Threshold If both linear and angular velocities are below this value, the object is considered stopped and will cease sending updates. ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody#teleporting) Teleporting Regular physics operations (`position`, `rotation`, `MovePosition`, `MoveRotation`, `AddForce`, etc.) are synced automatically through the normal state sync and should be used for all regular gameplay movement. For instant repositioning like respawns or portals, use the explicit `TeleportTo` method: `TeleportTo` differs from setting `position`/`rotation` directly in that it: * Zeros out linear and angular velocity * Clears the interpolation buffer on all observers * Sends a reliable RPC so the teleport is never missed * Prevents the spring from trying to smoothly chase a position that was meant to be instant If you're replacing a `Rigidbody` reference with `NetworkRigidbody`, you do **not** need to change any of your existing `MovePosition`, `MoveRotation`, or `AddForce` calls — they work identically. Only use `TeleportTo` when you specifically need an instant, non-physical reposition. [PreviousNetwork Transformchevron-left](https://purrnet.gitbook.io/docs/plug-n-play-components/network-transform) [NextNetwork Animatorchevron-right](https://purrnet.gitbook.io/docs/plug-n-play-components/network-animator) Last updated 11 days ago * [Settings](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody#settings) * [Teleporting](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody#teleporting) Copy // 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); --- # Server Auth (Safe) | PurrNet Server Auth or "Safe", are referencing the opposite of the [Client Auth](https://purrnet.gitbook.io/docs/terminology/client-auth-everyone-unsafe) option. This ensures that you as the developer are forced to take cheat safe routes with your development, similar to how other systems do it, like NetCode, Mirror or Fish-Net. [PreviousOwner Authchevron-left](https://purrnet.gitbook.io/docs/terminology/owner-auth) [NextPlayerIDchevron-right](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) Last updated 1 year ago --- # Unity Multiplayer Services with PurrNet | PurrNet [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#introduction) Introduction ---------------------------------------------------------------------------------------------------------------------------------- This guide covers how to integrate Unity 6's Multiplayer Services (Lobby, Relay, Matchmaker) with PurrNet using two community packages: * **Purrnity Transport** - A transport adapter bridging PurrNet with Unity's Transport Package * **PurrNet Multiplayer Services Handler** - A session management layer connecting Unity's Multiplayer Services to PurrNet Together, these packages let you use Unity's matchmaking and relay infrastructure while keeping PurrNet as your networking framework. [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#requirements) Requirements ---------------------------------------------------------------------------------------------------------------------------------- * Unity 6.0+ * PurrNet * Unity Transport 2.0+ (`com.unity.transport`) * Multiplayer Services 1.1.0+ (`com.unity.services.multiplayer`) [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#installation) Installation ---------------------------------------------------------------------------------------------------------------------------------- Install the packages via Unity Package Manager using Git URLs: 1. **Purrnity Transport**: Copy https://github.com/youngwoocho02/PurrnityTransport.git 1. **PurrNet Multiplayer Services Handler**: [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#setting-up-the-transport) Setting Up the Transport ---------------------------------------------------------------------------------------------------------------------------------------------------------- Add the `PurrnityTransport` component to your **NetworkManager** GameObject and assign it as the transport. ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#basic-connection-direct-udp) Basic Connection (Direct UDP) ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#using-unity-relay) Using Unity Relay For NAT traversal, you can configure the transport to use Unity Relay: ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#encryption) Encryption Enable DTLS encryption for UDP or TLS for WebSocket connections: [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#session-management-with-multiplayer-services-handler) Session Management with Multiplayer Services Handler ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ The handler provides a high-level API for session-based multiplayer using Unity's services. ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#initializing) Initializing ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#creating-a-session) Creating a Session ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#joining-a-session) Joining a Session ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#direct-p2p-connection) Direct P2P Connection [hashtag](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#resources) Resources ---------------------------------------------------------------------------------------------------------------------------- [![Logo](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fgithub.com%2Ffluidicon.png&width=20&dpr=3&quality=100&sign=a81e993e&sv=2)GitHub - youngwoocho02/PurrnityTransportGitHubchevron-right](https://github.com/youngwoocho02/PurrnityTransport) [![Logo](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fgithub.com%2Ffluidicon.png&width=20&dpr=3&quality=100&sign=a81e993e&sv=2)GitHub - youngwoocho02/PurrNetMultiplayerServicesHandlerGitHubchevron-right](https://github.com/youngwoocho02/PurrNetMultiplayerServicesHandler) [PreviousPurrdicted Character Controller Knockbackchevron-left](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction) [NextGame Server Hostingchevron-right](https://purrnet.gitbook.io/docs/game-server-hosting) Last updated 1 month ago * [Introduction](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#introduction) * [Requirements](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#requirements) * [Installation](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#installation) * [Setting Up the Transport](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#setting-up-the-transport) * [Basic Connection (Direct UDP)](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#basic-connection-direct-udp) * [Using Unity Relay](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#using-unity-relay) * [Encryption](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#encryption) * [Session Management with Multiplayer Services Handler](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#session-management-with-multiplayer-services-handler) * [Initializing](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#initializing) * [Creating a Session](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#creating-a-session) * [Joining a Session](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#joining-a-session) * [Direct P2P Connection](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#direct-p2p-connection) * [Resources](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet#resources) Copy https://github.com/youngwoocho02/PurrNetMultiplayerServicesHandler.git Copy // Server transport.Listen(7777); // Client transport.Connect("127.0.0.1", 7777); Copy // 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); Copy transport.SetServerSecrets(serverCertificate, serverPrivateKey); Copy using PurrNet.MultiplayerServices; // Initialize Unity Services first await UnityServices.InitializeAsync(); await AuthenticationService.Instance.SignInAnonymouslyAsync(); Copy var options = new SessionOptions() .WithPurrRelay() // Use relay-based connection .WithMaxPlayers(4); var session = await MultiplayerService.Instance.CreateSessionAsync(options); Copy // Join by session ID var session = await MultiplayerService.Instance.JoinSessionByIdAsync(sessionId); // Join by code var session = await MultiplayerService.Instance.JoinSessionByCodeAsync(sessionCode); // Quick match var options = new QuickJoinOptions(); var session = await MultiplayerService.Instance.QuickJoinSessionAsync(options); Copy var options = new SessionOptions() .WithPurrDirect() // Use direct P2P connection .WithMaxPlayers(4); var session = await MultiplayerService.Instance.CreateSessionAsync(options); --- # Filters | PurrNet Working with the filters should be very straight forward. They allow easy audio manipulation to get the voice chat effect you desire for your game! The filtering is multithreaded by nature, causing very low costs (virtually none) to your main thread. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-4acd4dd4b6ade9791c8eb608aa55fedba602d0fc%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=cf4548fa&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/filters#filter-level) Filter level Filters allow you to easily manipulate audio at any level of it's path. This in turn means that you can easily control whether the Sender, Server or Receiver will be the one applying the filter to the audio. **Sender:** This means that the audio is applied before it even reached the network, and as soon as input is received from your microphone device. This level is optimal for things such as noise cancellation, or funny effects that should be applied globally. **Receiver:** Here the receiver will be the one applying the effect. This is great for environment context relevant effects, such as a player being behind the wall for you, or under the water, etc. Essentially your local client sees someone else in some relevant context and need a filter reacting to that. ### [hashtag](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/filters#filter-strength) Filter Strength The strength of a filter is handled on a per filter level, so what it "means" can vary depending on the intention of a filter, so it can be a good idea to play around with this value to see the effect of this. The strength would generally mean how much a filters effect is applied to the voice, but again, this can vary. The strength of a filter is NOT dynamically synced, as it is rather pointless. Only for the level manipulating it, is it relevant to know a dynamic strength, so in order to save data we are not dynamically syncing this. Changing the strength through code is simple: ### [hashtag](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/filters#dynamically-adding-filters) Dynamically adding filters You can add and remove filters on the fly, and this will be synchronized initially. It's a very lightweight action to do, and only at this level is the strength synced (initial value) This can only be done by the [controller](https://purrnet.gitbook.io/docs/terminology/controller) . Here is a simple example: [PreviousAudio Source Voice Providerchevron-left](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/providers/output-providers/audio-source-voice-provider) [NextMutingchevron-right](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/muting) Last updated 3 days ago * [Filter level](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/filters#filter-level) * [Filter Strength](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/filters#filter-strength) * [Dynamically adding filters](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat/filters#dynamically-adding-filters) Copy [SerializeField] private PurrVoicePlayer _voicePlayer; [SerializeField] private PurrAudioFilter _testFilter; private void SetStrength(float strength) { // Filter reference/Index, new strength _voicePlayer.SetFilterStrength(_testFilter, strength); } Copy [SerializeField] private PurrVoicePlayer _voicePlayer; [SerializeField] private PurrAudioFilter _testFilter; private void ApplyFilter() { // Filter, Level, Initial strength _voicePlayer.AddFilter(_testFilter, FilterLevel.Receiver, 1); } private void RemoveFilter() { // This can be both a direct filter reference or an index of filter _voicePlayer.RemoveFilter(_testFilter); } --- # Network State Machine | PurrNet The Auto networked state machine of PurrNet makes it very easy to align state machines even with custom state data if necessary. The state machine also have a custom editor allowing for easy debugging and runtime manipulation of the active state. It also attempts to serialize custom node data. By default, the state machine is server auth, but you can use the bool in the inspector, to change whether you'd rather have it be owner auth. The main difference is who is allowed to change states on the state machine. The "controller" (whether that is the server or owner), will always handle state changes local to make it fast and responsive. The state machine is extremely useful at keeping games aligned, and we personally use it for game state handling, which could look something like: 1_. Countdown state_ _2\. Spawning enemies state_ _3\. Spawning boss state_ _4\. Round end state_ _5\. Shop state_ _They can also be used for other things like the state of your player, in the case of using owner auth._ You can move between states as you please, but also have an easy way of moving sequentially through state. ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#scene-setup) Scene setup The scene setup is easy! All you have to do, is add the State Machine to your scene. I recommend making a new gameobject for these, to keep it structured. For every **StateNode** that you make, you can add that to your scene as well. I typically do it as a child of the **StateMachine** gameobject. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-3195622221f08e288a2ee713443028a87e18b7ca%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=34dbe2f7&sv=2) Example setup of the State Machine ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#state-nodes) State Nodes State nodes are what you know as your states. There are 2 kinds of state nodes. Those that expect data, and those that don't. First off, in order to have a state node, you need a new script which inherits from **StateNode** or **StateNode** depending on whether the state expects data or not. The StateNode is a network behavior as well, so you can act with it exactly as you would any other networked script. The main difference are some of the virtual methods which you can override, as well as the ease access to the state machine in order to change states. ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#changing-state) Changing state Changing states with the networked state machine is very easy. Only thing you need is a reference to the state machine. **StateNodes** automatically have that reference by simply typing `machine`. There are 3 ways of changing the state. `Next`, `Previous` or `SetState` . ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#state-nodes-with-data) State Nodes with data Working with state nodes which require data is easy as well. You work with it super similar to a regular state node, except you make it generic with the type of required data: Beware that if the state is switched to without being fed data, it will run the normal Enter method without the data type. This way you can always use that as a fail safe in case a state might be entered wrongly. In order to send data with the state switch, you simply feed it when changing state ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#advanced-state-transitions) Advanced state transitions The PurrNet State Machine supports advanced transition logic through the `CanEnter` and `CanExit` methods, allowing for conditional state changes. **Implementing Conditional Transitions** Each `StateNode` can override these methods to define custom logic: * **Non-Generic States**: Override `CanEnter()` and `CanExit()` to implement logic without additional data. * **Generic States**: Override `CanEnter(T data)` and `CanExit()` to implement logic that considers external data. By utilizing these methods, you can ensure that your state machine transitions only occur when certain conditions are met, enhancing the robustness of your game's logic. ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#dynamically-add-and-remove-states) Dynamically add & remove states The networked state machine also holds the ability to dynamically modify states. This will be automatically synced as well, meaning that you could instantiate a new state and add it to an existing state machine, or remove existing states as needed. Adding and removing states dynamically, is very similar to working with a list: [PreviousNetwork Reflectionchevron-left](https://purrnet.gitbook.io/docs/plug-n-play-components/network-reflection-auto-sync) [NextNetwork Audio Sourcechevron-right](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source) Last updated 1 month ago * [Scene setup](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#scene-setup) * [State Nodes](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#state-nodes) * [Changing state](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#changing-state) * [State Nodes with data](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#state-nodes-with-data) * [Advanced state transitions](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#advanced-state-transitions) * [Dynamically add & remove states](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked#dynamically-add-and-remove-states) Copy using PurrNet.StateMachine; public class TestState : StateNode { } Copy public override void Enter(bool asServer) { //Runs when the state is entered } public override void Exit(bool asServer) { //Runs when the state is exited } public override void StateUpdate(bool asServer) { //Runs every frame while the state is active } Copy public StateNode specificState; private void StateChangeShowcase() { //Goes to the next state in the StateMachine list machine.Next(); //Goes to the previous state in the StateMachine list machine.Previous(); //Goes to a specific state in the StateMachine list machine.SetState(specificState); } Copy public class TestState : StateNode { public override void Enter(int data, bool asServer) { Debug.Log($"I received data: {data}"); } } Copy //Goes to the next state in the StateMachine list while feeding data machine.Next(5); //Goes to a specified state while feeding data machine.SetState(specificState, 5); Copy //Adds a state to the end of the states list machine.AddState(_stateToAdd); //Removes the state given as a reference machine.RemoveState(_stateToAdd); //Adds/inserts a state at a given index machine.InsertState(_stateToAdd, _indexTest); //Removes a state from a given index machine.RemoveStateAt(_indexTest); --- # Playomoji | PurrNet [Check it out on Steamarrow-up-right](https://store.steampowered.com/app/3669080/Playomoji/) Playomoji is a chaotic and fun 2D platformer you can play with up to 16 friends. Jump, slide, and take the weirdest paths to move forward, or sabotage your rivals and knock them off course! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-bbc414fc140a85ac39d3d2c0d48fa9cf592f12d1%252FGameplay_Gif.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=892ae50d&sv=2) **Run, jump, roll.** This is a parkour game. But here, it’s not just about obstacles — you mess with your friends too. Troll them, throw them off balance, get ahead. **Online multiplayer for up to 16 players.** Play with friends or join an online lobby alone. Chaos and competition are always present. **Play on different maps.** Each map has its own layout and challenges. The pace keeps changing. **Cosmetics are waiting.** Customize your character with outfits and looks. Unlock more by playing. **Simple rules, lots of fun.** The goal is clear: be the fastest or the funniest. How you play is up to you. [PreviousFive Headschevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/five-heads) [NextTower Arena Survivechevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/tower-arena-survive) Last updated 9 months ago --- # Converting from Mirror | PurrNet circle-info Keep in mind that both PurrNet and Mirror are systems which are constantly evolving and could be prone to change. Converting from Mirror to PurrNet is fairly simple given that both systems have similar logic for developers. It is recommended to do the conversion in a separate project in order to use the "old" project for comparison. circle-exclamation Make sure your project is backed up prior to beginning any conversion! [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#converting-individual-parts-and-differences) Converting individual parts (& differences) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#network-manager) Network Manager Both Mirror and PurrNet rely on a NetworkManager to handle network operations, but their implementations differ. This is somewhat what a setup on a gameobject with both systems looks like: **Mirror:** * Network Manager * Transport * A list of Spawnable Prefabs * Built-in callbacks for connection management & registering **PurrNet:** * Network Manager * Transport To easily setup your Network Manager in just seconds, please have a look at the [Getting Started](https://purrnet.gitbook.io/docs/getting-started/getting-started) guide. ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#purrnet-network-identity-vs-mirror-network-identity) PurrNet Network Identity vs Mirror Network Identity In both systems, everything that needs to act on the network, must have a Network Identity attached. The difference being that Mirror's network identity acts as a full gameobject networking component. PurrNet's [Network identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) is what every networked component inherits from, and isn't a component of its own. This is meant to give the ultimate flexibility for the developer(s). **Comparison** **Mirror:** ❌ No support for nested networked prefabs. ❌ Single ownership per GameObject. ❌ No runtime un-parenting of transforms. ❌ Can't do generic RPC's ❌ Can't do static RPC's ❌ Can't do returnable RPC's **PurrNet:** ✔️ Supports nested prefabs. ✔️ Allows different ownership per component on a GameObject ✔️ Enables dynamic transform management at runtime. ✔️ Can do generic RPC's ✔️ Cam do static RPC's ✔️ Can do returnable RPC's ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#spawning-and-despawning) Spawning & Despawning Handling new objects or removing existing networked objects is a bit different between the systems. In Mirror, you'd need to instantiate the object and spawn the attached NetworkIdentity component. And if you'd want to spawn with ownership, it would be added as a parameter to the spawn call. That would look like this: **Mirror:** PurrNet handles [spawning & despawning](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/spawning-and-despawning) automatically, however, if you want to modify something relative to the identity, you can do that at the same time as spawning it, and it will arrive in the same packet. **PurrNet:** ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#rpcs) RPC's Working with RPC's from **Mirror** to **PurrNet** is pretty easy, as the naming conventions change, but functionality remains. However, there are some key differences in terms of usage. This will depend on your [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) so make sure to read up on those. If you are running with a rule set similar to the default "ServerStrict" rules, then the usage will be the same. But if you're running with a rule set similar to the "Unsafe" rules, then you can send any RPC directly from any client! Mirror PurrNet \[Command\] \[ServerRpc\] \[ClientRpc\] \[ObserversRpc\] \[TargetRpc\] \[TargetRpc\] ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#synctypes) SyncTypes Synchronizing is a bit different between the systems. It's similar in naming and nature. Both systems are meant to automatically sync for you, however, the setup and usage slightly varies. Other than that, PurrNet also allows for fully owner authorized SyncTypes, making them instantly responsive to it's controller. Read more on this in the individual [SyncType guides](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types) . **Setting up a SyncVar and changing value in Mirror:** **Setting up a SyncVar and changing value in PurrNet:** **Subscribing to changes** with synchronized variables is also a bit different, but easy with both systems **Mirror:** **PurrNet** [PreviousConverting to PurrNetchevron-left](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet) [NextConverting from FishNetchevron-right](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet) Last updated 12 months ago * [Converting individual parts (& differences)](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#converting-individual-parts-and-differences) * [Network Manager](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#network-manager) * [PurrNet Network Identity vs Mirror Network Identity](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#purrnet-network-identity-vs-mirror-network-identity) * [Spawning & Despawning](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#spawning-and-despawning) * [RPC's](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#rpcs) * [SyncTypes](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror#synctypes) Copy GameObject go = Instantiate(myPrefab); NetworkServer.Spawn(go, connectionToClient); NetworkServer.Destroy(go); Copy var identity = Instantiate(myIdentityPrefab); identity.GiveOwnership(ownerPlayer); Destroy(identity.gameObject); Copy [SyncVar] private string mySyncName; private void MyMethod(string newName) { mySyncName = newName; } Copy private SyncVar mySyncName = new(); private void MyMethod(string newName) { mySyncName.value = newName; } Copy [SyncVar(hook = nameof(OnNameChange))] private string mySyncName; private void OnNameChange(string oldName, string newName) { } Copy private SyncVar mySyncName = new(); private void Awake() { mySyncName.onChanged += OnNameChange; } private void OnNameChange(string newName) { } --- # Bandwidth Profiler | PurrNet Networking bugs are some of the hardest to track down. When something feels laggy or your game starts stuttering with more players, you need to know exactly what data is being sent, how much of it, and which objects are responsible. Without visibility into your network traffic, you're debugging blind. The Bandwidth profiler gives you that visibility. It allows you to do: * Real-time Analysis: Visualize sent and received data with a live traffic graph. * Detailed Breakdown: Inspect every RPC, see its parameters, and instantly highlight the source `GameObject` in the hierarchy. * Save & Load Sessions: Debug builds by saving profiler data to a file and loading it back in the editor for analysis. Accessing the profiler through `Tools/PurrNet/Analysis/Bandwidth Profiler` ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-511f4711c5918125768e6409b63dcb41e4c827fe%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=a7443f2f&sv=2) ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-87b230a37b3192197ae204f644b16a94fee98762%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=6b90f2d5&sv=2) Bandwidth Profiler Window The profile also tries when possible to keep references to the sender/receiver so that you know exactly which components are sending the data. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-f225ecdbabb21340775ce31135fb4a11dfe98cd5%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=f5081e20&sv=2) It also allows you to save the data at runtime to a file to allow inspecting and analyzing it post play sessions. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e120843d15289123efb8a0aede90f5168f70771a%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=b2399ece&sv=2) ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e055ce0d2b719a77b06b2c7b11ad4bbcb23280fa%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=f24a3dde&sv=2) [PreviousCode strippingchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/code-stripping) [NextPurrDictionchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction) Last updated 11 days ago --- # A Smithing Game | PurrNet A Smithing Game is a chaotic multiplayer game where you forge weapons and use them to fight up to 3 other players. [Check it out on Steam!arrow-up-right](https://store.steampowered.com/app/4541460/A_Smithing_Game/) ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252F3O4KyDIiRZXF5zoG951T%252F01bc4e3381ed35b6f99b580b204cd0c1.gif%3Falt%3Dmedia%26token%3Dfac8374f-414b-48ac-b746-3f8a5363fdd9&width=768&dpr=3&quality=100&sign=e6e293ab&sv=2) This game utilizes the [prediction of PurrNet](https://purrnet.gitbook.io/docs/client-side-prediction) to ensure fair and snappy battles across all clients from around the world. Pick a metal, shape it, quench it, and sharpen it to create all sorts of unique weapons. Each step of the smithing will contribute to how your weapon performs in battle. Prove your skills as a bladesmith by wielding your weapon in exciting hand-to-hand combat. Eliminate all other players and try to be the last smith standing. Customize your smith with a wide variety of unique hats, outfits, and facial expressions to ensure you go into combat looking your best. [PreviousSymbiosischevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/land-of-symbiosis) [NextBao Bao's™ Cozy Cleaning Serviceschevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/bao-baos-tm-cozy-cleaning-services) Last updated 1 day ago --- # Chat system with broadcasts | PurrNet [hashtag](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#introduction) Introduction ---------------------------------------------------------------------------------------------------------------- Broadcasting in PurrNet is useful, as it allows us to do some basic network functionality without needing a [Network Behaviour](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) on our object. For things that are trivial, such as game chat, we don't necessarily need all the functionality of a [Network Behaviour](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) . The idea is as follows: 1. Create a `ChatMessage` struct to store data such as a `name` and `message`. 2. Send a `ChatMessage` with our desired `name` and `message` to the **Server**. 3. Receive `ChatMessage` on the **Server**, then broadcast the `ChatMessage` to all **Clients**. 4. Receive `ChatMessage` on the **Clients**, then print the `ChatMessage` out. [hashtag](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#creating-the-chatmessage-struct) Creating the `ChatMessage` struct: --------------------------------------------------------------------------------------------------------------------------------------------------------- To get our chat message to the **Server**, we need to first create a struct to hold our data. As previously mentioned, this struct will hold a `name`, and a `message`. This struct will need to implement the [IPackedAuto](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#IPackedAuto) interface, which will automatically handle the reading and writing of the data to the network. If this is not your style, take a look at the [IPacked](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#IPacked) and [IPackedSimple](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#IPackedSimple) interfaces. The final struct is as follows: Copy public struct ChatMessage : IPackedAuto { public string name; public string message; } [hashtag](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#sending-a-chatmessage-to-the-server) Sending a `ChatMessage` to the **Server** -------------------------------------------------------------------------------------------------------------------------------------------------------------------- For our **Clients** to be able to send a message to the **Server**, we first need to hook into the `Subscribe` event from the `NetworkManager` The easiest way to do this, is create a script that inherits from `PurrMonoBehaviour`, as this gives us access to two very useful events: For our case, let's create a `ChatManager` script, that inherits from `PurrMonoBehaviour` and subscribe to our chat events, as well as Creating an `OnChatMessage` function to pass in as our callback: Now that we've subscribed to the events required, we can actually send a `ChatMessage` to the **Server**! You can do this however you'd like, for testing, something like this will be more than sufficient for our needs: [hashtag](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#receiving-a-chatmessage-on-the-server-and-sending-it-to-all-clients) Receiving a `ChatMessage` on the **Server**, and sending it to all **Clients** ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Now that we are sending messages from the **Client**, let's update our `OnChatMessage` function to handle receiving a `ChatMessage` broadcast on the **Server**. As mentioned, if we are the **Server** receiving the broadcast, we want to relay this information and broadcast it back to all of our **Clients**, and we can do it very simply with `NetworkManager.SendToAll(ChatMessage)`: [hashtag](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#receiving-a-chatmessage-on-the-client) Receiving a `ChatMessage` on the **Client** ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Now that we are receiving messages from the **Server**, we can use our same `OnChatMessage` function to handle the data from the **Server**. For now, let's just debug the message: [hashtag](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#wrap-up) Wrap Up ------------------------------------------------------------------------------------------------------ With what we have, our final script should look like such: [PreviousSteam transport setupchevron-left](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam) [NextClient Side Predictionchevron-right](https://purrnet.gitbook.io/docs/community-guides/client-side-prediction) Last updated 1 year ago * [Introduction](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#introduction) * [Creating the ChatMessage struct:](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#creating-the-chatmessage-struct) * [Sending a ChatMessage to the Server](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#sending-a-chatmessage-to-the-server) * [Receiving a ChatMessage on the Server, and sending it to all Clients](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#receiving-a-chatmessage-on-the-server-and-sending-it-to-all-clients) * [Receiving a ChatMessage on the Client](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#receiving-a-chatmessage-on-the-client) * [Wrap Up](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system#wrap-up) Copy void NetworkManager.Subscribe(PlayerBroadcastDelegate callback, bool asServer) Copy public abstract void Subscribe(NetworkManager manager, bool asServer); public abstract void Unsubscribe(NetworkManager manager, bool asServer); Copy public class ChatManager : PurrMonoBehaviour { // Subscribe to ChatMessage events as either the Server, Client, or both public override void Subscribe(NetworkManager manager, bool asServer) { manager.Subscribe(OnChatMessage, asServer); } // Unsubscribe to ChatMessage events as either the Server, Client, or both public override void Unsubscribe(NetworkManager manager, bool asServer) { manager.Unsubscribe(OnChatMessage, asServer); } // Called when a ChatMessage broadcast is sent from either the Server or a Client private void OnChatMessage(PlayerID player, ChatMessage data, bool asServer) { // TODO: Make this work } } Copy void Update() { if (Keyboard.current.enterKey.wasPressedThisFrame) { ChatMessage message = new ChatMessage { name = InstanceHandler.NetworkManager.localPlayer.ToString(), message = "Hello World!" }; InstanceHandler.NetworkManager.SendToServer(message); } } Copy // Called when a ChatMessage broadcast is sent from either the Server or a Client private void OnChatMessage(PlayerID player, ChatMessage data, bool asServer) { if (asServer) // The broadcast was sent to the Server from a Client { // Send the broadcast down to the Clients InstanceHandler.NetworkManager.SendToAll(data); } } Copy // Called when a ChatMessage broadcast is sent from either the Server or a Client private void OnChatMessage(PlayerID player, ChatMessage data, bool asServer) { if (asServer) // The broadcast was sent to the Server from a Client { // Send the broadcast down to the Clients InstanceHandler.NetworkManager.SendToAll(data); } else // The broadcast was sent to the Clients from the Server { Debug.Log($"Received {data.message} from {data.name}!"); } } Copy public struct ChatMessage : IPackedAuto { public string name; public string message; } public class ChatManager : PurrMonoBehaviour { void Update() { if (Keyboard.current.enterKey.wasPressedThisFrame) { ChatMessage message = new ChatMessage { name = InstanceHandler.NetworkManager.localPlayer.ToString(), message = "Hello World!" }; InstanceHandler.NetworkManager.SendToServer(message); } } // Subscribe to ChatMessage events as either the Server, Client, or both public override void Subscribe(NetworkManager manager, bool asServer) { manager.Subscribe(OnChatMessage, asServer); } // Unsubscribe to ChatMessage events as either the Server, Client, or both public override void Unsubscribe(NetworkManager manager, bool asServer) { manager.Unsubscribe(OnChatMessage, asServer); } // Called when a ChatMessage broadcast is sent from either the Server or a Client private void OnChatMessage(PlayerID player, ChatMessage data, bool asServer) { if (asServer) // The broadcast was sent to the Server from a Client { // Send the broadcast down to the Clients InstanceHandler.NetworkManager.SendToAll(data); } else // The broadcast was sent to the Clients from the Server { Debug.Log($"Received {data.message} from {data.name}!"); } } } --- # Code stripping | PurrNet When you ship a client build of your game, all your code goes with it. That includes server-side logic like damage calculations, loot tables, or anti-cheat checks. Anyone who decompiles your build can read that code and use it to find exploits. Code stripping removes server-only code from client builds automatically, so that sensitive logic never leaves your server. This allows for an added layer of safety to your Server-Client games. And it's all handled for you by PurrNet. It will automatically strip all server specific code from all builds but Server builds. You can find the setting in Project Settings -> Multiplayer -> PurrNet -> Strip Code Mode, and simply set the desired strip mode. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-2470535189684656f958c378863068019d58c8e0%252Fimage%2520%2834%29.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=f6f6459c&sv=2) You also have `ServerOnly` attribute at your disposal to strip and ensure methods are only ever executed by server. [PreviousDelta Packerschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers) [NextBandwidth Profilerchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bandwidth-profiler) Last updated 11 days ago Copy public class TestingClass { [ServerOnly(StripCodeModeOverride.ReplaceWithLogError)] public void Testy() { Debug.Log("Testy called!"); } } --- # Made with PurrNet | PurrNet [🔥CRACKEDchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/cracked) [👾Symbiosischevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/land-of-symbiosis) [⚔️A Smithing Gamechevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/a-smithing-game) [🐼Bao Bao's™ Cozy Cleaning Serviceschevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/bao-baos-tm-cozy-cleaning-services) [🐍GeckoShopchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/geckoshop) [🐻Sport Animalschevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/sport-animals) [💖On-Togetherchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/on-together) [🤯Five Headschevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/five-heads) [🤪Playomojichevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/playomoji) [🏰Tower Arena Survivechevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/tower-arena-survive) [👿Cirque de Slaychevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/cirque-de-slay) [👖Pantlesschevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/pantless) [🐟Glorbchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/glorb) [PreviousCozy Weatherchevron-left](https://purrnet.gitbook.io/docs/integrations/cozy-weather) [NextCRACKEDchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/cracked) --- # Glorb | PurrNet [Check it out on Steamarrow-up-right](https://store.steampowered.com/app/3664600/GLORB/) Space fishing like you’ve never seen it! Catch bizarre Glorbs, upgrade your rod, and reel in rarer species. Play solo or co-op, and sell your haul to hungry aliens across the galaxy! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-16e6aec00585700673162231175ba2d6ea5f34d9%252FGlorbs%2520Swimming.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5bf9b2aa&sv=2) **Cast your rod in zero gravity and reel in the weirdest catches the galaxy has to offer—welcome to GLORB, the ultimate space fishing adventure!** Solo or co-op, you’ll fish for bizarre lifeforms called Glorbs (Gross Lumpy Organisms Roaming Blorptown), each with unique traits, behaviors, and delicious potential. Catch them, contain them, and cook them into galaxy-famous snacks like GLORB Hotdogs! Every Glorb you fish up affects gameplay: what you can craft, how you expand, and what chaos ensues when you test your creations yourself. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-0044071cbd81962a9ad78dbd4dff1317041c6bdc%252FGlorb%2520Catching.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=13a9bcd7&sv=2) [PreviousPantlesschevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/pantless) [NextAPI Documentationchevron-right](https://purrnet.gitbook.io/docs/api-documentation) Last updated 9 months ago --- # Bao Bao's™ Cozy Cleaning Services | PurrNet [Get it on Steam here!arrow-up-right](https://store.steampowered.com/app/3832380/Bao_Baos_Cozy_Cleaning_Services/) Relax with Bao Bao in this cozy 2D pressure washing sim. Clean pastel environments, chill lo-fi beats, and no stress, just peaceful vibes. Play solo or with up to 3 friends. A peaceful pressure washing game for quiet minds and cozy days. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-24da2f231c7582f79508c55cff84fec243df0fc7%252FMultiplayer.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=3aeef995&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/made-with-purrnet/bao-baos-tm-cozy-cleaning-services#welcome-to-bao-baos-cozy-cleaning-services) Welcome to Bao Bao’s Cozy Cleaning Services Where things don’t need to be perfect, just a little cleaner, a little calmer, and a little more you. Whether you're curled up with a podcast or just need something peaceful to help the world feel quieter, this gentle 2D pressure washing game is here for you. No timers, no stress, just pastel pixel art, soft sounds, and the satisfying swish of clean water. ### [hashtag](https://purrnet.gitbook.io/docs/made-with-purrnet/bao-baos-tm-cozy-cleaning-services#relax-your-mind-one-splash-at-a-time) Relax Your Mind, One Splash at a Time Ease your anxiety with every swipe of your washer. Let repetitive, simple tasks calm your thoughts while lo-fi beats and cozy visuals help you breathe a little deeper. [PreviousA Smithing Gamechevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/a-smithing-game) [NextGeckoShopchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/geckoshop) Last updated 7 months ago * [Welcome to Bao Bao’s Cozy Cleaning Services](https://purrnet.gitbook.io/docs/made-with-purrnet/bao-baos-tm-cozy-cleaning-services#welcome-to-bao-baos-cozy-cleaning-services) * [Relax Your Mind, One Splash at a Time](https://purrnet.gitbook.io/docs/made-with-purrnet/bao-baos-tm-cozy-cleaning-services#relax-your-mind-one-splash-at-a-time) --- # Five Heads | PurrNet [Check it out on Steamarrow-up-right](https://store.steampowered.com/app/3370840/Five_Heads/) Five prisoners each have a visible-to-others card on their forehead. Across three rounds, they logically deduce and rank their cards from lowest to highest. On the final round, each guesses their exact card, using every prior guess as a clue. A single mistake can doom them—can you solve it? ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-0924131585b3bdd5e44f9b3bfba6083018d8e8df%252Fss_789c753406bca804ad785c112e431222d3a99b00.600x338.jpg%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=50cd6ce1&sv=2) [PreviousOn-Togetherchevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/on-together) [NextPlayomojichevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/playomoji) Last updated 8 months ago --- # BitPacker (Serialization) | PurrNet [Networking custom classes, structs & typeschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types) [Async Serialization (packing)chevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing) [box-open-fullDelta Packerschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers) [PreviousCollider Rollback (Lag compensation)chevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback) [NextNetworking custom classes, structs & typeschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types) --- # GeckoShop | PurrNet [Get it on Steam now!arrow-up-right](https://store.steampowered.com/app/3392420/GeckoShop/) Collect and sell geckos. Manage and upgrade your shop. Explore the island, filled with life and secrets. Sell your geckos, or keep 'em as your Pets! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fshared.fastly.steamstatic.com%2Fstore_item_assets%2Fsteam%2Fapps%2F3392420%2Fheader.jpg%3Ft%3D1756109409&width=768&dpr=3&quality=100&sign=11bdddf7&sv=2) GeckoShop is a Sandbox **Shop Simulator + Creature Collector**, where you manage your own shop, selling all the different varieties of geckos! "Your dream of having your own Petshop finally comes true. Get cozy and have a fun time collecting and selling lizards, or grind hard and be the next millionare on this island!" **You'll have to:** * Deal with **customers**, set your own Prices. * Keep the store clean and working, or get **Employees** to do it for you! * Explore the small **open-world city**, with People to interact with, Quests and other things at every corner. * Find **Shells and ancient Artifacts** on the beach to **fill the museum**, to unlock even more content as you go. * Accept **Quests** from various NPCs on the island. * Catch **Thieves** trying to steal from your shop. * Decorate and upgrade your store, **however you want.** [PreviousBao Bao's™ Cozy Cleaning Serviceschevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/bao-baos-tm-cozy-cleaning-services) [NextSport Animalschevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/sport-animals) Last updated 7 months ago --- # On-Together | PurrNet [Check it out on Steamarrow-up-right](https://store.steampowered.com/app/3707400/OnTogether/) A social chatroom game to work, play games and hang out with your friends. Stay connected while you work, then use breaktimes to chat, chill, chew bubblegum and more! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-9f6de02eff15335421aa7aff8fef8a1d626a0380%252FOT_PinkFramed.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=835d96a8&sv=2) * **Work together!** Join your friends in the digital lounge for some cozy co-working. * **Share together!** Play in fullscreen or keep the screen neatly to the side! * **Track together!** Keep on track with timers, calendars, checklists and more. * **Play together!** Take regular breaks and do fun activities with your friends. * **On Together!** Discover new features, customize your avatar, and stay engaged. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-1cd609a77712a261009d3557b5c52b088bb6d046%252FOT_GreenFramed.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=6543feeb&sv=2) [PreviousSport Animalschevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/sport-animals) [NextFive Headschevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/five-heads) Last updated 9 months ago --- # Tower Arena Survive | PurrNet [Check it out on Steamarrow-up-right](https://store.steampowered.com/app/3348510/Tower_Arena_Survive/) Tower Arena Survive (TAS) is a dynamic mix of roguelike and tower defense, where your castle becomes an impregnable fortress. Use 80+ weapons and 88 upgrades to balance income, damage, and defense against up to 7 foes. Buy monsters to disrupt enemy waves and change the tide of battle! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-f59f64ad68beacb6960c47ef3de1f9e831c5b95a%252Fss_ee87009e146e09eca45885f63bcfc7516cff0335.600x338.jpg%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=2f72a648&sv=2) * **Endless Replayability:** Infinite tactical combinations ensure no two matches are ever the same. * **Innovative Mechanics:** Use over 80 weapons and 88 upgrades while deploying special monster waves to keep your adversaries on their toes. * **Thrilling Multiplayer:** Engage in epic battles against up to 7 players and prove your strategic supremacy. * **Fair Play:** Success is determined solely by your skill and tactics—no pay-to-win advantages. Step into the world of Tower Arena Survive and let your strategic brilliance shine as you build an unbreakable fortress and outsmart every challenger on the battlefield! [PreviousPlayomojichevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/playomoji) [NextCirque de Slaychevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/cirque-de-slay) Last updated 9 months ago --- # Purrdicted Character Controller Knockback | PurrNet [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#introduction) Introduction ------------------------------------------------------------------------------------------------------------------------------------------------ This guide will follow on from the [Purrdicted Character Controller](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction) guide and will show you how to implement knockback into your character controller! All the prerequisites from the previous guide still apply, so make sure to check that out first if you haven't already. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#getting-started) Getting Started ------------------------------------------------------------------------------------------------------------------------------------------------------ To get started, let's create a new class `PredictedKnockback`, and we'll go ahead and inherit from `PredictedIdentity` again. This time, however, we will only be creating a `STATE` struct, as we won't be needing any additional input for this. Copy using PurrNet.Prediction; using UnityEngine; public struct PredictedKnockbackState : IPredictedData { public void Dispose() { } } public class PredictedKnockback : PredictedIdentity { protected override void Simulate(ref PredictedKnockbackState state, float delta) { } } [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#state) STATE ---------------------------------------------------------------------------------------------------------------------------------- For our state, let's keep track of our `CurrentKnockbackVelocity`. We can use this to apply a knockback force to our character controller. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#preparing-to-simulate) Preparing to Simulate ------------------------------------------------------------------------------------------------------------------------------------------------------------------ Just like in the previous guide, we will want to get a reference to our `CharacterController`, as well as create a `drag` variable. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#simulate) Simulate ---------------------------------------------------------------------------------------------------------------------------------------- Now, let's implement the `Simulate` method. In here, we will apply the knockback velocity to our character controller. We'll do this by just moving the character controller by the `CurrentKnockbackVelocity` multiplied by `delta`. As well as that, we will reduce the `CurrentKnockbackVelocity` over time using `Vector3.MoveTowards` (you can probably use Lerp for this as well, or any other method). [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#applying-knockback) Applying Knockback ------------------------------------------------------------------------------------------------------------------------------------------------------------ Now that we have our knockback system set up, we need a way to apply knockback to our player. We can do this by creating a method called `ApplyKnockback`, which takes in a direction and a force. This method will be called on the server and will update the `CurrentKnockbackVelocity` in our state. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#final-script) Final Script ------------------------------------------------------------------------------------------------------------------------------------------------ Putting it all together, our final script looks like this: [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#testing) Testing -------------------------------------------------------------------------------------------------------------------------------------- As a very simple test, let's head back to our `PredictedCharacterController` and add a simple test to apply knockback when we press the `Shift` key. 1. Create a new `Dash` boolean in our `PredictedCharacterControllerInput` struct. 1. Check for input in `UpdateInput` 1. In `Simulate`, check if `input.Dash` is true, and if so, apply knockback to ourselves. 1. Add the `PredictedKnockback` component to the same GameObject as the `PredictedCharacterController` and `CharacterController` components (Our Player prefab). 2. Run the scene, and confirm hitting `Shift` applies "knockback" to the player! 3. (Optional) Try calling `ApplyKnockback` from another script you may have! [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#conclusion) Conclusion -------------------------------------------------------------------------------------------------------------------------------------------- Congratulations! You (hopefully) have a capsule that can now be knocked around! Fire up a client and use the [PurrTransport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/purr-transport) and try it out with real world latency! If you have any questions, feel free to ask in the [Discord serverarrow-up-right](https://discord.gg/HnNKdkq9ta) ! [PreviousPurrdicted Character Controllerchevron-left](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction) [NextUnity Multiplayer Services with PurrNetchevron-right](https://purrnet.gitbook.io/docs/community-guides/unity-multiplayer-services-with-purrnet) Last updated 2 months ago * [Introduction](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#introduction) * [Getting Started](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#getting-started) * [STATE](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#state) * [Preparing to Simulate](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#preparing-to-simulate) * [Simulate](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#simulate) * [Applying Knockback](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#applying-knockback) * [Final Script](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#final-script) * [Testing](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#testing) * [Conclusion](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction#conclusion) Copy public struct PredictedKnockbackState : IPredictedData { public Vector3 CurrentKnockbackVelocity; public void Dispose() { } } Copy [RequireComponent(typeof(CharacterController))] public class PredictedKnockback : PredictedIdentity { [SerializeField] float drag = 5f; CharacterController controller; protected void Awake() { controller = GetComponent(); } // ... rest of the code remains the same } Copy protected override void Simulate(ref PredictedKnockbackState state, float delta) { state.CurrentKnockbackVelocity = Vector3.MoveTowards(state.CurrentKnockbackVelocity, Vector3.zero, drag * delta); controller.Move(state.CurrentKnockbackVelocity * delta); } Copy public void ApplyKnockback(Vector3 velocity) { currentState.CurrentKnockbackVelocity += velocity; } Copy using PurrNet.Prediction; using UnityEngine; public struct PredictedKnockbackState : IPredictedData { public Vector3 CurrentKnockbackVelocity; public void Dispose() { } } [RequireComponent(typeof(CharacterController))] public class PredictedKnockback : PredictedIdentity { [SerializeField] float drag = 5f; CharacterController controller; protected void Awake() { controller = GetComponent(); } protected override void Simulate(ref PredictedKnockbackState state, float delta) { state.CurrentKnockbackVelocity = Vector3.MoveTowards(state.CurrentKnockbackVelocity, Vector3.zero, drag * delta); controller.Move(state.CurrentKnockbackVelocity * delta); } public void ApplyKnockback(Vector3 velocity) { currentState.CurrentKnockbackVelocity += velocity; } } Copy public struct PredictedCharacterControllerInput : IPredictedData { public Vector3 Movement; public bool Jump; public bool Dash; public void Dispose() { } } Copy protected override void UpdateInput(ref PredictedCharacterControllerInput input) { input.Movement = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")); input.Jump |= Input.GetKeyDown(KeyCode.Space); input.Dash |= Input.GetKeyDown(KeyCode.LeftShift); } Copy protected override void Simulate(PredictedCharacterControllerInput input, ref PredictedCharacterControllerState state, float delta) { // ... rest of the code remains the same if (input.Dash) GetComponent().ApplyKnockback(transform.forward * 10f); } --- # Network Audio Source | PurrNet Drop-in networked wrapper for Unity's `AudioSource`. Add it to any GameObject with an AudioSource to sync audio across the network. ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#setup) Setup 1. Add an `AudioSource` component to your GameObject. 2. Add `NetworkAudioSource` to the same GameObject (it auto-assigns the AudioSource via `Reset()`). 3. If you plan to change clips at runtime or use `PlayOneShot`, register those `AudioClip` assets in your `NetworkAssets` ScriptableObject. > If the clip is baked into the prefab/scene and never changes at runtime, you do **not** need to register it in NetworkAssets. ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#authority) Authority The `_ownerAuth` toggle (Inspector) controls who can drive the audio: * **Owner Auth (default):** The owning client controls playback. If no owner is set, the server controls it. * **Server Auth:** Only the server can control playback. All public setters and methods guard with `IsController(_ownerAuth)` and silently no-op on non-controllers. ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#public-api) Public API Use `NetworkAudioSource` the same way you would use Unity's `AudioSource`: #### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#properties) Properties Property Type Description `clip` `AudioClip` The clip to play. Must be in NetworkAssets if changed at runtime. `volume` `float` Volume (0.0 - 1.0) `pitch` `float` Pitch multiplier `loop` `bool` Whether to loop `mute` `bool` Whether muted `spatialBlend` `float` 2D (0.0) to 3D (1.0) mix `minDistance` `float` Distance where volume stops increasing `maxDistance` `float` Distance where attenuation stops `time` `float` Playback position in seconds `isPlaying` `bool` Read-only, current play state #### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#methods) Methods Method Description `Play()` Start playback with the current clip `Play(AudioClip)` Set clip and start playback `Stop()` Stop playback `Pause()` Pause playback `UnPause()` Resume from pause `PlayOneShot(AudioClip, float)` Fire-and-forget SFX (does not affect play state) ### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#how-it-works) How It Works #### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#dirty-flag-system) Dirty Flag System Each property setter and method sets a bit in a `AudioDirtyFlags` bitmask. On the next network tick, only the flagged fields are serialized and sent. If nothing changed since the last tick, nothing is sent. Example: calling only `volume = 0.5f` sends 2 bytes (flags) + 4 bytes (float) = **6 bytes**, not the full ~35 byte state. #### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#custom-serialization) Custom Serialization The `AudioSourceDelta` struct implements `IPacked` for hand-written bit-level serialization. It writes the flags bitmask first, then only the fields whose bits are set. Booleans (`loop`, `mute`) are packed as single bits. #### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#channel-selection) Channel Selection The dirty flags are checked each tick to decide reliable vs unreliable delivery: What changed Channel Reason `Play`, `Stop`, `Pause`, `UnPause` **Reliable** Discrete state transition; a dropped packet means permanent desync `clip` **Reliable** Discrete change; must arrive `volume`, `pitch`, `spatialBlend`, etc. **Unreliable** Continuous tweaks (fades); next tick resends latest value if dropped `PlayOneShot` **Reliable** Fire-and-forget SFX; must not be missed Late joiner reconcile **Reliable** Full state snapshot; must arrive If a tick contains both reliable and unreliable changes (e.g. `volume = 0.5f` + `Play()` in the same frame), the entire delta is sent reliably. #### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#rpc-routing) RPC Routing Follows the same pattern as `NetworkAnimator`: * **Server controller** -> `ObserversRpc(excludeSender: true)` directly to clients. * **Client controller** -> `ServerRpc` to server -> server applies locally -> `ObserversRpc(excludeSender: true)` to other clients. * **Late joiner** -> `OnObserverAdded` sends `TargetRpc` with full state to the new observer. #### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#playback-time) Playback Time * Time is only sent when explicitly changed (via `Play()`, `UnPause()`, or the `time` setter). * It is **not** auto-sent every tick while playing, avoiding constant bandwidth for background music. * On receiving end, if a time value arrives while playing and the local playback has drifted more than 0.1s, it snaps to the received time. #### [hashtag](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#audioclip-serialization) AudioClip Serialization AudioClips are serialized via PurrNet's `Packer` for `UnityEngine.Object`, which looks up the clip's index in `NetworkAssets`. This sends a small integer index, not the audio data. * If the clip **is not** in NetworkAssets, it resolves to `null` on the remote side. * If you never change the clip at runtime, it is never serialized (the `Clip` dirty flag is never set), so it doesn't matter whether it's registered. [PreviousNetwork State Machinechevron-left](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked) [NextFull game guideschevron-right](https://purrnet.gitbook.io/docs/full-game-guides) Last updated 1 month ago * [Setup](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#setup) * [Authority](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#authority) * [Public API](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#public-api) * [How It Works](https://purrnet.gitbook.io/docs/plug-n-play-components/network-audio-source#how-it-works) --- # Sport Animals | PurrNet [Check it out on Steam!arrow-up-right](https://store.steampowered.com/app/3950190/Sport_Animals/) Sport Animals is a physics-based game about a group of animals playing a sport! Customize your animal, practice your game, and get onto the field start scoring goals in 1v1s, 2v2s, or 3v3s. Compete against your friends or join public lobbies to play against other animals! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fshared.fastly.steamstatic.com%2Fstore_item_assets%2Fsteam%2Fapps%2F3950190%2F37f428a09de9dd054957113d519f2477dd3d736c%2Fheader.jpg%3Ft%3D1756345411&width=768&dpr=3&quality=100&sign=cdc8070d&sv=2) **Game Features** * **Character Customization:** Thousands of combinations of animals, colors, and accessories * **No Scripted Mechanics:** Physics-based ball and character movement * **Play with Anyone:** Public and friend-based lobby options * **Custom Lobbies:** Customize all game settings from the lobby, including gravity, shot speed, and more ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-2517cde3d39844d5a8555adff840d0e36c767140%252Fsport_animals_video.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=6fe2041d&sv=2) [PreviousGeckoShopchevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/geckoshop) [NextOn-Togetherchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/on-together) Last updated 7 months ago --- # RPC Benchmarks | PurrNet circle-exclamation PurrNet has yet to do most active performance optimization on RPC's. All results seen below are prior to most attempts at bettering performance, and it should only improve for future updates. Tests are benchmarked using the [NetLimiterarrow-up-right](https://www.netlimiter.com/) tool, as a neutral third party, to monitor process bandwidth for both download and upload rates. We also ensured that the Unity Editors (which this was tested within) was at 0 download and 0 upload before beginning any tests. Tests are setup with 1 editor as a server and 1 editor as a client (made with ParrelSync). The stats shown are data accumulative for both Unity Editors. All tests were performed in the exact same project. Tests carried out with the following occurring every tick at a tick-rate (send rate) of 20 - Sending 100 RPC’s to every client, both with and without parameters (with randomized values). [hashtag](https://purrnet.gitbook.io/docs/readme/performance/performance#results-and-conclusion) Results & Conclusion -------------------------------------------------------------------------------------------------------------------------- circle-info Mind that this only covers RPC benchmarks for now. See the [Why RPC Tests](https://purrnet.gitbook.io/docs/readme/performance/performance#why-rpc-tests) section for more on that. * PurrNet vs. FishNet: ~49.15% lower usage * PurrNet vs. Mirror: ~74.18% lower usage * PurrNet vs Netcode: ~73.81% lower usage * FishNet vs. Mirror: ~49.23% lower usage * FishNet vs. NetCode: ~48.48% lower usage * Mirror vs. NetCode: ~1.44% higher usage for Mirror ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e696bd1d93bce2d4ff7e5f2ee61cdfd5e0a7af5d%252Fimage%2520%283%29%2520%281%29.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5dcfd751&sv=2) [hashtag](https://purrnet.gitbook.io/docs/readme/performance/performance#average-usage) Average usage ---------------------------------------------------------------------------------------------------------- This is the average across 3 RPC tests with different data parameters. See the [Raw Data results](https://purrnet.gitbook.io/docs/readme/performance/performance#raw-results) for more information on each. System Download Upload PurrNet 12.357 KB/s 12.357 KB/s FishNet 24.303 KB/s 24.303 KB/s Mirror 47.867 KB/s 47.867 KB/s NetCode 47.187 KB/s 47.187 KB/s [hashtag](https://purrnet.gitbook.io/docs/readme/performance/performance#why-rpc-tests) Why RPC Tests? ----------------------------------------------------------------------------------------------------------- We will do more benchmark testing in the future, however. We found RPC's to be the core functionality of every networking system, and a good common denominator to test. Different networking systems build various components for various cases, so testing specific components will easily lead to faulty points of comparisons. [hashtag](https://purrnet.gitbook.io/docs/readme/performance/performance#raw-results) Raw results ------------------------------------------------------------------------------------------------------ Keep in mind that every data-point was fluctuating with ~0.2 KB/s. The picture was taken when seen around the mid-point of fluctuation, but this leaves margin for small inaccuracy in comparison. Download and Upload speed is the same, because both editors are represented. The server & the client. These numbers would differ if there was some faulty overhead on either side. ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/performance#purrnet-1.5.0) PurrNet 1.5.0 Vector3 + Quaternion + bool ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-a425c1afe544be2044e25e78e854e4413e8e9e67%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=71fae633&sv=2) Vector3 ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e3de0a5ddbe0d38de1ec71a25882db751c26ec3d%252Fimage%2520%2824%29.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=d1dd40bb&sv=2) RPC Only: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-da33e5de050ce511721acd21308f4aa2f1a1a646%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=bb117923&sv=2)\\ ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/performance#fishnet-4.5.6r-pro) FishNet 4.5.6R Pro Vector3 + Quaternion + bool ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-a58130691794fa35fed594439b835d33394e9763%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=1fc14b37&sv=2) Vector3 ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-9c2319de59e15e21796a06fc95e2381b5e63f834%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=32135933&sv=2) RPC Only: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-c11fc53f917b798eaaced504f1e7d226fdc7ddca%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=53b881eb&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/performance#mirror-93.0.1) Mirror 93.0.1 Vector3 + Quaternion + bool ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-1ec143ae4680da89abe0e56826f36cee8f5b9d0a%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=2ce4c5b&sv=2) Vector3 ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-ef7aeff54cb185ed22ae5ba383a48cb61fb5777c%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=a0d0744e&sv=2) RPC Only: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-b8b714642bb61ce4db77037c90ca4594dc5f27c3%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=6c564e53&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/performance#netcode-for-gameobjects-2.2.0) NetCode for GameObjects 2.2.0 Vector3 + Quaternion + bool ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-026b0dfea76acc14fe403188d15da38141f571e7%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=8d233da0&sv=2) Vector3 ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e4bd1856587bfadf6775566d814a7caf03e12da3%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=a118274f&sv=2) RPC Only ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-a245de34447000bd3a8a0e7c11cf15cdb0d64e4f%252Fimage.png%3Falt%3Dmedia&width=300&dpr=3&quality=100&sign=315d18b&sv=2) [PreviousPerformancechevron-left](https://purrnet.gitbook.io/docs/readme/performance) [NextNetwork Transform benchmarkschevron-right](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks) Last updated 1 year ago * [Results & Conclusion](https://purrnet.gitbook.io/docs/readme/performance/performance#results-and-conclusion) * [Average usage](https://purrnet.gitbook.io/docs/readme/performance/performance#average-usage) * [Why RPC Tests?](https://purrnet.gitbook.io/docs/readme/performance/performance#why-rpc-tests) * [Raw results](https://purrnet.gitbook.io/docs/readme/performance/performance#raw-results) * [PurrNet 1.5.0](https://purrnet.gitbook.io/docs/readme/performance/performance#purrnet-1.5.0) * [FishNet 4.5.6R Pro](https://purrnet.gitbook.io/docs/readme/performance/performance#fishnet-4.5.6r-pro) * [Mirror 93.0.1](https://purrnet.gitbook.io/docs/readme/performance/performance#mirror-93.0.1) * [NetCode for GameObjects 2.2.0](https://purrnet.gitbook.io/docs/readme/performance/performance#netcode-for-gameobjects-2.2.0) --- # Introduction | PurrNet PurrNet is a free, [performant](https://purrnet.gitbook.io/docs/readme/performance) and highly modular networking library for Unity, built by developers who have spent years working with multiplayer games. Our focus is on giving you the best possible developer experience: no clutter, no baking, no core features locked behind a paywall. Just a networking solution that works the way Unity works. [See our official website herearrow-up-right](https://purrnet.dev/) Check out the [ROADMAP](https://purrnet.gitbook.io/docs/readme/roadmap) to see what we are currently planning to add. [**Get it on the Unity Asset Store**arrow-up-right](https://assetstore.unity.com/packages/slug/297320) [**Get it on GitHub**arrow-up-right](https://github.com/BlenMiner/PurrNet) ### [hashtag](https://purrnet.gitbook.io/docs#our-mission) Our mission PurrNet is 100% free with no pro or premium version. You can use it to ship your game, and we ask nothing in return. No revenue sharing, no up-front cost, no strings attached. We built PurrNet because we felt that existing networking solutions fight against Unity's natural workflow instead of embracing it. You shouldn't have to learn a completely different way of working just because your game is multiplayer. With PurrNet, if you know how to use Unity and C#, you already know most of what you need. Read the [Unique to PurrNet](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet) section to see what sets us apart! Make sure to join our Discord here: [https://discord.gg/NP9tP9Qx9Rarrow-up-right](https://discord.gg/NP9tP9Qx9R) circle-info **Building a multiplayer game as a studio?** We offer hands-on project support, migration planning, architecture reviews, and custom feature development. Everything is tailored to what your team actually needs. [Learn more about our studio support](https://purrnet.gitbook.io/docs/readme/for-studios) or [get in touch directlyarrow-up-right](https://purrnet.dev/studios) . ### [hashtag](https://purrnet.gitbook.io/docs#what-makes-purrnet-different) What makes PurrNet different There are a few things that we think really set PurrNet apart. Here's a quick taste, and you can find the full list on the [Unique to PurrNet](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet) page. **Spawning and despawning just works.** You call `Instantiate()` and `Destroy()` the same way you already do in Unity, and PurrNet handles the networking for you. No special spawn calls, no extra steps. **You control the authority model.** Our [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) system lets you configure who can do what (spawn, despawn, call RPCs, sync data) without changing your code. Start with full client authority for fast prototyping, then tighten it to server authority when you're ready. One setting, not a rewrite. **No baking, no limitations.** We don't bake components or scene IDs. You can nest prefabs, rearrange your hierarchy, and work with version control without running into the edge cases that baking creates. **RPC innovations.** We support [Generic](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/generic-rpc) , [Static](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/static-rpc) , and [Awaitable](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc) RPCs, giving you more flexibility in how you structure your networked code. [hashtag](https://purrnet.gitbook.io/docs#everyone-vs-server-auth) Everyone vs Server Auth? ------------------------------------------------------------------------------------------------ Throughout PurrNet and this documentation, you'll see two terms come up a lot: [Server Auth](https://purrnet.gitbook.io/docs/terminology/server-auth-safe) and [Everyone](https://purrnet.gitbook.io/docs/terminology/client-auth-everyone-unsafe) . These define who is allowed to perform a given action. **Server Auth** means only the server can perform the action. This is the safe option for competitive games, since clients can't cheat by doing things they shouldn't be allowed to. **Everyone** means all clients and the server can perform the action. This makes development faster and easier, but does mean clients have more control, which could allow cheating. This is a great fit for co-op games, friendly PvP, or during early development when you just want things to work. The important thing is that you set this through [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) , not in your game code. So you can switch between the two at any time without rewriting anything. [hashtag](https://purrnet.gitbook.io/docs#persistent-user-data) Persistent user data ----------------------------------------------------------------------------------------- PurrNet has built-in support for persistent player data through its cookie system. If a player disconnects and later reconnects, the server still recognizes them as the same player and can restore their data. You can configure how persistent this is: scoped to the connection, the process, or the machine. [NextUnique to PurrNetchevron-right](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet) Last updated 11 days ago * [Our mission](https://purrnet.gitbook.io/docs#our-mission) * [What makes PurrNet different](https://purrnet.gitbook.io/docs#what-makes-purrnet-different) * [Everyone vs Server Auth?](https://purrnet.gitbook.io/docs#everyone-vs-server-auth) * [Persistent user data](https://purrnet.gitbook.io/docs#persistent-user-data) --- # Systems and modules | PurrNet [brainNetwork Managerchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) [wifiTransportschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports) [fingerprintNetwork Identitychevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) [chart-networkNetwork Moduleschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules) [phone-flipRemote Procedure Callschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc) [megaphoneBroadcastschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/broadcast) [mountain-sunScene Managementchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module) [address-bookAddressableschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/addressables) [diamonds-4Instance Handlerchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/instance-handler) [cubeCollider Rollback (Lag compensation)chevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback) [box-open-fullBitPacker (Serialization)chevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization) [qqCode strippingchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/code-stripping) [chart-columnBandwidth Profilerchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bandwidth-profiler) [PreviousEasy Multiplayer Physics (input Sync)chevron-left](https://purrnet.gitbook.io/docs/guides/easy-multiplayer-physics-input-sync) [NextNetwork Managerchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) --- # Pantless | PurrNet [Check it out on Steamarrow-up-right](https://store.steampowered.com/app/2952930/Pantless/) Pantless brings a hilarious twist to the rage-inducing game genre inspired by Getting Over It with Bennett Foddy. In this third-person, physics-based game, players control customizable characters hilariously devoid of pants, navigating through chaotic and fun challenges. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-5d7492ea40e2ca4196fc83e0a80de68eeb474988%252FMultiplayer.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=7f2ab278&sv=2) Inspired by the likes of Getting Over It with Bennett Foddy, this game challenges players to navigate through increasingly absurd and challenging terrains—all while hilariously lacking trousers. Whether you're flailing up a hill or tumbling towards a new obstacle, prepare for uncontrollable laughter and shouting. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-5b42c05fbaebadfcb4f5b62ca977b4f0d04f7fb4%252FCustomization.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=2ebd46a6&sv=2) [PreviousCirque de Slaychevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/cirque-de-slay) [NextGlorbchevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/glorb) Last updated 9 months ago --- # Symbiosis | PurrNet [Check it out on Steamarrow-up-right](https://store.steampowered.com/app/2874540/Land_of_Symbiosis/) Symbiosis is a solo or co-op Action RPG that blends base defense, exploration, and sandbox survival in a procedurally generated alien world. Battle through waves of thousands of mechanical creatures, scavenge their remains, and loot the environment to craft powerful weapons, armor, and automated defenses. Fast-paced top-down combat and cooperative gameplay makes it a perfect survival experience to share with friends or brave alone, ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-1e381f764cc1d7dc1e6e3718cde583cbf4bfc89a%252FNew_Release.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=cb74c577&sv=2) To survive, you must navigate the hostile environments of planet Sigma Century gathering resources, crafting tools, and upgrading your equipment to fend off relentless creatures. Drinking and eating are handled efficiently by your advanced suit technology. However, generating oxygen is another story. You'll need to find, harvest, and process specific elements to keep your life support system running ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-033992e8993bfac0864952fa542d25adf347ca76%252FNew_base_defense.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=b4b62a77&sv=2) [PreviousCRACKEDchevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/cracked) [NextA Smithing Gamechevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/a-smithing-game) Last updated 10 days ago --- # Network Transform benchmarks | PurrNet Tests were conducted using NetLimiter as a neutral third-party tool to monitor bandwidth usage (both download and upload). The Unity Editors were verified at **0 KB/s** before starting any test. Tests are setup with 1 editor as a server and 1 editor as a client (made with ParrelSync). The stats shown are data accumulative for both Unity Editors. All tests were performed in the exact same project. **Tests carried out with the following setup** **\-** Exact same [MonoBehaviour scriptarrow-up-right](https://gist.github.com/BobsiUnity/9e7379e7f6018ed10b32fa9460d0fe0e) used to move, rotate and scale objects - Network Systems own network transform added to all objects - Default Network Transform settings on all objects All the tests include 100 NT’s in the scene. The only thing that is modified between tests is how many of them actually move (toggled [monobehaviour scriptarrow-up-right](https://gist.github.com/BobsiUnity/9e7379e7f6018ed10b32fa9460d0fe0e) ). This is to test different scenarios where there are also inactive Network Transforms. Moving NTs are constantly rotating, scaling & positioning. ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#notable-adjustments) Notable Adjustments: **FishNet:** NTs were not spawning on the client until the **SceneCondition** observer check was removed from the **Network Manager**. This likely has no performance impact, but it hasn’t been confirmed. [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#results-and-conclusion) Results & Conclusion ------------------------------------------------------------------------------------------------------------------------------------------- * PurrNet vs. FishNet → 49.00% lower usage * PurrNet vs. Mirror → 61.43% lower usage * PurrNet vs. NetCode → 72.56% lower usage * FishNet vs. Mirror → 24.83% lower usage * FishNet vs. NetCode → 46.38% lower usage * Mirror vs. NetCode → 24.96% lower usage ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-a9f0ea976963d6dbfe1893fbffa9e39e1ce00ba4%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=56200941&sv=2) [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#average-usage) Average Usage --------------------------------------------------------------------------------------------------------------------------- This is the average across all 5 tests. See the [Raw results](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#raw-results-1) for data of the individual data points. System Download Upload PurrNet 11.598 KB/s 11.598 KB/s Mirror 30,356 KB/s 30,356 KB/s FishNet 22,676 KB/s 22,676 KB/s NetCode For GameObjects 40.434 KB/s 40.434 KB/s [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#raw-results) Why a Network Transform test? ----------------------------------------------------------------------------------------------------------------------------------------- The Network Transform is one of the most common components used with any networking solution. So if this is widely used across your project, knowing the performance of the Network Transform could be very important! Keep in mind that specific components can hold different functionality across different systems, and it can range widely how they are handled behind the scenes. This means that the [RPC benchmarks](https://purrnet.gitbook.io/docs/readme/performance/performance) is likely a better indicator for the overall performance of the networking solution. [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#raw-results-1) Raw results ------------------------------------------------------------------------------------------------------------------------- Keep in mind that every data-point was fluctuating with ~0.2 KB/s. The picture was taken when seen around the mid-point of fluctuation, but this leaves margin for small inaccuracy in comparison. Download and Upload speed is the same, because both editors are represented. The server & the client. These numbers would differ if there was some faulty overhead on either side. ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#purrnet-1.8.1) PurrNet 1.8.1 ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-2766c96b1a80dd48f8b047c24cf5e08f90238903%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=840d62e5&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#mirror-93.0.1-unreliable-nt) Mirror 93.0.1 (Unreliable NT) ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-6e2e03e9537102d545c74dcfa90d0f977c6d7769%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=1e888f6&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#fishnet-4.5.6r-pro) FishNet 4.5.6R Pro ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-300a42001201fb088dbe659af18165894d941cb8%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=789b228c&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#netcode-for-gameobjects-2.2.0) NetCode for GameObjects 2.2.0 ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-9d41ce4ea44ea0dbe2ef46ac21a1e09c8548652d%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=85145be3&sv=2) [PreviousRPC Benchmarkschevron-left](https://purrnet.gitbook.io/docs/readme/performance/performance) [NextSupport PurrNetchevron-right](https://purrnet.gitbook.io/docs/readme/support-purrnet) Last updated 1 year ago * [Notable Adjustments:](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#notable-adjustments) * [Results & Conclusion](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#results-and-conclusion) * [Average Usage](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#average-usage) * [Why a Network Transform test?](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#raw-results) * [Raw results](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#raw-results-1) * [PurrNet 1.8.1](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#purrnet-1.8.1) * [Mirror 93.0.1 (Unreliable NT)](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#mirror-93.0.1-unreliable-nt) * [FishNet 4.5.6R Pro](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#fishnet-4.5.6r-pro) * [NetCode for GameObjects 2.2.0](https://purrnet.gitbook.io/docs/readme/performance/network-transform-benchmarks#netcode-for-gameobjects-2.2.0) --- # Broadcasts | PurrNet Sometimes you need to send data over the network that isn't tied to any specific game object. Think of things like a chat message, a global game event ("the match is starting"), or sending some data before the player has even spawned in. RPCs are tied to Network Identities, so they can't help you here. Broadcasts fill that gap. They allow you to easily send and receive data without requiring network identities. It can be useful in certain systems and situations, however, for most setups, it will be just as good and easier to work with [RPC's](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc) . In order to work with broadcasts, you need 3 things: * Custom data structure to send * Subscribing to the broadcasting event * Sending the data The client can only broadcast directly to the server, where as the server/host can broadcast to everyone. A basic broadcasting setup will look something like this: [PreviousPre/Post Processorschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/pre-post-processors) [NextScene Managementchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module) Last updated 11 days ago Copy 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; } --- # Collider Rollback (Lag compensation) | PurrNet [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#what-is-collider-rollback) What is collider rollback? ------------------------------------------------------------------------------------------------------------------------------------------ Imagine you're playing catch with a friend over the internet, but there's a small delay between when you throw the ball and when your friend sees it. Sometimes it looks like you missed them even though on your screen it was a perfect throw! Collider rollback is like having a time machine that quickly goes back to check "Wait, where was my friend ACTUALLY standing when the ball was thrown?" This works best when there's a referee (the server) controlling where everyone can move - because then the referee knows exactly where everyone really was! But if everyone can move wherever they want without checking with the referee first (client-sided movement), using this time machine doesn't make much sense - because we already trust what each player sees on their screen anyway! So rollback is mostly useful in games where the server is in charge of all the movement, like many fighting games or competitive shooters. You can think of collider rollback as server validating hits accounting for your ping. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#setting-up-colliders-for-rollback) Setting up colliders for rollback --------------------------------------------------------------------------------------------------------------------------------------------------------- Add `ColliderRollback` component and you should be about done. You have two options from here, either leave it to it's default settings (auto add all children including the current game object) or you can manually specify which colliders to add. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-2f403eee30fb55ea48ffca0f4df3f8761261c22c%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5d4d08c&sv=2) Default Settings ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-3e77adbc156eb3e28783a52f303b6cf93e3aee91%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=cc4023ca&sv=2) Custom collider list This is enough to start recording the history of your colliders. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#validating-hits) Validating hits --------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#from-the-client-side) From the client side On the client side you need to send the time at which you acted, this is made available to `NetworkIdentity` via `rollbackTick` . But you can get it from the `NetworkManager` too via : So here is an example of shooting a raycast on the client's end and sending it to the server for validation: Locally (aka on client side) you do raycasts as you normally would and then simply forward that to the server. In this scenario `ShootOnServer` is a `ServerRpc` . ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#from-the-server-side) From the server side Let's validate this hit on the server now! This will require re-shooting the same ray accounting for the time difference like this: Notice how we don't use `Physics.Raycast` , this is because we should do the ray-casting through our module to properly account for state. Unlike other systems we don't physically move the colliders because this can trigger unwanted physics events leading to weird bugs if unaccounted for. Instead we bend the ray on a per collider basis and solve the hits that way so you don't have to worry about colliders moving about. [PreviousInstance Handlerchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/instance-handler) [NextBitPacker (Serialization)chevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization) Last updated 1 year ago * [What is collider rollback?](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#what-is-collider-rollback) * [Setting up colliders for rollback](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#setting-up-colliders-for-rollback) * [Validating hits](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#validating-hits) * [From the client side](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#from-the-client-side) * [From the server side](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback#from-the-server-side) Copy networkManager.tickModule.rollbackTick Copy var ray = _camera.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out var hit)) { // react locally with some hitmark or sound, blood vfx etc // we should let server do actual killing/damaging } ShootOnServer(rollbackTick, ray); Copy [ServerRpc(requireOwnership: false)] private void Shoot(double preciseTick, Ray ray) { if (rollbackModule.Raycast(preciseTick, ray, out var hit)) { // handle hit by damaging the player or something } } --- # Addressables | PurrNet PurrNet offers out of the box addressables support for Unity. It handles the usage fully asynchronous as well, so it doesn't bake all the addressables initially, also allowing for more flexibility and more optimized memory management. circle-info Addressables support is currently a young feature, and is likely to be improved in the future. Let us know if anything doesn't work as intended, and we'll be right on it! [PreviousScene Managementchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module) [NextAddressable Spawning & Despawningchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-spawning-and-despawning) Last updated 26 days ago --- # Scene Management | PurrNet In a singleplayer game, loading a new scene is straightforward. But in multiplayer, you have a real problem: if one player loads a scene, every other connected player needs to load it too, and they all need to end up in the same state. On top of that, you might want some players in one scene and others in a different one entirely. Without networked scene management, you'd have to manually tell every client which scene to load, track who is where, and handle edge cases like late joiners loading into the wrong place. PurrNet's Scene Module takes care of all of this for you. Using it is as easy as changing scenes with Unity's default scene manager, but it keeps all your players in sync automatically. There is also some more depth to understanding the logic of players in scenes. Scene management **needs to be handled on the Server**, so everything below is needed to be read with that context! [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module#loading-scenes) Loading Scenes -------------------------------------------------------------------------------------------------------------- For ease of use, you can simply call the `LoadSceneAsync` with the scene name as a string, or the scene id. There are multiple overloads to the `LoadSceneAsync` method (see image below), such as directly setting the load mode directly, using Unity `LoadSceneParameters`, or adding custom PurrNet settings. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-4ed9a5cf76f919a6385c5093ae2d821ec7ae02f8%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=1105dbfb&sv=2) Overloads for the LoadSceneAsync You can also load a scene with custom scene settings called `PurrSceneSettings`, see example below: ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module#purrscenesettings) PurrSceneSettings The `PurrSceneSettings` is a struct of setting for your scene loading. This has the following values: * mode : The Unity [SceneLoadModearrow-up-right](https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.html) , so whether it should be additive or single * physicsMode : The Unity [LocalPhysicsModearrow-up-right](https://docs.unity3d.com/ScriptReference/SceneManagement.LocalPhysicsMode.html) to load with * isPublic : Whether the scene automatically pulls all players into the scene or not [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module#unloading-scenes) Unloading scenes ------------------------------------------------------------------------------------------------------------------ Unloading scenes with PurrNet is as easy as calling the unload of the scene, similar to Unity's own scene management. This will unload the scene for all clients in the scene, as well as the server. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-5e355919cb40f1a7d2a37bf8c5a46d6c3af138cd%252Fimage%2520%2812%29.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=2beaf996&sv=2) Overloads for UnloadSceneAsync The [UnloadSceneOptionsarrow-up-right](https://docs.unity3d.com/ScriptReference/SceneManagement.UnloadSceneOptions.html) are a Unity class, which by default is None when using the UnloadSceneAsync. [PreviousBroadcastschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/broadcast) [NextAddressableschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/addressables) Last updated 11 days ago * [Loading Scenes](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module#loading-scenes) * [PurrSceneSettings](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module#purrscenesettings) * [Unloading scenes](https://purrnet.gitbook.io/docs/systems-and-modules/scene-module#unloading-scenes) Copy //Loads scene with default settings networkManager.sceneModule.LoadSceneAsync("sceneName"); Copy //Loads scene with custom settings var settings = new PurrSceneSettings(); settings.isPublic = true; //Default setting - Has all connections automatically switch scene settings.mode = LoadSceneMode.Single; //Default setting - Unloads all other scenes networkManager.sceneModule.LoadSceneAsync("sceneName", settings); Copy networkManager.sceneModule.UnloadSceneAsync("sceneName"); --- # Owner Auth | PurrNet Owner Auth is a setting found across PurrNet that determines whether the owning player or the server has authority over a networked value or component. When owner auth is **enabled**, the owning client is the one responsible for controlling and updating the value. This means only the owner can modify it, and changes are sent from the owner to everyone else. If the object has no owner, the server automatically takes over as the [controller](https://purrnet.gitbook.io/docs/terminology/controller) . When owner auth is **disabled**, only the server can control the value, regardless of who owns the object. [hashtag](https://purrnet.gitbook.io/docs/terminology/owner-auth#where-youll-see-it) Where you'll see it ------------------------------------------------------------------------------------------------------------- Owner auth shows up in two main places: **Sync types** have it as a constructor parameter: Copy // Server authed (default) private SyncVar health = new(100); // Owner authed private SyncVar health = new(100, ownerAuth: true); This works the same for [SyncList](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist) , [SyncDictionary](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncdictionary) , [SyncEvent](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncevent) and other sync types. **Plug n' play components** like [Network Transform](https://purrnet.gitbook.io/docs/plug-n-play-components/network-transform) and [Network Rigidbody](https://purrnet.gitbook.io/docs/plug-n-play-components/network-rigidbody) expose it as a toggle in the inspector. Enabling it lets the owning player directly control the object's movement, while disabling it means the server is in charge. [hashtag](https://purrnet.gitbook.io/docs/terminology/owner-auth#when-to-use-it) When to use it ---------------------------------------------------------------------------------------------------- Owner auth is great for things the owning player should directly control, such as player movement, camera-related values or personal settings. It gives the owner immediate responsiveness since they don't have to go through the server first. For anything gameplay-critical like scoring, health in competitive games, or shared world state, you're better off keeping owner auth disabled and letting the server be the authority. See [Server Auth (Safe)](https://purrnet.gitbook.io/docs/terminology/server-auth-safe) and [Client Auth/Everyone (Unsafe)](https://purrnet.gitbook.io/docs/terminology/client-auth-everyone-unsafe) for more on that. [hashtag](https://purrnet.gitbook.io/docs/terminology/owner-auth#related) Related -------------------------------------------------------------------------------------- * [Controller](https://purrnet.gitbook.io/docs/terminology/controller) - How PurrNet decides who is "in control" of an object * [Ownership](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/ownership) - How ownership itself works * [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) - Configuring authorization at a broader level [PreviousHostchevron-left](https://purrnet.gitbook.io/docs/terminology/host) [NextServer Auth (Safe)chevron-right](https://purrnet.gitbook.io/docs/terminology/server-auth-safe) Last updated 1 month ago * [Where you'll see it](https://purrnet.gitbook.io/docs/terminology/owner-auth#where-youll-see-it) * [When to use it](https://purrnet.gitbook.io/docs/terminology/owner-auth#when-to-use-it) * [Related](https://purrnet.gitbook.io/docs/terminology/owner-auth#related) --- # Purrdicted Character Controller | PurrNet [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#introduction) Introduction -------------------------------------------------------------------------------------------------------------------------------------- While PurrDiction's main flagship feature is supporting `Rigidbody` controllers and interaction, we can still predict with the `CharacterController` as well (as well as everything else)! Before we begin, it's highly recommended to read through the [PurrDiction](https://purrnet.gitbook.io/docs/client-side-prediction) docs to get a better understanding of how the system works. For further reading, you should also check out a series of articles on [Client Side Prediction](https://purrnet.gitbook.io/docs/community-guides/client-side-prediction) by Neotime, which provides a far more in-depth look at Client Side Prediction principles. This guide will assume you have a basic understanding of how PurrDiction works, as well as a basic understanding of PurrNet. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#getting-started) Getting Started -------------------------------------------------------------------------------------------------------------------------------------------- To get started, let's create a new script called `PredictedCharacterController` and inherit from `PredictedIdentity`. As well as that, let's create the `STATE` and `INPUT` structs, and implement the `Simulate` and `UpdateInput` methods. `UpdateInput` is called every **frame** on the client and is where we will gather our input to later simulate. `Simulate` is called every **tick**, and is where we will apply our input to the state of our player. Copy using PurrNet.Prediction; using UnityEngine; public struct PredictedCharacterControllerInput : IPredictedData { public void Dispose() { } } public struct PredictedCharacterControllerState : IPredictedData { public void Dispose() { } } [RequireComponent(typeof(CharacterController))] public class PredictedCharacterController : PredictedIdentity { protected override void UpdateInput(ref PredictedCharacterControllerInput input) {} protected override void Simulate(PredictedCharacterControllerInput input, ref PredictedCharacterControllerState state, float delta) { } } [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#input) INPUT ------------------------------------------------------------------------------------------------------------------------ The `INPUT` struct is what holds all of the input that we want to be able to check and use inside `Simulate`. For now, let's keep things extremely simple and check for Movement, and jumping, which we can do by adding a `Vector3` for movement and a `bool` for jumping. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#state) STATE ------------------------------------------------------------------------------------------------------------------------ The `STATE` struct is what holds the current state of our player. This can be a lot of things, but since we are just working on simple movement, we can just store our current Velocity. You may wonder why we are storing Velocity instead of Position, and that is because we will be using the `PredictedTransform` to handle our position and rotation state. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#gathering-input) Gathering Input -------------------------------------------------------------------------------------------------------------------------------------------- Now that we have our `INPUT` and `STATE` structs, we can begin gathering input to later simulate. To do this, we use the `UpdateInput` method, which is called every frame on the client. You can gather input in any way you like, but for simplicity, we will use Unity's old input system. You may be curious to why we are using `|=` for `input.Jump` instead of just `=`, and that is because while `UpdateInput` is called every frame, `Simulate` is called every tick, which means that if we press the jump button between ticks, we may miss the jump input. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#simulating-with-our-input) Simulating with Our Input ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Now that we have gathered our input, we can simulate! To do this, we can override the `Simulate` method, which is called every tick. If you are familiar with writing a basic `CharacterController`, the rest of this will be very familiar to you! This code is modified from the Unity documentation on [CharacterController.Movearrow-up-right](https://docs.unity3d.com/ScriptReference/CharacterController.Move.html) . [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#final-script) Final Script -------------------------------------------------------------------------------------------------------------------------------------- Putting it all together, our final script looks like this: [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#creating-the-player-prefab) Creating the Player Prefab ------------------------------------------------------------------------------------------------------------------------------------------------------------------ 1. Inside of Unity, Create a new Empty `GameObject` and name it **Player**. 2. Add the `PredictedCharacterController` component we just created, and also add the `PredictedTransform` component mentioned earlier to handle the Position and Rotation state. 3. Under the **Player**, Create a new `Capsule` and name it **Graphics**. This will be our graphical representation of our player. Make sure to remove the `CapsuleCollider` from the **Graphics** object, as having colliders on Graphical objects is not allowed. 4. Finally, assign the **Graphics** GameObject to the `PredictedTransform` component's `Graphics` field. [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#testing) Testing ---------------------------------------------------------------------------------------------------------------------------- 1. Create a new scene and add a plane for the player to walk on and create a `Camera` in the scene so we can see our player. 2. Create an empty `GameObject` and name it **NetworkManager**. Add the `NetworkManager` component to it. 3. Create an empty `GameObject`, and name it **Prediction Manager**. Add the `PredictionManager` component to it. Click on _New_ under the `PredictedPrefabs` field to create a new `PredictedPrefabs` asset. 4. Add the `PredictedPlayerSpawner` component to the **Prediction Manager** and assign the **Player** prefab to the `Player Prefab` field. 5. Run the scene and confirm you can jump and move around! [hashtag](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#conclusion) Conclusion ---------------------------------------------------------------------------------------------------------------------------------- Congratulations! You (hopefully) have a capsule that can run around and jump! Fire up a client and use the [PurrTransport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/purr-transport) and try it out with real world latency! As long as you always modify your state inside `Simulate`, and gather your input inside `UpdateInput` (or `GetFinalInput`), you'll notice the workflow becomes very similar to writing singleplayer code. If you have any questions, feel free to ask in the [Discord serverarrow-up-right](https://discord.gg/HnNKdkq9ta) ! [PreviousClient Side Predictionchevron-left](https://purrnet.gitbook.io/docs/community-guides/client-side-prediction) [NextPurrdicted Character Controller Knockbackchevron-right](https://purrnet.gitbook.io/docs/community-guides/character-controller-knockback-client-side-prediction) Last updated 2 months ago * [Introduction](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#introduction) * [Getting Started](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#getting-started) * [INPUT](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#input) * [STATE](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#state) * [Gathering Input](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#gathering-input) * [Simulating with Our Input](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#simulating-with-our-input) * [Final Script](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#final-script) * [Creating the Player Prefab](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#creating-the-player-prefab) * [Testing](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#testing) * [Conclusion](https://purrnet.gitbook.io/docs/community-guides/character-controller-client-side-prediction#conclusion) Copy public struct PredictedCharacterControllerInput : IPredictedData { public Vector3 Movement; public bool Jump; public void Dispose() { } } Copy public struct PredictedCharacterControllerState : IPredictedData { public Vector3 Velocity; public void Dispose() { } } Copy protected override void UpdateInput(ref PredictedCharacterControllerInput input) { input.Movement = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")); input.Jump |= Input.GetKeyDown(KeyCode.Space); // identical to: input.Jump = Input.GetKeyDown(KeyCode.Space) || input.Jump; } Copy // Variables for speed, gravity, and jump height [SerializeField] float speed = 5f; [SerializeField] float gravity = -10f; [SerializeField] float jumpHeight = 1f; private bool bool IsGrounded() { // controller.isGrounded is weird, dont use it return Physics.SphereCast( transform.position - Vector3.up * (controller.height / 2 - 0.1f), controller.radius - 0.05f, Vector3.down, out _, 0.2f ); } protected override void Simulate(PredictedCharacterControllerInput input, ref PredictedCharacterControllerState state, float delta) { bool groundedPlayer = IsGrounded(); if (groundedPlayer && state.Velocity.y < 0) { state.Velocity.y = 0f; } // Read input Vector3 move = new Vector3(input.Movement.x, 0, input.Movement.z); move = Vector3.ClampMagnitude(move, 1f); // Jump if (input.Jump && groundedPlayer) { state.Velocity.y = Mathf.Sqrt(jumpHeight * -2.0f * gravity); } // Apply gravity state.Velocity.y += gravity * delta; // Combine horizontal and vertical movement Vector3 finalMove = (move * speed) + (state.Velocity.y * Vector3.up); controller.Move(finalMove * delta); } Copy using PurrNet.Prediction; using UnityEngine; public struct PredictedCharacterControllerInput : IPredictedData { public Vector3 Movement; public bool Jump; public void Dispose() { } } public struct PredictedCharacterControllerState : IPredictedData { public Vector3 Velocity; public void Dispose() { } } [RequireComponent(typeof(CharacterController))] public class PredictedCharacterController : PredictedIdentity { [SerializeField] float speed = 5f; [SerializeField] float gravity = -10f; [SerializeField] float jumpHeight = 1f; CharacterController controller; protected void Awake() { controller = GetComponent(); } protected override void UpdateInput(ref PredictedCharacterControllerInput input) { input.Movement = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")); input.Jump |= Input.GetKeyDown(KeyCode.Space); } private bool bool IsGrounded() { return Physics.SphereCast( transform.position - Vector3.up * (controller.height / 2 - 0.1f), controller.radius - 0.05f, Vector3.down, out _, 0.2f ); } protected override void Simulate(PredictedCharacterControllerInput input, ref PredictedCharacterControllerState state, float delta) { bool groundedPlayer = IsGrounded(); if (groundedPlayer && state.Velocity.y < 0) { state.Velocity.y = 0f; } // Read input Vector3 move = new Vector3(input.Movement.x, 0, input.Movement.z); move = Vector3.ClampMagnitude(move, 1f); // Jump if (input.Jump && groundedPlayer) { state.Velocity.y = Mathf.Sqrt(jumpHeight * -2.0f * gravity); } // Apply gravity state.Velocity.y += gravity * delta; // Combine horizontal and vertical movement Vector3 finalMove = (move * speed) + (state.Velocity.y * Vector3.up); controller.Move(finalMove * delta); } } --- # Security Model | PurrNet Client‑side prediction keeps gameplay responsive, but authority lives on the server. This page explains what data is trusted, how inputs are validated, and patterns to avoid client‑driven exploits. * * * **Trust Model** * Server authoritative: Only player inputs are accepted from clients. Game state is never trusted from clients. * Server re‑simulates using received inputs and produces the authoritative state. Clients reconcile to that. * Ownership enforced: The server only accepts inputs for an identity from its owner. Implications: * A client can’t force state (position, health, spawns) — it can only propose inputs for identities it owns. * Desyncs are corrected by reconciliation; local client changes that don’t match server simulation are discarded visually over time. * * * **Input Validation** * Sanitize Inputs: Implement `SanitizeInput(ref INPUT)` to clamp, normalize, and drop invalid or out‑of‑range values. This runs on the server before simulation. * Extrapolate Input: For remote players, the client may extrapolate locally to smooth visuals. This is not trusted — the server still simulates from received inputs only. * Repeat Input Factor: Cap how many ticks a prior input is reused for remote interpolation; prevents long input stretching. Checklist: * Clamp analog ranges (e.g., normalize movement vectors). * Gate one‑shot actions (fire/jump) with game‑side cooldowns, not just input booleans. * Ignore inputs that don’t make sense for the current owned state (e.g., jump while already in the air if your design disallows it). [PreviousDisposable Collectionschevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/disposable-collections) [NextDevelopment Shortcutschevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/development-shortcuts) Last updated 4 months ago --- # CRACKED | PurrNet [Check it out on Steam!arrow-up-right](https://store.steampowered.com/app/3917010/CRACKED/) **CRACKED** is a fast-paced, 3D PvP roguelike where glass-headed mages go head-to-head (or head-to-shards) in chaotic arena duels. Inspired by _Rounds_ and powered by physics-based combat, every fight is unpredictable, explosive, and wildly replayable. Play solo or team up in 1v1, 2v1, or 2v2 showdowns. After every round, choose from a pool of powerful, quirky upgrades that stack into outrageous builds — from homing fireballs and knockback shockwaves to rapid-fire chaos or debilitating hexes. No two fights are the same. No glass head survives forever. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-8668dd64f720bf8a31008a9a92e18d4f02dddb99%252FGetting%2520killed.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=1542af31&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/made-with-purrnet/cracked#core-features) **Core Features:** * 🔮 **Team-based Brawling:** Battle it out in 1v1, 2v1, or 2v2 matches. * 🧠 **Glass Heads Will Roll:** Shatter your enemies in intense physics-driven wizard fights. * 🧪 **Build Your Madness:** Stack upgrades each round and create broken, over-the-top combos. * ⚡ **Fast & Fluid Rounds:** Jump into short, explosive matches with zero downtime. * 🎮 **Controller & Online Ready:** Built for online competitive fun. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-b3ab0753d53ab2f597793f8def607418a3fd6d70%252FPicking%2520upgrades.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=8cc3310a&sv=2) [PreviousMade with PurrNetchevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet) [NextSymbiosischevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/land-of-symbiosis) Last updated 7 months ago --- # SyncBigData | PurrNet `SyncBigData` is module that can be used by itself but also powers other modules related to sending big amount of data to other players. The general idea is simple, you give it a big array of bytes, specify if it's owner auth and how many kb/s you want it to use and then it does the rest by itself. This naturally makes it asynchronous, the data your provide will take time to arrive and the receivers need to wait for it to be ready. Ownership changes and re-connections are also handled gracefully: * If ownership is lost mid-upload then it is canceled and the previous valid data is kept. * If owner disconnects and re-connects (using the cookie session) they will start downloading the latest state from the server, if this was an image for example they will retrieve the same image that was last sent. [PreviousSyncInputchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput) [NextSyncTextureFilechevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/syncbigdata/synctexturefile) Last updated 3 months ago --- # SyncTextureFile | PurrNet ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252FfT3EybzS73aeqzQ3MXc1%252Fimage.png%3Falt%3Dmedia%26token%3Ddc991826-6ad7-4a82-8687-c43db8a44bf4&width=768&dpr=3&quality=100&sign=9a1e877&sv=2) Defining the module: Copy [SerializeField] private SyncTextureFile _avatar; Setting data as the controller: Reacting to new data received: [PreviousSyncBigDatachevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/syncbigdata) [NextRemote Procedure Callschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc) Last updated 3 months ago Copy _avatar.filePath = _path; Copy private void OnEnable() { _avatar.onDataChanged += OnAvatarChanged; } private void OnDisable() { _avatar.onDataChanged -= OnAvatarChanged; } private void OnAvatarChanged(Texture2D texture) { _avatarImage.texture = texture; } --- # PurrDiction | PurrNet Client-side prediction is a technique used particularly in games to provide a local, responsive experience in a multiplayer environment. It involves predicting future game states on the client side based on user inputs, allowing the game to feel instant and reactive. For example, if you are controlling a character and keep moving right, the client can predict that you will continue moving right and display this movement immediately. This way, when you interact with other players, both see a consistent outcome. Of course, predictions can sometimes be incorrect. When this happens, the client needs to apply corrections. This involves adjusting the game state to match the server's authoritative state, ensuring that everything remains consistent and accurate. Don't worry if this sounds a bit confusing at first—this overview is meant to set the stage and help you understand the design decisions and limitations involved in client-side prediction. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-aaf0b9928068ddefa22505aad8e1f3f9f5aed232%252F1715578825487.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=a889046e&sv=2) [PreviousBandwidth Profilerchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/bandwidth-profiler) [NextOverviewchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/overview) Last updated 7 months ago --- # Incremental Game Sample | PurrNet ### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#introduction) Introduction This sample includes a basic gameplay loop + simple polish (audio & visual) of a multiplayer incremental game where you collect resources, bring them to your base, upgrade and work towards an end goal that can be purchased in the shop. You utilize your dashing to move through resources to break them and gather their sweet sweet loot! The setup is built so it's easy to scale on the core elements such as: * Upgrades that are based on scriptables. * Player which is ran by state machine logic * Resource spawning which is fully modular and layer based. **Used in this sample:** * Kenney.nl for visual assets * Cinemachine * New Unity input system * PurrNet ### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#install-instructions) Install instructions 1. Clone or download the [Github repositoryarrow-up-right](https://github.com/PurrNet/PurrNet-Incremental-Sample) ([https://github.com/PurrNet/PurrNet-Incremental-Samplearrow-up-right](https://github.com/PurrNet/PurrNet-Incremental-Sample) ) 2. Open the project in Unity 3. Open `Scenes/GameScene` 4. Press Play. You should see your player spawn, be able to run around, chop trees, suck up loot and dump it at your base ### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#written-guide) Written guide When you first spin up the project, you'll see a familiar structure of folders, the most relevant for now being: - Art - Prefabs - Scripts But first, we gotta go into the Scenes folder and open the `GameScene`. Given everything is correct off the bat, you should be able to just press play, and the project should work. You should see your player spawn, be able to run around, chop trees, suck them up and put them in your base to purchase upgrades. You should also be able to spin up a clone and test with that as well! #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#network-manager) Network Manager The [Network Manager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) is setup in the most basic way, currently utilizing the [UDP transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/udp-transport) . This is something you'd want to change later in case you want to setup with Steam or even a dedicated server (Read the guide here). The [Network Prefabs](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-prefabs) are already setup and located in your Prefabs folder, and the [NetworkAssets](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-assets) are already setup to support the audio of the game and are located in the root Assets folder. The [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) are also setup as the unsafe rules, as this is generally not the type of game where we care about cheating, and it makes the workflow overall simpler. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#player-setup) Player setup The player is spawned using the PurrNet `PlayerSpawner` which is located on the "PlayerSpawner" game object in the scene. If you open the Player prefab, you'll see that the hierarchy holds the root player object, a "Visuals" object which holds a "Bodies" object which holds the actual character meshes. The reason for this is that the animations run on the "Visuals", whilst the code animations (squishing the player when dashing and sucking) are running on the "Bodies" transform. We also have the Camera object which holds the Cinemachine camera logic including the `PlayerCamera` component. This component is responsible for the dynamic camera and zooming effects when adding wood to the base. Going back to the root of the Player prefab, you'll notice that the player is running on the [Networked State Machine](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked) of PurrNet. This makes it very easy for us to network which state the player is currently in. It runs like any finite state machine, and depending on Input will switch between the states (this is defined in the individual states) The players states are currently: - Player Movement - Player Dash - Player Suck Before we delve into these states, it's important to mention that the player also consists of a [Network Transform](https://purrnet.gitbook.io/docs/plug-n-play-components/network-transform) which is [owner](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/ownership) authed, a normal Rigidbody, Box Collider and Audio source. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#player-states) Player States **Player Movement** The Player movement is a very basic take on some simple Rigidbody movement. It's only 100 lines of code, and that's including the state machine handling as well. **Player Dash** The Player Dash will handle dashing in a given direction. As soon as it enters it's state it'll start tracking the amount of time it has dashed. It'll then set it's collider to a trigger and continue to dash in a given direction. By the end of it's dash, it'll ensure that it's not inside any collider, and if it is, move it out. Afterwards, it'll re-enable it's own collider and exit the state back to the movement state. **Player Suck** The sucking state will attempt to suck up any Loot found in a given cone in front of it. First it'll take ownership of said loot. Then it'll utilize physics forces on the given loot and apply those to go back towards the player, and once close enough it'll be added to the players inventory and disabled from the world (not yet despawned/destroyed) #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#base) Base The base is the core of most of the gameplay loop. It holds and syncs the wood count which is also what's displayed in the top right corner of the screen. It'll "suck out" loot from the players inventory and into the base, handling some visual effects on the fly. As soon as that loot hits the base, it'll properly despawn/destroy the loot. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#resources-basemineable-imineable) Resources (BaseMineable, IMineable) Resources, in this case the trees, are setup to have x-amount of health. This is synced, and whoever hits the resource, takes ownership and changes the health sync immediately. This means that everything feels nice and responsive for the player hitting the resource, regardless of host or not. When they despawn, they will spawn some amount of loot, depending on the players upgrades and what's defined in editor. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#resource-spawning) Resource Spawning Resource spawning is handled by the `ResourceSpawner` component found on the GameManager in the scene. This is a layer based system, which would allow you to spawn any types of resources with various conditions and rarities. It is spawned utilizing noise, and limiting the amount of resources to spawn per frame in order to avoid stuttering individual frames. This component also tracks all the players on the server, so when players move, resources will spawn around them, giving the idea of an infinite world. And of course it also cleans up behind itself, so when there are no players in a given radius of a resource, it'll despawn. Lastly, the component also handles respawning of resources after a set interval of being harvested. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#loot) Loot This is currently seen as the currency, and just registered as wood. I went a simple route on this, to keep things linear, but it'd be easy to expand on if needed be. These are dropped of resources being harvested through dashing. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#object-pooling) Object Pooling This setup utilizes PurrNet [object pooling](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/pooling) which requires nearly no customization what so ever. This is enabled in the Network Prefabs scriptable. This is utilized for the trees in the world and the loot. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#upgrades) Upgrades The upgrades are the core of most gameplay mechanics. There is currently built in upgrades for: - Movement speed - Damage - Dash distance - Inventory space - Wood per tree - Win the game These are utilized to populate the shop through the `PlayerUpgrades` component found on the GameManager in the scene. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#network-audio) Network Audio Some of the audio are modularized to be called over the network using the `NetworkAudioHelper`. These are for example the wood hitting audio. As a tree is cut down, we'd still want to hear the audio, so it's instead handled using pooling by the `NetworkAudioHelper`. #### [hashtag](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#ui-handling) UI Handling The UI of this setup is extremely bare bones. There is a "HUDView" and a "ShopView", which are both found nested under the Canvas in the scene. They handle exactly what their name indicates, and various scripts communicate with them utilizing the [InstanceHandler](https://purrnet.gitbook.io/docs/systems-and-modules/instance-handler) of PurrNet. [PreviousFull game guideschevron-left](https://purrnet.gitbook.io/docs/full-game-guides) [NextFirst Person Shooterchevron-right](https://purrnet.gitbook.io/docs/full-game-guides/first-person-shooter) Last updated 28 days ago * [Introduction](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#introduction) * [Install instructions](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#install-instructions) * [Written guide](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample#written-guide) --- # Network Prefabs | PurrNet When you spawn an object over the network, the other side needs to know which prefab to instantiate. The server might say "spawn prefab #7", but if the client doesn't have the same list of prefabs in the same order, it'll spawn the wrong thing or fail entirely. Network Prefabs solve this by giving all machines a shared registry of prefabs that PurrNet can reference by ID. The Network Prefabs is a scriptable object used by PurrNet to cross identify prefabs. This is used for [spawning and despawning](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/spawning-and-despawning) of objects. This is set as a reference on your [NetworkManager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-d6df8574f78117be352241839ff72ddb8d65c241%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=8ac0631f&sv=2) The Network Prefabs scriptable having pulled a bunch of project prefabs Settings of the Network Prefabs scriptable allows you to easily modify from where it pulls the objects. If you have a prefabs folder which you intend to hold all prefabs, it would be optimal to set the folder to that. It will automatically search any sub-folders. Settings can also modify whether it should auto generate to find prefabs or you'd want to handle it manually, and lastly it can also find any non-networked objects and add them as well, so even objects without a network identity script, can be spawned over the network. This will automatically add the PrefabLink script to it. [PreviousNetwork Ruleschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) [NextNetwork Assetschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-assets) Last updated 11 days ago --- # Network Assets | PurrNet The Network Assets is a scriptable object used by PurrNet to cross identify any object in your project. This is used for cross referencing during the reading and writing process, meaning anything in this scriptable can be utilized for syncing, RPC's and more! This is set as a reference on your [NetworkManager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) Settings of the Network Assets scriptable allows you to easily modify which project objects/assets it will pull when you hit the generate button. Refreshing the type list might be necessary if you add or remove types from your game, like making a new scriptable object for example. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-assets#how-to-use-it) How to use it? All you need is the network assets assigned in your network manager and do networking as you normally would and it will just work! (Make sure your asset is in the assets list) ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-assets#is-this-needed) Is this needed? Absolutely not. This is an "opt-in" situation. You can utilize it for networking anything, but you don't have to, and you can still easily write your own serialization as well. This is used as a first layer of fallback, so if some type can't be written, it'll send it from the Network Assets Scriptable. [PreviousNetwork Prefabschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-prefabs) [NextNetwork Visibilitychevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility) Last updated 9 months ago * [How to use it?](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-assets#how-to-use-it) * [Is this needed?](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-assets#is-this-needed) --- # Addressable RPC's | PurrNet In order to make it exceptionally easy to work with Addressables, it's important to also make it easy to work with RPC's. For this reason, the [Async packing](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing) was added to PurrNet, and we've also built an out of the box networked type called `NetworkAddressable` to help you easily send assets over the network. Here is an example usage. This works with any AssetReference, in this example a gameobject: Copy public AssetReferenceGameObject prefabReference; private void SendTestReference() { //Implicit conversion to the NetworkAddressable: ReceiveAddressable(prefabReference); } [ObserversRpc] private void ReceiveAddressable(NetworkAddressable addressable) { //Implicit conversion back to a normal addressable Debug.Log($"Received addressable: {addressable}", addressable.Asset); } It handles implicit conversions for you, so as you can see in the example code, you just work with it as you would with any addressable. [PreviousAddressable Scene handlingchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-scene-handling) [NextInstance Handlerchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/instance-handler) Last updated 26 days ago --- # Instance Handler | PurrNet In many games you'll have managers or systems (like a GameManager, ScoreManager, etc.) that need to be accessed from all sorts of places in your code. Without a clean way to find these instances, you end up with messy singleton patterns, static references, or FindObjectOfType calls scattered everywhere. The Instance Handler gives you a simple, centralized way to register and retrieve any instance from anywhere. The Instance Handler allows you to easily get access the [Network Manager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) from anywhere, even non [Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) or [Network Behaviour](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) scripts: Copy var nm = InstanceHandler.NetworkManager; But more importantly, it allows you to easily register, unregister and get instances yourself! Below is an example of registering and unregistering the instance, as well as the instances being used from static methods. Mind that you can also get the instances from other scripts, whether they are MonoBehaviour or not. Copy 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); } } [PreviousAddressable RPC'schevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-rpcs) [NextCollider Rollback (Lag compensation)chevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback) Last updated 11 days ago --- # Don't destroy on load | PurrNet Some networked objects need to persist across scene changes. The Network Manager itself is a good example, but this also applies to things like a player's persistent inventory or a global chat system. If these get destroyed when a new scene loads, you lose the connection and all the state that came with it. You can move Network Identities to DDOL (Dont Destroy On Load) by simply doing it as you would normally, but it has to be prior to the network starting. So calling it in awake is best. Copy private void Awake() { DontDestroyOnLoad(this); } [PreviousPlayerIdentitychevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/playeridentity) [NextPoolingchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/pooling) Last updated 11 days ago --- # Static RPC | PurrNet Utilizing statics are super common during your development flow, so why shouldn't you with networking as well! Now keep in mind that this can be unsafe if you run gameplay logic through it, as we don't have a [network identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) to check ownership with. This works with all RPC types such as: **ServerRpc, ObserversRPC, TargetRPC**. It also works with [generic](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/generic-rpc) and [awaitable](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc) RPC's! [PreviousGeneric RPCchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/generic-rpc) [NextAwaitable RPCchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc) Last updated 1 year ago Copy private void SendData() { TestRpc(123); } [ServerRpc] private static void TestRpc(int myNumber) { Debug.Log($"Received {myNumber}"); } --- # Network Visibility | PurrNet In any multiplayer game with a larger world, you don't want every player receiving data about every single object at all times. If there are hundreds of objects on the other side of the map that a player can't even see, sending all that data is wasted bandwidth and can hurt performance. Network Visibility lets you control which objects each player actually receives data about, so you only send what matters. Overall this system deciphers whether the server should send data from a Network Identity to a specific client. The [Network Manager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) takes in a network visibility rule-set, and this rule-set consists of rules/conditions. In the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) you can modify the behavior of this as to whether the object is destroyed, de-spawned or simply stays without receiving or sending data. A simple example of this is the [Distance Condition](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility/distance-condition) . If the distance is set to 50 units, if the player is further away from an object than 50 units, they won't receive any data. This is great for performance, especially in larger environments and games, so we don't have to send unnecessary data. The network visibility rules/conditions, can be added as global conditions, but they can also be added on a per [Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) basis, by adding a custom rule-set as an override. You can also custom make your own condition for cases where the pre-made conditions aren't fitting. All you need to do is inherit from **INetworkVisibilityRule** and implement the **HasVisibility** rule, and simply return true or false depending on whether it should send data or not. [PreviousNetwork Assetschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-assets) [NextUsing the visibilitychevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility/using-the-visibility) Last updated 11 days ago --- # Network Rules | PurrNet In most networking solutions, you're locked into a single authority model. Either the server controls everything, or you're on your own. This means that during early development, you're often fighting the networking just to test basic gameplay, and when you're ready to ship, switching to a stricter model can require rewriting a lot of code. Network Rules solve this by letting you configure your authority model in one place, without changing any of your game code. These allow for you to fully customize your networking experience for the easiest possible workflow or full server auth to ensure a cheat free experience. You can make your own network rule-set, but PurrNet also comes pre-packaged with some. In most cases, you won't need any more than these. If you want the best/easiest possible development loop, you should go for the "Unsafe" rule-set. This allows for the most freedom as a developer. If you want it to act as most networking systems with full server authority, you should select the "ServerStrict". However, the "ServerOwner" will also allow a great deal more flexibility, without great cost of cheating. The rules are pretty self-explanatory if you take a look at the scriptable. It will allow you to adjust things such as (but not exclusive to): * Authorization to spawn and despawn * The default owner * How ownership acts when given * Who can assign ownership * Who is responsible of synchronizing values * And much more. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules#include-instantiated-scene-objects) Include Instantiated Scene Objects ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- By default, PurrNet only considers objects baked into the scene (tracked via `PurrSceneInfo`) as scene identities. This gives you a deterministic ordering across all clients, since Unity likes to reorder hierarchy roots between builds for no good reason. If you set up your scene programmatically, for example instantiating objects before the `NetworkManager` starts, those objects won't be picked up as scene identities. You can enable **Include Instantiated Scene Objects** in your spawn rules to change this. When enabled, any root `NetworkIdentity` objects in the scene at the time of collection get appended after the baked set. All clients need to instantiate these objects in the same order to keep identity assignments consistent. circle-exclamation This is opt-in. If your clients don't set up the scene in the same order, identity assignments will diverge. Only enable this if your programmatic scene setup is deterministic across all clients. [PreviousNetwork Managerchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) [NextNetwork Prefabschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-prefabs) Last updated 7 days ago --- # Development Shortcuts | PurrNet Sometimes you want to move fast and "just make it work," even if it isn’t fully deterministic yet. PurrDiction’s reconciliation can paper over some differences: the server remains authoritative and clients will correct. This page outlines pragmatic shortcuts, when they’re acceptable, and how to minimize the jank. We still recommend keeping simulation deterministic whenever possible. Use these techniques as stepping‑stones, then graduate to deterministic implementations. * * * **What This Means** * You perform an action only on the server (or with server‑only branches) that the client didn’t predict. * The server’s authoritative state diverges from the client’s predicted state. * Clients receive the verified frame and correct via rollback/interpolation (possible pops or snaps). Acceptable when: * Prototyping features and iterating quickly. * Visual differences are minor and short‑lived. * You rely on interpolation to hide most of the correction. * * * **Common Shortcuts** * Server‑Only State Change * Branch on server during simulation to adjust state (e.g., grant a buff, change velocity) without an equivalent predicted path. * Clients will snap/adjust to the verified state; use Interpolation Settings to smooth. * Server‑Only Spawn/Despawn * Create or delete predicted objects only on the server. Clients will not create them until verified; expect a visible pop. * Mitigation: Use PredictedIdentitySpawner to mirror verified spawns deterministically; tune smoothing on affected visuals. * Server‑Only Teleport * Teleport or re‑position on the server in response to a trigger. Clients will hard‑correct. * Mitigation: Call Reset Interpolation on the affected identity (e.g., PredictedTransform) to avoid dragging back. * Clamp Inputs Only On Server * Skip Sanitize Input on clients while enforcing it on the server. Expected behavior wins on server; minor local feel differences may occur. * * * **Mitigations For Visual Jank** * Interpolation Settings * Increase correction rates and thresholds to eat small errors; snap only when exceeding a larger threshold. * Predicted Events For Feedback * Fire a Predicted Event to give immediate local SFX/VFX/UI before verification arrives. * Events are view‑only and won’t cause double‑fires during replay. * Hide Pop‑Ins * Spawn with a short fade‑in or particle puff to mask late appearance. * Defer critical camera cuts until after verification. * * * **Caveats & Risks** * Player Experience * Overuse creates a "floaty" or inconsistent feel. Favor deterministic paths for core control and combat. * Debugging Cost * Server‑only branches can hide logic errors until later. Add logs/telemetry during development and remove branches as you stabilize. * Accumulated Error * Continuous server‑only changes can constantly fight interpolation. Prefer one‑off authoritative corrections with clear visuals. * * * **Graduating To Determinism** * Replace server‑only branches with shared deterministic logic in Simulate/Late Simulate. * Move triggers to inputs or states owned by the controller; derive server results from the same inputs. * Use Predicted Hierarchy to pre‑create expected objects locally and let verification finalize them. * For strict stability, consider Deterministic Identity and Validate Deterministic Data during QA. [PreviousSecurity Modelchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/security) [NextToolschevron-right](https://purrnet.gitbook.io/docs/tools) Last updated 4 months ago --- # UniTask | PurrNet PurrNet supports UniTask automatically if it is installed through the `Package Manager` otherwise you need to manually set the `UNITASK_PURRNET_SUPPORT` script symbol in your player settings. We have a convenience button in the menus to install/uninstall `UniTask` using the `Package Manager`. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-eb18974b6aedfd9384e6715a4be98dcc3c9eeacb%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=b29a5e55&sv=2) UniTask is just a better and more efficient way to work with asynchronous methods in Unity. [PreviousIntegrationschevron-left](https://purrnet.gitbook.io/docs/integrations) [NextDissonancechevron-right](https://purrnet.gitbook.io/docs/integrations/dissonance) Last updated 9 months ago --- # Network Manager | PurrNet Every multiplayer game needs a central place that manages the connection, keeps track of who's connected, and holds the settings that define how your networking behaves. Without it, you'd have to wire up connections, transports, prefab registration, and rules all manually across different scripts. The Network Manager is that central place. It acts as the heart and brain of PurrNet, so all settings of your network are setup through your network manager. Look at the [Installation/Setup](https://purrnet.gitbook.io/docs/getting-started/installation-setup) segment for a guide on the setup. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager#settings-in-the-network-manager) Settings in the Network manager **Start Server Flags: These** are the conditions for the server to automatically start upon seeing the NetworkManager **Start Client Flags:** These are the conditions for the client to automatically start upon seeing the NetworkManager **Cookie Scope**: PurrNet has cookie/caching for data, and this is where and how the cookies act within PurrNet. - Live With Connection: Nothing is stored once the connection stops - Live With Process: Nothing is stored once the game/process stops - Store Persistently: Cookies are stored in the player prefs of your system **Transport:** This is what transport to use for the data sending. By default, you'd likely want to be using the UDP transport. You can just add the transport to the same gameobject and drag it to this field. **Network Prefabs:** These are the [Network Prefabs](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-prefabs) to use. Just simply select the scriptable. **Network Rules:** These are the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) to use with the Network Manager. Just simply select the scriptable you want to use. This can be overwritten on the individual [network identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) . **Visibility Rules:** This is the [Visibility](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility) rule-set to use. This will decipher the default rules for player to Network Identity relation. This can be overwritten on the individual [network identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) . [PreviousSystems and moduleschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules) [NextNetwork Ruleschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) Last updated 11 days ago --- # Generic RPC | PurrNet Generics in C# are awesome for creating modular and scalable systems, and we didn't want PurrNet holding you back. This works with all RPC types such as: **ServerRpc, ObserversRpC, TargetRPC**. It also works with [static](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/static-rpc) and [awaitable](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc) RPC's! Below is a super simple example usage of generics with RPC's. It's as easy as that! [PreviousRemote Procedure Callschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc) [NextStatic RPCchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/static-rpc) Last updated 1 year ago Copy 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()}"); } --- # Views and Interpolation | PurrNet PurrDiction separates simulation state from presentation. You simulate and reconcile state deterministically, then render a smooth view from that state. This page explains the view pipeline, interpolation, and how to customize smoothing and snapping. * * * **Update Flow** * Simulation runs at the network tick rate, including local prediction and server‑verified replays. * Each frame, the Prediction Manager calls `UpdateView(deltaTime)` on predicted identities in either `Update` or `LateUpdate` based on the Update View Mode setting. * For `PredictedIdentity`: * A small interpolation buffer smooths from the last verified state toward the latest predicted state. * `UpdateView(STATE viewState, STATE? verified)` receives the interpolated view plus the most recent verified snapshot, if available. Key APIs on `PredictedIdentity`: * `public override void ResetInterpolation()` * `public override void UpdateRollbackInterpolationState(float delta, bool accumulateError)` * `protected virtual void ModifyRollbackViewState(ref STATE state, float delta, bool accumulateError)` * `protected virtual void UpdateView(STATE viewState, STATE? verified)` Terminology: * `viewState`: The state you should render this frame (often interpolated and error‑corrected). * `verified`: The last server‑verified state if present, otherwise null. * * * **Interpolation Buffer** * A per‑identity interpolation helper buffers several ticks (`~tickRate/10`, min 2) to absorb jitter. * Default interpolation blends linearly: `IMath.Add/Scale/Negate` on `STATE`. You can override `Interpolate(STATE from, STATE to, float t)`. * When reconciliation happens, the system snapshots and adjusts the buffer to smoothly approach the corrected timeline. Tip: If your `STATE` contains non‑linear fields (e.g., quaternions), override `Interpolate` and use slerp or domain‑specific blending. * * * **PredictedTransform** `PredictedTransform` demonstrates a robust, out‑of‑the‑box interpolation and correction strategy: * Uses `TransformInterpolationSettings` to control smoothing and snap thresholds for both position and rotation. * Accumulates error on reconcile and gradually corrects it over multiple frames. * Snaps (teleports) when error exceeds an upper threshold; skips correction when below a lower threshold. Adjustable settings via `TransformInterpolationSettings`: * `useInterpolation`: Toggle smoothing corrections. * `positionInterpolation` and `rotationInterpolation` (`PredictedInterpolation`): * `correctionRateMinMax`: Base correction speed range. * `correctionBlendMinMax`: Scales correction rate based on error magnitude. * `teleportThresholdMinMax`: Lower bound to ignore tiny error; upper bound to snap immediately. When you need to reset visual drift (e.g., teleporting gameplay), call `ResetInterpolation()` to clear accumulated error and re‑align view with prediction. * * * **Owner vs. Controller** * `isOwner`: True when the identity’s owner matches `PredictionManager.localPlayer`. * `isController`: True for the owner on clients; on server, also true for bots/AI. * Views should be purely visual; avoid branching simulation on these flags. Use them to toggle camera, UI, or local effects. * * * **Tuning and Debugging** * Choose `Update` vs `LateUpdate` from the Prediction Manager (Update View Mode) if visuals depend on post‑physics order. * Log and compare `viewState` against `verified` to validate correction behavior. * If you see oscillation, widen lower thresholds or increase correction rate. * For large warps (portals, respawns), prefer snapping by calling `ResetInterpolation()`. [PreviousPredicted State Nodechevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine/predicted-state-node) [NextBest Practiceschevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/best-practices) Last updated 4 months ago --- # State Handling | PurrNet `STATE` is the single source of truth for your simulation. PurrDiction records, reconciles, and interpolates states so clients stay responsive while remaining consistent with server authority. * * * **Requirements** * `STATE` must be a struct implementing `IPredictedData` (which extends `IMath` and `IPackedAuto`). * Provide deterministic math on your state via `IMath` methods (the default linear interpolation uses `Add`, `Scale`, `Negate`). * * * **What Goes in STATE** * Simulation‑critical data only: positions, rotations, velocities, timers, flags; anything that affects future ticks. * Do not store transient view data; compute those in `UpdateView`. * * * **Lifecycle and Reconciliation** * Simulation loop writes `currentState` each tick. * PurrDiction saves snapshots and reconciles against server frames as they arrive. * On rollback: * `ReadState` loads the verified snapshot. * `Rollback(tick)` applies it and calls `SetUnityState(state)` for external components. * `UpdateRollbackInterpolationState(delta, accumulateError)` adjusts view smoothing. Unity bridging: * `protected override void GetUnityState(ref STATE state)` — Read Unity → STATE when needed. * `protected override void SetUnityState(STATE state)` — Write STATE → Unity on rollback. * * * **View vs Verified** * `viewState` is the smoothed state to render this frame. * `verifiedState` exposes the most recent authoritative snapshot, when present. * Override `UpdateView(STATE viewState, STATE? verified)` to drive visuals. * Call `ResetInterpolation()` to clear accumulated error (e.g., teleports). * * * **Example: Minimal STATE** [PreviousInput Handlingchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/input-handling) [NextExecution Flowchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/flow) Last updated 4 months ago Copy public struct MyState : IPredictedData { public Vector3 pos; public Quaternion rot; public void Dispose() {} // IMath default ops come from packer codegen; override Interpolate if non-linear } public class Mover : 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 Simulate(ref MyState s, float dt) { /* mutate s deterministically */ } protected override void UpdateView(MyState view, MyState? verified) { transform.SetPositionAndRotation(view.pos, view.rot); } } --- # Using the visibility | PurrNet You have to manually tick the visibility check, and this is for good reason! Working with custom conditions could potentially be taxing in terms of processing, and a lot of unnecessary evaluations could easily happen. Working with visibility is generally depending on the use case, for example: If you work with the [Distance condition](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility/distance-condition) , you might only want to re-evaluate the visibility once the identity has moved x-amount of units. So if we just evaluated every 1 second, and you have 1.000 identities all standing still, that's 1.000 evaluations every second that could have been easily avoidable. Actually evaluating and identities visibility is luckily very easy! All you have to do is call the `EvaluateVisibility` method on any given identity. So this way you can easily custom make your own logic for when you check visibility. [PreviousNetwork Visibilitychevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility) [NextDistance conditionchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility/distance-condition) Last updated 6 months ago --- # Pooling | PurrNet In games where objects are frequently spawned and destroyed (projectiles, enemies, pickups, etc.), constantly creating and destroying GameObjects is expensive. Each instantiation allocates memory, and each destruction triggers garbage collection, which can cause frame hitches. This gets even worse in multiplayer where spawning and despawning happens more often across all players. Pooling solves this by recycling objects instead of destroying them. In PurrNet you can use pooling with networked objects. The way it works is that when an object is despawned or you lost visibility of it, instead of `Destroying` it, we will store it behind the scenes until you try to spawn another of that thing. To can enable pooling on your network prefabs: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-c903a1fc0d585388579728e405512d3980e4c567%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5189002c&sv=2) The way to utilize pooling is quite simple with PurrNet, in fact if you ticked the box you are already done. You keep calling `Instantiate` and `Destroy` and we handle it for you. If you need to reset some data once it get pooled you have an override on your `NetworkIdentities` that gets called for cleanup: There is one caveat. PurrNet allows pooling of children separately and even partial pooling where a prefab might be constructed partly from pooled parts and partly from instantiated parts. But this does cause one issues which is possible mangling or loss of references. Note that this is only an issue when you are mix and matching prefab parts and parents. To fix this we made a `Reference<>` module available for you and it is meant to be used like this: Now your `MeshRenderer` reference will be repaired if it ever breaks. [PreviousDon't destroy on loadchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/dont-destroy-on-load) [NextNetwork Moduleschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules) Last updated 11 days ago Copy protected override void OnPoolReset() { // reset some stuff } Copy [SerializedField] private Reference someRenderer; --- # Awaitable RPC | PurrNet Awaitable RPCs is a gamechanger for networking and optimal development properly integrating with the natural workflow of C# They utilize Tasks and also support UniTask. optionally they can be made async as well. It not only allows for you to wait till RPC logic is finished, but allows you to return values from the receiving end of the RPC. Below is a real use-case example of a way for the player to set ready on the server and it will return true if all players are ready, and false if not all players are ready (handled by the CheckForReady method) It also has a fail safe in with the [networked State Machine](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked) setup in case this isn't the game state were in: And for the client all that needs to be done is have an async method which can await for the result of the RPC. You can read more about the [async keyword herearrow-up-right](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/async) . Essentially it will allow the method to run asynchronous of your otherwise fully sequential code, allowing it to halt execution until it has received a response. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc#bare-bones-example) Bare bones example Here is the bare minimum of using this setup [PreviousStatic RPCchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/static-rpc) [NextDirect Local Execution of RPCschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc) Last updated 1 year ago Copy //The asyncTimeoutInSec is how long the caller should await a response. //In this case it would fail after 10 seconds (default: 5) [ServerRpc(requireOwnership:false, asyncTimeoutInSec:10)] public async Task SetReady(RPCInfo info = default) { if (machine.currentStateNode != this) return false; if(!_readyPlayers.Contains(info.sender)) _readyPlayers.Add(info.sender); return CheckForReady(); } Copy private async void OnReady() { var allReady = await shopPhase.SetReady(); //If everyone is ready, the server handles switching state and we don't do any more if (allReady) return; ShowWaitingScreen(); } Copy 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; } --- # Pre/Post Processors | PurrNet In the name of being modular, we made at your disposal some processors you can register to handle what happens before and after an RPC is sent. Here is what they look like: Copy public class RPCModule { public delegate void RPCPreProcessDelegate(ref ByteData rpcData, RPCSignature signature, ref BitPacker packer); public delegate void RPCPostProcessDelegate(ByteData rpcData, RPCInfo info, ref BitPacker packer); public static event RPCPreProcessDelegate onPreProcessRpc; public static event RPCPostProcessDelegate onPostProcessRpc; } These can be used to append, intercept, change or edit data coming in and out of RPC traffic. Internally we use this for compression, you may have other uses. [PreviousDirect Local Execution of RPCschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc) [NextBroadcastschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/broadcast) Last updated 9 months ago --- # Overview | PurrNet The **Client-Side Prediction (CSP)** system is designed to provide a seamless and responsive multiplayer experience by predicting game states locally on the client. This system is built around a modular architecture that allows for flexible and extensible design, enabling systems that behave as if the game were single-player, while still synchronizing with the server automatically. **Key Components** 1. **PredictionManager**: * Acts as the central "world" for client-side prediction. * Manages all predicted entities and systems within the scene it resides. * Handles the lifecycle of predicted states, including prediction, reconciliation, and view updates. 2. **PredictedIdentity**: * Unity components that define the behavior of predicted entities. * Created by users to handle specific functionalities, such as movement, physics, or custom logic. 3. **PredictedHierarchy**: * Provides a prediction compatible version of Unity's Instantiate and Destroy methods. **Design Philosophy** * **Decoupled from Traditional Networking**: * This system is completely disconnected from the usual `NetworkIdentity` setup. * **RPCs (Remote Procedure Calls)** are not supported or needed in this architecture, as prediction handles state synchronization naturally. * Logic is executed locally on the client, mimicking a single-player experience, while still maintaining consistency with the server. * This approach simplifies development, as developers can focus on writing game logic without worrying about networking intricacies. **Benefits** * **Responsive Gameplay**: Predictions provide immediate feedback to the player, reducing the perceived latency. * **Modularity**: Systems can be easily added or modified, allowing for flexible and scalable game design. * **Consistency**: Reconciliation ensures that the client’s state aligns with the server’s authoritative state, maintaining a consistent game world. **Limitations** * **Prediction Errors**: Incorrect predictions may require corrections, which can occasionally result in visual "snapping" or adjustments. * **Complexity**: While the system simplifies networking, it introduces new challenges in managing predicted states and reconciliation. [PreviousPurrDictionchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction) [NextInstallationchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/installation) Last updated 4 months ago --- # Deterministic Identity | PurrNet `DeterministicIdentity` is a variant designed for strict, bit‑stable simulation. It mirrors the lifecycle of `PredictedIdentity` but uses deterministic math (`sfloat`) and can validate state equality across client/server when enabled. * * * **When to Use** * Systems that can produce identical results on all machines from the same inputs (e.g., strategy sim, deterministic AI, fixed math gameplay). If you don’t require strict bit‑determinism, prefer [`PredictedIdentity`](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-identities) for simpler float‑based logic. * * * **Key Properties** * Uses `sfloat` delta in `Simulate`/`LateSimulate` for deterministic time steps. * History, rollback, and interpolation are the same pattern as stateful identities. * Networking: by default it doesn’t send state each tick; instead, you can turn on validation to compare states. Prediction Manager setting: * Validate Deterministic Data (bool): when enabled, the server writes each deterministic state and clients read+compare it. Mismatches log an error and trigger a `Debug.Break()` to help diagnose. * * * **Overrides** * `protected virtual void GetUnityState(ref STATE state)` / `protected virtual void SetUnityState(STATE state)` * `protected virtual void SimulationStart()` * `protected virtual void Simulate(ref STATE state, sfloat delta)` * `protected virtual void LateSimulate(ref STATE state, sfloat delta)` * `protected virtual void UpdateView(STATE viewState, STATE? verified)` * `protected virtual STATE Interpolate(STATE from, STATE to, float t)` Note the `sfloat` delta — use deterministic math throughout your simulation. You can also mix in `FP` for fixed point math. * * * **STATE Requirements** * `STATE : struct, IPredictedData` — same as other identities. * Provide deterministic operations via `IMath` (used by default interpolation): `Add`, `Scale`, `Negate`. * Avoid non‑deterministic data (raw `float` computation); prefer `sfloat` or integer/fixed‑point math inside state operations. * * * **Example** * * * **Best Practices** * Use `sfloat` and integer math for all simulation‑impacting calculations. * Avoid sampling Unity time or random APIs directly; use `PredictedTime` and `PredictedRandom`. * Keep any conversions to `float` purely in `UpdateView`. * Enable Validate Deterministic Data only during development; it adds extra packing/comparison overhead. [PreviousPredicted Identity Spawnerchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components/predicted-identity-spawner) [NextPredicted Hierarchychevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-hierarchy) Last updated 4 months ago Copy 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 } } --- # Installation | PurrNet [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/installation#adding-it-to-your-project) Adding it to your project --------------------------------------------------------------------------------------------------------------------------------------- You first need to make sure you have `PurrNet v1.14.0` or higher [installed](https://purrnet.gitbook.io/docs/getting-started/installation-setup) as PurrDiction uses PurrNet to function. ### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/installation#git-through-package-manager) **Git through Package Manager** Through the package manager you can add a package using a git URL. Just add the following URL, and whenever you need PurrDiction updated, you can update it directly from the package manager: Copy https://github.com/PurrNet/PurrDiction.git?path=/Assets/PurrDiction#release You can also use the `dev` branch if you're looking to access the latest features, though it may come at the cost of stability. You can switch at any time by using either URLs: Copy https://github.com/PurrNet/PurrDiction.git?path=/Assets/PurrDiction#dev #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/installation#requirements) ⚠️ Requirements * **You must have Git installed** on your system for Unity to fetch packages via git URLs. * If you just installed Git, **restart Unity and Unity Hub** before trying again. * If it still doesn't work after restarting Unity, **restart your computer**. ### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/installation#openupm) **OpenUPM** We are also registered on OpenUPM if you prefer using it [![openupm](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fimg.shields.io%2Fnpm%2Fv%2Fdev.purrnet.purrdiction%3Flabel%3Dopenupm%26registry_uri%3Dhttps%3A%2F%2Fpackage.openupm.com&width=300&dpr=3&quality=100&sign=a036923b&sv=2)arrow-up-right](https://openupm.com/packages/dev.purrnet.purrdiction/) Copy openupm add dev.purrnet.purrdiction ### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/installation#unity-asset-store) **Unity Asset store** circle-exclamation The asset is still in the process of being approved by the Unity team It's as easy as going to the asset store using this link, and adding it like a normal package: [https://assetstore.unity.com/packages/slug/329734arrow-up-right](https://assetstore.unity.com/packages/slug/329734) ### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/installation#package-import) **Package Import** You can download the latest release from [this pagearrow-up-right](https://github.com/PurrNet/PurrDiction/releases) and simply double click it to import it into your project [PreviousOverviewchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/overview) [NextPredicted Identitieschevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-identities) Last updated 7 months ago * [Adding it to your project](https://purrnet.gitbook.io/docs/client-side-prediction/installation#adding-it-to-your-project) * [Git through Package Manager](https://purrnet.gitbook.io/docs/client-side-prediction/installation#git-through-package-manager) * [OpenUPM](https://purrnet.gitbook.io/docs/client-side-prediction/installation#openupm) * [Unity Asset store](https://purrnet.gitbook.io/docs/client-side-prediction/installation#unity-asset-store) * [Package Import](https://purrnet.gitbook.io/docs/client-side-prediction/installation#package-import) --- # Predicted Identities | PurrNet Predicted identities are regular Unity components that PurrDiction simulates deterministically. The system snapshots and reconciles their `STATE` against server authority, while your code focuses on clean, single‑player‑style logic. **Variants** * `PredictedIdentity` * For stateful systems without direct player input (AI, timers, pooled objects). * Implement: `GetInitialState`, `GetUnityState`, `SetUnityState`, `Simulate`, `LateSimulate` (optional), `UpdateView` (optional). * `PredictedIdentity` * Adds an `INPUT` pipe for local/remote control. Implement: `GetFinalInput`, `UpdateInput` (optional), `SanitizeInput`, `Simulate(INPUT, ref STATE, float)`, `ModifyExtrapolatedInput` (optional). * `StatelessPredictedIdentity` * For pure event/logic systems that don’t carry custom state. Override `Simulate(float delta)`. Both `INPUT` and `STATE` must be structs that implement the appropriate prediction interfaces (`IPredictedData`, `IPredictedData`). * * * **Lifecycle Hooks** * `LateAwake()` — Called once after fresh spawn initialization. View‑only setup is appropriate here. * `SimulationStart()` — First tick only, before simulation begins. Good for caching or one‑time transitions. * `Simulate(...)` — Deterministic simulation each tick. Use only `STATE`/`INPUT` and deterministic data. * `LateSimulate(...)` — Optional late pass after `Simulate` each tick (e.g., composing derived values). * `Destroyed()` — Called when despawning/cleanup occurs (pool or destroy). * `ResetState()` — Clears ownership/IDs and resets interpolation and internal caches for pooling. Unity bridging: * `GetUnityState(ref STATE state)` — Read Unity components into `STATE`. * `SetUnityState(STATE state)` — Apply `STATE` back to Unity components after rollback. View & interpolation: * `UpdateView(STATE viewState, STATE? verified)` — Render using the current interpolated `viewState`. `verified` holds the last server snapshot, when present. * `ResetInterpolation()` — Clear internal smoothing/error. * * * **Ownership and Control** * `owner` — Optional `PlayerID` who owns this identity. * `isOwner` — True if `owner == PredictionManager.localPlayer` on this client. * `isController` — True for the owner on clients; on server, also true for bots/AI cases. * `OnViewOwnerChanged(oldOwner, newOwner)` — View‑only callback when ownership changes; do not mutate simulation here. Use these flags for visuals (camera, highlights, UI). Keep simulation deterministic and independent of local presentation. * * * **Example Skeleton (Stateful with Input)** [PreviousInstallationchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/installation) [NextPredicted Moduleschevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules) Last updated 4 months ago Copy public struct MyInput : IPredictedData { public float x, y; 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.x = Input.GetAxisRaw("Horizontal"); i.y = Input.GetAxisRaw("Vertical"); } protected override void SanitizeInput(ref MyInput i) { var v = Vector2.ClampMagnitude(new Vector2(i.x, i.y), 1f); i.x = v.x; i.y = v.y; } protected override void Simulate(MyInput i, ref MyState s, float dt) { s.pos += new Vector3(i.x, 0, i.y) * dt * 5f; } protected override void UpdateView(MyState view, MyState? verified) { transform.SetPositionAndRotation(view.pos, view.rot); } } --- # Networking custom classes, structs & types | PurrNet PurrNet auto networks structs, classes and interfaces for you, given they directly or indirectly consist of serializable data. Networking custom classes and structs can be extremely useful to send over the network, for example by using basic [RPC's](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc) . ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#ipackedauto) IPackedAuto This will allow PurrNet to automatically handle the serialization for you. This is useful for [Broadcastingarrow-up-right](https://github.com/BlenMiner/PurrNet-Docs/blob/main/systems-and-modules/broadcast.md) , as data outside of a [Network Behaviourarrow-up-right](https://github.com/BlenMiner/PurrNet-Docs/blob/main/systems-and-modules/broadcast.md) may have trouble serializing over the network. An example of using this is below: ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#ipackedsimple) IPackedSimple This allows you to handle the serialization yourself. This is the easiest way if you need to make your own serializer. An example of using this is below: ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#ipacked) IPacked This also allows you to handle the serialization yourself. Contrary to the previous method, this allows you to disconnect the reading and writing of data, for more special cases. An example of using this is below: ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#static-custom-type) Static custom type This is the most performant way of handling custom reading and writing of your data. The PurrNet serialization system will automatically find your static type. Below is an example of using this with a Vector2: ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#packing-directly-rpc) Packing directly RPC You can pack data directly and optimally using the BitPackerPool and simply sending the BitPacker directly through an RPC. This uses less memory and allows you to easily pack custom data on the fly. [PreviousBitPacker (Serialization)chevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization) [NextAsync Serialization (packing)chevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing) Last updated 1 year ago * [IPackedAuto](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#ipackedauto) * [IPackedSimple](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#ipackedsimple) * [IPacked](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#ipacked) * [Static custom type](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#static-custom-type) * [Packing directly RPC](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types#packing-directly-rpc) Copy public struct ManagedStruct : IPackedAuto { public int myInt; public object data; } Copy public struct ManagedStruct : IPackedSimple { public int myInt; public object data; public void Serialize(BitPacker packer) { Packer.Serialize(packer, ref myInt); Packer.Serialize(packer, ref data); } } Copy public struct ManagedStruct : IPacked { public int myInt; public object data; public void Write(BitPacker packer) { Packer.Write(packer, myInt); Packer.Write(packer, data); } public void Read(BitPacker packer) { Packer.Read(packer, ref myInt); Packer.Read(packer, ref data); } } Copy public static CustomSerializerForExternalType { public static void Write(this BitPacker packer, Vector2 value) { packer.Write(value.x); packer.Write(value.y); } public static void Read(this BitPacker packer, ref Vector2 value) { packer.Read(ref value.x); packer.Read(ref value.y); } } Copy protected override void OnSpawned(bool asServer) { if (!asServer) { using var writer = BitPackerPool.Get(); Packer.Write(writer, "Hello, server!"); Packer.Write(writer, "Maybe some audio data being pumped?"); MyRPC(writer); } } [ServerRpc(requireOwnership: false)] private void MyRPC(BitPacker data) { using (data) { string message = default; string audioData = default; Packer.Read(data, ref message); Packer.Read(data, ref audioData); PurrLogger.Log(message); // Hello, server! PurrLogger.Log(audioData); // Maybe some audio data being pumped? } } --- # Predicted Projectile (3D) | PurrNet * Purpose: Emulate simple physics like a straight velocity or simple bouncing bullets or other spherical objects. Utilizes casting during flight to avoid weird collision issues as seen with the Unity Rigidbody. * Notes: * Similar to the PredictedRigidbody, it has it's own physics events to handle collision detection. These are similar to the known Unity ones and can be subscribed to by referencing the component. * Can utilize a physics material * Doesn't require collider setup, as it uses it's own build in radius value for the casting. * Can act as a trigger ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252FCa82nLHiAXdzduHB6hvN%252Fimage.png%3Falt%3Dmedia%26token%3Dec1a6766-a8af-4773-b7eb-3474cdf0b14e&width=768&dpr=3&quality=100&sign=199fe2a8&sv=2) [PreviousPredicted Rigidbody (2D & 3D)chevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components/predicted-rigidbody-2d-and-3d) [NextPredicted Identity Spawnerchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components/predicted-identity-spawner) Last updated 1 month ago --- # EOS Transport (Community) | PurrNet This is a community made transport layer, allowing you to utilize the Epic Online Services as a transport layer for PurrNet. [![Logo](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fgithub.com%2Ffluidicon.png&width=20&dpr=3&quality=100&sign=a81e993e&sv=2)GitHub - quentinleon/PurrNetEOSTransport: A wip experimental transport for PurrNetGitHubchevron-right](https://github.com/quentinleon/PurrNetEOSTransport) [PreviousPurr Transportchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/purr-transport) [NextPurrnity Transport (Community)chevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/purrnity-transport-community) Last updated 4 months ago --- # Built-in Components | PurrNet Quick references for the most commonly used PurrDiction components you can drop into scenes. **Prebuilt Samples** * Located under `Assets/PurrDiction/Runtime/Prebuilt/`. * Examples: `TopDownMovement_CC`, `TopDownMovement_RB`, `RigidbodyShooter`, `RigidbodyKnockback`, `RigidbodyJump`. * Intended as reference implementations; adapt logic to your project’s states/inputs. * * * **Related Systems** * `PredictedStateMachine`: Modular state logic with view hooks. See the State Machine docs. * `PredictedRandom`: Deterministic RNG for predicted code paths. * `PredictedPlayers`, `PredictedTime`, `PredictedHierarchy`: Built‑ins registered by `PredictionManager` to support common gameplay needs. [PreviousExecution Flowchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/flow) [NextPredicted Transformchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components/predicted-transform) Last updated 1 month ago --- # Predicted State Machine | PurrNet The predicted state machine allows you to easily utilize the beautiful modularity and scalability of a finite state machine. In order to work with the predicted state machine, you first need some [predicted state nodes](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine/predicted-state-node) . From the simulate loop of any predicted identity, you can easily reference the machine and call one of the following: * `Next();` allowing you to go to the next state in the list * `Previous();` going backwards to the previous state node * `SetState(PredictedStateNode state);` allowing you to easily go to a specific state node. ### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine#scene-setup) Scene setup In order to work with the predicted state machine, all you need it to set it up in the scene or on your desired prefab (this can also be utilized for player logic). And in the states list, simply add a reference to your states (which are typically on or childed to the state machine gameobject) And that's it. The state machine will now start in the first state in the list, and you can easily navigate it using the methods above. ### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine#minimal-example) Minimal Example Copy // A state without input public class IdleState : PredictedStateNode { public override void Enter() { /* start idle anim */ } protected override void StateSimulate(ref MyState s, float dt) { /* no-op */ } } // A state with input public class MoveState : PredictedStateNode { protected override void StateSimulate(in MyInput i, ref MyState s, float dt) { s.velocity = new Vector3(i.x, 0, i.y) * s.speed; } } // Somewhere in a predicted identity or node void HandleTransitions() { if (/* want next */) machine.Next(); if (/* want specific */) machine.SetState(mySpecificNode); } Notes: * `ViewEnter`/`ViewExit` fire for both `viewState` and `verified` transitions, allowing you to scope VFX/UI to predicted vs verified changes. * Keep `StateSimulate` deterministic; drive visuals in `ViewEnter`/`ViewExit`/`UpdateView`. [PreviousInteracting With Multiple Identitieschevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/multi-identity-interactions) [NextPredicted State Nodechevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine/predicted-state-node) Last updated 4 months ago * [Scene setup](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine#scene-setup) * [Minimal Example](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine#minimal-example) --- # Interacting With Multiple Identities | PurrNet Many features require one predicted identity to read or affect another (e.g., projectiles applying knockback, an ability system modifying player state, or a shared input bus). This guide covers safe, deterministic patterns to discover, read, and modify other identities and their input. * * * **Discovery and References** * Prefer stable handles over direct `GameObject` references when objects can be created/destroyed under prediction. * Use the Predicted Hierarchy to resolve components from IDs: * `TryCreate(...)` / `Create(...)` → returns `PredictedObjectID` * `GetComponent(PredictedObjectID?)` → resolve an identity or component at runtime * `TryGetId(GameObject, out PredictedObjectID)` → convert a known object into a stable ID * For long‑lived relationships, store `PredictedObjectID` in your `STATE` so it survives rollback/replay. Copy public struct AbilityState : IPredictedData { public PredictedObjectID target; public void Dispose() {} } // Later during simulate var targetCtrl = predictionManager.hierarchy.GetComponent(state.target); * * * **Reading and Writing Another Identity’s STATE** * You may read or modify another identity’s state during simulation. Keep it deterministic and do it only while simulating. * Access the other identity as its concrete type and mutate via its `currentState`: Tips: * Only mutate in `Simulate`/`LateSimulate` (the simulation phase). You can annotate helpers with `[SimulationOnly]` to ensure they only run while simulating. * Keep inter‑identity logic order‑independent when possible. If order matters, consolidate logic into a single orchestrator identity. * * * **Using Another Identity’s Input** * You can read another identity’s input for the current tick via its `currentInput` (for identities with input). * Use this sparingly; prefer sharing intent/state rather than raw input when possible. Note: Do not attempt to modify another identity’s input history. Instead, mutate its state or publish events that it reacts to. * * * **Cross‑Identity Events** * Use `PredictedEvent`/`PredictedEvent` for view‑safe eventing across identities. Events only invoke when it’s valid to do so (server, owner while not replaying, or verified client), preventing double‑fire during replays. * * * **Ownership and Control** * If you only want the controller/owner to make a change, gate logic with `isController` or `IsOwner()`. * For player‑centric lookups and events, use `predictionManager.players` to iterate or react to players joining/leaving. * * * **Create/Destroy + IDs** * When spawning transient objects (e.g., projectiles), capture their `PredictedObjectID` in the originating identity’s `STATE` if you’ll need to interact with them later. * Use the Hierarchy to `Delete(id)` deterministically when cleaning up. * * * **Ordering and Determinism** * The manager updates identities in a consistent order (by object ID then component ID). Avoid relying on a specific order for correctness. * If strict ordering is required (e.g., system A before B), move the shared mutation into a single identity (an aggregator) and have others read the outcome. * * * **Example: Projectile Applies Knockback** [PreviousPredicted Hierarchychevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-hierarchy) [NextPredicted State Machinechevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine) Last updated 4 months ago Copy // inside Simulate(...) var target = predictionManager.hierarchy.GetComponent(state.target); if (target != null) { ref var ts = ref target.currentState; // by ref ts.velocity += knockback; // deterministic mutation } Copy var player = predictionManager.hierarchy.GetComponent(state.player); if (player != null) { var input = player.currentInput; // read‑only view for this tick // derive effects from input (e.g., combo triggers, assist behaviors) } Copy // In an identity private PredictedEvent onHit; protected override void LateAwake() { onHit = new PredictedEvent(predictionManager, this); } void SimulateHit() { onHit.Invoke(); } // will invoke only in valid contexts Copy 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 } } --- # Delta Packers | PurrNet Delta packers write only the differences between an old value and a new value. This reduces bandwidth compared to writing the full value every time. PurrNet automatically generates delta serializers for your types. You can also define custom delta logic when you need more control. If something goes wrong, we fall back to a safe path that behaves like "changed flag + full value". [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#when-to-use) When to use --------------------------------------------------------------------------------------------------------------------------------- Use delta packers when: * Your values change slightly or sparsely (e.g., vectors, rotations, small structs). * You want fewer bits on the wire than full value writes. * You're fine with the automatic delta, or you want to override it with your own logic. If a delta isn't applicable (or an error occurs), we'll automatically write a small "changed" marker and, if needed, the full value. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#api-per-type) API (per-type) ------------------------------------------------------------------------------------------------------------------------------------- Only the essentials you'll typically call: Copy // Emits a delta from oldValue -> newValue. // Returns true if any bits were written. bool DeltaPacker.Write(BitPacker packer, T oldValue, T newValue) // Reads a delta and applies it to oldValue, producing the result. void DeltaPacker.Read(BitPacker packer, T oldValue, ref T newValue) Notes: * You don't need to register anything for built-in or auto-discovered static serializers. * If no delta is available, a compact fallback is used automatically. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#how-it-works-high-level) How it works (high level) ----------------------------------------------------------------------------------------------------------------------------------------------------------- * Write decides if anything changed and writes only what's needed. * Read consumes exactly what Write produced to rebuild the current value from the old one. * If a type doesn't have a delta serializer available, we write a single "changed" bit and, when true, the full value (safe fallback). [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#examples) Examples --------------------------------------------------------------------------------------------------------------------------- Only write changed fields of a Vector3. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#custom-delta-advanced-optional) Custom delta (advanced, optional) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- If you want to override the automatic behavior, define your own static delta serializer similar to how you'd do for normal packers. PurrNet will discover static serializers defined under your static class and use them automatically. You don't need to manually register these—discovery is automatic. [PreviousAsync Serialization (packing)chevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing) [NextCode strippingchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/code-stripping) Last updated 7 months ago * [When to use](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#when-to-use) * [API (per-type)](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#api-per-type) * [How it works (high level)](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#how-it-works-high-level) * [Examples](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#examples) * [Custom delta (advanced, optional)](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers#custom-delta-advanced-optional) Copy // Writing DeltaPacker.Write(packer, oldPosition, newPosition); // Reading Vector3 oldValue = ..; Vector3 result = default; var currentPosition = DeltaPacker.Read(packer, oldValue, ref result); --- # Addressable Scene handling | PurrNet PurrNet can load and unload Addressable scenes over the network. When the server loads an addressable scene, all connected clients automatically load the same scene, given the scene is public. Only the server can load and unload scenes. To load a scene, use the `ScenesModule`: Copy public AssetReference sceneReference; void LoadScene() { var scenes = networkManager.GetModule(true); scenes.LoadAddressableSceneAsync(sceneReference, new PurrSceneSettings { mode = LoadSceneMode.Additive, isPublic = true }); } You can also load by GUID string if you don't have the AssetReference directly: Copy scenes.LoadAddressableSceneAsync("your-asset-guid-here", settings); To unload, you can either unload by GUID (which unloads all instances of that scene): Copy scenes.UnloadAddressableSceneByGuid(sceneReference.AssetGUID); Or unload a specific instance by its SceneID: You can also listen to events for when addressable scenes start loading or finish: [PreviousAddressable Spawning & Despawningchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-spawning-and-despawning) [NextAddressable RPC'schevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-rpcs) Last updated 26 days ago Copy if (scenes.TryGetSceneIdByAddressableGuid(sceneReference.AssetGUID, out var sceneId)) scenes.UnloadSceneAsync(sceneId); Copy scenes.onAddressableSceneStartLoading += (sceneId, guid, asServer) => { Debug.Log($"Loading addressable scene: {guid}"); }; scenes.onAddressableSceneLoaded += (sceneId, guid, asServer) => { Debug.Log($"Addressable scene loaded: {guid}"); }; --- # Purrnity Transport (Community) | PurrNet This is a community-made transport layer that bridges PurrNet with Unity's Transport Package (com.unity.transport). It supports UDP, WebSocket, and Unity Relay connections with built-in DTLS/TLS encryption and batched message optimization. [![Logo](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2Fgithub.com%2Ffluidicon.png&width=20&dpr=3&quality=100&sign=a81e993e&sv=2)GitHub - youngwoocho02/PurrnityTransportGitHubchevron-right](https://github.com/youngwoocho02/PurrnityTransport) [PreviousEOS Transport (Community)chevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/eos-transport-community) [NextSimulating latencychevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/simulating-latency) Last updated 1 month ago --- # Predicted Rigidbody (2D & 3D) | PurrNet **PredictedRigidbody (3D)** * Purpose: Predicts `Rigidbody` motion with deterministic simulation and reconciles state. * Requirements: Enable 3D physics in `PredictionManager` via `BuiltInSystems.Physics3D` and select a physics provider that includes Unity 3D physics. * Notes: * PurrDiction switches Unity to script-driven simulation for ticks; avoid using `FixedUpdate` for physics. * For callbacks, see `IPredictedPhysicsCallbacks` and `PredictedPhysicsCallbacks` in the UnityPhysics runtime folder. **PredictedRigidbody2D (2D)** * Purpose: Predicts `Rigidbody2D` motion similarly to 3D. * Requirements: Enable 2D physics in `PredictionManager` via `BuiltInSystems.Physics2D` and include the Unity 2D physics provider. [PreviousPredicted Transformchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components/predicted-transform) [NextPredicted Projectile (3D)chevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components/predicted-projectile-3d) Last updated 1 month ago --- # Spawning & Despawning | PurrNet When you create or destroy an object in singleplayer, it only exists on your machine. In multiplayer, all connected players need to see the same objects appear and disappear at the same time. Normally, this means you'd need separate "spawn over network" calls, server-side checks, and extra boilerplate every time you want to create an object. PurrNet removes all of that. Spawning and Despawning in PurrNet is as easy as instantiating and destroying in Unity! If the object contains a network identity (example your own scripts or the prefab link, network transform, etc.), it will automatically be spawned for other clients. You can also easily drag and drop prefabs from your project into the scene in the Unity Editor, and it should just work as well! Keep in mind that for spawning and despawning the [network rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) need to allow your relative role to the object to spawn or despawn it. With the unsafe rules, everyone can do it. [PreviousOwnershipchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/ownership) [NextClient Spawning Validationchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/spawning-and-despawning/client-spawning-validation) Last updated 11 days ago Copy public NetworkIdentity myObject; private NetworkIdentity _spawnedObject; private void SpawnMyObject() { //This will auto spawn the object _spawnedObject = Instantiate(myObject); //This will spawn it with the ownership _spawnedObject.GiveOwnership(localPlayer); } private void DespawnMyObject() { //Similar to what you do in Unity, just destroy the gameobject and it'll despawn if(_spawnedObject) Destroy(_spawnedObject.gameObject); } --- # Predicted Identity Spawner | PurrNet **PredictedIdentitySpawner** * Purpose: Spawns a set of `NetworkIdentity` objects deterministically from prediction. * Usage: * Add component, list identities to spawn. * On server, spawns and assigns observers/ownership. * On clients, mirrors spawns during verified replays. * Caveat: Marked `[PredictionUnsafe]` because it orchestrates networked side‑effects; keep core simulation deterministic. * See: Predicted Hierarchy docs for details. [PreviousPredicted Projectile (3D)chevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components/predicted-projectile-3d) [NextDeterministic Identitychevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/deterministic-identity) Last updated 1 month ago --- # Predicted State Node | PurrNet A predicted state node is a class you can inherit from in order to build your own states for the [predicted state machine](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine) . There are multiple predicted state node types to inherit from: * `PredictedStateNode` This allows your statenode to hold and simulate state * `PredictedStateNode` This allows your statenode to hold and simulate state w. input. Optimal for characters/players' ### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine/predicted-state-node#overrides) Overrides These can override a few different methods, in order for you to run functionality unique to the individual states: * `Enter` This is optimal for running simulation logic upon entering the state * `Exit` This is optimal for running simulation logic upon exiting the state * `StateSimulate` This is your per‑tick state simulation; runs only when this state is active * `ViewEnter` Run view/visual logic upon entering the state (predicted or verified) * `ViewExit` Run view/visual logic upon exiting the state (predicted or verified) Mind you, you still have the normal predicted identity override as well! ### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine/predicted-state-node#snippet) Snippet Copy 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 */ } } [PreviousPredicted State Machinechevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine) [NextViews and Interpolationchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/views-and-interpolation) Last updated 4 months ago * [Overrides](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine/predicted-state-node#overrides) * [Snippet](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine/predicted-state-node#snippet) --- # Cirque de Slay | PurrNet [Check it out on Steamarrow-up-right](https://store.steampowered.com/app/3595740/Cirque_de_Slay/) Cirque de Slay is a competitive online action-roguelike for up to four players, set in a twisted carnival. Build decks, play cards, and twist the rules to challenge your opponents as they scavenge for loot, evade traps, and hunt down the boss to escape… choose your friends wisely. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-29d95963dd1211fda6f2ab0509577a2a1779ac97%252FCirque_De_Slay_Combat.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=d96e515c&sv=2) Each round, players take turns exploring this twisted circus, looting gear, avoiding traps, and racing to find the boss. But while one player explores, the others spectate... and interfere. Spectators play sinister cards that alter the active player’s run in real time, spawning enemies, closing doors, flipping traps, or stealing their loot. No run is ever safe… and no victory is ever guaranteed. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-f6c50afeeab816f9531f0e7af9246d3cac150ef5%252FCirque_de_Slay_Spectator.gif%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5ce415af&sv=2) [PreviousTower Arena Survivechevron-left](https://purrnet.gitbook.io/docs/made-with-purrnet/tower-arena-survive) [NextPantlesschevron-right](https://purrnet.gitbook.io/docs/made-with-purrnet/pantless) Last updated 9 months ago --- # NetworkBehaviour | PurrNet In Unity, MonoBehaviour is the base class for any script that lives on a GameObject. But MonoBehaviour has no awareness of the network. It doesn't know if it's running on the server or a client, who owns the object, or how to send data to other machines. NetworkBehaviour adds all of that. Network Behaviour should be treated like your MonoBehaviour but for acting on the network. This also inherits from [Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) . From your Network Behaviour scripts, you'll be able to call [RPC's](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc) , handle [synchronization](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncvar) , transfer [ownership](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/ownership) , and more. [PreviousClient Spawning Validationchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/spawning-and-despawning/client-spawning-validation) [NextPlayerIdentitychevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/playeridentity) Last updated 11 days ago --- # Disposable Collections | PurrNet PurrNet provides pooled, disposable collections for GC‑friendly state and deterministic iteration: * `DisposableList` * `DisposableDictionary` * `DisposableArray` Use these in your `STATE` structs and long‑lived prediction data. They integrate with packing/history to duplicate safely and dispose cleanly. * * * **Why Disposable Collections** * Minimize allocations by renting from pools under the hood (`ListPool`, `DictionaryPool`, `ArrayPool`). * Deterministic iteration for dictionaries via an internal stable key list. * Codegen/packing support: deep copy via `Duplicate()` during history snapshots and deltas. * * * **General Rules** * Always create via `Create(...)` factory; do not use `new List()`, `new Dictionary<,>()`, or constructors. * Always call `Dispose()` when you are done with the collection. * If a collection lives inside a `STATE`, dispose it in `STATE.Dispose()`. * Do not struct‑copy a disposable collection and dispose both; use `.Duplicate()` to create an independent copy. * * * **DisposableList** * Create: `var list = DisposableList.Create(capacity);` or `Create()` or `Create(IEnumerable)`. * Use like a regular `List` (`Add`, indexer, `Count`, etc.). * Dispose: `list.Dispose();` * In `STATE`, implement dispose: * * * **DisposableDictionary** * Create: `var dict = DisposableDictionary.Create();` * Iteration order is deterministic using an internal key list. * Use: `Add`, indexer, `TryGetValue`, `Remove`, `ContainsKey`. * Example in a `STATE` (from PlayerSpawner): `players = DisposableDictionary.Create();` * Dispose in `STATE.Dispose()`: Tip: When enumerating, `foreach (var (k, v) in dict)` is safe and stable. Avoid mutating structure while iterating. * * * **DisposableArray** * Fixed‑size, pooled array with optional `Resize(int)` growth. * Create: `var arr = DisposableArray.Create(size);` * No `Add/Remove/Insert/Clear`; indexer for read/write. * Dispose: `arr.Dispose();` * * * **Copying and History** * The prediction history uses packers that deep‑copy disposable collections by calling `Duplicate()` under the hood. * This ensures snapshots are independent. You should still implement `Dispose()` on your `STATE` to release each snapshot when it is discarded. Anti‑pattern: * Avoid `var b = a; b.Dispose(); a.Dispose();` on disposable structs — both point to the same pooled container. Use `var b = a.Duplicate();` if you truly need a separate copy. * * * **Short‑Lived Temporaries** * For per‑frame or function‑local scratch collections, prefer non‑disposable pools: * `var tmp = ListPool.Instantiate(); ... ListPool.Destroy(tmp);` * `var tmp = DictionaryPool.Instantiate(); ... DictionaryPool.Destroy(tmp);` * These do not need to be part of state and should not be stored across frames. * * * **Leak Checks (Editor)** * When `PURR_LEAKS_CHECK` is enabled in Editor, pooled allocations are tracked and usage is updated on access. This helps catch missed `Dispose()`/`Destroy()` calls during development. * * * **IDuplicate and Performance** * The packer copies state snapshots via `Packer.Copy(value)`. * If `T : IDuplicate`, the packer calls `Duplicate()` directly instead of serializing/deserializing to clone. * Implementing `IDuplicate` on your custom structs nested inside `STATE` can significantly reduce GC and CPU during prediction history copies and reconciliation. Example: Tips: * Implement `IEquatable` as well for fast equality checks (`Packer.AreEqual`) used in delta packing. * Disposable collections already implement `IDuplicate`; use and dispose them correctly to benefit automatically. [PreviousBest Practiceschevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/best-practices) [NextSecurity Modelchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/security) Last updated 4 months ago Copy public struct InventoryState : IPredictedData { public DisposableList items; public void Dispose() { items.Dispose(); } } Copy public void Dispose() { players.Dispose(); } Copy using PurrNet.Packing; public struct MySubState : IDuplicate { public DisposableList indices; public float weight; public MySubState Duplicate() { return new MySubState { indices = indices.Duplicate(), // deep copy pooled list weight = weight }; } } public struct MyState : IPredictedData { public MySubState data; public void Dispose() { data.indices.Dispose(); } } --- # Common Pitfalls | PurrNet [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#common-pitfalls) Common Pitfalls ----------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-1.-modules-must-be-fields-or-properties) 1\. Modules Must Be Fields or Properties Network modules **must** be declared as a field or property on a `NetworkIdentity`. **Do not** create them dynamically or store them in collections. Copy public class MyNetworkObject : NetworkIdentity { public SyncVar health; // ✅ Correct private SyncList names; // ✅ Correct // ❌ Incorrect: Do not use lists of modules public List> invalidList; } * * * ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-2.-editor-serialization) 2\. Editor Serialization Modules can be serialized and shown in the Unity Editor. Just mark them with `[SerializeField]` or make them `public`. Copy [SerializeField] private SyncVar health; // Will show up in the Inspector public SyncList names; // Also visible if public * * * ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-3.-initialization-use-oninitializemodules) 3\. Initialization: Use `OnInitializeModules()` You can create, override, or initialize modules in the `protected virtual void OnInitializeModules()` callback. This runs **before** networking sends data or assigns IDs. * * * ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-4.-do-not-replace-modules-after-spawn) 4\. Do Not Replace Modules After Spawn **Once spawned, never assign a new module instance to a field/property.** Doing so will break networking or cause undefined behavior. Modules are meant to be **constant** after spawn. * * * ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-5.-nesting-modules) 5\. Nesting Modules You can nest modules—meaning a network module can use other modules like `SyncVar` as fields or properties. **Do not create circular dependencies.** If Module A uses Module B, and Module B uses Module A, it will break. Nesting must form a tree, not a loop. * * * ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-6.-no-lists-of-modules) 6\. No Lists of Modules You **cannot** create a list or array of modules. Modules must be declared as fields or properties at compile time. * * * ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-7.-module-events) 7\. Module Events Modules have similar events to `NetworkIdentity`, such as: * `OnSpawned` * `OnOwnerChanged` * `OnDespawned` You can use these for custom logic inside your modules. [PreviousNetwork Moduleschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules) [NextSync Typeschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types) Last updated 10 months ago * [Common Pitfalls](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#common-pitfalls) * [1\. Modules Must Be Fields or Properties](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-1.-modules-must-be-fields-or-properties) * [2\. Editor Serialization](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-2.-editor-serialization) * [3\. Initialization: Use OnInitializeModules()](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-3.-initialization-use-oninitializemodules) * [4\. Do Not Replace Modules After Spawn](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-4.-do-not-replace-modules-after-spawn) * [5\. Nesting Modules](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-5.-nesting-modules) * [6\. No Lists of Modules](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-6.-no-lists-of-modules) * [7\. Module Events](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls#id-7.-module-events) Copy protected override void OnInitializeModules() { health = new SyncVar(100); // Custom initialization here } Copy // ❌ Don't do this after spawn: health = new SyncVar(200); // Will break networking! Copy public class MyModule : NetworkModule { public SyncVar score; // ✅ Allowed } Copy // ❌ Not allowed: Circular reference public class ModuleA : NetworkModule { public ModuleB b; } public class ModuleB : NetworkModule { public ModuleA a; // This creates a cycle and will cause compiler error } Copy // ❌ Not allowed: public List> moduleList; // ✅ Allowed: public SyncVar health; public SyncVar mana; Copy public class MyModule : NetworkModule { protected override void OnSpawned() { /* ... */ } protected override void OnOwnerChanged(..) { /* ... */ } protected override void OnDespawned() { /* ... */ } } --- # Addressable Spawning & Despawning | PurrNet PurrNet supports spawning networked objects directly from Addressable prefabs. To use this, create an `AddressableNetworkPrefabs` asset via _Create > PurrNet > Network Prefabs > Addressable Network Prefabs_, add your addressable prefab entries to it, and assign it on your NetworkManager. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-spawning-and-despawning#using-addressables.instantiateasync) Using Addressables.InstantiateAsync -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- PurrNet automatically intercepts calls to `Addressables.InstantiateAsync` and `AssetReference.InstantiateAsync` via IL weaving. Any instantiated object with a `NetworkIdentity` will be network spawned automatically, just like regular `Instantiate` calls. This means you can use the standard Addressables API and PurrNet handles the networking for you: Copy public AssetReferenceGameObject prefabReference; async void SpawnByReference() { var handle = prefabReference.InstantiateAsync(position, rotation); await handle.Task; } You can also use the static `Addressables.InstantiateAsync` with a key or GUID: Copy async void SpawnByGuid() { var handle = Addressables.InstantiateAsync(prefabReference.AssetGUID, position, rotation); await handle.Task; } To despawn and release the instance, use `Addressables.ReleaseInstance` as you normally would. PurrNet intercepts this too and despawns any `NetworkIdentity` on the object before releasing it: [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-spawning-and-despawning#using-networkmanager-api) Using NetworkManager API ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- If you prefer explicit control, you can use the `NetworkManager` methods directly. `SpawnAddressableAsync` handles loading the prefab if it hasn't been loaded yet: If you've already pre-loaded your prefabs (via `AddressableNetworkPrefabs.LoadAllAsync`), you can use the synchronous version instead: Both methods support optional `position`, `rotation`, and `parent` parameters, just like regular instantiation. To despawn, call: `networkManager.DespawnAddressable(go);` [PreviousAddressableschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/addressables) [NextAddressable Scene handlingchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-scene-handling) Last updated 10 days ago * [Using Addressables.InstantiateAsync](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-spawning-and-despawning#using-addressables.instantiateasync) * [Using NetworkManager API](https://purrnet.gitbook.io/docs/systems-and-modules/addressables/addressable-spawning-and-despawning#using-networkmanager-api) Copy Addressables.ReleaseInstance(handle); Copy public AssetReferenceGameObject prefabReference; async void SpawnObject() { var go = await networkManager.SpawnAddressableAsync(prefabReference); if (!go) return; Debug.Log($"Spawned: {go.name}"); } Copy void SpawnObject() { var go = networkManager.SpawnAddressable(prefabReference); } --- # Async Serialization (packing) | PurrNet PurrNet supports asynchronous serialization, allowing you to pack and unpack data fully async for RPC utilization. This is particularly useful for workflows sensitive to timing, such as working with Addressables, databases, or anything requiring data to be fetched from outside of the project at runtime. #### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing#iasyncpackable) IAsyncPackable To make a type async-packable, implement the `IAsyncPackable` interface. It exposes two lifecycle methods that PurrNet calls automatically around serialization: Method When it's called PrepareForPackAsync Before the data is packed and sent PrepareAfterUnpackAsync After the data has been received and unpacked These methods return `Task`, so you can `await` anything inside them — asset loading, database calls, scene queries, etc. #### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing#dontpack) \[DontPack\] Fields marked with `[DontPack]` are excluded from serialization entirely. Use this for references that can't or shouldn't be sent over the network, and instead resolve them inside `PrepareAfterUnpackAsync`. #### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing#example) Example The following example sends a `Renderer` reference across the network. Since `Renderer` isn't directly serializable, the struct stores the GameObject's name as a string, then resolves the renderer by name on the receiving end. Copy 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; } } **What's happening here:** * **Sender** — `PrepareForPackAsync` runs before the RPC is sent. It stores the GameObject's name into `goName`, which gets packed and transmitted. * **Receiver** — `PrepareAfterUnpackAsync` runs after unpacking. It uses `goName` to find the GameObject in the scene and resolves the local `Renderer` reference. * The `[DontPack]` attribute on `renderer` ensures the non-serializable component is never included in the packet itself. To put very simply, since the renderer isn't serializable, we utilize the \[DontPack\] attribute, to instruct the [bitpacker](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization) to avoid packing this piece of data. And then we can simple get it using the name of the gameobject that's sent. This is obviously a quite silly use-case that doesn't have a big need for async code. But hopefully you can see how this is applicable in many scenarios where async can be helpful! [PreviousNetworking custom classes, structs & typeschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/networking-custom-classes-structs-and-types) [NextDelta Packerschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/delta-packers) Last updated 1 month ago --- # Composite Transport | PurrNet The composite transport takes in multiple transports and will automatically pick the support transport. The easiest example is if you have the [Web Transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/web-transport) and the [UDP Transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/udp-transport) , and upload the game to the web, it will automatically utilize the [Web Transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/web-transport) . This works by automatically picking the first supported transport layer in the list. This allows you to easily utilize multiplayer transport layers in your project without needing to manually manage anything. This also allows for easy cross platform support, as the server spins up all possible transport layers, and clients will pick one to connect to. So you can have players playing together between web, Steam, Epic and more! ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e6cc992af95c80c99c3d33ebf4fe65b665f32981%252FCompositeTransportExample.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=886f512f&sv=2) Example of composite transport component with web transport and udp transport [PreviousTransportschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports) [NextUDP Transportchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/udp-transport) Last updated 5 months ago --- # UDP Transport | PurrNet The UDP transport is the most commonly used transport layer for multiplayer. It allows you to easily connect to any public IP with a given port. We're utilizing the [LiteNetLibarrow-up-right](https://github.com/RevenantX/LiteNetLib) MIT licensed project to support reliable and unreliable messages. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/transports/udp-transport#parameters) Parameters * Server Port - The port which the server will start on, and clients connect to * Max Connections - The max amount of client connections allowed * Address - This is the IP of the server. Default is the local host ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-59799fa2867ccd4e6d6fbbe9f83ecdd1e2b664bc%252FUdpTransportExample.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=52d21cd2&sv=2) Example of UDP transport component [PreviousComposite Transportchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/composite-transport) [NextWeb Transportchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/web-transport) Last updated 5 months ago --- # Input Handling | PurrNet Input handling in PurrDiction centers around the generic `PredictedIdentity` base class. Implement `GetFinalInput(ref INPUT input)` to gather per‑frame input, and optionally `UpdateInput(ref INPUT input)` to cache edge‑triggered inputs (e.g., key down) from Unity’s frame loop. * * * **Key Concepts** * `INPUT` must be a struct implementing `IPredictedData`. * Implement `protected virtual void GetFinalInput(ref INPUT input)` to populate inputs deterministically each tick. * Use `protected virtual void UpdateInput(ref INPUT input)` inside Unity’s frame loop to accumulate one‑shot inputs. * Use `protected virtual void SanitizeInput(ref INPUT input)` to clamp or normalize values for determinism. * * * **Basic Example** Copy public struct SimpleWASDInput : IPredictedData { public NormalizedFloat horizontal; public NormalizedFloat vertical; public bool jump; public bool dash; public void Dispose() {} } public class SimpleCC : PredictedIdentity { protected override void GetFinalInput(ref SimpleWASDInput input) { input.horizontal = Input.GetAxisRaw("Horizontal"); input.vertical = Input.GetAxisRaw("Vertical"); input.dash = Input.GetKey(KeyCode.LeftShift); } protected override void UpdateInput(ref SimpleWASDInput input) { // Edge-triggered input is cached here and consumed once per tick input.jump |= Input.GetKeyDown(KeyCode.Space); } protected override void SanitizeInput(ref SimpleWASDInput input) { var move = Vector2.ClampMagnitude(new Vector2(input.horizontal, input.vertical), 1f); input.horizontal = move.x; input.vertical = move.y; } } * * * **Extrapolation and Repeat** * Remote players can use extrapolated input if the latest input is missing. * Control behavior via fields on `PredictedIdentity`: * Extrapolate Input: enables extrapolation for remote input. * Repeat Input Factor: caps how many ticks a prior input can be reused. * Override `ModifyExtrapolatedInput(ref INPUT input)` to disable non‑continuous inputs during extrapolation (e.g., `jump = false`). * * * **Why This Pattern** * Determinism: All input used in simulation is captured, sanitized, and stored per tick. * One‑shot safety: Edge‑triggered inputs are gathered in `UpdateInput` and consumed once, preventing repeats. * Flexibility: Works with any control scheme while keeping authoritative reconciliation stable. [PreviousPredicted Timer Modulechevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules/predicted-timer-module) [NextState Handlingchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/state-handling) Last updated 4 months ago --- # Direct Local Execution of RPCs | PurrNet PurrNet allows you to execute RPCs directly in the local context without sending them over the network. This can be useful when you want to bypass network transmission and handle the logic locally instead. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc#scoped-local-execution-with-compiler-flags) **Scoped Local Execution with Compiler Flags** ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To execute an RPC locally within a specific part of the method, use `PurrCompilerFlags.EnterLocalExecution()` and `PurrCompilerFlags.ExitLocalExecution()`. Copy [ObserversRpc] public void PerformAction() { PurrCompilerFlags.EnterLocalExecution(); // Any RPCs called here will bypass purrnet // They will be called locally without sending it over the network PurrCompilerFlags.ExitLocalExecution(); // Here we are back to sending RPCs over the network } * **What It Does**: Only the code between the `EnterLocalExecution` and `ExitLocalExecution` calls runs locally. The rest of the method behaves as a normal RPC. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc#full-method-local-execution-with-localmode) **Full Method Local Execution with LocalMode** ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To make the entire method execute locally, use the `LocalMode` attribute. * **What It Does**: The method runs locally and does not send any data over the network. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc#use-cases) Use Cases --------------------------------------------------------------------------------------------------------------------------------- * **Scoped Local Execution**: Use this if only a specific part of the method needs to bypass the network. * **Full Method Local Execution**: Use this when the entire method should be local. With these options, you can handle local execution of RPCs efficiently and flexibly. [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc#disclaimer) Disclaimer ----------------------------------------------------------------------------------------------------------------------------------- These are compiler hints used by PurrNet to determine how the code should behave. They: 1. Do **not** execute or alter any logic directly. 2. Are scoped **only to the current function**. They do not propagate to other function calls. 3. Do **not** track the logical flow of the code. For instance, you can start local execution inside an `if` statement and end it outside the `if` block. In such cases, every instruction between `EnterLocalExecution` and `ExitLocalExecution` will still be processed, even if the `if` condition would not have been met. Use these tools with care to ensure proper execution and avoid unintended behavior. [PreviousAwaitable RPCchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc) [NextPre/Post Processorschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/pre-post-processors) Last updated 1 year ago * [Scoped Local Execution with Compiler Flags](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc#scoped-local-execution-with-compiler-flags) * [Full Method Local Execution with LocalMode](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc#full-method-local-execution-with-localmode) * [Use Cases](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc#use-cases) * [Disclaimer](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/fully-local-rpc#disclaimer) Copy [ObserversRpc, LocalMode] public void PerformAction() { Debug.Log("This entire method runs locally without sending an RPC."); } --- # Network Modules | PurrNet As your multiplayer game grows, you'll quickly find yourself wanting to reuse networking logic across different objects. Maybe multiple enemy types all need a health system, or several different objects share the same sync pattern. Putting all of that into MonoBehaviours can get messy, especially when you want to share logic between objects that don't share a component hierarchy. NetworkModule is a base class that solves this. It allows you to create modular, network-aware components that are not MonoBehaviours. These modules can be attached to any [NetworkIdentity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) , enabling code reuse and clean separation of concerns in your networked game. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules#key-features) Key Features * Modularity: Encapsulate specific functionality into reusable modules. * Network Integration: Access networking properties like isServer, isClient, and send RPCs. * Lifecycle Hooks: Override OnSpawn and OnDespawned for initialization and cleanup and many others. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules#example-healthmodule-with-state-synchronization) Example: HealthModule with State Synchronization Here's a concise example of a HealthModule that synchronizes a health value across the network, ensuring proper state synchronization even if clients reconnect. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules#explanation) Explanation * Buffered RPC: The RpcUpdateHealth method uses bufferLast: true to buffer the last health value so new or reconnecting clients receive the correct state. * Server Authority: Only the server can modify the Health property to maintain authoritative game state. * Event Handling: Clients can subscribe to OnHealthChanged to react to health updates. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules#attaching-healthmodule-to-a-networkidentity) Attaching HealthModule to a NetworkIdentity [PreviousPoolingchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/pooling) [NextCommon Pitfallschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls) Last updated 11 days ago * [Key Features](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules#key-features) * [Example: HealthModule with State Synchronization](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules#example-healthmodule-with-state-synchronization) * [Explanation](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules#explanation) * [Attaching HealthModule to a NetworkIdentity](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules#attaching-healthmodule-to-a-networkidentity) Copy using PurrNet; using PurrNet.Transports; using System; public class HealthModule : NetworkModule { private float _health; public event Action OnHealthChanged; public float Health { get => _health; set { if (!isServer) return; // Synchronize health with all clients RpcUpdateHealth(value); } } public HealthModule(float initialHealth) { _health = initialHealth; } public override void OnSpawn() { base.OnSpawn(); if (isServer) { // Ensure new or reconnecting clients receive the current health RpcUpdateHealth(_health); } } [ObserversRPC(Channel.Reliable, bufferLast: true, runLocally: true)] private void RpcUpdateHealth(float healthValue) { _health = healthValue; OnHealthChanged?.Invoke(_health); } } Copy using UnityEngine; using PurrNet; public class Player : NetworkIdentity { private HealthModule healthModule = new HealthModule(100); //Default to 100hp public override void OnSpawn() { healthModule.OnHealthChanged += OnHealthChanged; } public override void OnDespawned() { healthModule.OnHealthChanged -= OnHealthChanged; } private void OnHealthChanged(float newHealth) { // Update UI or other client-side logic Debug.Log($"Player health is now {newHealth}"); } public void TakeDamage(float damage) { if (!isServer) return; healthModule.Health -= damage; if (healthModule.Health <= 0) { // Handle player death Debug.Log("Player has died."); } } } --- # PlayerIdentity | PurrNet A very common need in multiplayer games is to find a specific player's object, whether that's the local player, a targeted player, or all players at once. Without a dedicated system for this, you'd end up writing your own player registry, managing dictionaries of player references, and handling edge cases like players joining and leaving. PlayerIdentity takes care of all that. The PlayerIdentity allows you to very easily have access to your players based on ownership. It inherits from NetworkIdentity, so you still have all networking capabilities available, it's simply a layer on top to make your life easier! You can easily utilize any of these: **bool TryGetLocal(out T player)** -> Will output the local player **bool TryGetPlayer(PlayerID playerId, out T player)** -> Will output the player with the given playerId **allPlayers** -> returns a readonly dictionary of all the registered players Simple example usage: Copy 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); } } } [PreviousNetworkBehaviourchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) [NextDon't destroy on loadchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/dont-destroy-on-load) Last updated 11 days ago --- # Local Transport | PurrNet The local transport mimics how the Network acts, but never actually sends any data. This is great if you want your game to work in single player still using networking code and components, but doesn't require you to start a server or client. The player will still act as a [host](https://purrnet.gitbook.io/docs/terminology/host) . ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-ff38f2645ca46e5b2b44f1c6747bbaa749210111%252FLocalTransportComponent.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=c2cacb2b&sv=2) Example of local transport component [PreviousWeb Transportchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/web-transport) [NextSteam Transportchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/steam-transport) Last updated 5 months ago --- # Predicted Timer Module | PurrNet The TimerModule is a built-in module for handling networked countdowns. It manages the synchronization of time between server and client, ensuring that timers roll back correctly during prediction corrections. #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules/predicted-timer-module#basic-usage) Basic Usage Instantiate the module within your `PredictedIdentity`. By default, the timer automatically ticks down by `delta` every simulation tick. Copy public class Bomb : PredictedIdentity { private TimerModule _fuseTimer; protected override void LateAwake() { // Create the timer (default: automatic countdown) _fuseTimer = new TimerModule(this); // Hook into events _fuseTimer.onTimerEnded += Explode; _fuseTimer.onPredictedTimerUpdated_View += UpdateTimerUI; } public void Activate() { // Only the server (or owner, if allowed) needs to start it if (isServer) _fuseTimer.StartTimer(5.0f); } private void Explode() { /* Boom */ } private void UpdateTimerUI(float timeRemaining) { // Use the event value for smooth UI updates _uiText.text = timeRemaining.ToString("F1"); } } #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules/predicted-timer-module#manual-ticking) Manual Ticking You can disable automatic counting to control the timer manually (e.g., for a charging weapon that only advances while a button is held). #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules/predicted-timer-module#api-references) API References **Member** **Description** Properties `remaining` The current authoritative time remaining. `isTimerRunning` Returns `true` if the timer is currently active (value is not null). `predictedViewTimer` The smoothed time value for visual use (Update/LateUpdate). Methods `StartTimer(float time)` Sets the timer to the specified value and starts it. `StopTimer(bool silent)` Stops the timer. If `silent` is true, `onTimerEnded` is not invoked. `TickTimer(float amount)` Manually adjusts the timer by the given amount. Events `onTimerEnded` Invoked when the timer reaches 0. `onPredictedTimerUpdated_View` Invoked when the visual time changes. Use this for UI. `onVerifiedTimerUpdated_View` Invoked when the authoritative state is updated. [PreviousPredicted Moduleschevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules) [NextInput Handlingchevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/input-handling) Last updated 3 months ago Copy protected override void LateAwake() { // Enable manual tick mode _chargeTimer = new TimerModule(this, manualTick: true); } protected override void Simulate(ref MyState state, float delta) { if (state.isCharging) { // Manually advance the timer // Use -delta to count down, or +delta to count up _chargeTimer.TickTimer(-delta); } } --- # Predicted Hierarchy | PurrNet The `Predicted Hierarchy` system provides a set of methods for dynamically creating, deleting, and managing objects within the simulation. These methods allow developers to interact with the hierarchy in a way that feels natural and Unity-like, while ensuring that all changes are predictable and synchronized with the server. **Key Methods** 1. **Object Creation**: * `**Create**` **Methods**: * Instantiates objects in the simulation. * Supports creating objects from prefab IDs or `GameObject` references. * Optionally allows specifying position and rotation. * Returns a `PredictedObjectID` to uniquely identify the created object. Copy public PredictedObjectID? Create(int prefabId); public PredictedObjectID? Create(int prefabId, Vector3 position, Quaternion rotation); public PredictedObjectID? Create(GameObject prefab); public PredictedObjectID? Create(GameObject prefab, Vector3 position, Quaternion rotation); * `**TryCreate**` **Methods**: * Attempts to create an object and returns a boolean indicating success. * Outputs the `PredictedObjectID` of the created object. * Fails if prefab id is invalid or prefab isn't part of the registered prefab list. Copy public bool TryCreate(int prefabId, out PredictedObjectID id); public bool TryCreate(GameObject prefab, out PredictedObjectID id); 2. **Object Deletion**: * `**Delete**` **Methods**: * Removes objects from the simulation using their `PredictedObjectID`. * Handles both pooled and non-pooled objects. Copy public void Delete(PredictedObjectID id); public void Delete(PredictedObjectID? id); 3. **Object Retrieval**: * `**GetGameObject**`: * Retrieves the `GameObject` associated with a `PredictedObjectID`. Copy public GameObject GetGameObject(PredictedObjectID? id); * `**GetComponent**`: * Retrieves a specific component from the `GameObject` associated with a `PredictedObjectID`. Copy public T GetComponent(PredictedObjectID? id) where T : Component; * `**TryGetId**`: * Retrieves the `PredictedObjectID` associated with a `GameObject`. Copy public bool TryGetId(GameObject go, out PredictedObjectID id); * `**TryGetGameObject**`: * Retrieves the `GameObject` associated with a `PredictedObjectID` and returns a boolean indicating success. Copy public bool TryGetGameObject(PredictedObjectID? id, out GameObject go); **Prefabs and Pooling** * Predicted object creation uses a central `PredictedPrefabs` asset referenced by `PredictionManager`. * Prefabs can opt into pooling; pooled instances are kept under a hidden parent for fast reuse. * When reconciling, pooled objects may be disabled/enabled; avoid parenting critical visuals to the root identity if you rely on `SetActive` state — or use `PredictedTransform`’s optional graphics unparenting. **Spawning Network Identities** * Use `PredictedIdentitySpawner` to mirror a set of `NetworkIdentity` objects between server and clients based on prediction state. * The spawner coordinates early spawn, observer assignment, ownership, and finalization in both server and verified client passes. **Best Practices** * Only create/destroy from within simulation code paths (or via machine/state transitions) to keep behavior deterministic under replay. * Prefer `TryCreate` and check the boolean to handle invalid or unregistered prefabs gracefully. * Store and pass around `PredictedObjectID` rather than `GameObject` to remain resilient across rollbacks. [PreviousDeterministic Identitychevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/deterministic-identity) [NextInteracting With Multiple Identitieschevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/multi-identity-interactions) Last updated 4 months ago --- # Web Transport | PurrNet The web transport handles connections through web, which is handled differently and doesn't work with the UDP transport. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/transports/web-transport#parameters) Parameters * Server Port - The port which the server will start on, and clients connect to * Max Connections - The max amount of client connections allowed * Address - This is the IP of the server. Default is the local host * Query - Used to specify custom URL query * Path - Used to specify custom URL path * Enable SSL - Whether to utilize SSL * Cert Path - Path of the SSL Certificate * Cert Password - The password of the SSL * Ssl Protocols - The protocol for the SSL ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-b7cd7a0ebffbbafc7f12138c9711fec8258bcfff%252FWebTransportComponent.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=21b5d268&sv=2) Example of web transport component [PreviousUDP Transportchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/udp-transport) [NextLocal Transportchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/local-transport) Last updated 5 months ago --- # Network Identity | PurrNet In multiplayer, the server and all clients need to agree on which objects exist and who they belong to. Every networked object needs some way to be uniquely identified across all machines, so that when the server says "update object X", every client knows exactly which object that is. Without this, there's no way to keep the game state consistent. The Network Identity is what makes this possible, and it's the core of most networking logic. This plays into the component based nature of object oriented programming with Unity. The [Network Behaviour](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) also inherits from Network Identity, meaning that nearly any script interacting on the network, is also a Network Identity. You can inherit from Network Identity directly, but it is mostly recommended to have your script inherit from [Network Behaviour](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) . Network Identity components can have an owner, and the behavior and control given to said owner will depend on the settings within the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) . A single gameobject can have as many Network Identities as you'd want, and [ownership](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/ownership) doesn't necessarily act on a per gameobject level, but can also be handled on a **per component level**. So one gameobject can have multiple components with different owners. There is a setting on the network identity, to propagate this ownership to the entire object on which it resides. This is also a default setting on the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) . ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-4cacc1b7169704b06cc36044878862c59d898538%252FPurrNet%2520execution%2520order%282%29.jpg%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=b772e5cc&sv=2) Order of execution within the Network Identity [PreviousSimulating latencychevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/simulating-latency) [NextOwnershipchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/ownership) Last updated 11 days ago --- # Predicted Transform | PurrNet * Purpose: Predicts and reconciles position/rotation; renders a smoothed view. * Works with: bare `Transform`, `Rigidbody`, `Rigidbody2D`, and optionally `CharacterController`. * Key fields: * Graphics: Optional child transform to move for visuals only. * Unparent Graphics: Avoid disable/enable artifacts on reconcile by unparenting. * Character Controller Patch: Temporarily disable the controller when applying rollback state. * Interpolation Settings: Assign a `TransformInterpolationSettings` asset; a default asset ships at `Assets/PurrDiction/Runtime/Transform/DefaultInterpolation.asset`. * Float Accuracy: `Purrfect` (full), `Medium` (compressed), `Low` (half) network packing for state. [PreviousBuilt-in Componentschevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components) [NextPredicted Rigidbody (2D & 3D)chevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components/predicted-rigidbody-2d-and-3d) Last updated 1 month ago --- # Predicted Modules | PurrNet circle-exclamation This is new functionality and currently only available on the dev branch. Once it's been tested, it'll be released fully. This was introduced in 1.2.2-beta.4 In case you don't see the functionality, ensure you are at least on this version of PurrDiction The PredictedModule system allows you to encapsulate specific game logic and state into reusable, self-contained units. Instead of writing a `PredictedIdentity` that handles singular logic like timers, inventory, health, and such all in one script, you can break these features down into individual modules. #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules#why-use-modules) Why use Modules? * Encapsulation: Keep logic and state (e.g., a Timer or Health system) isolated from other systems. * Reusability: Write a module once (like a `ProjectileMovementModule`) and drop it into any `PredictedIdentity`. * Network Efficiency: Modules have their own delta compression. If only one module changes, only that module's data is sent over the network. * Automatic History & Rollbacks: Modules automatically participate in the prediction rollback system, saving you from manually managing history buffers for every variable. * * * [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules#predicted-modules) Predicted Modules ---------------------------------------------------------------------------------------------------------------------------- The PredictedModule system allows you to encapsulate specific game logic and state into reusable, self-contained units. Instead of writing a monolithic `PredictedIdentity` that handles movement, health, inventory, and abilities all in one script, you can break these features down into individual modules. #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules#why-use-modules-1) Why use Modules? * Encapsulation: Keep logic and state (e.g., a Timer or Health system) isolated from other systems. * Reusability: Write a module once (like a `ProjectileMovementModule`) and drop it into any `PredictedIdentity`. * Automatic History & Rollbacks: Modules automatically participate in the prediction rollback system, saving you from manually managing history buffers for every variable. circle-info Performance note: A predicted module acts similar to a predicted identity. This means that it's another state, and another simulation to handle. Pros of this is that it adds flexibility and modularity easily. The con being that now your identity is handling multiple simulations and multiple states which can be heavier for performance on both CPU and bandwidth. It's about weighing re-usability and flexibility vs performance. However, this is **not** heavier than having multiple predicted identities. * * * #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules#implementation-guide) Implementation Guide Creating a module involves two steps: defining the state and creating the module logic. **1\. Define the State** Create a struct that implements `IPredictedData`. This holds the data you want to sync and predict. **2\. Create the Module** Inherit from `PredictedModule`. You typically override `Simulate` for logic and `UpdateView` for visuals. * * * #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules#using-a-module) Using a Module To use a module, instantiate it within your `PredictedIdentity`. It will automatically register itself with the identity's prediction lifecycle. * * * #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules#key-overrides) Key Overrides **Method** **Description** `Simulate` Main Logic. Executed every tick. Modify `state` here to advance simulation. `UpdateView` Visuals. Executed every frame. Use `viewState` (interpolated) for smooth rendering. `Initialize` Setup. Called when the module is created. Use this instead of Awake/Start. `Interpolate` Smoothing. (Optional) Custom logic for blending states between ticks. Defaults to standard linear interpolation. [PreviousPredicted Identitieschevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-identities) [NextPredicted Timer Modulechevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-modules/predicted-timer-module) Last updated 3 months ago Copy public struct HealthState : IPredictedData { public int currentHealth; public int maxHealth; public void Dispose() { } } Copy public class HealthModule : PredictedModule { // You can customize the constructor for custom needs public HealthModule(PredictedIdentity identity, int startingHealth) : base(identity) { currentState.currentHealth = startingHealth; // Updates the visual buffer to match the new current state immediately ResetInterpolation(); } // logic: runs on fixed ticks protected override void Simulate(ref HealthState state, float delta) { // Example: Regen health over time if (state.currentHealth < state.maxHealth) { state.currentHealth++; } } public void ChangeHealth(int change) => currentState.currentHealth += change; // Visuals: runs every frame protected override void UpdateView(HealthState viewState, HealthState? verifiedState) { // Update UI or visual effects based on the interpolated viewState // Easiest to utilize events to communicate out of the module } } Copy public class PlayerController : PredictedIdentity { private HealthModule _health; private TimerModule _timer; // Built-in example protected override void LateAwake() { base.Awake(); // Create and register the modules _health = new HealthModule(this, 100); _timer = new TimerModule(this); } // You can now access public methods or properties of your modules public void TakeDamage(int amount) { _health.ChangeHealth(-amount); } } --- # Steam transport setup | PurrNet Greetings! So you want to make a multiplayer Steam game, and you're looking to get started with PurrNet? You've come to the right place! My name is [Judsinarrow-up-right](https://github.com/Spell-Bound-Studios) , and I'm going to show you how to connect with your friends over Steam so that you can get started testing your game today! I will do my best to be extremely clear and provide you with screenshots every step of the way. Before we begin, let's clarify what this document is and is not below: #### [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#this-is) This is: ✔️ A guide on how to use the Steam Transport ✔️ A resource for how you can leverage PurrNet and Heathens to connect with your friends in Unity over Steam #### [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#this-is-not) This is not: ❌ A guide on how to use a lobby ❌ A guide on how to use Steamworks.NET ❌ A guide on how to test steam connections on a single machine **Additional Remarks:** As a solo developer, it is often challenging to test on Steam as it requires two Steam accounts. There are many different ways to do this solo, but for the sake of this guide, I am going to assume that you have a friend or teammate that can help you test. [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#getting-started) Getting Started ------------------------------------------------------------------------------------------------------------------- This guide is being written using the following: * Unity 6002.2.6f1 * PurrNet 1.15.0 * Heathen's Toolkit for Steakworks 2025 * Steam installed and running For PurrNet installation instructions please reference the [installation guide](https://purrnet.gitbook.io/docs/getting-started/installation-setup) . If you aren't familiar with Heathen's Toolkit for Steakworks SDK I highly recommend checking it out if you're serious about using Steam—it just makes your life easier. If you don't want to use Heathen's, then the script example I show won't work out of the box for you, but the majority of this guide will still be helpful. We will install Heathen's Toolkit at the end of this guide to ensure that everyone can follow along until then. For reference, my Package Manager looks like the following after PurrNet installation: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-6a72b245b3f1aa02262c4b6a7988c56657e548f7%252Fstg_fresh_package_manager.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=570850b6&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-1-create-the-scene) Step 1 Create the Scene: This is a fresh project, so I'm going to start by creating a new scene and putting it in the Assets/Scenes folder. I will call this scene "NetworkedSteamScene" and I will delete the "SampleScene" already in the folder: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-fe3b70d4eeb90fd00de87ba0d7f8a80eedda44b5%252Fstg_create_scene.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=75638d21&sv=2) Open the scene: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-ca098d3567b8b929cb7db19362356687c2c308c1%252Fstg_open_scene.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=d7dfc3bb&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-2-create-the-networkmanager) Step 2 Create the NetworkManager: Right-click anywhere in the Hierarchy and go to PurrNet → NetworkManager: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-04ca5af0be9b668fac50da92275c05547b790d6e%252Fstg_create_network_manager.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=db8908ac&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-3-configure-the-gameobject) Step 3 Configure the GameObject: Click on the newly created PurrNet GameObject and feel free to name it whatever you want; I prefer "NetworkManager": ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-5e5a366658586db62905e005142bb2ae2b4e7d9e%252Fstg_rename_network_manager.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5fe38350&sv=2) Remove the UDP Transport and add the Steam Transport: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e669379983b996824d3f88b1a36ca0762dc0c1bb%252Fstg_remove_udp.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=11dce176&sv=2) If you want to know more about the Steam Transport, please reference [Steam Transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/steam-transport) . Now click "Add SteamWorks.Net to Package Manager" on the Steam Transport component—this will add Steamworks.NET to your project. If this gives you an error, please make sure you have git installed on your machine. If this works correctly, then your setup should look like this: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-93f685d8ebfd2c2c005698b7cb8ba980b2152459%252Fstg_add_steam_transport.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=28a054cf&sv=2) Let's add the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) to the Network Manager component by clicking the target icon and add the default rules: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-ca1f63b512f802ae7327580235fec6a38217ba37%252Fstg_network_rules.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=a6a75d14&sv=2) Select the "Unsafe" rules for now if you don't know what these are and just want to get Steam working. Doing so will make the NetworkManager look like this: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-5845ba3ca1fb750bb886990ccc502d52013412f6%252Fstg_network_manager_after_rule_selection.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=1a094787&sv=2) Let's configure our NetworkManager to look like this: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-eafa00337da8503300b4141aa6dd6e6c4d66723a%252Fstg_final_network_manager.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=395c39c9&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-4-script-setup) Step 4 Script Setup: This step is where we will use Heathen's Toolkit for Steamworks to make our lives easier. If you don't have this package, the code below will still be useful to you. I will comment what is a Heathen's API call so that you know what parts you need to go solve with whatever Steamworks API wrapper you're using. Now let's install Heathen's Toolkit for Steamworks. Below is a screenshot of my PackageManager: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-534659075484a487e601c00024d647f6bd39dd20%252Fstg_heathens_install_package_manager.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=3ee2983e&sv=2) First things first, we need to create a Steam Settings object. You can skip this part if you don't have Heathens. Below is a copy and paste from the Heathen's website on how to do this: Open your Project Settings and select Player > Steamworks ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3622319385-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkfE3ZAs6TeNW5sBM2C3i%252Fuploads%252F2sgOBIYmVwUNtczwf4GS%252Fimage.png%3Falt%3Dmedia%26token%3Dd9f90e0a-56ce-418b-b5b4-c334cd8e1d6d&width=768&dpr=3&quality=100&sign=96a3e7c5&sv=2) When you first do this, it will create a SteamMain Steam Settings object in your project's Settings folder. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3622319385-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkfE3ZAs6TeNW5sBM2C3i%252Fuploads%252FFTVxnO4siYbyDIJ9BLaK%252Fimage.png%3Falt%3Dmedia%26token%3D0f3313b0-5e0f-4e2a-9ac1-c741a712237d&width=768&dpr=3&quality=100&sign=3f2a4e3&sv=2) You can optionally add a Demo setting and as many Playtest settings as you would like. All of them will be added to the Settings folder in their own Steam Settings object. Next, let's add a canvas with a Host and Join button as well as a text field to display the Host ID (that only the host will see) and a text input field that the client can type in. This prompted me to install TextMesh Pro so I installed it: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-aca869ab7b9cd58730b2d3112776cc0273517a01%252Fstg_canvas_view.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=e7ece02c&sv=2) Next, let's create a script to handle the connection to Steam. Create a new script called "ConnectionManager" and put it in your scripts folder. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e2ec4f1ddc43f782ae80b37bfad9ff23b1eb9555%252Fstg_connection_manager_script.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=16e00a41&sv=2) Below is a loose way of structuring your code but one that should give you some insight and intuition on how to adapt it to your game. It looks long, but I promise it's really only two methods—I just tried to make it as clear as possible.\\ Let's add this script as a component to an empty GameObject in the scene and then drag and drop our Steam settings, buttons, and TMP objects into their SerializedFields. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-d28259019cf1dd107392b5925b4ea65263f89b3d%252Fstg_connection_manager_drag_drop.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=dc2ab369&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-5-app-id) Step 5 App ID: Now that we have our Steam Transport setup, we need to get our App ID. You can get your App ID by going to the [Steamworks Developer Dashboardarrow-up-right](https://partner.steamgames.com/dashboard/apps) or if you don't have one, you can use the "480"-app ID (Spacewar) that Steam provides. Open the folder that your project is in and at the root directory create or modify the "steam\_appid.txt" file and place your ID inside and save. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-a479e4240256e3799138db6e97c08d0db7e7ae11%252Fstg_app_id.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=1cf0dfef&sv=2) **App ID Remarks:** The 480-app ID is Steams' free app ID for development and testing. When you start your game in Unity, Steam will automatically say that you are playing the game "Spacewar". This is intended and should be used until you register your game with Steam and get an App ID of your own. You can read more about this [herearrow-up-right](https://partner.steamgames.com/doc/sdk/api/example) ### [hashtag](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-6-testing) Step 6 Testing: Now that we have everything set up, we can finally test our connection to Steam. Press play, and you should see a series of initialization messages in the console: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-6e80c745cdf7df6fed129f2d5fe696f0e10c3221%252Fstg_testing_console.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=993a5c1c&sv=2) Press the Host button, and you should see your Host ID in the text field if the PurrNet server is running. You can also go click on the NetworkManager and confirm that the player count is 1 and that you're connected: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-f5224712aee71acc2b4137f6a7b0cc94a27a2111%252Fstg_testing_host_clicked.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=9e9a3ce3&sv=2) If you are using a friend or teammate to test, have them connect in Unity editor or over a build and provide them with your Host ID. If they connect successfully, their screen will look like this: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-59daeef95fae223242fde8dc8b33deda19adccad%252Fstg_testing_client_clicked.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=96ba405f&sv=2) Congratulations! You have successfully connected to Steam, and you're ready to continue your journey with multiplayer! **Concluding Remarks:** Please feel free to ping me in the [PurrNet Community Discordarrow-up-right](https://discord.gg/HnNKdkq9ta) if you found this guide helpful or have feedback on how we can improve it. This is certainly not an exhaustive guide, but we hope it helps you get started! Cheers, [Judsinarrow-up-right](https://github.com/Spell-Bound-Studios) [PreviousCommunity Guideschevron-left](https://purrnet.gitbook.io/docs/community-guides) [NextChat system with broadcastschevron-right](https://purrnet.gitbook.io/docs/community-guides/broadcast-chat-system) Last updated 6 months ago * [Getting Started](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#getting-started) * [Step 1 Create the Scene:](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-1-create-the-scene) * [Step 2 Create the NetworkManager:](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-2-create-the-networkmanager) * [Step 3 Configure the GameObject:](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-3-configure-the-gameobject) * [Step 4 Script Setup:](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-4-script-setup) * [Step 5 App ID:](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-5-app-id) * [Step 6 Testing:](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam#step-6-testing) Copy using Heathen.SteamworksIntegration; using PurrNet; using PurrNet.Steam; using TMPro; using UnityEngine; using UnityEngine.UI; namespace SteamExample { /// /// The purpose of this class is to manage the host and the clients connection to the Steam server. It must live /// on a GameObject in the scene that is connecting to the Networked Scene. If you only have one scene, then it /// lives in that scene. /// /// /// I have partitioned the StartHost() and the StartClient() methods to try to make them extremely clear. You are /// free to refactor them into the same method or structure them however you like. This is by no means a one-size /// fits all solution; but instead, an example of how you might leverage PurrNet and Heathen.SteamworksIntegration /// together to connect with your friends on steam. /// public sealed class ConnectionManager : NetworkIdentity { // Be sure to drag and drop each element if you're copying this directly into your project. [SerializeField] private SteamSettings steamSettings; [SerializeField] private Button hostButton; [SerializeField] private Button clientButton; [SerializeField] private TMP_Text hostTextField; [SerializeField] private TMP_InputField clientInputField; private void Awake() { InstanceHandler.RegisterInstance(this); DontDestroyOnLoad(this); InitializeSteam(); } private void OnEnable() { hostButton?.onClick.AddListener(HandleHostClicked); clientButton?.onClick.AddListener(HandleClientClicked); } private void OnDisable() { hostButton?.onClick.RemoveListener(HandleHostClicked); clientButton?.onClick.RemoveListener(HandleClientClicked); } protected override void OnDestroy() { base.OnDestroy(); InstanceHandler.UnregisterInstance(); } /// /// The purpose of this method is to initialize Steam. /// /// /// Am I wrapping a single method? Yes. But you may want to do more things here for whatever reason if you're /// making a steam game! So again, this is just an example, feel free to refactor to fit your needs. /// public void InitializeSteam() { steamSettings.Initialize(); } /// /// This method is responsible for calling PurrNet's StartHost() 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 StartHost() { var steamTransport = NetworkManager.main.transport as SteamTransport; if (steamTransport == null) { Debug.LogError("SteamTransport missing on NetworkManager", this); return; } // This is a Heathens API call. var user = UserData.Get(); 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); } } } --- # Transports | PurrNet Not every game connects players the same way. Some games run over direct UDP connections, others need WebSocket support for browser builds, and some use platform-specific systems like Steam's networking. The transport layer is what actually moves your data between machines, and being able to swap it out without changing any of your game code means you can support different platforms and connection methods easily. Transports are what's used to transfer the actual data over the network. Your chosen transport has to be referenced in the Network Manager. The default transport of PurrNet is the [UDP Transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/udp-transport) . [PreviousAuthenticationchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication) [NextComposite Transportchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/composite-transport) Last updated 11 days ago --- # Converting from FishNet | PurrNet circle-info Keep in mind that both PurrNet and FishNet are systems which are constantly evolving and could be prone to change. Converting from FishNet to PurrNet is likely as simple as it gets. A lot of the naming conventions from FishNet are similar to those in PurrNet. It is recommended doing the conversion in a separate project in order to use the "old" project for comparison. circle-exclamation Make sure your project is backed up prior to beginning any conversion! [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#converting-individual-parts-and-differences) Converting individual parts (& differences) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#network-manager) Network Manager Both **FishNet** and **PurrNet** have a [Network Manager](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#network-manager) component, which needs to be present for you to start acting on the network. **FishNet's** network manager can hold several components like: * Network Manager * Transport(s) * Observer Manager * Client Manager * Server Manager * Time Manager * Statistics Manager * Prediction Manager * etc. etc. **PurrNet** utilizes internal manager classes for our Network Manager setup, meaning you generally only need your Network Manager object to hold 2 components: * Network Manager * Transport So setting up your Network Manager with PurrNet is as easy as making a new gameObject in your scene and adding the Network Manager. See the [Getting Started](https://purrnet.gitbook.io/docs/getting-started/getting-started) page for more info. ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#networkobject-vs-networkidentity) NetworkObject vs NetworkIdentity A major difference between FishNet and PurrNet, is the understanding of ownership and networked objects/identities. In FishNet, NetworkObjects are automatically added to the root of any object that needs to act on the network. Though this adds a solid level of simplicity to the understanding of something like ownership, it can also easily impose some limitations to the developer. For example: ❌Can't do generic RPC's ❌Can't do returnable RPC's ❌Only supports RPC's in NetworkBehaviours (under MonoBehaviour) ❌Requires baking process (scenes, prefabs, etc.) ❌Can't have different owners per component In PurrNet every networked component stands on its own, meaning that every networked component ([Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) ) can have individual ownership, and be nested exactly as you would with the normal Unity workflow. For example: ✔️ Can do generic RPC's ✔️ Can do returnable RPC's ✔️ Has convenient static RPC's for static type manipulation ✔️ Network modules allowing non-mono networking (also nested) ✔️ Doesn't require a baking process ✔️ Can have split ownership on a gameobject across several components ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#spawning-and-despawning) Spawning & Despawning Handling new objects or removing existing networked objects is a bit different between the systems. In FishNet, you'd need to instantiate the object and spawn the attached NetworkObject component. And if you'd want to spawn with ownership, it would be added as a parameter to the spawn call. That would look like this: It's similar for despawning where you have to call the despawn method for the NetworkObject component. In PurrNet, spawning and despawning is handled for you, meaning that if you just want to spawn an object, any networked action handled for that object in the same tick, will arrive with the spawn packet. So spawning with ownership using PurrNet would look like this: And despawning in PurrNet is as easy as calling Destroy on the object as you would normally in Unity. Give the specific page on [Spawning and despawning](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/spawning-and-despawning) a read for a more in depth explanation. ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#rpcs) RPC's Working with RPC's when transitioning from **FishNet** to **PurrNet** is extremely easy, as the naming conventions are the exact same. However, there are some key differences in terms of usage. This will however depend on your [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) so make sure to read up on those. If you are running with a rule set similar to the default "ServerStrict" rules, then the usage will be the same. But if you're running with a rule set similar to the "Unsafe" rules, then you can send any RPC directly from any client! FishNet PurrNet \[ServerRpc\] \[ServerRpc\] \[ObserversRpc\] \[ObserversRpc\] \[TargetRpc\] \[TargetRpc\] PurrNet also has more options in terms of RPC usage than FishNet. For example, PurrNet also has [RPC's that return values](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc) , [generic RPC's](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/generic-rpc) and [static RPC's](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/static-rpc) . All of which can also be used and even combined as needed. RPC's also hold parameters, this is slightly different but mostly the same. **FishNet** **PurrNet** ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#sync-types) Sync types Working with Synchronizing from FishNet to PurrNet is generally the same as well. The major difference between the two, is that PurrNet also allows for owner authorized SyncTypes, meaning you can handle them locally, and not forcibly through the server. You can read more on his on the individual page for the SyncTypes. Our SyncTypes are built using the [Network Modules](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules) of PurrNet, however, usage wise they are nearly identical. FishNet PurrNet SyncVar [SyncVar](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncvar) SyncList [SyncList](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist) SyncHashSet [SyncHashSet](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synchashset) SyncDictionary [SyncDictionary](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncdictionary) SyncTimer [SyncTimer](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synctimer) There are few functional differences with some of them, for example the SyncTimer of PurrNet handles reconciliation, ensuring that the timers align on clients and doesn't get de-synced ### [hashtag](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#callbacks) Callbacks Callbacks in FishNet are found on the respective manager component which the NetworkManager works with. Using PurrNet, all the callbacks are found in the [NetworkManager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) itself. The video on the [NetworkManager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) found on the relative page, explains this more in depth. Using your IDE should give you access to all the callbacks you need. [PreviousConverting from Mirrorchevron-left](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-mirror) [NextSimple Guideschevron-right](https://purrnet.gitbook.io/docs/guides) Last updated 11 months ago * [Converting individual parts (& differences)](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#converting-individual-parts-and-differences) * [Network Manager](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#network-manager) * [NetworkObject vs NetworkIdentity](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#networkobject-vs-networkidentity) * [Spawning & Despawning](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#spawning-and-despawning) * [RPC's](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#rpcs) * [Sync types](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#sync-types) * [Callbacks](https://purrnet.gitbook.io/docs/getting-started/converting-to-purrnet/converting-from-fishnet#callbacks) Copy GameObject go = Instantiate(_yourPrefab); InstanceFinder.ServerManager.Spawn(go, ownerConnection); Copy var identity = Instantiate(myIdentityPrefab); identity.GiveOwnership(ownerPlayer); Copy [ServerRpc(RequireOwnership = false)] private void MyRpc() { } Copy [ServerRpc(requireOwnership:false)] private void MyRpc() { } --- # Best Practices | PurrNet **Use Structs for** `**INPUT**` **and** `**STATE**` * Define `INPUT` and `STATE` as **structs** in C#, not classes. * Structs are value types, which ensures predictable behavior during prediction and reconciliation. * Example: Copy public struct MyInput : IPredictedData { public float horizontal; public float vertical; public bool jump; } public struct MyState : IPredictedData { public Vector3 position; public Quaternion rotation; public bool isJumping; } **Why?** * Structs are copied by value, making them ideal for storing snapshot data that needs to be reconciled. * Avoids unintended side effects that can occur with reference types (classes). * * * **2\. Initialize State with** `**GetInitialState**` * Use the `protected override STATE GetInitialState()` method to define the default values for your `STATE` struct. * This method is called when the entity is first created, ensuring that it starts with a valid initial state. **Example**: **Why?** * Ensures that your entity starts with a consistent and predictable state. * Avoids undefined behavior caused by uninitialized state variables. * * * **3\. Treat** `**STATE**` **as the Source of Truth** * Any data that affects the simulation should be part of the `STATE` struct. * Use `STATE` to store: * Entity position, rotation, and velocity. * Flags or variables that control behavior (e.g., `isJumping`, `isShooting`). * Avoid modifying Unity components directly (e.g., `Transform.position`) without synchronizing them with the `STATE`. **Why?** * The `STATE` struct is reconciled by the CSP system, ensuring consistency between the client and server. * Directly modifying Unity components can lead to desynchronization and unpredictable behavior. * * * **4\. Use** `**GetUnityState**` **and** `**SetUnityState**` **for External Components** * If your `STATE` affects Unity components (e.g., `Transform`, `Rigidbody`), use these overrides to synchronize them: * `**protected override void GetUnityState(ref STATE state)**`: * Updates the `STATE` struct with data from Unity components (e.g., reading the `Transform.position`). * `**protected override void SetUnityState(STATE state)**`: * Applies the `STATE` to Unity components (e.g., setting the `Transform.position`). **Example**: **Why?** * These methods ensure that Unity components are properly synchronized with the `STATE`, maintaining consistency during prediction and reconciliation. * * * **5\. Make** `**SerializeField**` **Constants Only** * Use `SerializeField` only for **constant values** that do not change during simulation (e.g., speed, prefab references). * Avoid using `SerializeField` for variables that are part of the simulation logic (e.g., position, velocity). **Why?** * `SerializeField` variables are not reconciled by the CSP system, so changing them during simulation can lead to desynchronization. * * * **6\. Keep Simulation Logic Deterministic** * Ensure that all simulation logic (e.g., movement, physics) is deterministic and based on the `STATE` or `INPUT`. **Why?** * Deterministic logic ensures that the client and server produce the same results, even when running at different times or frame rates. * * * **7\. Implement** `**IDuplicate**` **on Complex Structs** * PurrDiction copies states frequently for history and reconciliation. * If your `STATE` contains nested complex structs, implement `IDuplicate` on those types so the packer can clone them without serialize/deserialize overhead. * Also implement `IEquatable` to speed up delta/equality checks. * * * **8\. Prefer Inspector Labels in Docs** * Use the Inspector’s friendly labels in guidance (e.g., Interpolation Settings, Float Accuracy). * Code still references exact identifiers; search by label or identifier as needed. * * * #### [hashtag](https://purrnet.gitbook.io/docs/client-side-prediction/best-practices#summary-of-best-practices) Summary of Best Practices **Practice** **Why It Matters** Use structs for `INPUT` and `STATE` Ensures predictable behavior and avoids side effects of reference types. Use `GetInitialState` for defaults Provides a consistent starting state for entities. Treat `STATE` as the source of truth Prevents desynchronization and maintains consistency between client and server. Use `GetUnityState` and `SetUnityState` Properly synchronizes Unity components with the `STATE`. Make `SerializeField` constants only Avoids desynchronization caused by unreconciled changes. [PreviousViews and Interpolationchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/views-and-interpolation) [NextDisposable Collectionschevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/disposable-collections) Last updated 4 months ago Copy protected override MyState GetInitialState() { return new MyState { position = Vector3.zero, rotation = Quaternion.identity, isJumping = false }; } Copy protected override void GetUnityState(ref MyState state) { state.position = transform.position; state.rotation = transform.rotation; } protected override void SetUnityState(MyState state) { transform.position = state.position; transform.rotation = state.rotation; } --- # Simulating latency | PurrNet In order to test locally and still have a realistic scenario, we need to be able to simulate latency. Some tools have built in latency simulators, but we've found these to not be good representations of reality (as some things might get simulated different to others) The best/only way to get a true to world latency experience when testing locally, would be to throttle the whole process (Unity or builds) or even the whole system. We do intend to implement a system in the future that can throttle on a process specific basis for you, but this is not something we have for now. For this there are plenty of tools that can work, we personally use [Clumsyarrow-up-right](https://github.com/jagt/clumsy) [PreviousPurrnity Transport (Community)chevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/purrnity-transport-community) [NextNetwork Identitychevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) Last updated 10 months ago --- # Authentication | PurrNet If anyone can connect to your game server without verification, you have no way to prevent unauthorized players from joining, and no way to identify who is who. Authentication gives you a gate at the connection level, so you can verify players before they get access to anything in your game. This could be as simple as a password check, or as complex as verifying a token from a backend service. In PurrNet, custom authenticators enable you to define your own authentication logic for multiplayer games. This guide will walk you through the steps to create a custom authenticator using a simple password-based example. circle-info Any new connections will never be promoted to `PlayerID` unless they have authenticated. They won't spawn any objects, receive any RPCs or player scoped Broadcasts, they will only receive `Connection` scoped broadcasts. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication#id-1.-create-a-script-for-your-authenticator) **1\. Create a script for your Authenticator** Create a new C# script in your Unity project, and name it something meaningful, such as `CustomAuthenticator`. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication#id-2.-implement-the-custom-authenticator-class) **2\. Implement the Custom Authenticator Class** Your custom authenticator class should inherit from `AuthenticationBehaviour`. This ensures that your class adheres to the PurrNet authentication structure. Copy using System.Threading.Tasks; using UnityEngine; using PurrNet.Authentication; namespace YourNamespace { // this attributes ensures the correct Type variant is registered [RegisterNetworkType(typeof(AuthenticationRequest))] public class CustomAuthenticator : AuthenticationBehaviour { [Tooltip("The password required to authenticate the client.")] [SerializeField] private string _password = "YourSecretPassword"; protected override Task> GetClientPayload() { // the client will send his password to the server return Task.FromResult(new AuthenticationRequest(_password)); } protected override Task ValidateClientPayload(string payload) { // the server will validate it and return the appropriate response bool isValid = _password == payload; return Task.FromResult(new AuthenticationResponse(isValid)); } } } ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication#id-3.-using-the-custom-authenticator) **3\. Using the Custom Authenticator** To use your custom authenticator add it somewhere on your scene as a component and link it to your network manager like so: ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-55e425003dfd6d6d4ac9e145a22b0303e9ec2346%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=37d2ea7a&sv=2) Congratulations! You've successfully created a custom authenticator in PurrNet. This allows you to control access to your multiplayer game more securely and flexibly. Experiment with different types of authenticators and tailor them to fit your specific requirements. [PreviousDistance conditionchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility/distance-condition) [NextTransportschevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports) Last updated 11 days ago * [1\. Create a script for your Authenticator](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication#id-1.-create-a-script-for-your-authenticator) * [2\. Implement the Custom Authenticator Class](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication#id-2.-implement-the-custom-authenticator-class) * [3\. Using the Custom Authenticator](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication#id-3.-using-the-custom-authenticator) --- # Steam Transport | PurrNet The Steam Transport comes with PurrNet by default, but requires at a minimum the Steamworks.Net package in order to work. [Read the comprehensive guide by Judsin here.](https://purrnet.gitbook.io/docs/community-guides/connect-with-steam) ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/transports/steam-transport#parameters) Parameters * Server Port - The port to connect through * Dedicated server - Whether you're connecting as/to a [Steam Game Serverarrow-up-right](https://partner.steamgames.com/doc/features/multiplayer/game_servers) * Peer To Peer - Whether you want clients connecting directly using P2P * Address - The address to connect to. In case of P2P, this is the CSteamID ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/transports/steam-transport#steam-isnt-networking) Steam isn't networking! It's highly important that devs understand that networking isn't Steam and Steam isn't networking. A Steam "Lobby" isn't network related either. Purely a way for Steam users to share meta data through Steam (usernames, Steam ID, chat, etc.) ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-e3cb993c1a936636a1cf34de2c0cc31e37e4616a%252FSteamTransportExample.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=31533b6f&sv=2) Example of steam transport component [PreviousLocal Transportchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/local-transport) [NextPurr Transportchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/purr-transport) Last updated 5 months ago * [Parameters](https://purrnet.gitbook.io/docs/systems-and-modules/transports/steam-transport#parameters) * [Steam isn't networking!](https://purrnet.gitbook.io/docs/systems-and-modules/transports/steam-transport#steam-isnt-networking) --- # Unique to PurrNet | PurrNet PurrNet has all the core networking features you'd expect: [SyncVars](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncvar) , [RPCs](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc) , [spawning & despawning](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/spawning-and-despawning) , and much more. But beyond the basics, there are a number of things we do differently that we think make a real difference in how you build multiplayer games. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#works-with-unity-not-against-it) Works with Unity, not against it A lot of networking solutions force you into a different workflow. Nested prefabs don't work, scene objects need baking, and you have to learn a whole new way of doing things you already know how to do. PurrNet is built to feel like Unity. If you can Instantiate, Destroy, and write MonoBehaviours, you can build multiplayer games with PurrNet. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#no-baking) No baking We don't bake components or scene IDs. Scene IDs are calculated at runtime based on hierarchy order, so as long as your scenes match, the IDs match. This means no conflicts in version control, no mysterious bake issues, and no extra build steps. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#no-network-objects) No Network Objects Most networking solutions require a single "Network Object" component per GameObject to identify it across the network. This creates limitations on how you structure your objects. PurrNet doesn't do this. Instead, every [NetworkBehaviour](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) acts as its own [Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) , giving you per-component ownership and much more flexibility in how you build your objects. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#spawning-and-despawning) Spawning and despawning To spawn a networked object, you just call `Instantiate()`. To despawn it, you call `Destroy()`. That's it. PurrNet handles the rest. Even our built-in [object pooling](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/pooling) hooks into these same Unity calls, so you don't have to change your workflow at all. If you prefer an explicit spawn call like other systems offer, you can configure that through [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) . ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#network-rules) Network Rules [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) let you configure your entire authority model from a single scriptable object. Who can spawn? Who can call RPCs? Who syncs data? You control all of this without touching your game code. This means you can prototype with full client authority and tighten security later, all by swapping a scriptable. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#network-modules) Network Modules [NetworkModule](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules) is a base class that lets you create reusable, network-aware components that aren't MonoBehaviours. You can attach them to any NetworkIdentity, which makes it easy to share networking logic across different objects without duplicate code. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#generic-rpcs) Generic RPCs You can use generics with [RPCs](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/generic-rpc) . This is great for modular code and tool makers who want to build flexible addons and systems on top of PurrNet. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#static-rpcs) Static RPCs You can call [RPCs on static methods](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/static-rpc) , not just on NetworkBehaviours. This gives you more freedom in how you structure your code. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#returnable-rpcs) Returnable RPCs [Awaitable RPCs](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc) let you return values from remote calls, similar to how you'd work with async/await in regular C#. Call a method on the server, await the result, and keep going. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#persistent-player-data) Persistent player data With PurrNet's [cookie system](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) , if a player disconnects and reconnects, they keep the same player ID and metadata. The server knows it's the same person without you having to build that yourself. ### [hashtag](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#easy-testing) Easy testing At runtime, you can drag and drop any prefab from your [Network Prefabs](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-prefabs) into the scene and it will automatically be spawned on the network. Deleting a network identity from the hierarchy will despawn it. This makes multiplayer testing in the editor much faster. [PreviousIntroductionchevron-left](https://purrnet.gitbook.io/docs) [NextPricingchevron-right](https://purrnet.gitbook.io/docs/readme/pricing) Last updated 11 days ago * [Works with Unity, not against it](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#works-with-unity-not-against-it) * [No baking](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#no-baking) * [No Network Objects](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#no-network-objects) * [Spawning and despawning](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#spawning-and-despawning) * [Network Rules](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#network-rules) * [Network Modules](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#network-modules) * [Generic RPCs](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#generic-rpcs) * [Static RPCs](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#static-rpcs) * [Returnable RPCs](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#returnable-rpcs) * [Persistent player data](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#persistent-player-data) * [Easy testing](https://purrnet.gitbook.io/docs/readme/unique-to-purrnet#easy-testing) --- # Purr Transport | PurrNet The Purr Transport is a relay provided by us to you. We wanted to allow the best developer experience and not having to setup servers or do port forwarding to test your game in an online scenario is a big pain point we are trying to take away from you. The transport is in very early stages and will undergo multiple changes and you might experience server restarts once in a while. That being said it should be more than fine for testing purposes. It works through a room system, you just need to specify a unique room and anyone can connect to it through the relay. There is still the notion of server and client and only one client can act as the host. Once the host disconnects everyone else is also kicked out similar to the other transports. There are multiple regions available to you, right now when you create a room it will ping and get the one closest to you. We have 2 servers in the US, one in EU, one in AP, one in Brazil and one in South-Africa. This should be enough coverage and if needed we might expand on these regions in the future. To use this transport simply add the `PurrTransport` component and assign it to your `NetworkManager`. Region only matters when creating a server/room. When joining an existing session, region is not taken into account. This means that rooms are global and their names are shared across all regions. circle-exclamation The default relay address found in the transport is meant for development only, any production use is prohibited. You can use the transport layer for production if you setup your own relay server for it. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-d6f5f273add06c10a8f7ebc440d29301cbbef737%252FPurrTransportExample.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=dd8244c2&sv=2) Example of Purr transport component [PreviousSteam Transportchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/transports/steam-transport) [NextEOS Transport (Community)chevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/transports/eos-transport-community) Last updated 5 months ago --- # Ownership | PurrNet In multiplayer, you need a way to determine which player is allowed to control which object. Without ownership, either everyone can modify everything (which leads to chaos and cheating), or only the server can do anything (which makes the development loop more complex). Ownership lets you define who is "in charge" of a given object, and the networking layer can then enforce permissions based on that. Ownership is the relation between a [player](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) and a [Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) . Upon spawning a new Network Identity, some level of ownership exists, whether that'd be no owner meaning the server has full control, or a [player/client](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) can be the owner. Being the owner of a [Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) , gives you different levels of authorization, depending on the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) of the [network manager](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager) . Keep in mind that being the owner of a network identity, doesn't mean you are directly the owner of every identity on the object. This is a setting that is specific per [network identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) . You can easily change ownership on your Network Identity by calling the `GiveOwnership` method, and feeding it the [PlayerID](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) to be the new owner You can easily check for ownership by simply getting the owner. Be mindful that the owner and often other PlayerID references are nullable, meaning you should check whether they have a value or not. An easy way to check whether you have "control" of the object, meaning one of the two statements below is true: - There is an owner and I am the owner - There is no owner and I am the server Is to use the `IsController` bool. [PreviousNetwork Identitychevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) [NextSpawning & Despawningchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/spawning-and-despawning) Last updated 11 days ago Copy networkIdentity.GiveOwnership(newOwnerPlayerId); Copy if(owner.HasValue) { //This object has an owner Debug.Log($"Owner of the object is: {owner.Value}"); } else { //This object has no owner } //You can also easily check if you are the owner if(isOwner) { //If we are here, we are the owner of the object } Copy if(IsController) { //We are either the owner, or there is no owner and we're the server } else { //We are not in control of this object } --- # Distance condition | PurrNet The distance visibility condition allows you to set an amount of units, and if the player is that amount of units away from an Identity, they won't receive data from said identity. Copy using UnityEngine; namespace YourNamespace { [CreateAssetMenu(menuName = "PurrNet/NetworkVisibility/DistanceRule")] public class DistanceRule : NetworkVisibilityRule { [SerializeField] private LayerMask _layerMask = ~0; [SerializeField, Min(0)] private float _distance = 30f; [SerializeField, Min(0)] private float _deadZone = 5f; public override int complexity => 100; public override bool CanSee(PlayerID player, NetworkIdentity networkIdentity) { var myPos = networkIdentity.transform.position; bool wasPreviouslyVisible = networkIdentity.IsObserver(player); foreach (var playerIdentity in manager.EnumerateAllPlayerOwnedIds(player, true)) { var layer = playerIdentity.layer; if ((_layerMask & (1 << layer)) == 0) continue; if (!playerIdentity.isActiveAndEnabled) continue; var playerPos = playerIdentity.transform.position; var distance = Vector3.Distance(myPos, playerPos); if (wasPreviouslyVisible) { if (!(distance <= _distance + _deadZone)) continue; } else if (!(distance <= _distance)) continue; return true; } return false; } } } [PreviousUsing the visibilitychevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-visibility/using-the-visibility) [NextAuthenticationchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication) Last updated 5 months ago --- # Client Spawning Validation | PurrNet Client side spawning is great for instant feedback on the client's end but if you want to avoid abuse in a more controlled fashion we allow you to register validators. These validators only run on the server and allow you to deny spawns entirely. If a spawn is denied the original client will despawn it on their end. One thing to keep in mind is that `SpawnPacket` in `PurrNet` can be partial because every identity is independent. To ensure that a spawn is as simple as a prefab spawn we provide a `TryGetRawPrefab` method that validates it is a simple spawn and also gives you the prefab used. Here is a bare bones example of validating all normal (not partial) prefab spawns: Copy using PurrNet; using PurrNet.Logging; using PurrNet.Modules; using UnityEngine; public class TestValidator : MonoBehaviour { [SerializeField] private NetworkManager _networkManager; private void Awake() { _networkManager.onClientSpawnValidate += ValidateSpawn; } private void OnDestroy() { if (_networkManager != null) _networkManager.onClientSpawnValidate -= ValidateSpawn; } private bool ValidateSpawn(PlayerID player, SpawnPacket data) { // TryGetRawPrefab outputs the prefab used to trigger this spawn packet // it also validates that it is complete (we have partial spawns in PurrNet) if (data.TryGetRawPrefab(_networkManager, out var prefab)) { PurrLogger.Log($"Spawn validated for player {player}: {data.prototype}\nPrefab: {prefab.name}"); return true; } return false; } } [PreviousSpawning & Despawningchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/spawning-and-despawning) [NextNetworkBehaviourchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) Last updated 7 months ago --- # Sync Types | PurrNet One of the most common things you need in multiplayer is keeping a value in sync across all players. A player's health, a scoreboard, an inventory list. Without sync types, you'd have to manually send RPCs every time a value changes, handle late joiners who missed earlier updates, and write a bunch of boilerplate to keep everything consistent. Sync types handle all of that for you automatically. All sync types (e.g., `SyncVar`, `SyncList`) are built on our network module system. Any limitations or features of `NetworkModule` apply to them as well. This means you can create your own sync types or modules—`SyncVars` are just network modules, not magic. Anyone, including you, can build custom modules using the same system. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types#what-to-expect-of-sync-types) What to expect of Sync types There are some default expected behaviour of anything called a "Sync"-anything. * Live syncing: Any change done now, will be pushed to all players * Buffering: When a new player joins mid-session (Late Join), they don't need to wait for the next change. Of course bare in mind that there could be exceptions depending on the individual implementation. [PreviousCommon Pitfallschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/common-pitfalls) [NextSyncVarchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncvar) Last updated 11 days ago --- # Execution Flow | PurrNet This page clarifies when each override runs and how a tick proceeds from input → simulation → physics → networking → view. * * * **At a Glance** spinner Client reconciliation (when a verified frame arrives): spinner Per-frame view update (client): spinner * * * **Startup & Registration** * `PredictionManager.Awake` * Registers instance per scene, sets Unity physics to script mode per provider, initializes pooling. * `OnEarlySpawn` * Registers built‑in systems (Hierarchy, Players, Physics2D/3D, Time). * Registers scene `PredictedIdentity` components and assigns IDs/owners. * `PredictedIdentity.Setup` * Called per identity with `NetworkManager`, `PredictionManager`, component ID, and owner. * On first spawn, calls `LateAwake()` once. * * * **Per‑Tick (OnPreTick)** Order (server and client): 1. Mark simulation context * `isSimulating = true`; on server, `isVerified = true`. 1. Input preparation * Server: dequeues pending client input frames. * For each identity: `PrepareInput(isServer, isController, localTick, extrapolate)` * On local controller: calls your `GetFinalInput`, `SanitizeInput`, writes to history. * On server for remotes: consumes `QueueInput` or optionally extrapolates. 1. Save pre‑simulation state * `SaveStateInHistory(localTick)` for non‑event identities. * Server: write initial frame payloads for state and inputs. 1. Simulation passes * `SimulateTick(localTick, delta)` → your `Simulate(...)` implementation. * `LateSimulateTick(delta)` → your `LateSimulate(...)` implementation. * `DoPhysicsPass()` integrates Unity physics for the tick. 1. Save post‑simulation state * `SaveStateInHistory(localTick)` for event‑handler identities. * Server: write event streams and send frames to clients. 1. Post‑simulate & finish * `PostSimulate()` per identity for any finalization. * Finalize input/state per role (server vs client). * Advance `localTick`. * `isSimulating = false`. * * * **Reconciliation (OnPostTick on client)** * Receive verified server frames and compute a target tick to reconcile to. * Fire `onStartingToRollback`. * Perform one of: * In‑place: `RollbackToFrame(tick)` → `SimulateFrameInPlace(tick)` → `SimulateFrame(tick, save=true)` * From previous frame: `RollbackToFrame(frame, stateTick, inputTick)` → `SimulateFrame(verifiedTick, save=true)` * Mark `isVerified = false`, then replay to the latest local tick: `ReplayToLatestTick(lastVerified + 1)`. * `SyncTransforms()` and `UpdateInterpolation(accumulateError: true)` to smooth visual corrections. * Fire `onRollbackFinished`. View updates are not part of tick callbacks; see below. * * * **View Update (Client)** * `PredictionManager.Update` or `LateUpdate` (based on `UpdateViewMode`): * For each identity: `UpdateView(Time.unscaledDeltaTime)` * Identities compute/advance `viewState` and render visuals. * Ownership changes trigger `OnViewOwnerChanged(old, new)` inside `UpdateView`. * * * **Override Cheat‑Sheet** * Spawning/Pooling * `OnPreSetup()` — before `Setup` on server‑side systems. * `LateAwake()` — once on fresh spawn; view‑only setup. * `ResetState()` — clear owner/IDs and interpolation; called when reusing pooled instances. * `Destroyed()` — cleanup on despawn/unregister. * Input (on `PredictedIdentity`) * `GetFinalInput(ref INPUT)` — per tick, returns current frame input for the controller. * `UpdateInput(ref INPUT)` — per Unity frame; cache edge‑triggered inputs. * `SanitizeInput(ref INPUT)` — clamp/normalize before simulation. * `ModifyExtrapolatedInput(ref INPUT)` — strip non‑continuous inputs during remote extrapolation. * Simulation * `SimulationStart()` — before first `Simulate` for this identity. * `Simulate(...)` — deterministic tick logic; mutate only `STATE`. * `LateSimulate(...)` — optional second pass each tick. * `PostSimulate()` — finalize per tick. * State ↔ Unity * `GetUnityState(ref STATE)` — read Unity → state when needed. * `SetUnityState(STATE)` — apply rollback state → Unity. * Reconciliation / History * `SaveStateInHistory(tick)` — invoked by manager before/after simulation. * `Rollback(tick)` — restore snapshot and apply to Unity. * `ClearFuture(tick)` — drop states beyond a given tick. * `GetLatestUnityState()` — manager asks identities to resync after replays. * View & Interpolation * `UpdateRollbackInterpolationState(delta, accumulateError)` — accumulate/compute view corrections. * `ResetInterpolation()` — clear smoothing (e.g., teleports). * `UpdateView(STATE viewState, STATE? verified)` — render visuals. * `OnViewOwnerChanged(old, new)` — view‑only ownership transitions. [PreviousState Handlingchevron-left](https://purrnet.gitbook.io/docs/client-side-prediction/state-handling) [NextBuilt-in Componentschevron-right](https://purrnet.gitbook.io/docs/client-side-prediction/built-in-components) Last updated 4 months ago --- # Remote Procedure Calls | PurrNet In multiplayer, your game logic runs on multiple machines at the same time. But sometimes one machine needs to tell another to do something. For example, a client wants to tell the server "I just fired my weapon", or the server needs to tell all clients "this player just scored a point". You can't just call a method on another machine the way you would locally. That's exactly what RPCs (Remote Procedure Calls) are for. An RPC allows you to call a method on another device/machine. If you're on a client and successfully call a ServerRpc, that method will now be called on the server machine and not on the local machine actually calling the method. This makes it very easy to interact with other machines over the network. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#calling-rpcs-is-easy) Calling RPC's is easy In order to call RPC's, you have to make sure your script inherits from _at least_ [Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) , but it is recommended that you inherit from [Network Behaviour](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/networkbehaviour) to get more functionality. The RPC logic _**can**_ depend on ownership, and this is modifiable in your [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) . #### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#example-script-called-networktest) Example script called NetworkTest: ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#types-of-rpcs) Types of RPC's There are 3 different kinds of RPC's: - ServerRPC - ObserversRPC - TargetRPC Different RPC's has different parameters to take in, like so: Below you'll find the different RPC types with the parameters they each hold. #### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#serverrpc) ServerRPC: The ServerRPC will call **Client -> Server** meaning that the method which you execute on the client, will be ran on the Server instead. **It holds the following parameters:** * _**RequireOwnership**_ - This will override any settings within the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) or the [Network Identity](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity) inspector. With this, you can modify whether ownership is required to call the RPC. * _**RunLocally**_ - By default, this is **false,** however, if you override it to **true** it will mean that the called will run the logic as well as the server. #### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#observersrpc) ObserversRPC: The ObserversRPC will call **Server -> All clients,** or **Client -> All clients** if your [network rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) allow it. This means that the method being called will trigger for every client. **It holds the following parameters:** * _**RequireServer**_ - This will override any settings within the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) as to whether clients can call the ObserversRpc directly. * _**BufferLast**_ - If set to true, when a new [client](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) joins, they will get the most recent call of the method with the data within the parameters. * _**RunLocally**_ - If set to true, the caller will run the method logic locally, avoiding the networking route. If the server calls it, the server will also run the method. If a client calls it, the client will run the method immediately, instead of awaiting the servers call. #### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#targetrpc) TargetRPC: The TargetRPC will call **Server -> Client**, or **Client -> Client** if your [network rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) allow it. This means that the method called, will only be triggered on the client which [PlayerID](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) is given. **It holds the following parameters:** * _**RequireServer**_ - This will override any settings within the [Network Rules](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-rules) as to whether clients can call the ObserversRpc directly. * _**BufferLast**_ - If set to true, when the target [client](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) joins again (we can only hold their data if we've seen them before), they will get the most recent call of the method with the data within the parameters. * _**RunLocally**_ - If set to true, the caller will run the method logic locally, avoiding the networking route. If the server calls it, the server will also run the method. If a client calls it, the client will run the method immediately, instead of awaiting the servers call. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#rpc-info) RPC Info RPC Info is a super useful tool to get information about an RPC that has just been sent. You simply add the RPCInfo as the last parameter of your RPC and default the value to **default** and it will auto populate upon receiving the RPC This value is mostly used to get the sender. [PreviousSyncTextureFilechevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/syncbigdata/synctexturefile) [NextGeneric RPCchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/generic-rpc) Last updated 11 days ago * [Calling RPC's is easy](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#calling-rpcs-is-easy) * [Types of RPC's](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#types-of-rpcs) * [RPC Info](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc#rpc-info) Copy using PurrNet; public class NetworkTest : NetworkBehaviour { //On spawned will be called when the object is first seen by the network. //If you are a host, this OnSpawned will be called twice, once for server and once for client protected override void OnSpawned(bool asServer) { if (asServer) return; //If your network rules are set to everyone on ServerRpc, this can be called from all clients //Alternatively, it can only be called by the owner of the identity TestServerMethod(); } [ServerRpc] private void TestServerMethod() { //This code will be called on the server. //The server is now calling the observers rpc method on all observing clients. //If your Network Rules allow for it, this could also be called from clients, essentially skipping the Server RPC TestObserverMethod(); } [ObserversRpc] private void TestObserverMethod() { //This code will be called on every observing clients machine. //This is a good way to update all observing clients with the same information. } [TargetRpc] private void TestTargetMethod(PlayerID target) { //This code will only be called on the local instance of the target //This is a good way to update a target client } } Copy [ServerRpc(RequireOwnership:false, RunLocally:true)] Copy [ServerRpc] private void TestRpc(int myValue, RPCInfo info = default) { //Here we now have the RPC info Debug.Log($"Sender: {info.sender}") } --- # SyncArray | PurrNet A Synchronized Array, more commonly referred to as a "SyncArray", is easily definable in your code. It will handle automatically aligning the contents of an array between all [players](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) . Working with SyncArrays is as easy as using a regular array, you have only to be mindful of who has authority over it. SyncArrays are built with the Network Module setup, meaning that you have to initialize it. Below is a usage example: Copy //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; } ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist-1#syncarraychange) SyncArrayChange When working with the SyncArrayChange which comes with the onChanged callback, you have some basic information available to you. The `.operation` tells you what the change is, that can be: Set, Cleared, Resized. The `.value` is the new value which it has been changed. This is of type T to match the type of the SyncArray. The `.index` tells you at what index this change is relevant. [PreviousSyncListchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist) [NextSyncQueuechevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist-1-1) Last updated 10 months ago --- # SyncTimer | PurrNet The SyncTimer allows for easy synchronizing of a timer automatically counting down. You can easily do actions such as **starting**, **stopping, pausing and resuming** the timer. Most methods accessible should be quite self-explanatory. The SyncTimer also automatically handles reconciliation of the timer, meaning that it will force align all clients to ensure de-syncing doesn't happen. The more frequent, the more precise it will be, but the more data is used. Generally it is very data light, so don't fear for making the number lower if necessary. Below is an example usage of the SyncTimer being server authorized (default) and having a reconcile interval of 3 (default) Copy 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(); } Beyond using the SyncTimers automatic countdown, you can also advance the timer yourself with whatever delta you please giving you more freedom to count up, down, faster or slower. The bare minimum setup of this would love like this: [PreviousSyncHashsetchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synchashset) [NextSyncInputchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput) Last updated 5 months ago Copy private SyncTimer _timer = new(manualUpdate:true); private void Update() { _timer.Advance(Time.deltaTime); } --- # SyncEvent | PurrNet Synchronized Event, or most commonly referred as "SyncEvent" are easily definable in your code, and will handle automatically calling events to all [players](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) listening to the event. Working with SyncEvent is as easy as using a regular UnityEvent, only having to be mindful of who has authority. SyncEvents are built with the Network Module setup, meaning that you have to initialize it. Below is a usage example: Copy //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); } The SyncEvent is serializable in the Unity editor as a Unity Event, meaning that you can easily add events straight from the inspector view. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-7779f9838071416329b6f5851d107b7d60c2c143%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5ef373fa&sv=2) [PreviousSyncDictionarychevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncdictionary) [NextSyncHashsetchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synchashset) Last updated 1 year ago --- # SyncVar | PurrNet Synchronized variables, or most commonly referred as "SyncVar" are easily definable in your code, and will handle automatically align a script variable between all [players](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) . SyncVars are built with the Network Module setup, meaning that you have to initialize it. Below is a usage example: Copy private SyncVar mySync = new(2); //Now defaults to 2 private SyncVar myOtherSync = new(5, ownerAuth:true); //Defaults to 5, and is owner auth protected override void OnSpawned(bool asServer) { mySync.onChanged += OnMySyncChange; if (asServer) mySync.value = 420; if (isOwner) myOtherSync.value = 69; } private void OnMySyncChange(int newValue) { Debug.Log("SyncVar has changed to: " + newValue); } When populating the SyncVar, you can do a few things: * Feed it nothing: `new();` * Feed it a default: `new(5);` //In case of number * Feed it settings like `ownerAuth` or `sendIntervalInSeconds` [PreviousSync Typeschevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types) [NextValidated SyncVarchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/validated-syncvar) Last updated 11 months ago --- # SyncQueue | PurrNet A Synchronized Queue, more commonly referred to as a "SyncQueue", is easily definable in your code. It will handle automatically aligning the contents of a list between all [players](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) . Working with a SyncQueue is as easy as using a regular queue, you have only to be mindful of who has authority over it. SyncQueue's are built with the Network Module setup, meaning that you have to initialize it. Below is a usage example: Copy //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(); } The SyncQueue is serialized in editor. It will also attempt to auto serialize non-serializable classes/structs by grabbing the ToString(). ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-0ea133b119b4387e2ab9c17a70030175e2884d8c%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=30af7df5&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist-1-1#syncqueuechange) SyncQueueChange When working with the SyncQueueChange which comes with the onChanged callback, you have some basic information available to you. The `.operation` tells you what the change is, that can be: Enqueued, Dequeued, Cleared. The `.value` is the new value which it has been changed. This is of type T to match the type of the SyncQueue. [PreviousSyncArraychevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist-1) [NextSyncDictionarychevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncdictionary) Last updated 10 months ago --- # SyncHashset | PurrNet Synchronized HashSet, or most commonly referred as "SyncHashSet" are easily definable in your code, and will handle automatically align a list between all [players](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) . Working with SyncHashSet is as easy as using a regular HashSet, only having to be mindful of who has authority. SyncHashSet are built with the Network Module setup, meaning that you have to initialize it. Below is a usage example: Copy //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}"); } The HashSet is serialized in the Unity editor as a list, for easy debugging. You can't modify values here. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-1b22705872664710f53075c5eaf23e31a9904fde%252Fimage%2520%285%29%2520%281%29%2520%281%29.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=5d3c2208&sv=2) [PreviousSyncEventchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncevent) [NextSyncTimerchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synctimer) Last updated 1 year ago --- # SyncInput | PurrNet The SyncInput sync types is good for owner authorized input synchronization with server authorized gameplay. This might sounds advanced, but is actually very simple! The idea is: The server is responsible for all gameplay, and simply the necessary input is sent from client to server and from there the server will use it. This is easy enough to do by utilizing RPC's, but can become non-performant quite easily by utilizing too many unnecessary input calls and too much data utilized. So PurrNet luckily provides a super easy to use module for this! ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#why-utilize-input-synchronization) Why utilize Input Synchronization? There are really only a few good ways of going about player on player interactions in Multiplayer. 1. [Client Side Prediction](https://purrnet.gitbook.io/docs/client-side-prediction) - Difficult to work with and understand, but most responsive outcome 2. Input Synchronization - Easy to work with, but ping = input delay Essentially the big strength of Input Synchronization is the workflow. It's extremely easy to understand and get working, given that all the game logic just has to run on the server and be conveyed to the clients. That's the whole idea. This is utilized in games such as: * Gang Beasts * Totally Accurate Battle Simulator (TABS) Multiplayer * Human: Fall Flat * Stick Fight: The Game ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#the-type) The type `SyncInput` takes any unmanaged or IEquatable type, meaning that you can not only use simple types, but also custom structs or classes as long as they can be compared with the equatable setup. ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#simulate-host-ping-delay) Simulate host ping (delay) Other than just sending input from client to server, is also allows the case of host input to also have a simulated delay. The delay will cause the host to experience a similar experience to what clients do, in order to improve overall fairness. This can be easily adjusted in editor, or during creation of the SyncInput. This is defined in **ms** (ping): This can also easily be adjusted dynamically, if for example you'd want to match clients avg. ping. You can set it easily on the server/host by doing: ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#simple-usage-example) Simple usage example ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#movement-example) Movement example: This setup requires a Rigidbody, a collider and a network transform component with owner auth toggled off. This will give you players that can collider cleanly using physics! [PreviousSyncTimerchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synctimer) [NextSyncBigDatachevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/syncbigdata) Last updated 10 months ago * [Why utilize Input Synchronization?](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#why-utilize-input-synchronization) * [The type](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#the-type) * [Simulate host ping (delay)](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#simulate-host-ping-delay) * [Simple usage example](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#simple-usage-example) * [Movement example:](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncinput#movement-example) Copy private SyncInput _mySyncInput = new(defaultValue:false, hostPing:100f); Copy _mySyncInput.simulatedHostPing = 250f; Copy [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; } Copy [SerializeField] private Rigidbody _rigidbody; [SerializeField] private float _moveSpeed = 5f; [SerializeField] private float _jumpForce = 5f; [SerializeField] private SyncInput _moveInput = new(); [SerializeField] private SyncInput _jumpInput = new(); private bool _jump; private void Awake() { _jumpInput.onChanged += OnJump; _jumpInput.onSentData += OnSentData; } private void OnDestroy() { _jumpInput.onChanged -= OnJump; _jumpInput.onSentData -= OnSentData; } private void OnSentData() { _jump = false; } private void OnJump(bool newInput) { if(newInput) _rigidbody.AddForce(Vector3.up * _jumpForce, ForceMode.Impulse); } private void Update() { if (!isOwner) return; _moveInput.value = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")); if(!_jump) _jump = Input.GetKeyDown(KeyCode.Space); _jumpInput.value = _jump; } private void FixedUpdate() { if (!isServer) return; Vector3 move = new Vector3(_moveInput.value.x, 0, _moveInput.value.y).normalized; _rigidbody.AddForce(move * _moveSpeed); } --- # SyncList | PurrNet A Synchronized List, more commonly referred to as a "SyncList", is easily definable in your code. It will handle automatically aligning the contents of a list between all [players](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) . Working with SyncLists is as easy as using a regular list, you have only to be mindful of who has authority over it. SyncLists are built with the Network Module setup, meaning that you have to initialize it. Below is a usage example: Copy //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); } The SyncList is serialized in editor, however, you shouldn't edit it here. This is purely for visual debugging ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-32d994344ac858fa88628b8bfd1a65a40b1dcf35%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=2086b0e1&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist#synclistchange) SyncListChange When working with the SyncListChange which comes with the onChanged callback, you have some basic information available to you. The `.operation` tells you what the change is, that can be: Added, Removed, Insert, Set, Cleared. The `.value` is the new value which it has been changed. This is of type T to match the type of the SyncList. The `.index` tells you at what index this change is relevant. [PreviousValidated SyncVarchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/validated-syncvar) [NextSyncArraychevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist-1) Last updated 10 months ago --- # Validated SyncVar | PurrNet This is a more advanced variation of the [SyncVar](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncvar) which allows you to get the best of both world in terms of client sided responsiveness with server authorized changes. circle-exclamation This module is automatically owner-authenticated and bound to the **NetworkIdentity** it resides in. The idea of the validated SyncVar is quite easy: - Client makes local change and gets immediate response - Server is notified of this change and can validate whether the change should be validated - If the change is validated, it's sent to everyone - If the change is not validated, it's returned to the owner gets a callback This allows you to easily add cheat proofing to the Validated SyncVar whilst still keeping it fully responsive in fair/valid scenarios. Below is a simple example usage, which only allows the value to be counted up, but never down. Copy private ValidatedSyncVar _testVar = new(); private void OnEnable() { _testVar.serverValidation += ServerValidator; _testVar.onValidationFail += OnValidationFail; _testVar.onChangedWithOld += OnChanged; } private void OnDisable() { _testVar.serverValidation -= ServerValidator; _testVar.onValidationFail -= OnValidationFail; _testVar.onChangedWithOld -= OnChanged; } private bool ServerValidator(int oldValue, int newValue) { //Only the server will reach this. This is called whenever a change occurs. if (oldValue > newValue) return false; return true; } private void OnValidationFail(int failedValue, int authoritativeValue) { //Only the owner will receive this if the validation fails Debug.Log($"Validation failed on {failedValue}. Returning to {authoritativeValue}"); } private void OnChanged(int oldValue, int newValue, bool validated) { //This is called immediately with validated false for the owner, //and everyone received with validated = true if it goes through Debug.Log($"Value changed: {oldValue} -> {newValue} | Validated: {validated}"); } ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/validated-syncvar#behavior-and-expectations) Behavior & Expectations * Authority * Uses `parent.IsController(true)` for writes. * If `owner` is null, only server can write. * Module is owner-authed and bound to its `NetworkIdentity`. * Events * `onChanged(old, new, validated)` * `onChangedWithOld(old, new, validated)` * Owner emits twice on success: first with `validated=false` (local predict), then `validated=true` (server accept). * On reject, owner emits `validated=true` when reverting to authoritative, and fires `onValidationFail(failed, authoritative)`. * Non-owners receive only one event with `validated=true`. * [Host](https://purrnet.gitbook.io/docs/terminology/host) also emits predict then validated (consistent with clients). * Validation * `serverValidation` is multicast; all handlers must return true to accept. * Runs on server for every candidate change (including host). * IDs & Ordering * Each candidate has a `PackedUInt` packetId. * Client ignores `AcceptOwner/RejectOwner` with `packetId < _pendingId` (stale). * This keeps the display at the most recent local prediction until the matching latest ack arrives. * Common Scenarios * Owner set accepted: owner gets `(old→new, false)` then `(old→new, true)`; others get `(old→new, true)`. * Owner set rejected: owner gets `(old→candidate, false)` then `(candidate→authoritative, true)` + `onValidationFail(candidate, authoritative)`; others get nothing. * Rapid 1→2→3 with latency: owner shows 3 immediately; stale acks for 1/2 are ignored; only ack for 3 applies. * Server sets value (no prediction): everyone gets a single `(old→new, true)`. Owner does not get a `false` event in this path. * No owner: only server may set; client attempts are rejected. * Owner change: non-owner listeners subscribe to authoritative stream; pending owner state is cleared on change. * Late joiner: receives current authoritative via inner `SyncVar`; only a single `(old→new, true)` if different. [PreviousSyncVarchevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncvar) [NextSyncListchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist) Last updated 4 months ago --- # Roadmap | PurrNet * PurrLobby V2 * Network LOD * PurrDiction sample * Host migration * Client owned scenes * PurrVoice (WWise support) * Deterministic physics for prediction * Predicted Animator * Network Transform V2 * check Network visualization tooling * check Network Pipe logic (local transporting layer to components) * check PurrVoice (FMOD support) * check Easy network conversion tools (Components) * check Easy network conversion tools (Code) * check [Full game sample](https://purrnet.gitbook.io/docs/full-game-guides/incremental-game-sample) (w. source code) * check [Addressables](https://purrnet.gitbook.io/docs/systems-and-modules/addressables) support (scenes, objects, RPC's) * check [Async serialization](https://purrnet.gitbook.io/docs/systems-and-modules/bitpacker-serialization/async-serialization-packing) (async packing) * check Epic Games Transport (EOS) * check [Network Any Asset](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/network-assets) * check PurrNet Plug-n-play [**voice chat**](https://purrnet.gitbook.io/docs/tools/purrvoice-voice-chat) * check Plug-n-play prediction components * check Compression of prediction * check Modularized delta compression * check [Code-stripping](https://purrnet.gitbook.io/docs/systems-and-modules/code-stripping) * check [Network Profiler tool](https://purrnet.gitbook.io/docs/systems-and-modules/bandwidth-profiler) * check Improved logging * check Lag compensation component ([collider rollback](https://purrnet.gitbook.io/docs/systems-and-modules/collider-rollback) ) * check [Predicted state machine](https://purrnet.gitbook.io/docs/client-side-prediction/predicted-state-machine) * check [Client Side Prediction](https://purrnet.gitbook.io/docs/client-side-prediction) * check Delta serialization * check Easy packet compression * check [Authentication](https://purrnet.gitbook.io/docs/systems-and-modules/network-manager/authentication) * check Plug n' play [lobby setup](https://purrnet.gitbook.io/docs/addons/lobby-system) * check [Free relay](https://purrnet.gitbook.io/docs/systems-and-modules/transports/purr-transport) for easy development testing * check Auto network [object pooling](https://purrnet.gitbook.io/docs/systems-and-modules/network-identity/pooling) * check Nesting network modules * check [Steamworks Transport](https://purrnet.gitbook.io/docs/systems-and-modules/transports/steam-transport) * check Auto [networked state machine](https://purrnet.gitbook.io/docs/plug-n-play-components/state-machine-auto-networked) * check [Auto Sync Component](https://purrnet.gitbook.io/docs/plug-n-play-components/network-reflection-auto-sync) * check [Returnable/Awaitable RPCs](https://purrnet.gitbook.io/docs/systems-and-modules/remote-procedure-call-rpc/awaitable-rpc) * check [SyncList](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist) & [SyncDictionary](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncdictionary) & [SyncHashset](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synchashset) * check [SyncTimer](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synctimer) * check [SyncEvent](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncevent) (Auto network invoked events) * check Auto serialization * check Awaitable Coroutines [PreviousAddon librarychevron-left](https://purrnet.gitbook.io/docs/readme/addon-library) [NextPerformancechevron-right](https://purrnet.gitbook.io/docs/readme/performance) Last updated 10 days ago --- # SyncDictionary | PurrNet Synchronized Dictionary, or most commonly referred as "SyncDictionary" are easily definable in your code, and will handle automatically align a dictionary between all [players](https://purrnet.gitbook.io/docs/terminology/playerid-client-connection) . In theory, you could just convert a dictionary to a SyncDictionary, and it should work. Working with it is the exact same with adding, setting, removing or clearing. SyncDictionaries are built with the Network Module setup, meaning that you have to initialize it. Below is a usage example: Copy //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); } The SyncDictionary is custom serialized in editor, in order to make visual debugging easier. ![](https://purrnet.gitbook.io/docs/~gitbook/image?url=https%3A%2F%2F3317286851-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FsgxJN58uttGJCEXbBIwp%252Fuploads%252Fgit-blob-5f89968cac9ca97a9e1bff2c05bac9f5f8714f5a%252Fimage.png%3Falt%3Dmedia&width=768&dpr=3&quality=100&sign=537e4d6c&sv=2) ### [hashtag](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncdictionary#syncdictionarychange) SyncDictionaryChange When working with the SyncDictionaryChange which comes with the onChanged callback, you have some basic information available to you. The `.operation` tells you what the change is, that can be: Set, Cleared, Resized. The `.key` tells you at what key the change has occurred. This is the type of TKey to match the type of the SyncDictionary The `.value` is the new value which it has been changed. This is of type TValue to match the type of the SyncDictionary. [PreviousSyncQueuechevron-left](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/synclist-1-1) [NextSyncEventchevron-right](https://purrnet.gitbook.io/docs/systems-and-modules/network-modules/sync-types/syncevent) Last updated 10 months ago ---