PUN Classic (v1)、PUN 2 和 Bolt 處於維護模式。 PUN 2 將支援 Unity 2019 至 2022,但不會添加新功能。 當然,您所有的 PUN & Bolt 專案可以用已知性能繼續運行使用。 對於任何即將開始或新的專案:請切換到 Photon Fusion 或 Quantum。

BoltEntity

The BoltEntity is a Unity GameObject that will be represented on the network by Photon Bolt. In order to mark a GameObject as networked, you need to use the BoltEntity component. By using this component, you transform an ordinary Unity Prefab into a networked element, allowing Bolt to extract, sync, and manage its data. It's also because of this component, that you are able to interact with the Entity's state, modifying it to reflect the state of your game.

BoltEntity is similar to a Unity/uLink NetworkView or PUN's PhotonView. It is the representation of a network-aware object, and is the base for having Bolt control and replicate an actual GameObject in Unity.

Entity Ownership

A BoltEntity is neither from a Server or Client, in Bolt perspective, the ownership of entities is broken down into two separate categories: Owner and Controller. They are not mutually exclusive, you can be any of the following:

  1. Owner: where entity.isOwner == true;
  2. Controlled: where entity.hasControl == true;
  3. Proxy: where entity.isOwner == false && entity.hasControl == false.

Owner

Being an Owner is a non-changeable property that is assigned to the peer which the BoltNetwork.Instantiate call is issued on. This peer is the only one where BoltEntity.isOwner will return true. Ownership of an entity can not be transferred to anyone else. The owner of an entity has absolute control over everything (state, transform, etc.).

Controller

Being a Controller of an entity is something which can be assigned, taken away and transferred to other peers. Only the Owner can assign and take away the control of an entity.

The Owner can assign control of an entity to another peer by calling entity.AssignControl(otherConnection) and passing in the connection to the other peer. Control can also be removed by calling entity.RevokeControl(). This peer will now be considered the Controller of this entity.

The Owner can also take control of an entity himself, he will then be considered both the Owner and Controller. Taking control of an entity you are the Owner of is done by calling entity.TakeControl() and releasing control is done with entity.ReleaseControl().

The Controller is the only one that the entity.hasControl property will return true on.

Neither

If you are neither the Owner or Controller of an entity, both entity.isOwner and entity.hasControl will return false. In general you can't do very much with the entity in question and it's usually something you don't have any control of in the game (like another players avatar for example).

BoltEntity Component Settings

Here is a breakdown of all the settings available on the BoltEntity component. You also see the description of each field on Untiy by enabling the Show BoltEntity Settings hints in the Bolt Settings window, under the Miscellaneous section.

bolt entity component
Bolt Entity Component.

Prefab & State

  • Type: utility field which shows the Unity PrefabType value of the current Game Object;
  • Scene Id: the unique ID of this Entity, if it's a Scene Entity that Bolt uses to reference it over the network for the initial setup;
  • Id: the internal ID assigned to this prefab inside Bolt. This is always 0 for scene objects;
  • State: the state that will be used for this Bolt Entity.

