Instantiation

Most multiplayer games need to create and synchronize some GameObjects. Maybe it's a character, some units or monsters that should be present on all clients inside a room. PUN provides a convenient way to do just that.

As usual in Unity, Instantiate and Destroy are used to manage the lifetime of GameObjects. PUN 2 can use a pool to create (and return) them. Each networked GameObject must have a PhotonView component (and a ViewID) as identifier via the network.

This page and the following ones explain how to create, sync. and use networked GameObjects.

Contents

PhotonNetwork.Instantiate

To create networked GameObjects, use PhotonNetwork.Instantiate instead of Unity's Object.Instantiate. Any client in a room can call this to create objects which it will control.

PhotonNetwork.Instantiate("MyPrefabName", new Vector3(0, 0, 0), Quaternion.identity, 0);

The first parameter of PhotonNetwork.Instantiate is a string which defines the "prefab" to instantiate. Internally, PUN will fetch the GameObject from the PhotonNetwork.PrefabPool, set it up for the network and enable it. The position and rotation where to create the object must be set. Players who join later, will initially instantiate the object at this place, even if it moved already.

Any prefab must have a PhotonView component. This contains a ViewID (the identifier for network messages), who owns the object, which scripts will write and read network updates (the "observed" list) and how those updates are sent (the "Observe option"). Check the inspector to setup a PhotonView via the Editor. A networked object may contain more than one PhotonView but for performance reasons we suggest to use only one.

By default, PUN instantiate uses the DefaultPool, which loads prefabs from "Resources" folders and Destroys the GameObject later on. A more sophisticated IPunPrefabPool implementation can return objects to a pool in Destroy and re-use them in Instantiate. In that case, the GameObjects are not truly created in Instantiate, which means that Start() is not being called by Unity in such a case. Due to this, scripts on networked GameObjects should just implement OnEnable() and OnDisable().

To setup GameObjects when they got instantiated, you could also implement IPunInstantiateMagicCallback in a script. PUN will check if the interface is implemented on a component and calls OnPhotonInstantiate(PhotonMessageInfo info) when the instance comes into use. The PhotonMessageInfo parameter contains who instantiated the GameObject and when.

Note: Looking up IPunInstantiateMagicCallback implementations is a costly action, so PUN caches which prefabs are not making use of the interface and skips this lookup when this prefab is used again.

For example, you could setup the instantiated GameObject as a player's Tag object:

void OnPhotonInstantiate(PhotonMessageInfo info)
{
    // e.g. store this gameobject as this player's charater in Player.TagObject
    info.sender.TagObject = this.GameObject;
}

Behind the scenes, PhotonNetwork.Instantiate stores an event on the server for players who join later on.

Back To Top

Lifetime Of Networked Objects

By default, GameObjects created with PhotonNetwork.Instantiate exist as long as the creator is in the room. When you swap rooms, objects don't carry over, just like when you switch a scene in Unity.

When a client leaves a room, the remaining players will destroy the GameObjects created by the leaving player. If this doesn't fit your game logic, you can disable this: Set the RoomOptions.CleanupCacheOnLeave to false, when you create a room.

The Master Client can create GameObjects that have the lifetime of the room by using PhotonNetwork.InstantiateSceneObject(). Note: The object is not associated with the Master Client but the room. By default, the Master Client controls these objects but you can pass on control with photonView.TransferOwnership().

Back To Top

Networked Scene Objects

It is perfectly fine to place PhotonViews on objects in a scene. They will be controlled by the Master Client by default and can be useful to have a "neutral" object to send room-related RPCs.

Important: When you load a scene with networked objects before being in a room, some PhotonView values are not useful yet. For example: You can't check IsMine in Awake() when you're not in a room.

Back To Top

Switching Scenes

When you load a scene, Unity usually destroys all GameObjects currently in the hierarchy. This includes networked objects, which can be confusing at times.

Example: In a menu scene, you join a room and load another. You might actually arrive in the room a bit too early and get the initial messages of the room. PUN begins to instantiate networked objects but your logic loads another scene and they are gone.

To avoid issues with loading scenes, you can set PhotonNetwork.AutomaticallySyncScene to true and use PhotonNetwork.LoadLevel() to switch scenes.

Read Timing for RPCs and Loading Levels

Back To Top

Custom Instantiation Data

You can send some initial custom data when the instantiation call. Just make use of the last parameter in the PhotonNetwork.Instantiate* method.

This has two main advantages:

  • save traffic by avoiding extra messages: we don't have to use a separate RPC or RaiseEvent call for synchronizing this kind of information
  • timing: the data exchanged is available on the time of the prefab instantiation which could be useful to do some initialization

The instantiation data is an object array (object[]) of anything Photon can serialize.

Example:

Instantiate with custom data:

object[] myCustomInitData = GetInitData();
PhotonNetwork.Instantiate("MyPrefabName", new Vector3(0, 0, 0), Quaternion.identity, 0, myCustomInitData);

Receive custom data:

public void OnPhotonInstantiate(PhotonMessageInfo info)
{
    object[] instantiationData = info.photonView.InstantiationData;
    // ...
}

Back To Top

Using The PrefabPool

By default, PUN uses a simple DefaultPool to instantiate and destroy GameObjects. This uses the Resources folder to load prefabs and it will not pool objects that get destroyed (to simplify usage). If either affects your game's performance negatively, it is time to set a custom PrefabPool.

