Fish-Net: Networking Evolved
  • Overview
    • What is FishNet?
      • Features
        • Unity Compatibility
        • Performance
          • Benchmark Setup
          • Fish-Networking Vs Mirror
      • Pro, Projects, and Support
      • Business Support
      • Development
        • Changelog
        • Roadmap
      • Branding
      • Legal Restrictions
    • Showcase
      • Upcoming Releases
    • Asset Integrations
      • Fish-Network-Discovery
    • Community Resources
  • Guides
    • Getting Started
      • Installing Fish-Networking
      • Getting Connected
      • Preparing Your Player
      • Moving Your Player Around
      • Spawning and Despawning Items
      • Using SyncVars to Sync Colors
      • Connecting to Remote Devices
      • Beyond the Basics
    • High-Level Overview
      • Fundamentals
      • Networking Models
      • Terminology
        • Server, Client, Host
        • Communicating
        • Miscellaneous
      • Transports
    • Features
      • Server and Client Identification
        • Executing on Server or Client
        • NetworkConnections
      • Networked GameObjects and Scripts
        • NetworkObjects
        • NetworkBehaviour
        • Spawning and Despawning
          • Predicted Spawning
          • Nested NetworkObjects
          • Object Pooling
      • Network State Events
      • Network Communication
        • Remote Procedure Calls
        • SyncTypes
          • Customizing Behavior
          • SyncVar
          • SyncList
          • SyncHashSet
          • SyncDictionary
          • SyncTimer
          • SyncStopwatch
          • Custom SyncType
        • Broadcasts
      • Data Serialization
        • Custom Serializers
          • Interface Serializers
          • Inheritance Serializers
      • Ownership
        • Using Ownership To Read Values
      • Area of Interest (Observer System)
        • Modifying Conditions
        • Custom Conditions
      • Scene Management
        • Scene Events
        • Scene Data
          • SceneLookupData
          • SceneLoadData
          • SceneUnloadData
        • Loading Scenes
          • Automatic Online and Offline Scenes
        • Unloading Scenes
        • Scene Stacking
        • Scene Caching
        • Scene Visibility
        • Persisting NetworkObjects
        • Custom Scene Processors
          • Addressables
      • InstanceFinder
      • Addressables
      • Transports
        • Multipass
      • Prediction
        • What Is Client-Side Prediction
        • Configuring PredictionManager
        • Configuring TimeManager
        • Configuring NetworkObject
        • Offline Rigidbodies
        • Interpolations
        • Creating Code
          • Controlling An Object
          • Non-Controlled Object
          • Understanding ReplicateState
            • Using States In Code
            • Predicting States In Code
          • Advanced Controls
        • Custom Comparers
        • PredictionRigidbody
        • Using NetworkColliders
      • Lag Compensation
        • States
        • Raycast
        • Projectiles
    • Upgrading API
    • Server Hosting
      • Edgegap - Official Partner
        • Getting Started with Edgegap
      • Hathora
        • Getting Started with Hathora
      • Amazon Web Services (AWS)
        • Getting Started with AWS
    • Upgrading To Fish-Networking
    • Troubleshooting
      • Technical Limitations
      • Creating Bug Reports
        • Report Example
      • FAQ
  • FishNet Building Blocks
    • Components
      • Managers
        • NetworkManager
        • TimeManager
        • PredictionManager
        • ServerManager
        • ClientManager
        • SceneManager
        • TransportManager
          • IntermediateLayer
        • StatisticsManager
        • ObserverManager
          • HashGrid
        • RollbackManager (Pro Feature)
      • Prediction
        • Network Collider
          • NetworkCollision
          • NetworkCollision2D
          • NetworkTrigger
          • NetworkTrigger2D
        • OfflineRigidbody
        • PredictedOwner
        • PredictedSpawn
      • Utilities
        • PingDisplay
        • BandwidthDisplay
        • Tick Smoothers
          • NetworkTickSmoother
          • OfflineTickSmoother
          • MonoTickSmoother [Obsolete]
          • DetachableNetworkTickSmoother [Obsolete]
      • PlayerSpawner
      • DefaultScene
      • ServerSpawner
      • Authenticator
      • ColliderRollback
      • NetworkAnimator
      • NetworkBehaviour
      • NetworkTransform
      • NetworkObject
      • NetworkObserver
    • Prefabs
      • NetworkManager
      • NetworkHudCanvas
    • ScriptableObjects
      • ObserverConditions
        • DistanceCondition
        • GridCondition
        • HostOnlyCondition
        • MatchCondition
        • OwnerOnlyCondition
        • SceneCondition
      • SpawnablePrefabs
        • DefaultPrefabObjects
        • SinglePrefabObjects
        • DualPrefabObjects
      • LevelLoggingConfiguration
    • Transports
      • Tugboat
      • Multipass
      • Yak (Pro Feature)
      • Bayou
      • FishyWebRTC
      • FishyUnityTransport
      • FishySteamworks (Steam)
      • FishyEOS (Epic Online Services)
      • FishyFacepunch (Steam)
      • FishyRealtime (Photon)
  • API Documentation
    • API Reference