Settings

  • Replication Rate: controls how often Bolt should try to send updates for this entity: (i) 1 = every packet, 2 = every other packet, etc;
  • Persistent: if Bolt should keep this object around between scenes loaded with BoltNetwork.LoadScene;
  • Always Replicate: this setting lets you control if Bolt should ignore the current loading state of the game and always proxy this entity even if you are in the middle of a scene load. This can be seen as Bolt's version of GameObject.DontDestroyOnLoad in Unity. This is useful for 'meta' object which contain information about the game as a whole and not a specific map or character;
  • Proxy When Frozen: if enabled Bolt will allow this entity to perform its first replication even if its frozen;
  • Detach On Disable: if enabled this entity will be detached from the network when its disabled;
  • Auto Attach On Load: if enabled, scene entities will be automatically attached on map load;
  • Auto Freeze Frames: if larger than 0, this entity will be automatically frozen by Bolt for non-owners if it has not received a network update for the amount of frames specified;
  • Controller Predicted Movement: this setting lets you specify to Bolt if you are using local prediction on the controller of an entity, this means you are using SimulateController and ExecuteCommand with a character motor that is able to do local prediction and corrections;
  • Remove Parent On Detach: if enabled this tells Bolt to search the entire transform hierarchy of the entity being detached for nested entities and set their transform.parent to null.
  • Allow Client Instantiate: is only available if you have switched Instantiate Mode to Individual On Each Prefab in the Bolt Settings window. Lets you control if the clients can call BoltNetwork.Instantiate for this prefab.
  • IEntityBehaviour Query: Bolt dynamically queries for IBoltEntityBehaviour implementors when instantiating new entities. By default it uses the Global Setting which does a GetComponentsInChildren, but you can change this behaviour here.
  • IPriorityCalculator Query: Bolt dynamically queries for IPriorityCalculator implementors when instantiating new entities. By default it uses the Global Setting which does a GetComponentsInChildren, but you can change this behaviour here. Note: most people will not have this implemented at all and should just choose None for performance reasons either in global or in the prefab.
  • IEntityReplicationFilter Query: Bolt dynamically queries for IEntityReplicationFilter implementors when instantiating new entities. By default it uses the Global Settings which does a GetComponentsInChildren, but you can change this behaviour here. Note: most people will not have this implemented at all and should just choose None for performance reasons either in global or in the prefab.

Bolt Entity Details

Synching the BoltEntity to others

To sync state from the Owner to the other peers you'll need to create a State. This can be done through Bolt/Assets window. By creating a new State, you are creating the description of all properties that need to be networked and will be attached to a particular BoltEntity. You can read more about the Bolt State at it's own dedicated page here.

The properties types availables are:

  • Array: a collection of values of the same type, organized into an array format. You can access individually items by index;
  • Bool: a boolean value;
  • Color: Unity Color instance;
  • Color32: Unity Color32 instance;
  • Entity: reference to other BoltEntity;
  • Float: a float value;
  • Guid: a instance of System.Guid;
  • Integer: a integer value;
  • Matrix4x4: Unity Matrix4x4 instance.
  • NetworkId: reference to any Bolt.NetworkId;
  • Object: instance of a Object Bolt Asset type;
  • PrefabId: reference to any Bolt.PrefabId;
  • ProtocolToken: reference to a custom token that implements the Bolt.IProtocolToken;
  • Quaternion: Unity Quaternion instance.
  • String: a string value;
  • Transform: Unity Transform instance.
  • Trigger: special one-fire state.
  • Vector: Unity Vector3 instance.

Make sure to recompile Bolt Assets after creating/changing a State

Controlling a BoltEntity

To setup how a BoltEntity is controlled you'll need to override the entity's ExecuteCommand() and SimulateController() methods. This is (usually) done for client-side prediction. Note that the Owner will still have full control over the actual State of the BoltEntity.

Note that to be able to use authoritative and client predicted movement, you have to use either the transform component directly or a Character Controller component. You can not use mecanim root motion or rigidbodies to control your characters.

Method: BoltEntity.SimulateController()

Only runs on the person which has been assigned control of an entity. This can either be the Owner that has given itself control by calling entity.TakeControl() on an Entity, or it can be someone which has a remote proxy of an object and has been given control from the owner, which the owner does by calling entity.AssignControl(BoltConnection connection). You are allowed to call entity.QueueCommand inside of SimulateController, which is used for queuing up a command for execution.

Method: BoltEntity.ExecuteCommand(BoltCommand cmd, bool resetState)

This function runs on both the owner and controller, but has different behaviors depending on if it runs on the owner or a remote controller.