A custom pool class must implement the IPunPrefabPool interface with just two methods:

GameObject Instantiate(string prefabId, Vector3 position, Quaternion rotation) gets an instance of a prefab. It must return a valid, disabled GameObject with a PhotonView.

Destroy(GameObject gameObject) gets called to destroy (or just return) the instance of a prefab. The GameObject is already disabled and the pool may reset and cache it for later use in Instantiate.

Note: When a custom IPunPrefabPool is used, PhotonNetwork.Instantiate is probably not creating the GameObject and (e.g.) Start() is not called. Use OnEnable() and OnDisable() accordingly and disable any physical or other components that may still run otherwise.

Back To Top

Manual Instantiation

If you don't want to use PUN's built-in instantiation and pools, you can reimplement the behaviour with RPCs or RaiseEvent as shown in the following example.

You need to tell the remote clients which object to instantiate (prefab name) and how to identify it (ViewID).

The PhotonView.ViewID is the key to routing network messages to the correct GameObject/scripts. If you instantiate manually, you have to allocate a new ViewID with PhotonNetwork.AllocateViewID() and send it along. Everyone in the room has to set the same ID on the new object.

Keep in mind that a Manual Instantiation event needs to be buffered: Clients that connect later have to receive the spawn instructions as well.

public void SpawnPlayer()
{
    GameObject player = Instantiate(PlayerPrefab);
    PhotonView photonView = player.GetComponent<PhotonView>();

    if (PhotonNetwork.AllocateViewID(photonView))
    {
        object[] data = new object[]
        {
            player.transform.position, player.transform.rotation, photonView.ViewID
        };

        RaiseEventOptions raiseEventOptions = new RaiseEventOptions
        {
            Receivers = ReceiverGroup.Others,
            CachingOption = EventCaching.AddToRoomCache
        };

        SendOptions sendOptions = new SendOptions
        {
            Reliability = true
        };

        PhotonNetwork.RaiseEvent(CustomManualInstantiationEventCode, data, raiseEventOptions, sendOptions);
    }
    else
    {
        Debug.LogError("Failed to allocate a ViewId.");

        Destroy(player);
    }
}

We are instantiating the player prefab locally first. This is necessary because we need the reference to the object's PhotonView component. If we have successfully allocated an ID for the PhotonView, we are collecting all the data we want to send to the other clients and store them in an array of objects. In this example we are sending the position and the rotation of the instantiated object and - most important - the allocated ViewID. Afterwards we are creating the RaiseEventOptions and the SendOptions. With the RaiseEventOptions we make sure, that this event is added to the room's cache and only send to the other clients, because we already have instantiated our object locally. With the SendOptions we just define, that this event is sent reliable. Finally we are using PhotonNetwork.RaiseEvent(...) to send our custom event to the server. In this case we are using CustomManualInstantiationEventCode, which is simply a byte value representing this certain event. If allocating an ID for the PhotonView fails, we log an error message and destroy the previously instantiated object.

Since we are using PhotonNetwork.RaiseEvent, we have to use an OnEvent callback handler. Don't forget, to register it properly. To see how this works, you can take a look at the RPCs and RaiseEvent documentation page. In this example the OnEvent handler looks like this:

public void OnEvent(EventData photonEvent)
{
    if (photonEvent.Code == CustomManualInstantiationEventCode)
    {
        object[] data = (object[]) photonEvent.CustomData;

        GameObject player = (GameObject) Instantiate(PlayerPrefab, (Vector3) data[0], (Quaternion) data[1]);
        PhotonView photonView = player.GetComponent<PhotonView>();
        photonView.ViewID = (int) data[2];
    }
}

Here we simply check, if the received event is our custom manual instantiation event. If so, we instantiate the player prefab with the position and rotation information we have received. Afterwards we get a reference to the object's PhotonView component and assign the ViewID we have received, too.

If you want to use asset bundles to load your network objects, all you have to do is to add your own asset bundle loading code and replace the PlayerPrefab from the example with the prefab from your asset bundle.

Back To Top

ViewID Limits

Most games will never need more than a few PhotonViews per player; one or two for the character and that's usually it. If you need many more you might be doing something wrong. For example, it is extremely inefficient to network instantiate and assign a PhotonView for every bullet that your weapon fires, instead keep track of your fired bullets via the player or weapon's PhotonView via RPCs.

By default, PUN supports up to 1000 PhotonViews per player and a maximum theoritical number of players equal to 2,147,483. You can easily allow for more PhotonViews per player.

This works as follows:

PhotonViews send out a viewID for every network message. This viewID is an integer, composed of the actor number and the player's viewID. The maximum size of an int is 2,147,483,647 (int.MaxValue), divided by our PhotonNetwork.MAX_VIEW_IDS (1000) that allows for over 2 million players, each having 1000 view IDs.

So for the default case of MAX_VIEW_IDS equal to 1000:

  • ViewID 0 is reserved. It means that a viewID is not allocated yet.
  • Scene objects have viewIDs between 1 and 999.
  • Actor number 1's objects have viewIDs between 1000 and 1999.
  • Actor number x's objects have viewIDs between x * MAX_VIEW_IDS and (x + 1) * MAX_VIEW_IDS - 1.
  • Actor number 2,147,483's objects have viewIDs between 2,147,483,000 and 2,147,483,647. Only 648 possible values since we hit the int.MaxValue limit.

To Document Top