// ----------------------------------------------------------------------------
//
// PhotonNetwork Framework for Unity - Copyright (C) 2011 Exit Games GmbH
//
//
//
//
// developer@exitgames.com
// ----------------------------------------------------------------------------
using System;
using UnityEngine;
using System.Reflection;
using System.Collections.Generic;
using ExitGames.Client.Photon;
#if UNITY_EDITOR
using UnityEditor;
#endif
public enum ViewSynchronization { Off, ReliableDeltaCompressed, Unreliable, UnreliableOnChange }
public enum OnSerializeTransform { OnlyPosition, OnlyRotation, OnlyScale, PositionAndRotation, All }
public enum OnSerializeRigidBody { OnlyVelocity, OnlyAngularVelocity, All }
///
/// Options to define how Ownership Transfer is handled per PhotonView.
///
///
/// This setting affects how RequestOwnership and TransferOwnership work at runtime.
///
public enum OwnershipOption
{
///
/// Ownership is fixed. Instantiated objects stick with their creator, scene objects always belong to the Master Client.
///
Fixed,
///
/// Ownership can be taken away from the current owner who can't object.
///
Takeover,
///
/// Ownership can be requested with PhotonView.RequestOwnership but the current owner has to agree to give up ownership.
///
/// The current owner has to implement IPunCallbacks.OnOwnershipRequest to react to the ownership request.
Request
}
///
/// PUN's NetworkView replacement class for networking. Use it like a NetworkView.
///
/// \ingroup publicApi
[AddComponentMenu("Photon Networking/Photon View &v")]
public class PhotonView : Photon.MonoBehaviour
{
#if UNITY_EDITOR
[ContextMenu("Open PUN Wizard")]
void OpenPunWizard()
{
EditorApplication.ExecuteMenuItem("Window/Photon Unity Networking");
}
#endif
public int ownerId;
public int group = 0;
protected internal bool mixedModeIsReliable = false;
///
/// Flag to check if ownership of this photonView was set during the lifecycle. Used for checking when joining late if event with mismatched owner and sender needs addressing.
///
/// true if owner ship was transfered; otherwise, false.
public bool OwnerShipWasTransfered;
// NOTE: this is now an integer because unity won't serialize short (needed for instantiation). we SEND only a short though!
// NOTE: prefabs have a prefixBackup of -1. this is replaced with any currentLevelPrefix that's used at runtime. instantiated GOs get their prefix set pre-instantiation (so those are not -1 anymore)
public int prefix
{
get
{
if (this.prefixBackup == -1 && PhotonNetwork.networkingPeer != null)
{
this.prefixBackup = PhotonNetwork.networkingPeer.currentLevelPrefix;
}
return this.prefixBackup;
}
set { this.prefixBackup = value; }
}
// this field is serialized by unity. that means it is copied when instantiating a persistent obj into the scene
public int prefixBackup = -1;
///
/// This is the instantiationData that was passed when calling PhotonNetwork.Instantiate* (if that was used to spawn this prefab)
///
public object[] instantiationData
{
get
{
if (!this.didAwake)
{
// even though viewID and instantiationID are setup before the GO goes live, this data can't be set. as workaround: fetch it if needed
this.instantiationDataField = PhotonNetwork.networkingPeer.FetchInstantiationData(this.instantiationId);
}
return this.instantiationDataField;
}
set { this.instantiationDataField = value; }
}
internal object[] instantiationDataField;
///
/// For internal use only, don't use
///
protected internal object[] lastOnSerializeDataSent = null;
///
/// For internal use only, don't use
///
protected internal object[] lastOnSerializeDataReceived = null;
public ViewSynchronization synchronization;
public OnSerializeTransform onSerializeTransformOption = OnSerializeTransform.PositionAndRotation;
public OnSerializeRigidBody onSerializeRigidBodyOption = OnSerializeRigidBody.All;
/// Defines if ownership of this PhotonView is fixed, can be requested or simply taken.
///
/// Note that you can't edit this value at runtime.
/// The options are described in enum OwnershipOption.
/// The current owner has to implement IPunCallbacks.OnOwnershipRequest to react to the ownership request.
///
public OwnershipOption ownershipTransfer = OwnershipOption.Fixed;
public List ObservedComponents;
Dictionary m_OnSerializeMethodInfos = new Dictionary(3);
#if UNITY_EDITOR
// Suppressing compiler warning "this variable is never used". Only used in the CustomEditor, only in Editor
#pragma warning disable 0414
[SerializeField]
bool ObservedComponentsFoldoutOpen = true;
#pragma warning restore 0414
#endif
[SerializeField]
private int viewIdField = 0;
///
/// The ID of the PhotonView. Identifies it in a networked game (per room).
///
/// See: [Network Instantiation](@ref instantiateManual)
public int viewID
{
get { return this.viewIdField; }
set
{
// if ID was 0 for an awakened PhotonView, the view should add itself into the networkingPeer.photonViewList after setup
bool viewMustRegister = this.didAwake && this.viewIdField == 0;
// TODO: decide if a viewID can be changed once it wasn't 0. most likely that is not a good idea
// check if this view is in networkingPeer.photonViewList and UPDATE said list (so we don't keep the old viewID with a reference to this object)
// PhotonNetwork.networkingPeer.RemovePhotonView(this, true);
this.ownerId = value / PhotonNetwork.MAX_VIEW_IDS;
this.viewIdField = value;
if (viewMustRegister)
{
PhotonNetwork.networkingPeer.RegisterPhotonView(this);
}
//Debug.Log("Set viewID: " + value + " -> owner: " + this.ownerId + " subId: " + this.subId);
}
}
public int instantiationId; // if the view was instantiated with a GO, this GO has a instantiationID (first view's viewID)
/// True if the PhotonView was loaded with the scene (game object) or instantiated with InstantiateSceneObject.
///
/// Scene objects are not owned by a particular player but belong to the scene. Thus they don't get destroyed when their
/// creator leaves the game and the current Master Client can control them (whoever that is).
/// The ownerId is 0 (player IDs are 1 and up).
///
public bool isSceneView
{
get { return this.CreatorActorNr == 0; }
}
///
/// The owner of a PhotonView is the player who created the GameObject with that view. Objects in the scene don't have an owner.
///
///
/// The owner/controller of a PhotonView is also the client which sends position updates of the GameObject.
///
/// Ownership can be transferred to another player with PhotonView.TransferOwnership or any player can request
/// ownership by calling the PhotonView's RequestOwnership method.
/// The current owner has to implement IPunCallbacks.OnOwnershipRequest to react to the ownership request.
///
public PhotonPlayer owner
{
get
{
return PhotonPlayer.Find(this.ownerId);
}
}
public int OwnerActorNr
{
get { return this.ownerId; }
}
public bool isOwnerActive
{
get { return this.ownerId != 0 && PhotonNetwork.networkingPeer.mActors.ContainsKey(this.ownerId); }
}
public int CreatorActorNr
{
get { return this.viewIdField / PhotonNetwork.MAX_VIEW_IDS; }
}
///
/// True if the PhotonView is "mine" and can be controlled by this client.
///
///
/// PUN has an ownership concept that defines who can control and destroy each PhotonView.
/// True in case the owner matches the local PhotonPlayer.
/// True if this is a scene photonview on the Master client.
///
public bool isMine
{
get
{
return (this.ownerId == PhotonNetwork.player.ID) || (!this.isOwnerActive && PhotonNetwork.isMasterClient);
}
}
///
/// The current master ID so that we can compare when we receive OnMasterClientSwitched() callback
/// It's public so that we can check it during ownerId assignments in networkPeer script
/// TODO: Maybe we can have the networkPeer always aware of the previous MasterClient?
///
public int currentMasterID = -1;
protected internal bool didAwake;
[SerializeField]
protected internal bool isRuntimeInstantiated;
protected internal bool removedFromLocalViewList;
internal MonoBehaviour[] RpcMonoBehaviours;
private MethodInfo OnSerializeMethodInfo;
private bool failedToFindOnSerialize;
/// Called by Unity on start of the application and does a setup the PhotonView.
protected internal void Awake()
{
if (this.viewID != 0)
{
// registration might be too late when some script (on this GO) searches this view BUT GetPhotonView() can search ALL in that case
PhotonNetwork.networkingPeer.RegisterPhotonView(this);
this.instantiationDataField = PhotonNetwork.networkingPeer.FetchInstantiationData(this.instantiationId);
}
this.didAwake = true;
}
///
/// Depending on the PhotonView's ownershipTransfer setting, any client can request to become owner of the PhotonView.
///
///
/// Requesting ownership can give you control over a PhotonView, if the ownershipTransfer setting allows that.
/// The current owner might have to implement IPunCallbacks.OnOwnershipRequest to react to the ownership request.
///
/// The owner/controller of a PhotonView is also the client which sends position updates of the GameObject.
///
public void RequestOwnership()
{
PhotonNetwork.networkingPeer.RequestOwnership(this.viewID, this.ownerId);
}
///
/// Transfers the ownership of this PhotonView (and GameObject) to another player.
///
///
/// The owner/controller of a PhotonView is also the client which sends position updates of the GameObject.
///
public void TransferOwnership(PhotonPlayer newOwner)
{
this.TransferOwnership(newOwner.ID);
}
///
/// Transfers the ownership of this PhotonView (and GameObject) to another player.
///
///
/// The owner/controller of a PhotonView is also the client which sends position updates of the GameObject.
///
public void TransferOwnership(int newOwnerId)
{
PhotonNetwork.networkingPeer.TransferOwnership(this.viewID, newOwnerId);
this.ownerId = newOwnerId; // immediately switch ownership locally, to avoid more updates sent from this client.
}
///
///Check ownerId assignment for sceneObjects to keep being owned by the MasterClient.
///
/// New master client.
public void OnMasterClientSwitched(PhotonPlayer newMasterClient)
{
if (this.CreatorActorNr == 0 && !this.OwnerShipWasTransfered && (this.currentMasterID== -1 || this.ownerId==this.currentMasterID))
{
this.ownerId = newMasterClient.ID;
}
this.currentMasterID = newMasterClient.ID;
}
protected internal void OnDestroy()
{
if (!this.removedFromLocalViewList)
{
bool wasInList = PhotonNetwork.networkingPeer.LocalCleanPhotonView(this);
bool loading = false;
#if !UNITY_5 || UNITY_5_0 || UNITY_5_1
loading = Application.isLoadingLevel;
#endif
if (wasInList && !loading && this.instantiationId > 0 && !PhotonHandler.AppQuits && PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
{
Debug.Log("PUN-instantiated '" + this.gameObject.name + "' got destroyed by engine. This is OK when loading levels. Otherwise use: PhotonNetwork.Destroy().");
}
}
}
public void SerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (this.ObservedComponents != null && this.ObservedComponents.Count > 0)
{
for (int i = 0; i < this.ObservedComponents.Count; ++i)
{
SerializeComponent(this.ObservedComponents[i], stream, info);
}
}
}
public void DeserializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (this.ObservedComponents != null && this.ObservedComponents.Count > 0)
{
for (int i = 0; i < this.ObservedComponents.Count; ++i)
{
DeserializeComponent(this.ObservedComponents[i], stream, info);
}
}
}
protected internal void DeserializeComponent(Component component, PhotonStream stream, PhotonMessageInfo info)
{
if (component == null)
{
return;
}
// Use incoming data according to observed type
if (component is MonoBehaviour)
{
ExecuteComponentOnSerialize(component, stream, info);
}
else if (component is Transform)
{
Transform trans = (Transform) component;
switch (this.onSerializeTransformOption)
{
case OnSerializeTransform.All:
trans.localPosition = (Vector3) stream.ReceiveNext();
trans.localRotation = (Quaternion) stream.ReceiveNext();
trans.localScale = (Vector3) stream.ReceiveNext();
break;
case OnSerializeTransform.OnlyPosition:
trans.localPosition = (Vector3) stream.ReceiveNext();
break;
case OnSerializeTransform.OnlyRotation:
trans.localRotation = (Quaternion) stream.ReceiveNext();
break;
case OnSerializeTransform.OnlyScale:
trans.localScale = (Vector3) stream.ReceiveNext();
break;
case OnSerializeTransform.PositionAndRotation:
trans.localPosition = (Vector3) stream.ReceiveNext();
trans.localRotation = (Quaternion) stream.ReceiveNext();
break;
}
}
else if (component is Rigidbody)
{
Rigidbody rigidB = (Rigidbody) component;
switch (this.onSerializeRigidBodyOption)
{
case OnSerializeRigidBody.All:
rigidB.velocity = (Vector3) stream.ReceiveNext();
rigidB.angularVelocity = (Vector3) stream.ReceiveNext();
break;
case OnSerializeRigidBody.OnlyAngularVelocity:
rigidB.angularVelocity = (Vector3) stream.ReceiveNext();
break;
case OnSerializeRigidBody.OnlyVelocity:
rigidB.velocity = (Vector3) stream.ReceiveNext();
break;
}
}
else if (component is Rigidbody2D)
{
Rigidbody2D rigidB = (Rigidbody2D) component;
switch (this.onSerializeRigidBodyOption)
{
case OnSerializeRigidBody.All:
rigidB.velocity = (Vector2) stream.ReceiveNext();
rigidB.angularVelocity = (float) stream.ReceiveNext();
break;
case OnSerializeRigidBody.OnlyAngularVelocity:
rigidB.angularVelocity = (float) stream.ReceiveNext();
break;
case OnSerializeRigidBody.OnlyVelocity:
rigidB.velocity = (Vector2) stream.ReceiveNext();
break;
}
}
else
{
Debug.LogError("Type of observed is unknown when receiving.");
}
}
protected internal void SerializeComponent(Component component, PhotonStream stream, PhotonMessageInfo info)
{
if (component == null)
{
return;
}
if (component is MonoBehaviour)
{
ExecuteComponentOnSerialize(component, stream, info);
}
else if (component is Transform)
{
Transform trans = (Transform) component;
switch (this.onSerializeTransformOption)
{
case OnSerializeTransform.All:
stream.SendNext(trans.localPosition);
stream.SendNext(trans.localRotation);
stream.SendNext(trans.localScale);
break;
case OnSerializeTransform.OnlyPosition:
stream.SendNext(trans.localPosition);
break;
case OnSerializeTransform.OnlyRotation:
stream.SendNext(trans.localRotation);
break;
case OnSerializeTransform.OnlyScale:
stream.SendNext(trans.localScale);
break;
case OnSerializeTransform.PositionAndRotation:
stream.SendNext(trans.localPosition);
stream.SendNext(trans.localRotation);
break;
}
}
else if (component is Rigidbody)
{
Rigidbody rigidB = (Rigidbody) component;
switch (this.onSerializeRigidBodyOption)
{
case OnSerializeRigidBody.All:
stream.SendNext(rigidB.velocity);
stream.SendNext(rigidB.angularVelocity);
break;
case OnSerializeRigidBody.OnlyAngularVelocity:
stream.SendNext(rigidB.angularVelocity);
break;
case OnSerializeRigidBody.OnlyVelocity:
stream.SendNext(rigidB.velocity);
break;
}
}
else if (component is Rigidbody2D)
{
Rigidbody2D rigidB = (Rigidbody2D) component;
switch (this.onSerializeRigidBodyOption)
{
case OnSerializeRigidBody.All:
stream.SendNext(rigidB.velocity);
stream.SendNext(rigidB.angularVelocity);
break;
case OnSerializeRigidBody.OnlyAngularVelocity:
stream.SendNext(rigidB.angularVelocity);
break;
case OnSerializeRigidBody.OnlyVelocity:
stream.SendNext(rigidB.velocity);
break;
}
}
else
{
Debug.LogError("Observed type is not serializable: " + component.GetType());
}
}
protected internal void ExecuteComponentOnSerialize(Component component, PhotonStream stream, PhotonMessageInfo info)
{
IPunObservable observable = component as IPunObservable;
if (observable != null)
{
observable.OnPhotonSerializeView(stream, info);
}
else if (component != null)
{
MethodInfo method = null;
bool found = this.m_OnSerializeMethodInfos.TryGetValue(component, out method);
if (!found)
{
bool foundMethod = NetworkingPeer.GetMethod(component as MonoBehaviour, PhotonNetworkingMessage.OnPhotonSerializeView.ToString(), out method);
if (foundMethod == false)
{
Debug.LogError("The observed monobehaviour (" + component.name + ") of this PhotonView does not implement OnPhotonSerializeView()!");
method = null;
}
this.m_OnSerializeMethodInfos.Add(component, method);
}
if (method != null)
{
method.Invoke(component, new object[] {stream, info});
}
}
}
///
/// Can be used to refesh the list of MonoBehaviours on this GameObject while PhotonNetwork.UseRpcMonoBehaviourCache is true.
///
///
/// Set PhotonNetwork.UseRpcMonoBehaviourCache to true to enable the caching.
/// Uses this.GetComponents() to get a list of MonoBehaviours to call RPCs on (potentially).
///
/// While PhotonNetwork.UseRpcMonoBehaviourCache is false, this method has no effect,
/// because the list is refreshed when a RPC gets called.
///
public void RefreshRpcMonoBehaviourCache()
{
this.RpcMonoBehaviours = this.GetComponents();
}
///
/// Call a RPC method of this GameObject on remote clients of this room (or on all, inclunding this client).
///
///
/// [Remote Procedure Calls](@ref rpcManual) are an essential tool in making multiplayer games with PUN.
/// It enables you to make every client in a room call a specific method.
///
/// RPC calls can target "All" or the "Others".
/// Usually, the target "All" gets executed locally immediately after sending the RPC.
/// The "*ViaServer" options send the RPC to the server and execute it on this client when it's sent back.
/// Of course, calls are affected by this client's lag and that of remote clients.
///
/// Each call automatically is routed to the same PhotonView (and GameObject) that was used on the
/// originating client.
///
/// See: [Remote Procedure Calls](@ref rpcManual).
///
/// The name of a fitting method that was has the RPC attribute.
/// The group of targets and the way the RPC gets sent.
/// The parameters that the RPC method has (must fit this call!).
public void RPC(string methodName, PhotonTargets target, params object[] parameters)
{
PhotonNetwork.RPC(this, methodName, target, false, parameters);
}
///
/// Call a RPC method of this GameObject on remote clients of this room (or on all, inclunding this client).
///
///
/// [Remote Procedure Calls](@ref rpcManual) are an essential tool in making multiplayer games with PUN.
/// It enables you to make every client in a room call a specific method.
///
/// RPC calls can target "All" or the "Others".
/// Usually, the target "All" gets executed locally immediately after sending the RPC.
/// The "*ViaServer" options send the RPC to the server and execute it on this client when it's sent back.
/// Of course, calls are affected by this client's lag and that of remote clients.
///
/// Each call automatically is routed to the same PhotonView (and GameObject) that was used on the
/// originating client.
///
/// See: [Remote Procedure Calls](@ref rpcManual).
///
///The name of a fitting method that was has the RPC attribute.
///The group of targets and the way the RPC gets sent.
///
///The parameters that the RPC method has (must fit this call!).
public void RpcSecure(string methodName, PhotonTargets target, bool encrypt, params object[] parameters)
{
PhotonNetwork.RPC(this, methodName, target, encrypt, parameters);
}
///
/// Call a RPC method of this GameObject on remote clients of this room (or on all, inclunding this client).
///
///
/// [Remote Procedure Calls](@ref rpcManual) are an essential tool in making multiplayer games with PUN.
/// It enables you to make every client in a room call a specific method.
///
/// This method allows you to make an RPC calls on a specific player's client.
/// Of course, calls are affected by this client's lag and that of remote clients.
///
/// Each call automatically is routed to the same PhotonView (and GameObject) that was used on the
/// originating client.
///
/// See: [Remote Procedure Calls](@ref rpcManual).
///
/// The name of a fitting method that was has the RPC attribute.
/// The group of targets and the way the RPC gets sent.
/// The parameters that the RPC method has (must fit this call!).
public void RPC(string methodName, PhotonPlayer targetPlayer, params object[] parameters)
{
PhotonNetwork.RPC(this, methodName, targetPlayer, false, parameters);
}
///
/// Call a RPC method of this GameObject on remote clients of this room (or on all, inclunding this client).
///
///
/// [Remote Procedure Calls](@ref rpcManual) are an essential tool in making multiplayer games with PUN.
/// It enables you to make every client in a room call a specific method.
///
/// This method allows you to make an RPC calls on a specific player's client.
/// Of course, calls are affected by this client's lag and that of remote clients.
///
/// Each call automatically is routed to the same PhotonView (and GameObject) that was used on the
/// originating client.
///
/// See: [Remote Procedure Calls](@ref rpcManual).
///
///The name of a fitting method that was has the RPC attribute.
///The group of targets and the way the RPC gets sent.
///
///The parameters that the RPC method has (must fit this call!).
public void RpcSecure(string methodName, PhotonPlayer targetPlayer, bool encrypt, params object[] parameters)
{
PhotonNetwork.RPC(this, methodName, targetPlayer, encrypt, parameters);
}
public static PhotonView Get(Component component)
{
return component.GetComponent();
}
public static PhotonView Get(GameObject gameObj)
{
return gameObj.GetComponent();
}
public static PhotonView Find(int viewID)
{
return PhotonNetwork.networkingPeer.GetPhotonView(viewID);
}
public override string ToString()
{
return string.Format("View ({3}){0} on {1} {2}", this.viewID, (this.gameObject != null) ? this.gameObject.name : "GO==null", (this.isSceneView) ? "(scene)" : string.Empty, this.prefix);
}
}