Simple is an extension library for PUN2, which adds timing mechanisms, compression, syncvars and commonly needed component systems.
The core PUN2 library primarily focuses on replication of fields and events from one client,
without enforcing any kind of timing nor deterministic order in their capture and synchronization.
Simple in contrast uses its own update manager singletons (
to produce master timing segments for networking.
Simple also includes a bitpacking library with tools tuned for compressing floats, vector3 and quaternions.
Simple is a separate download on GitHub.
- Libary Highlights
- Base set of 'Just Works' Components and Interfaces
- Simulation-based tick system with segmented deferred timings
- Numbered State buffers ([Circular Buffer](https://en.wikipedia.org/wiki/Circular_buffer))
- Compression Libraries
- Serialization writes to a unified bitpacked byte array
- Syncvars tied to the core simulation tick timing system
- Keyframe based eventual consistency
- Getting Started
Base set of 'Just Works' Components and Interfaces
The first and most obvious benefit of the new extension library is the quantity of code that exists in the form of inter-operational components. Without writing a single line of code you can have a networked scene with synced:
- Hitscans & Projectiles
- Health/Vitals and automation of UI
- State (Attached, Visible, Thrown/Dropped, etc.)
- Visibility / Rigidbody changes in response to State changes
- Mounting to other objects
- Damage/Healing/Buff/Debuff zones
- Inventory and Inventory Items
- Health / Energy Pickups
These components can be derived and extended, or the base interfaces can be used to create components from scratch that interact with these systems.
Simulation-based tick system with segmented deferred timings
The NetMaster singleton acts as a central Update Manager, so the entire system is polled every tick
to produce a series of tick-based callbacks that very tightly control execution
of PreSimulation, PostSimulaton, State Capture, Serialization, Deserialization, State Application,
Interpolation and Extrapolation code.
This eliminates race conditions and keeps all objects tightly synchronized to themselves and other objects with the same owner. As such, many things become largely deterministic in nature. For example, when a player triggers a hitscan, the shot is replicated on other clients by applying states in the same order, reproducing the hitscan as it happened on the owner.
The Simple component set is tuned for games that simulate/move/animate using timings based around FixedUpdate(). It is designed to work in simulation-free or Update() based simulations as well, but those are generally less than ideal for networking and are prone to micro-jitter. It is recommended to use the NetMaster timing segments to better ensure order:
- Perform all game logic in Fixed - OnPreSimulate and OnPostSimulate.
- Perform all interpolation/extrapolation in Update - OnPreUpdate, OnPostUpate, OnPreLateUpdate & OnPostLateUpdate
Numbered State buffers (Circular Buffer)
The core FixedUpdate based timing system captures the state of owned objects on a regular timing. These are stored in a circular buffer on the owner and are serialized to all clients, where they are then deserialized into a local circular buffer. These numbered states allow for very smooth interpolation/extrapolation of states even with packet loss and maintain synchronization between all objects that share an owner.
Networking consists primarily of transferring these state buffers from the owner to other clients (as opposed to directly replicating field values from one client to others).
Simple includes several compression libraries that are used in serialization
These tools allow for writing/reading values directly to/from a byte or any primitive integer type.
There are also specific compressors tunes specificially for compressing floats, Vector2, Vector3 and Quaternion types.
Serialization writes to a unified bitpacked byte array
This allows for extremely high levels of data compression, as well as allowing components to employ in-line serialization logic. Writes start with a single timing from the NetMaster immediately after each Simulation (PostPhysX), and a single byte array is passed to all of the state producing NetObjects, which in turn pass it to every child SyncObject and PackObject (Synvars) component for serialization in a deterministic order. This produces one highly compressed and tightly ordered byte, rather than having scattered objects calling Write() methods ad hoc, and without boxing/unboxing.
Example of bitbacking :
PhotonAnimatorView (PUN2) vs
|Uncompressed (PUN2)||Bitpacked (Simple)|
|Normalized Floats||40 bits||2-16 bits|
|Other Floats||40 bits||2-16 bits|
|State/Trigger Hashes||40 bits||1-8 bits|
|Bools/Triggers||16 bits||1 bit|
Syncvars tied to the core simulation tick timing system
PackObjects allow for fields to be tagged with attributes that will automatically generate code that syncs values from Owner to All on the same deferred timings as NetObjects/SyncObjects. This means you can simply synchronize fields rather than having to write your own SyncObjects. Syncvars have built-in compression options, and advanced users can write their own compression methods as well.
Keyframe based eventual consistency
The Simple library primarily uses Unreliable UDP with Keyframe and Delta frames, similar to how video works. Rather than using Acks to negotiate eventual consistency between clients and a server (which would become very cumbersome in a relay environment where every client would need to negotiate reliability with every other client). Lost or late packets that produce disagreement in state between the owner and other clients will eventually resolve when forced updates (keyframes or changes) arrive. Many of the built in components have a keyframe rate value you can set. This determines the frequency of keyframes. For rarely changed items this can produce undesirable amounts of extra traffic, so there is the option of setting the keyframe rate to 0, which indicates changes should be sent are Reliable.
Simple is a separate download on GitHub.
Before trying to use this library, it is recommended that you first run the tutorial to get familiar with the components and work flow of Simple.
This library contains replacements for PhotonTransformView and PhotonAnimatorView as well as a collection of drop in components that handle common objects typical to networking, such as syncing vitals, items, states, etc.
Typical starting usage is to install the Simple library and:
- Replace uses of
- Replace uses of
Then make use of the Assists to convert/add objects, and explore the components available in Simple. Be sure to do the Getting Started Tutorial before trying to use this library.
To reference the library in your own code, use the include:
Assists are the menu items that automate the tasks of creating/converting to networked objects and they are used almost entirely for this demo. They are basically one-click wizards, as they have no interface. They will attempt to perform a task on the gameobject that is selected in Unity or will create a new scene object if applicable.
Many components in the Simple library have a custom header that may contain an Instructions foldout and/or may have a link to the documentation (seen as [?] icon). The goal is to make most of the base components just work well enough by default that heavy reading of this documentation isn't needed. However, some of the concepts and API components will require some reading due to the complexity of the systems involved.