PurrNet
  • 🐈Introduction
    • ‼️Unique to PurrNet
    • 💲Pricing
    • 💻Compatibility
    • 📚Addon library
    • 🗺️Roadmap
    • 🏎️Performance
      • RPC Benchmarks
      • Network Transform benchmarks
    • 🥰Support PurrNet
  • 🏁Getting Started
    • Installation
    • Minimal Setup
    • Converting to PurrNet
      • Converting from Mirror
      • Converting from FishNet
  • 📚Simple Guides
    • Easy Multiplayer Physics (input Sync)
  • 🖥️Systems and modules
    • Network Manager
      • Network Rules
      • Network Prefabs
      • Network Visibility
        • Distance condition
      • Authentication
    • Transports
      • Composite Transport
      • UDP Transport
      • Web Transport
      • Local Transport
      • Steam Transport
      • Purr Transport
      • Simulating latency
    • Network Identity
      • Ownership
      • Spawning & Despawning
      • NetworkBehaviour
      • Don't destroy on load
    • Network Modules
      • Common Pitfalls
      • Sync Types
        • SyncVar
        • SyncList
        • SyncArray
        • SyncQueue
        • SyncDictionary
        • SyncEvent
        • SyncHashset
        • SyncTimer
        • SyncInput
    • Remote Procedure Calls
      • Generic RPC
      • Static RPC
      • Awaitable RPC
      • Direct Local Execution of RPCs
    • Broadcasts
    • Scene Management
    • Client Side Prediction
      • Overview
      • Predicted Identities
      • Predicted Hierarchy
      • Best Practices
      • Input Handling
      • State Handling
    • Instance Handler
    • Collider Rollback
    • BitPacker
      • Networking custom classes, structs & types
  • Plug n' play components
    • Network Transform
    • Network Animator
    • Network Ownership Toggle
    • Network Reflection
    • Network State Machine
  • 🎮Full game guides
    • 🔫First Person Shooter
    • 🪓Survival Game
  • ✊Community Guides
    • Chat system with broadcasts
  • 🧩Addons
    • Lobby System
  • 🤓Terminology
    • Early Access
    • Channels
    • Client Auth/Everyone (Unsafe)
    • Host
    • Server Auth (Safe)
    • PlayerID
  • 💡Integrations
    • Dissonance
    • Cozy Weather
Powered by GitBook
On this page
  • Why utilize Input Synchronization?
  • The type
  • Simulate host ping (delay)
  • Simple usage example
  • Movement example:
  1. Systems and modules
  2. Network Modules
  3. Sync Types

SyncInput

PreviousSyncTimerNextRemote Procedure Calls

Last updated 5 days ago

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!

Why utilize Input Synchronization?

There are really only a few good ways of going about player on player interactions in Multiplayer. 1. - 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

The type

SyncInput<T> 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.

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):

private SyncInput<bool> _mySyncInput = new(defaultValue:false, hostPing:100f);

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:

_mySyncInput.simulatedHostPing = 250f;

Simple usage example

[SerializeField] private SyncInput<Vector2> _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;
}

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!

[SerializeField] private Rigidbody _rigidbody;
[SerializeField] private float _moveSpeed = 5f;
[SerializeField] private float _jumpForce = 5f;

[SerializeField] private SyncInput<Vector2> _moveInput = new();
[SerializeField] private SyncInput<bool> _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);
}
🖥️
Client Side Prediction