PhotonView ID Resetting
The whole answer can be found below.
Try Our
Documentation
Please check if you can find an answer in our extensive documentation on PUN.
Join Us
on Discord
Meet and talk to our staff and the entire Photon-Community via Discord.
Read More on
Stack Overflow
Find more information on Stack Overflow (for Circle members only).
PhotonView ID Resetting
Ante_SL
2019-05-01 16:54:10
Hi, I'm writing about an issue my team is trying to get around regarding PhotonView IDs being modified for prefab instances at Editor time.
This is not an issue at runtime off of a build, or in Editor runtime without remote clients. However, in a multiple client situation set up using multiple Unity Editor runtime instances (i.e. not running a build runtime) running on the same or multiple systems, there is a good chance that Photon Editor script logic will modify the PhotonView ID, causing different Unity Editor instances to have different view ID sets, breaking view ID syncing.
We discovered that the view ID change is being caused by PhotonViewHandler.cs modifying the PhotonView ID of the prefab itself and setting it to zero. E.g. when a prefab instance override is made, a prefab instances will get the zero value and Photon Editor logic will assign it a new ID.
Reproduction steps:
- In Editor, create a prefab and attach a PhotonView component to it.
- In Editor, create an instance of that prefab in any scene. Observe the PhotonView ID of the instance.
- Make a modification to the prefab in the scene hierarchy view.
- Apply the prefab instance override change to the prefab.
- Observe the PhotonView ID after the override. It will be different.
Outside of these steps, we have also observed that a PhotonView ID changes before entering runtime (in Editor) as well.
We would like to understand the official purpose/effectiveness of the ID reassignment behaviour, and how it fits with the intended usage of PhotonView IDs. In case we are misusing Photon, we are looking for advice on how to get around the issue of PhotonView ID desync across client instances. So far, the only path forward is to share a consistent build version across clients when testing multiplayer behaviour, but this isn't always efficient while developing.
Thus far we have tried applying the following solutions to get around this issue (we've settled on the 3rd):
Use version control to sync your unity project across clients. However, this is unreliable due to IDs sometimes changing on entering Play Mode.
Modify the Photon Code (PhotonViewHandler.cs) to make sure the prefab ID isn't modified for a prefab and that the IsPrefab check identifies the prefab correctly. However, we wish to avoid modifying plugins at all costs, for future maintenance reasons and to avoid more undesirable issues.
Develop a script that maintains a list of all PhotonView gameobjects in a scene during Editor mode and uses RaiseEvents during runtime to ensure that all the PhotonView IDs are synchronized across multiple clients. (See the script attached)
We are moving ahead with option 3, but it feels like overkill to do this, so we'd like to check if we're approaching this problem correctly.
Thanks!
Ante
PhotonViewIdSync script we wrote:
(partial class body simplified of irrelevant bits)
public class PhotonViewIdSync : MonoBehaviour
{
#if UNITY_EDITOR
[SerializeField] private List _photonViewInScene;
private static List PhotonViewsInScene
{
get => Instance._photonViewInScene;
set => Instance._photonViewInScene = value;
}
#region MonoBehaviour
private void OnEnable()
{
NetworkManager.AddRaiseEventListener(RaiseEventCode.PhotonViewIDSync, ReceivePhotonViewId);
}
private void OnDisable()
{
NetworkManager.RemoveRaiseEventListener(RaiseEventCode.PhotonViewIDSync, ReceivePhotonViewId);
}
private void Start()
{
StartCoroutine(SynchronizePhotonViewID(SceneManager.GetActiveScene(), LoadSceneMode.Single));
}
#endregion MonoBehaviour
#region Synchronization
private IEnumerator SynchronizePhotonViewID(Scene scene, LoadSceneMode mode)
{
yield return new WaitUntil(() => NetworkManager.IsReady);
if (!PhotonNetwork.IsMasterClient) yield break;
Debug.LogFormat(DebugLogFormat, "SynchronizePhotonViewID", " Synchronizing Photon Views in Scene " + scene.name);
for (int i = 0; i < PhotonViewsInScene.Count; i++)
{
SendPhotonViewId(i, PhotonViewsInScene[i].ViewID);
}
}
public static void SendPhotonViewId(int index, int viewID)
{
SendOptions sendOptions = new SendOptions {Reliability = true};
object[] eventContent = new object[] {index, viewID};
RaiseEventOptions raiseEventOptions =
new RaiseEventOptions {CachingOption = EventCaching.AddToRoomCache, Receivers = ReceiverGroup.Others};
PhotonNetwork.RaiseEvent(RaiseEventCode.PhotonViewIDSync, eventContent, raiseEventOptions, sendOptions);
}
public void ReceivePhotonViewId(EventData photonEventData)
{
object[] sentData = (object[])photonEventData.CustomData;
int index = (int)sentData[0];
if (index < PhotonViewsInScene.Count)
{
PhotonViewsInScene[index].ViewID = (int)sentData[1];
}
}
#endregion Synchronization
#region EditorMode
private static EditorBuildSettingsScene[] _scenes;
private static int _currentSceneIndex = 0;
private static string _activeScene;
[MenuItem("Photon/Update Photon View List %g")]
private static void LoadViewsToList()
{
if (Instance == null)
{
Debug.LogFormat(DebugErrorFormat, " Missing Component", " Please add a PhotonViewIDSync component to any gameobject.");
return;
}
_scenes = EditorBuildSettings.scenes;
EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
EditorSceneManager.activeSceneChangedInEditMode += AddSceneViewsToList;
_activeScene = EditorSceneManager.GetActiveScene().path;
EditorSceneManager.CloseScene(EditorSceneManager.GetActiveScene(), false);
LoadNextScene();
}
private static void LoadNextScene(string sceneToLoad = null)
{
EditorSceneManager.OpenScene(sceneToLoad ?? _scenes[_currentSceneIndex].path);
}
private static void AddSceneViewsToList(Scene one, Scene two)
{
_currentSceneIndex++;
object[] obj = FindObjectsOfType(typeof(GameObject));
PhotonViewsInScene = new List();
Scene currentScene = EditorSceneManager.GetActiveScene();
foreach (object o in obj)
{
GameObject newGO = (GameObject)o;
PhotonView newView = newGO.GetComponent();
if (newView == null) continue;
PhotonViewsInScene.Add(newView);
Debug.LogFormat(DebugLogFormat, "AddSceneViewsToList", "Adding PhotonView " + newGO.name);
}
if (_currentSceneIndex < _scenes.Length)
{
LoadNextScene();
}
else
{
EditorSceneManager.activeSceneChangedInEditMode -= AddSceneViewsToList;
_currentSceneIndex = 0;
Debug.LogFormat(DebugLogFormat, "AddSceneViewsToList", " PhotonView List Updated. ");
EditorSceneManager.SaveScene(two);
LoadNextScene(_activeScene);
}
}
#endregion EditorMode
#endif //UNITY_EDITOR
}
Comments
Sorry for the really late reply. This was open as tab for a while but I missed replying.
There is certainly room for improvement for the assignment of the IDs within scenes and on prefabs.
Yes, it's overkill to sync the lists and we would like to fix that.
Things changed over time (we started with Unity 3.5 or such) and the workflow should get an update.
Which version of Unity are you using and which PUN version?
Originally, we set the ID to a default value on Prefabs, so we can reliably replace it at runtime. The point of using a serialized field was, that we could set this pre-instantiation and it would be copied over to any new instance implicitly. This means any script on the new GO could use it in any execution order.
By now, this is probably no longer feasible. Let us have a look.
Thanks for the reply and for looking into this. We are using Unity 2019.1.0f2 and PUN v2.5 (20th November 2018, as observed from changelog file inside PhotonUnityNetworking).
Another extremely late reply:
There is a change in PUN 2.14, which preserves viewIDs of objects in the scene. Changing any viewID to a conflict, will just fix the current viewID to non-clashing, keeping the others.
Internally, we still set the viewID to 0 on prefabs. This value is used in some places as check and it's also cleaner for the version control, if the viewID doesn't change in prefabs.