On the owner, no matter if the Owner is the controller or not, it only runs once for each command. No matter if the command was created locally, by the owner itself or if it came from a remote connection which has been given control. The second parameter called resetState will never be true on the owner.

On a controller, if it's not the owner, ExecuteCommand is a little bit more complex. It needs to be able to handle both local prediction of the movement but also corrections of the state from the Owner. The first thing that happens every frame is that Bolt will pass in the last command which has it's state verified from the Owner, when this command get passed in the resetState parameter will be true.

Once the latest verified command has executed with resetState, Bolt will then run all other commands which have not been verified by the Owner yet. What this means in practice is that ExecuteCommand will be called several times for one command on a remote controller during multiple frames in a row until it has been verified by the owner.

When the command which has resetState set to true executes, Bolt wants you to set the local state of the character motor you are using to the state that is represented by the command result. You should not use the BoltCommand.Input here, only the BoltCommand.Result. This is what allows Bolt to correct the local movement of a remote controller (often a client) with the correct state from the owner (often the server).

Note that the state you need to reset is all of the state which effects movement of your entity in any way, usually this is position, rotation and some state variables like if you are grounded or crouching, etc. If you have a complex controller other things you want to reset could be velocity, acceleration, external forces, etc.

Incorrectly/incompletely resetting state is one of the most common problems with "jittery movement" on the Controller's side

What is BoltCommand.isFirstExecution used for?

Code which perform direct actions from your commands, for example firing your weapon or setting the animation state - should be wrapped in a block that looks like this:

C#

public override void ExecuteCommand(Bolt.Command cmd, bool resetState)
{
    if (resetState)
    {
        // reset code
    }
    else
    {
        if (cmd.isFirstExecution)
        {
            // First Execution code ...
        }
    }
}

Since on a remote controller ExecuteCommand will be called several times for the same command, if you don't check so that things like animations and other direct actions only happen on the first execution - your character will act very weird on the remote controllers.

What about normal proxies, which are not the controller?

The position, rotation, animation and state sync is done through the Bolt state object for these entities. This means that any type of state, action, event, etc. you want visible to everyone connected has to be sent with either the Bolt State mechanism or over a Bolt Event, as Bolt Commands never execute on anyone but the Controller and Owner.

Entity Pooling

Internally Bolt implements an interface IPrefabPool. The default Bolt implementation simply dynamically allocates and destroys prefabs when requested (clearly, the default internal Bolt implementation does no pooling internally). If you wish to override this behaviour, you can implement IPrefabPool on your own custom class and override Bolt’s implementation using BoltNetwork.SetPrefabPool(IPrefabPool pool). If you do this, you have complete control over how Bolt prefabs are created and destroyed. You can then simply implement calls to your own custom pooling solution to convert Bolt from using dynamic instantiation to a pooling pattern. You can even replace prefabs with different prefabs if you wish - for example, if you wanted to have a server prefab and a client prefab, this is possible using this mechanism.

This is the simple pool implementation that Bolt uses by default. Notice that it does not actually pool anything (it just dynamically instantiates and destroys the entity).

C#

using UnityEngine;
using Bolt;

public class DefaultPrefabPool : IPrefabPool
{
    public GameObject Instantiate(PrefabId prefabId, Vector3 position, Quaternion rotation)
    {
        GameObject go;

        go = GameObject.Instantiate(LoadPrefab(prefabId), position, rotation);
        go.GetComponent<BoltEntity>().enabled = true;

        return go;
    }

    public void Destroy(GameObject gameObject)
    {
        GameObject.Destroy(gameObject);
    }

    public GameObject LoadPrefab(PrefabId prefabId)
    {
        return PrefabDatabase.Find(prefabId);
    }
}

In order to use your implementation class, you need to register it when Bolt finishes the startup:

C#

public class MyGlobalListener : Bolt.GlobalEventListener
{
    public override void BoltStartDone()
    {
        BoltNetwork.SetPrefabPool(new MyPool());
    }
}
Back to top