Skip to main content

Fusion Custom Interest Management

Summary

Due to default AOI's limited functionality and documentation, a suggestion to use custom interest management comes to mind.

Thus instead of using Default AOI for Network Culling, it is possible to implement a custom interest management that employs a similar feature to default AOI.

Implementation

Note please do not forget to setup Network Project Config [to be able support Interest Management](Fusion Interest Management 7a31122202bb44e8a743ca3c1374b297.md).

Custom Interest Management (CIM) works around function below :

Runner.SetPlayerAlwaysInterested(PlayerRef refs, NetworkObject Object, bool interest);

Above functions allow a player to set ‘interest’ to a certain NetworkObject, this behavior enables the player client to receive an update from that NetworkObject upon interest set to true.

Combine that with simple OnTriggerEnter and OnTriggerExit, an Custom AOI is halfway to completion.

As for the other half, actual behavior and simple NetworkBehaviour class to storage data should do, the behavior itself must be something like this :

  1. Each player has simple sphere collider with IsTrigger set to true.

  2. Each player that enter trigger will fire function that notify its presence.

  3. Similar to its counterpart, when player leave, it also fire function that notify it exiting trigger.

  4. Set a Synced NetworkBool that determine if is enter (Not Culled) or exit (Culled) render radius. note this variable should exist on different NetworkObject that always received update from server.

    public class PlayerData : NetworkBehaviour
        {
            [Networked] public string PlayerName { get; set; }
            [Networked] public string ClipName { get; set; }
            [Networked] public NetworkBool Culled { get; set; }
    
    				//rest of code....
    
    		}
    
  5. When ‘Not Culled’, set player to interest to NetworkObject that enter trigger, and set to lost interest when ‘Culled’.

    private void SetCulled(Collider obj, bool culled)
            {
                //simple statement to exclude own character controller to be detected.
                if (obj == _ncc.Controller) return;
                if (LauncherExtension.IsServer)
                    if (Object.InputAuthority)
                        if (obj.TryGetComponent(out ObjectCuller _culler))
                        {
                            LauncherExtension.Launcher.GetPlayer(_culler.Object.InputAuthority).Culled = culled;
                            Runner.SetPlayerAlwaysInterested(Object.InputAuthority, _culler.Object, !culled);
                        }
            }
    
    private void OnRenderExit(Collider obj) => SetCulled(obj, true);
    
    private void OnRenderEnter(Collider obj) => SetCulled(obj, false);
    
  6. All steps above should happen on the server side.

At this point, custom interest management is done, what's left is to set the client to render if NetworkObject is within the render radius.

In order client to recognize whether some player is culled or not, there is should exist a simple variable to indicate this, that where Step 4 from above come for. below how its done :

  1. Create a simple function that disable or enable collider/renderer at bool value.

    private void SetCulledState(NetworkBool cull)
            {
                //prevent executing when value is same from last value.
                if (_isCulled == cull) return;
                _isCulled = cull;
    
                for (int i = 0; i < _renderers.Length; i++)
                {
                    _renderers[i].enabled = !cull;
                }
    
                //prevent server from disabling collider when host mode.
                if (Runner.Mode == SimulationModes.Host)
                    if (LauncherExtension.IsServer) return;
    
                for (int i = 0; i < _colliders.Length; i++)
                {
                    _colliders[i].enabled = !cull;
                }
            }
    
  2. Set that function value relative to synced NetworkBool.

  3. Put that function inside of Render function (derived from NetworkBehaviour), and add simple if statement that only Proxy (Object without Input Authority to control or State Authority, in this case its player character in another player client) will execute.

    public override void Render()
            {
                //handle for auto-host / host mode.
                if (Runner.GameMode == GameMode.AutoHostOrClient || Runner.GameMode == GameMode.Host)
                {
                    if (Runner.Mode == SimulationModes.Host)
                    {
                        if (LauncherExtension.IsServer)
                        {
                            if (!Object.HasInputAuthority)
                            {
                                if (LauncherExtension.Launcher.GetPlayer(Object.InputAuthority) == null) return;
                                SetCulledState(LauncherExtension.Launcher.GetPlayer(Object.InputAuthority).Culled);
                            }
                        }
                    }
                }
    
    						//make that only client in other client environment run the process.
                //handle for client-server mode. 
                if (Object.IsProxy)
                {
                    if (LauncherExtension.Launcher.GetPlayer(Object.InputAuthority) == null) return;
                    SetCulledState(LauncherExtension.Launcher.GetPlayer(Object.InputAuthority).Culled);
                }
            }
    

Conclusion

Below are quick demo of this Custom Interest Management :

  • Host Mode

    https://youtu.be/YT2bJv-J9wo

    Since the host will always receive client updates no matter what, thus removing the host ‘interest’ from clients is almost guaranteed to break things, hence host should only stop rendering another client when outside of the render radius.

  • Client-Server Mode

    https://youtu.be/_4DjERbd_PA

    This Custom Interest Management implementation should work best in a Client-Server environment.

  • Behind the scene of Client-Server Mode

    https://youtu.be/fMj2oVqkCNo

    What happens in this video, the smallest scene is the server, while the other two are clients. when client 1 exits client 2 render radius or vice versa, notice that client 1 object on client 2 sides did not update its position. that because the server removes client 2 ‘interest’ from client 1 object. things will return normal once both objects are within each other render radius.

For more information about Interest Management, kindly check this page.

For more detailed implementation, visit repository page.

Note that this implementation is one of many implementations, one should aim to create better and more efficient from this.