This document is about: PUN 1
SWITCH TO

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

Instantiation

In about every game you need to instantiate one or more objects per player.
For objects that should synchornize in a networked game, you need to use a special workflow.

PhotonNetwork.Instantiate

PUN can automatically take care of spawning a networked object by passing a starting position, rotation and a prefab name to the PhotonNetwork.Instantiate method.
Requirement: The prefab should be available directly under "Resources" folder (to load it at runtime) and it must have a PhotonView component.

Watch out with web players: everything in the resources folder will be streamed at the very first scene per default.
Under the web player settings you can specify the first level that uses assets from the resources folder by using the "First streamed level".
If you set this to your first game scene, your preloader and main menu will not be slowed down if they don't use the Resources folder assets.

C#

void SpawnMyPlayerEverywhere() 
{ 
    PhotonNetwork.Instantiate("MyPrefabName", new Vector3(0, 0, 0), Quaternion.identity, 0); 
    //The last argument is an optional group number, feel free to ignore it for now. 
}

When you need to setup new GameObjects when they got instantiated, you can implement OnPhotonInstantiate(PhotonMessageInfo info) in a script on them.
It's being called with the info who triggered the instantiation. You can setup the GameObject as a player's Tag object, for example:

C#

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

Lifetime of Networked Objects

GameObjects created with PhotonNetwork.Instantiate will usually exist as long as you are in the same room.
When you swap rooms, objects don't carry over, just like when you switch a scene in Unity.

Whan a client leaves a room, all others destroy the GameObjects owned/created by that player.
If this doesn't fit your game logic, you can skip this step. Set PhotonNetwork.autoCleanUpPlayerObjects to false for your game.

Alternatively, the Master Client can create GameObjects that have the lifetime of the room by using PhotonNetwork.InstantiateSceneObject().
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().

Check out the Demo for Ownership Transfer

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!

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

Using the PrefabPool

If you have many objects that you want to reuse very often, you might want to have a Prefab Pool for them in order to avoid continuous memory allocation and freeing, which might have a bad influence on the game's performance.

To use PUN's prefab pool, you have to implement the IPunPrefabPool interface. This interface provides two functions to add objects to or remove them from the pool. These functions are GameObject Instantiate(string prefabId, Vector3 position, Quaternion rotation); and void Destroy(GameObject gameObject);. The pool itself can be any kind of a (dynamic) data structure. In the following example we are using a Queue. However using a List or Hashset is possible, too. We also add a public reference to a GameObject, which can be set in the inspector later on. So far our class - it is simply named Pool - looks like this.

C#

public class Pool : MonoBehaviour, IPunPrefabPool
{
    private Queue<GameObject> pool;

    public GameObject Prefab;

    public new GameObject Instantiate(string prefabId, Vector3 position, Quaternion rotation)
    {
    }

    public void Destroy(GameObject gameObject)
    {
    }
}

To get our pooling system working properly, we are adding Unity's Awake function, which we use to instantiate the Queue and to notify PUN about it.

C#

public void Awake()
{
    pool = new Queue<GameObject>();

    PhotonNetwork.PrefabPool = this;
}

Whenever you use PhotonNetwork.Instantiate or PhotonNetwork.Destroy now, these calls make use of our previously implemented Pool and especially it's Instantiate and Destroy functions. Obviously both are doing nothing right now, so it's time to adjust their behaviour.

When Instantiate gets called, we have two possible situations: either there is at least one object in the Pool which can be used again or we have to instantiate a new one. Knowing this, we can update this function accordingly.

C#

public new GameObject Instantiate(string prefabId, Vector3 position, Quaternion rotation)
{
    if (pool.Count > 0)
    {
        GameObject go = pool.Dequeue();
        go.transform.position = position;
        go.transform.rotation = rotation;
        go.SetActive(true);

        return go;
    }

    return Instantiate(Prefab, position, rotation);
}

Since we are using a Queue in this example, getting the object and removing it from the Pool is just one Dequeue() call. If you use a List for example, you would have to implement a similar behaviour. Having retrieved the object from the Queue, we are setting it's transform data and enabling it. If the Pool is empty, we simply Instantiate a new object.

When Destroy gets called, we have to disable the object and return it to the Pool. With this knowledge we can update this function, too.

C#

public void Destroy(GameObject gameObject)
{
    gameObject.SetActive(false);

    pool.Enqueue(gameObject);
}

Note: disabling the GameObject might be important in terms of performance. We are doing this, to avoid running scripts, rendering the object, checking for collisions, etc. as long as we don't need the object.

Don't forget to attach this script to an object in your scene.

Manual Instantiation

If you don't want to rely on the Resources folders to instantiate objects
over the network you'll have to manually instantiate them as shown in
the example at the end of this section.

The main reason for wanting to instantiate manually is gaining control
over what is downloaded when for streaming web players. The details about
streaming and the resources folder in Unity can be found
here.

You can send RPCs to instantiate objects.
Of course you need some way to tell the remote clients which object to instantiate.
You can't just send a reference to a GameObject, so you need to come up with a name or something for it.

As important as the type of object, is a network id for it.
The PhotonView.viewID is the key to routing network messages to the correct gameobject/scripts.
If you spawn manually, you have to allocate a new viewID using PhotonNetwork.AllocateViewID() and send it along.
Everyone in the room has to set the same ID on the new object.

Keep in mind that an RPC for instantiation needs to be buffered:
Clients that connect later have to receive the spawn instructions as well.

C#


void SpawnPlayerEverywhere()
{
    // You must be in a Room already
    
    // Manually allocate PhotonViewID
    int id1 = PhotonNetwork.AllocateViewID();

    PhotonView photonView = this.GetComponent<PhotonView>();
    photonView.RPC("SpawnOnNetwork", PhotonTargets.AllBuffered, transform.position, transform.rotation, id1);
}
 
public Transform playerPrefab; //set this in the inspector 
 
[RPC]
void SpawnOnNetwork(Vector3 pos, Quaternion rot, int id1)
{
    Transform newPlayer = Instantiate(playerPrefab, pos, rot) as Transform;
    
    // Set player's PhotonView
    PhotonView[] nViews = newPlayer.GetComponentsInChildren<PhotonView>(); 
    nViews[0].viewID = id1;
} 

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