Creating Fika-Compatible Mods
Updated for 4.0
Fika Events
Fika has a lot of events that you can subscribe to, which makes it easier to run code at certain key moments of the raid. To subscribe to an event, use:
/// <summary>
/// Subscribes a callback to a specific type of Fika event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to subscribe to.</typeparam>
/// <param name="callback">The callback to invoke when the event is dispatched.</param>
public static void SubscribeEvent<TEvent>(Action<TEvent> callback) where TEvent : FikaEvent
To unsubscribe, use:
/// <summary>
/// Unsubscribes a callback from a specific type of Fika event.
/// </summary>
/// <typeparam name="TEvent">The type of the event to unsubscribe from.</typeparam>
/// <param name="callback">The callback to remove from the event subscription.</param>
public static void UnsubscribeEvent<TEvent>(Action<TEvent> callback) where TEvent : FikaEvent
The event triggered will usually pass an important object related to the event, e.g. FikaNetworkManagerCreatedEvent
passes a IFikaNetworkManager
(named Manager
in the object). This object can then be accessed if needed.
You can read the source code here to find all events.
Registering Packets
To register packets, subscribe to the FikaNetworkManagerCreatedEvent
and access the IFikaNetworkManager
. In the manager you can call either of these methods:
/// <summary>
/// Registers a packet to the <see cref="NetPacketProcessor"/>.
/// </summary>
/// <typeparam name="T">The packet type.</typeparam>
/// <param name="handle">The <see cref="Action"/> to run when receiving the packet.</param>
void RegisterPacket<T>(Action<T> handle) where T : INetSerializable, new();
/// <summary>
/// Registers a packet to the <see cref="NetPacketProcessor"/> with user data.
/// </summary>
/// <typeparam name="T">The packet type.</typeparam>
/// <typeparam name="TUserData">The user data type.</typeparam>
/// <param name="handle">The <see cref="Action"/> to run when receiving the packet.</param>
void RegisterPacket<T, TUserData>(Action<T, TUserData> handle) where T : INetSerializable, new();
The INetSerializable
needs to be a packet that you have created, and these methods are invoked when that packet is received. The second method also passes the NetPeer
, which is useful on the FikaServer
. You handle the logic however you want when receiving the packet with these methods.
Failing to register a packet will result in endless exceptions
being thrown. Please register your packets correctly!
Creating a Packet
To create a packet, implement the INetSerializable
interface into a new class
. For packets that are sent often, I highly recommend using a struct
. Add the data that you need in the form of Field
and make all of them Public
. Use the Serialize()
and Deserialize()
methods to write/read data. You can find an example here, which also includes how to write an enum
. There are also a lot of extensions to write EFT/Unity specific data (e.g. Vector3
) that you can find here.
Do not instantiate and send new collections in packets that are sent often, e.g. List<T>
or T[]
. The allocations will become expensive. Fika has an interface IReusable
that you can use to reuse a single instance of a packet, and only mutate the collections that exist in it.
/// <summary>
/// Registers a reusable packet to the <see cref="NetPacketProcessor"/> with user data. Reusable uses the same instance throughout the lifetime of the <see cref="NetManager"/>.
/// Custom types must be registered with <see cref="RegisterCustomType{T}(Action{NetDataWriter, T}, Func{NetDataReader, T})"/> first.
/// </summary>
/// <typeparam name="T">The packet type.</typeparam>
/// <typeparam name="TUserData">The user data type.</typeparam>
/// <param name="handle">The <see cref="Action"/> to run when receiving the packet.</param>
void RegisterReusable<T, TUserData>(Action<T, TUserData> handle) where T : class, IReusable, new();
An example of these packets can be found here. Create the class somewhere, keep track of it and reuse it. You can find an example of that in the FikaClientWorld.
Sending a Packet
To send a packet, you need a IFikaNetworkManager
. This is either a FikaServer
or FikaClient
that you can access with the Comfort.Common
namespace using Singleton<IFikaNetworkManager>.Instance
. To determine whether you are a server or client, use FikaBackendUtils.IsServer
.
Use this method to send packets:
/// <summary>
/// Sends a packet.
/// </summary>
/// <typeparam name="T">The type of packet to send, which must implement <see cref="INetSerializable"/>.</typeparam>
/// <param name="packet">The packet instance to send, passed by reference.</param>
/// <param name="deliveryMethod">The delivery method (reliable, unreliable, etc.) to use for sending the packet.</param>
/// <param name="broadcast">If <see langword="true"/>, the packet will be sent to multiple recipients; otherwise, it will be sent to a single target (server is always broadcast).</param>
void SendData<T>(ref T packet, DeliveryMethod deliveryMethod, bool broadcast = false) where T : INetSerializable;
The broadcast
argument determines whether it will be sent to all other clients. As the server, this is always true
.
If you want to send to just one specific NetPeer
, e.g. after receiving a packet and you want to respond to that peer:
/// <summary>
/// Sends a packet of data directly to a specific peer.
/// </summary>
/// <typeparam name="T">The type of packet to send, which must implement <see cref="INetSerializable"/>.</typeparam>
/// <param name="packet">The packet instance to send, passed by reference.</param>
/// <param name="deliveryMethod">The delivery method (reliable, unreliable, etc.) to use for sending the packet.</param>
/// <param name="peer">The target <see cref="NetPeer"/> that will receive the packet.</param>
/// <remarks>
/// Should only be used as a <see cref="FikaServer"/>, since a <see cref="FikaClient"/> only has one <see cref="NetPeer"/>.
/// </remarks>
void SendDataToPeer<T>(ref T packet, DeliveryMethod deliveryMethod, NetPeer peer) where T : INetSerializable;
A client only has one NetPeer
and it is always the server! A client is never aware of other clients.
Some specific functions are class specific, and cannot be called from the interface singleton. You can access the specific FikaServer
or FikaClient
using e.g. Singleton<FikaServer>.Instance
.
The specific methods are:
Client
/// <summary>
/// Sends a reusable packet
/// </summary>
/// <typeparam name="T">The <see cref="IReusable"/> to send</typeparam>
/// <param name="packet">The <see cref="INetSerializable"/> to send</param>
/// <param name="deliveryMethod">The deliverymethod</param>
/// <remarks>
/// Reusable will always be of type broadcast when sent from a client
/// </remarks>
public void SendReusable<T>(T packet, DeliveryMethod deliveryMethod) where T : class, IReusable, new()
Server
public void SendReusableToAll<T>(T packet, DeliveryMethod deliveryMethod, NetPeer peerToExlude = null) where T : class, IReusable, new()
General Information About Data
Calculating Packet Size (UDP with Headers)
When sending data over a network using UDP, each packet consists of:
Your payload (the actual data, e.g., floats)
Packet-specific overhead (1–4 bytes depending on the type, e.g.
Unreliable
orReliableOrdered
)UDP header (8 bytes)
IP header (20–60 bytes, depending on IPv4 options)
It’s important to account for all headers, not just the payload, because small payloads can become inefficient due to header overhead.
Formula
Let:
N = number of elements being sent
Selement = size of one element in bytes (e.g., 4 bytes for a
float
)Hpacket = packet-specific overhead (1–4 bytes)
HUDPH = UDP header size (8 bytes)
HIPH = IP header size (20–60 bytes)
Then, the total packet size in bytes is:
To convert to bits:
Example
Suppose you are sending a Vector3
, which is 3 floats (4 bytes each), with 2 bytes of packet-specific overhead, 8 bytes UDP header, and 20 bytes IP header:
Even this small payload, if sent frequently (e.g., every frame in a game), can consume significant bandwidth. Notice that even though the payload is only 12 bytes, headers increase the total packet size almost 4×. Now imagine this being unthrottled, and the client is running at 120 FPS:
We already have:
If we send 120 packets per second:
Step-by-step
Packet size is 42 bytes.
Sending 120 packets per second.
Multiply packet size by number of packets: 42 × 120.
Break it down:
Convert to bits:
This is a lot of wasted bandwidth and CPU usage. Unless it's critical, do not send data every tick. Rather, interpolate values on the receiving end if needed. Breaking it down further:
That is 300KB per minute for one, single Vector3
. That is almost ¼ of the bandwidth that Fika sends for all bots states every minute.
The size of one entire player state (52 bytes), 20/s:
Assuming we have 20 bots:
Now per minute:
Interpolation
As you can see from the breakdown, this is a lot of wasted data that could be throttled and sent less frequently, and potentially interpolated instead by lerping the values and sending the time when sending and comparing with the time when received.
Interpolation factor t:
Clamp t between 0 and 1:
Linear interpolation formula:
You can then send the current time (Time.unscaledTime
) and compare it with current time when received, and smooth out differences using the equations above.
Now comparing the different methods of sending
20 messages/sec at 16 bytes:
120 messages/sec at 12 bytes:
That is ~1080bytes saved per second:
Summary
By combining time synchronization with lerp-based smoothing, we can create fluid, latency-tolerant motion without spamming the network. Instead of sending full position updates every frame, we send fewer messages that include a timestamp and interpolate locally:
This allows clients to smoothly reconstruct movement based on timing differences rather than raw frequency.
For example, reducing from 120 messages/sec at 12 bytes to 20 messages/sec at 16 bytes saves:
While 1,12 KB/sec per stream might seem small, it scales quickly — each actor can send multiple data streams (position, rotation, animation state, etc.), and with many actors, the savings multiply dramatically.
Optimizing send frequency and payload size is one of the most effective ways to achieve smooth, efficient, and scalable networked movement.
Tips and useful classes
FikaBackendUtils has tons of useful methods/properties/fields that can be used.
FikaGlobals has some helper methods that can be useful during development.
CoopHandler has useful properties and methods, mainly to track players (especially human). It can be accessed on the
Singleton<IFikaNetworkManager>.Instance
or CoopHandler.TryGetCoopHandler() depending on your code style preference.FikaSerializationExtensions have tons of good extension methods to handle data, e.g. packing a
float
. If precision is not important to the last decimal, it's recommended to pack your primitives.
Last updated