Powered by GitBook
On this page
  • Class Example
  • Creating The Writer
  1. Guides
  2. Features
  3. Data Serialization
  4. Custom Serializers

Inheritance Serializers

PreviousInterface SerializersNextOwnership

Last updated 1 day ago

Another frequently asked question is how to handle serialization for classes which are inherited by multiple other classes. These are often used to allow to use the base class as a parameter, while permitting other inheriting types to be used as arguments. This approach is similar to

Class Example

Here is an example of a class you want to serialize, and two other types which inherit it.

public class Item : ItemBase
{
    public string ItemName;
}

public class Weapon : Item
{
    public int Damage;
}

public class Currency : Item
{
    public byte StackSize;
}

/* This is a wrapper to prevent endless loops in
* your serializer. Why this is used is explained
* further down. */
public abstract class ItemBase {}

Using an RPC which can take all of the types above might look something like this.

public void DoThing()
{
    Weapon wp = new Weapon()
    {
        Itemname = "Dagger",
        Damage = 50,
    };
    ObsSendItem(wp);
}

[ObserversRpc]
private void ObsSendItem(ItemBase ib)
{
    /* You could check for other types or just convert it without checks
    *  if you know it will be Weapon.
    *  EG: Weapon wp = (Weapon)ib; */
    if (ib is Weapon wp)
        Debug.Log($"Recv: Item name {wp.ItemName}, damage value {wp.Damage}.");
}

Creating The Writer

Since you are accepting ItemBase through your RPC you must handle the different possibilities of what is being sent. Below is a serializer which does just that.

When using this approach it is very important that you check for the child-most types first.

For example: Weapon is before Item, and so is Currency, so those two are checked first. Just as if you had Melee : Weapon, then Melee would be before Weapon, and so on.

public static void WriteItembase(this Writer writer, ItemBase ib)
{
    if (ib is Weapon wp)
    {
        // 1 will be the identifer for the reader that this is Weapon.
        writer.WriteByte(1); 
        writer.Write(wp);
    }
    else if (ib is Currency cc)
    {
        writer.WriteByte(2)
        writer.Write(cc);
    }
    else if (ib is Item it)
    {
        writer.WriteByte(3)
        writer.Write(it);
    }
}

public static ItemBase ReadItembase(this Reader reader)
{
    byte clsType = reader.ReadByte();
    /* These are still in order like the write method, for
    *  readability, but since we are using a clsType indicator
    *  the type is known so we can just compare against the clsType. */
    if (clsType == 1)
        return reader.Read<Weapon>();
    else if (clsType == 2)
        return reader.Read<Currency>();
    else if (clsType == 1)
        return reader.Read<Item>();
    // Unhandled, this would probably result in read errors.
    else
        return null;
}

You can still create custom serializers for individual classes in addition to encapsulating ones as shown! If for example you had a custom serializer for Currency then using the code above would use your serializer for Currency rather than the one Fish-Networking generates.

Finally, disclosing why we made the ItemBase class. The sole purpose of ItemBase is to prevent an endless loop in the reader. Imagine if we were able to return only Item, and we were also using that as our base. Your reader might look like this...

public static Item ReadItem(this Reader reader)
{
    byte clsType = reader.ReadByte();
    /* These are still in order like the write method, for
    *  readability, but since we are using a clsType indicator
    *  the type is known so we can just compare against the clsType. */
    if (clsType == 1)
        return reader.Read<Weapon>();
    else if (clsType == 2)
        return reader.Read<Currency>();
    else if (clsType == 1)
        return reader.Read<Item>();
    // Unhandled, this would probably result in read errors.
    else
        return null;
}

The line return reader.Read<Item>(); is the problem. By calling read on the same type as the serializer you would in result call the ReadItem method again, and then the line return reader.Read<Item>(); and then ReadItem again, and then, well you get the idea.

Having a base class, in our case ItemBase, which cannot be returned ensures no endless loop.

RPCs
Interface serializers.