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.