// -------------------------------------------------------------------------------------------------------------------- // // Part of: Photon Unity Networking // // -------------------------------------------------------------------------------------------------------------------- using System.Diagnostics; using UnityEngine; using System; using System.Collections.Generic; using System.IO; using ExitGames.Client.Photon; using UnityEditor; using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; using Hashtable = ExitGames.Client.Photon.Hashtable; #if UNITY_EDITOR using UnityEditor; using System.IO; #endif /// /// The main class to use the PhotonNetwork plugin. /// This class is static. /// /// \ingroup publicApi public static class PhotonNetwork { /// Version number of PUN. Also used in GameVersion to separate client version from each other. public const string versionPUN = "1.80"; /// Version string for your this build. Can be used to separate incompatible clients. Sent during connect. /// This is only sent when you connect so that is also the place you set it usually (e.g. in ConnectUsingSettings). public static string gameVersion { get; set; } /// /// This Monobehaviour allows Photon to run an Update loop. /// internal static readonly PhotonHandler photonMono; /// /// Photon peer class that implements LoadBalancing in PUN. /// Primary use is internal (by PUN itself). /// internal static NetworkingPeer networkingPeer; /// /// The maximum number of assigned PhotonViews per player (or scene). See the [General Documentation](@ref general) topic "Limitations" on how to raise this limitation. /// public static readonly int MAX_VIEW_IDS = 1000; // VIEW & PLAYER LIMIT CAN BE EASILY CHANGED, SEE DOCS /// Name of the PhotonServerSettings file (used to load and by PhotonEditor to save new files). internal const string serverSettingsAssetFile = "PhotonServerSettings"; /// Path to the PhotonServerSettings file (used by PhotonEditor). internal const string serverSettingsAssetPath = "Assets/Photon Unity Networking/Resources/" + PhotonNetwork.serverSettingsAssetFile + ".asset"; /// Serialized server settings, written by the Setup Wizard for use in ConnectUsingSettings. public static ServerSettings PhotonServerSettings = (ServerSettings)Resources.Load(PhotonNetwork.serverSettingsAssetFile, typeof(ServerSettings)); /// Currently used server address (no matter if master or game server). public static string ServerAddress { get { return (networkingPeer != null) ? networkingPeer.ServerAddress : ""; } } /// /// False until you connected to Photon initially. True in offline mode, while connected to any server and even while switching servers. /// public static bool connected { get { if (offlineMode) { return true; } if (networkingPeer == null) { return false; } return !networkingPeer.IsInitialConnect && networkingPeer.State != ClientState.PeerCreated && networkingPeer.State != ClientState.Disconnected && networkingPeer.State != ClientState.Disconnecting && networkingPeer.State != ClientState.ConnectingToNameServer; } } /// /// True when you called ConnectUsingSettings (or similar) until the low level connection to Photon gets established. /// public static bool connecting { get { return networkingPeer.IsInitialConnect && !offlineMode; } } /// /// A refined version of connected which is true only if your connection to the server is ready to accept operations like join, leave, etc. /// public static bool connectedAndReady { get { // connected property will check offlineMode and networkingPeer being null if (!connected) { return false; } if (offlineMode) { return true; } switch (connectionStateDetailed) { case ClientState.PeerCreated: case ClientState.Disconnected: case ClientState.Disconnecting: case ClientState.Authenticating: case ClientState.ConnectingToGameserver: case ClientState.ConnectingToMasterserver: case ClientState.ConnectingToNameServer: case ClientState.Joining: return false; // we are not ready to execute any operations } return true; } } /// /// Simplified connection state /// public static ConnectionState connectionState { get { if (offlineMode) { return ConnectionState.Connected; } if (networkingPeer == null) { return ConnectionState.Disconnected; } switch (networkingPeer.PeerState) { case PeerStateValue.Disconnected: return ConnectionState.Disconnected; case PeerStateValue.Connecting: return ConnectionState.Connecting; case PeerStateValue.Connected: return ConnectionState.Connected; case PeerStateValue.Disconnecting: return ConnectionState.Disconnecting; case PeerStateValue.InitializingApplication: return ConnectionState.InitializingApplication; } return ConnectionState.Disconnected; } } /// /// Detailed connection state (ignorant of PUN, so it can be "disconnected" while switching servers). /// /// /// In OfflineMode, this is ClientState.Joined (after create/join) or it is ConnectedToMaster in all other cases. /// public static ClientState connectionStateDetailed { get { if (offlineMode) { return (offlineModeRoom != null) ? ClientState.Joined : ClientState.ConnectedToMaster; } if (networkingPeer == null) { return ClientState.Disconnected; } return networkingPeer.State; } } /// The server (type) this client is currently connected or connecting to. /// Photon uses 3 different roles of servers: Name Server, Master Server and Game Server. public static ServerConnection Server { get { return (PhotonNetwork.networkingPeer != null) ? PhotonNetwork.networkingPeer.Server : ServerConnection.NameServer; } } /// /// A user's authentication values used during connect. /// /// /// Set these before calling Connect if you want custom authentication. /// These values set the userId, if and how that userId gets verified (server-side), etc.. /// /// If authentication fails for any values, PUN will call your implementation of OnCustomAuthenticationFailed(string debugMsg). /// See: PhotonNetworkingMessage.OnCustomAuthenticationFailed /// public static AuthenticationValues AuthValues { get { return (networkingPeer != null) ? networkingPeer.AuthValues : null; } set { if (networkingPeer != null) networkingPeer.AuthValues = value; } } /// /// Get the room we're currently in. Null if we aren't in any room. /// public static Room room { get { if (isOfflineMode) { return offlineModeRoom; } return networkingPeer.CurrentRoom; } } /// If true, Instantiate methods will check if you are in a room and fail if you are not. /// /// Instantiating anything outside of a specific room is very likely to break things. /// Turn this off only if you know what you do. public static bool InstantiateInRoomOnly = true; /// /// Network log level. Controls how verbose PUN is. /// public static PhotonLogLevel logLevel = PhotonLogLevel.ErrorsOnly; /// /// The local PhotonPlayer. Always available and represents this player. /// CustomProperties can be set before entering a room and will be synced as well. /// public static PhotonPlayer player { get { if (networkingPeer == null) { return null; // Surpress ExitApplication errors } return networkingPeer.LocalPlayer; } } /// /// The Master Client of the current room or null (outside of rooms). /// /// /// Can be used as "authoritative" client/player to make descisions, run AI or other. /// /// If the current Master Client leaves the room (leave/disconnect), the server will quickly assign someone else. /// If the current Master Client times out (closed app, lost connection, etc), messages sent to this client are /// effectively lost for the others! A timeout can take 10 seconds in which no Master Client is active. /// /// Implement the method IPunCallbacks.OnMasterClientSwitched to be called when the Master Client switched. /// /// Use PhotonNetwork.SetMasterClient, to switch manually to some other player / client. /// /// With offlineMode == true, this always returns the PhotonNetwork.player. /// public static PhotonPlayer masterClient { get { if (offlineMode) { return PhotonNetwork.player; } if (networkingPeer == null) { return null; } return networkingPeer.GetPlayerWithId(networkingPeer.mMasterClientId); } } /// /// Set to synchronize the player's nickname with everyone in the room(s) you enter. This sets PhotonNetwork.player.NickName. /// /// /// The playerName is just a nickname and does not have to be unique or backed up with some account.
/// Set the value any time (e.g. before you connect) and it will be available to everyone you play with.
/// Access the names of players by: PhotonPlayer.NickName.
/// PhotonNetwork.otherPlayers is a list of other players - each contains the playerName the remote player set. ///
public static string playerName { get { return networkingPeer.PlayerName; } set { networkingPeer.PlayerName = value; } } /// The list of players in the current room, including the local player. /// /// This list is only valid, while the client is in a room. /// It automatically gets updated when someone joins or leaves. /// /// This can be used to list all players in a room. /// Each player's PhotonPlayer.customProperties are accessible (set and synchronized via /// PhotonPlayer.SetCustomProperties). /// /// You can use a PhotonPlayer.TagObject to store an arbitrary object for reference. /// That is not synchronized via the network. /// public static PhotonPlayer[] playerList { get { if (networkingPeer == null) return new PhotonPlayer[0]; return networkingPeer.mPlayerListCopy; } } /// The list of players in the current room, excluding the local player. /// /// This list is only valid, while the client is in a room. /// It automatically gets updated when someone joins or leaves. /// /// This can be used to list all other players in a room. /// Each player's PhotonPlayer.customProperties are accessible (set and synchronized via /// PhotonPlayer.SetCustomProperties). /// /// You can use a PhotonPlayer.TagObject to store an arbitrary object for reference. /// That is not synchronized via the network. /// public static PhotonPlayer[] otherPlayers { get { if (networkingPeer == null) return new PhotonPlayer[0]; return networkingPeer.mOtherPlayerListCopy; } } /// /// Read-only list of friends, their online status and the room they are in. Null until initialized by a FindFriends call. /// /// /// Do not modify this list! /// It is internally handled by FindFriends and only available to read the values. /// The value of FriendListAge tells you how old the data is in milliseconds. /// /// Don't get this list more often than useful (> 10 seconds). In best case, keep the list you fetch really short. /// You could (e.g.) get the full list only once, then request a few updates only for friends who are online. /// After a while (e.g. 1 minute), you can get the full list again (to update online states). /// public static List Friends { get; internal set; } /// /// Age of friend list info (in milliseconds). It's 0 until a friend list is fetched. /// public static int FriendsListAge { get { return (networkingPeer != null) ? networkingPeer.FriendListAge : 0; } } /// /// The minimum difference that a Vector2 or Vector3(e.g. a transforms rotation) needs to change before we send it via a PhotonView's OnSerialize/ObservingComponent. /// /// /// Note that this is the sqrMagnitude. E.g. to send only after a 0.01 change on the Y-axix, we use 0.01f*0.01f=0.0001f. As a remedy against float inaccuracy we use 0.000099f instead of 0.0001f. /// public static float precisionForVectorSynchronization = 0.000099f; /// /// The minimum angle that a rotation needs to change before we send it via a PhotonView's OnSerialize/ObservingComponent. /// public static float precisionForQuaternionSynchronization = 1.0f; /// /// The minimum difference between floats before we send it via a PhotonView's OnSerialize/ObservingComponent. /// public static float precisionForFloatSynchronization = 0.01f; /// /// While enabled, the MonoBehaviours on which we call RPCs are cached, avoiding costly GetComponents() calls. /// /// /// RPCs are called on the MonoBehaviours of a target PhotonView. Those have to be found via GetComponents. /// /// When set this to true, the list of MonoBehaviours gets cached in each PhotonView. /// You can use photonView.RefreshRpcMonoBehaviourCache() to manually refresh a PhotonView's /// list of MonoBehaviours on demand (when a new MonoBehaviour gets added to a networked GameObject, e.g.). /// public static bool UseRpcMonoBehaviourCache; /// /// While enabled (true), Instantiate uses PhotonNetwork.PrefabCache to keep game objects in memory (improving instantiation of the same prefab). /// /// /// Setting UsePrefabCache to false during runtime will not clear PrefabCache but will ignore it right away. /// You could clean and modify the cache yourself. Read its comments. /// public static bool UsePrefabCache = true; /// /// An Object Pool can be used to keep and reuse instantiated object instances. It replaced Unity's default Instantiate and Destroy methods. /// /// /// To use a GameObject pool, implement IPunPrefabPool and assign it here. /// Prefabs are identified by name. /// public static IPunPrefabPool PrefabPool { get { return networkingPeer.ObjectPool; } set { networkingPeer.ObjectPool = value; }} /// /// Keeps references to GameObjects for frequent instantiation (out of memory instead of loading the Resources). /// /// /// You should be able to modify the cache anytime you like, except while Instantiate is used. Best do it only in the main-Thread. /// public static Dictionary PrefabCache = new Dictionary(); /// /// If not null, this is the (exclusive) list of GameObjects that get called by PUN SendMonoMessage(). /// /// /// For all callbacks defined in PhotonNetworkingMessage, PUN will use SendMonoMessage and /// call FindObjectsOfType() to find all scripts and GameObjects that might want a callback by PUN. /// /// PUN callbacks are not very frequent (in-game, property updates are most frequent) but /// FindObjectsOfType is time consuming and with a large number of GameObjects, performance might /// suffer. /// /// Optionally, SendMonoMessageTargets can be used to supply a list of target GameObjects. This /// skips the FindObjectsOfType() but any GameObject that needs callbacks will have to Add itself /// to this list. /// /// If null, the default behaviour is to do a SendMessage on each GameObject with a MonoBehaviour. /// public static HashSet SendMonoMessageTargets; /// /// Defines which classes can contain PUN Callback implementations. /// /// /// This provides the option to optimize your runtime for speed.
/// The more specific this Type is, the fewer classes will be checked with reflection for callback methods. ///
public static Type SendMonoMessageTargetType = typeof(MonoBehaviour); /// /// Can be used to skip starting RPCs as Coroutine, which can be a performance issue. /// public static bool StartRpcsAsCoroutine = true; /// /// Offline mode can be set to re-use your multiplayer code in singleplayer game modes. /// When this is on PhotonNetwork will not create any connections and there is near to /// no overhead. Mostly usefull for reusing RPC's and PhotonNetwork.Instantiate /// public static bool offlineMode { get { return isOfflineMode; } set { if (value == isOfflineMode) { return; } if (value && connected) { Debug.LogError("Can't start OFFLINE mode while connected!"); return; } if (networkingPeer.PeerState != PeerStateValue.Disconnected) { networkingPeer.Disconnect(); // Cleanup (also calls OnLeftRoom to reset stuff) } isOfflineMode = value; if (isOfflineMode) { networkingPeer.ChangeLocalID(-1); NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnConnectedToMaster); } else { offlineModeRoom = null; networkingPeer.ChangeLocalID(-1); } } } private static bool isOfflineMode = false; private static Room offlineModeRoom = null; /// Only used in Unity Networking. In PUN, set the number of players in PhotonNetwork.CreateRoom. [Obsolete("Used for compatibility with Unity networking only.")] public static int maxConnections; /// Defines if all clients in a room should load the same level as the Master Client (if that used PhotonNetwork.LoadLevel). /// /// To synchronize the loaded level, the Master Client should use PhotonNetwork.LoadLevel. /// All clients will load the new scene when they get the update or when they join. /// /// Internally, a Custom Room Property is set for the loaded scene. When a client reads that /// and is not in the same scene yet, it will immediately pause the Message Queue /// (PhotonNetwork.isMessageQueueRunning = false) and load. When the scene finished loading, /// PUN will automatically re-enable the Message Queue. /// public static bool automaticallySyncScene { get { return _mAutomaticallySyncScene; } set { _mAutomaticallySyncScene = value; if (_mAutomaticallySyncScene && room != null) { networkingPeer.LoadLevelIfSynced(); } } } private static bool _mAutomaticallySyncScene = false; /// /// This setting defines per room, if network-instantiated GameObjects (with PhotonView) get cleaned up when the creator of it leaves. /// /// /// This setting is done per room. It can't be changed in the room and it will override the settings of individual clients. /// /// If room.AutoCleanUp is enabled in a room, the PUN clients will destroy a player's GameObjects on leave. /// This includes GameObjects manually instantiated (via RPCs, e.g.). /// When enabled, the server will clean RPCs, instantiated GameObjects and PhotonViews of the leaving player, too. and /// Players who join after someone left, won't get the events of that player anymore. /// /// Under the hood, this setting is stored as a Custom Room Property. /// Enabled by default. /// public static bool autoCleanUpPlayerObjects { get { return m_autoCleanUpPlayerObjects; } set { if (room != null) Debug.LogError("Setting autoCleanUpPlayerObjects while in a room is not supported."); else m_autoCleanUpPlayerObjects = value; } } private static bool m_autoCleanUpPlayerObjects = true; /// /// Set in PhotonServerSettings asset. Defines if the PhotonNetwork should join the "lobby" when connected to the Master server. /// /// /// If this is false, OnConnectedToMaster() will be called when connection to the Master is available. /// OnJoinedLobby() will NOT be called if this is false. /// /// Enabled by default. /// /// The room listing will not become available. /// Rooms can be created and joined (randomly) without joining the lobby (and getting sent the room list). /// public static bool autoJoinLobby { get { return PhotonNetwork.PhotonServerSettings.JoinLobby; } set { PhotonNetwork.PhotonServerSettings.JoinLobby = value; } } /// /// Set in PhotonServerSettings asset. Enable to get a list of active lobbies from the Master Server. /// /// /// Lobby Statistics can be useful if a game uses multiple lobbies and you want /// to show activity of each to players. /// /// This value is stored in PhotonServerSettings. /// /// PhotonNetwork.LobbyStatistics is updated when you connect to the Master Server. /// There is also a callback PunBehaviour. /// public static bool EnableLobbyStatistics { get { return PhotonNetwork.PhotonServerSettings.EnableLobbyStatistics; } set { PhotonNetwork.PhotonServerSettings.EnableLobbyStatistics = value; } } /// /// If turned on, the Master Server will provide information about active lobbies for this application. /// /// /// Lobby Statistics can be useful if a game uses multiple lobbies and you want /// to show activity of each to players. Per lobby, you get: name, type, room- and player-count. /// /// PhotonNetwork.LobbyStatistics is updated when you connect to the Master Server. /// There is also a callback PunBehaviour.OnLobbyStatisticsUpdate, which you should implement /// to update your UI (e.g.). /// /// Lobby Statistics are not turned on by default. /// Enable them in the PhotonServerSettings file of the project. /// public static List LobbyStatistics { get { return PhotonNetwork.networkingPeer.LobbyStatistics; } // only available to reset the state conveniently. done by state updates of PUN private set { PhotonNetwork.networkingPeer.LobbyStatistics = value; } } /// True while this client is in a lobby. /// /// Implement IPunCallbacks.OnReceivedRoomListUpdate() for a notification when the list of rooms /// becomes available or updated. /// /// You are automatically leaving any lobby when you join a room! /// Lobbies only exist on the Master Server (whereas rooms are handled by Game Servers). /// public static bool insideLobby { get { return networkingPeer.insideLobby; } } /// /// The lobby that will be used when PUN joins a lobby or creates a game. /// /// /// The default lobby uses an empty string as name. /// PUN will enter a lobby on the Master Server if autoJoinLobby is set to true. /// So when you connect or leave a room, PUN automatically gets you into a lobby again. /// /// Check PhotonNetwork.insideLobby if the client is in a lobby. /// (@ref masterServerAndLobby) /// public static TypedLobby lobby { get { return networkingPeer.lobby; } set { networkingPeer.lobby = value; } } /// /// Defines how many times per second PhotonNetwork should send a package. If you change /// this, do not forget to also change 'sendRateOnSerialize'. /// /// /// Less packages are less overhead but more delay. /// Setting the sendRate to 50 will create up to 50 packages per second (which is a lot!). /// Keep your target platform in mind: mobile networks are slower and less reliable. /// public static int sendRate { get { return 1000 / sendInterval; } set { sendInterval = 1000 / value; if (photonMono != null) { photonMono.updateInterval = sendInterval; } if (value < sendRateOnSerialize) { // sendRateOnSerialize needs to be <= sendRate sendRateOnSerialize = value; } } } /// /// Defines how many times per second OnPhotonSerialize should be called on PhotonViews. /// /// /// Choose this value in relation to PhotonNetwork.sendRate. OnPhotonSerialize will create updates and messages to be sent.
/// A lower rate takes up less performance but will cause more lag. ///
public static int sendRateOnSerialize { get { return 1000 / sendIntervalOnSerialize; } set { if (value > sendRate) { Debug.LogError("Error: Can not set the OnSerialize rate higher than the overall SendRate."); value = sendRate; } sendIntervalOnSerialize = 1000 / value; if (photonMono != null) { photonMono.updateIntervalOnSerialize = sendIntervalOnSerialize; } } } private static int sendInterval = 50; // in miliseconds. private static int sendIntervalOnSerialize = 100; // in miliseconds. I.e. 100 = 100ms which makes 10 times/second /// /// Can be used to pause dispatching of incoming evtents (RPCs, Instantiates and anything else incoming). /// /// /// While IsMessageQueueRunning == false, the OnPhotonSerializeView calls are not done and nothing is sent by /// a client. Also, incoming messages will be queued until you re-activate the message queue. /// /// This can be useful if you first want to load a level, then go on receiving data of PhotonViews and RPCs. /// The client will go on receiving and sending acknowledgements for incoming packages and your RPCs/Events. /// This adds "lag" and can cause issues when the pause is longer, as all incoming messages are just queued. /// public static bool isMessageQueueRunning { get { return m_isMessageQueueRunning; } set { if (value) PhotonHandler.StartFallbackSendAckThread(); networkingPeer.IsSendingOnlyAcks = !value; m_isMessageQueueRunning = value; } } /// Backup for property isMessageQueueRunning. private static bool m_isMessageQueueRunning = true; /// /// Used once per dispatch to limit unreliable commands per channel (so after a pause, many channels can still cause a lot of unreliable commands) /// public static int unreliableCommandsLimit { get { return networkingPeer.LimitOfUnreliableCommands; } set { networkingPeer.LimitOfUnreliableCommands = value; } } /// /// Photon network time, synched with the server. /// /// /// v1.55
/// This time value depends on the server's Environment.TickCount. It is different per server /// but inside a Room, all clients should have the same value (Rooms are on one server only).
/// This is not a DateTime!
/// /// Use this value with care:
/// It can start with any positive value.
/// It will "wrap around" from 4294967.295 to 0! ///
public static double time { get { uint u = (uint)ServerTimestamp; double t = u; return t / 1000; } } /// /// The current server's millisecond timestamp. /// /// /// This can be useful to sync actions and events on all clients in one room. /// The timestamp is based on the server's Environment.TickCount. /// /// It will overflow from a positive to a negative value every so often, so /// be careful to use only time-differences to check the time delta when things /// happen. /// /// This is the basis for PhotonNetwork.time. /// public static int ServerTimestamp { get { if (offlineMode) { if (UsePreciseTimer && startupStopwatch != null && startupStopwatch.IsRunning) { return (int)startupStopwatch.ElapsedMilliseconds; } return Environment.TickCount; } return networkingPeer.ServerTimeInMilliSeconds; } } /// If true, PUN will use a Stopwatch to measure time since start/connect. This is more precise than the Environment.TickCount used by default. private static bool UsePreciseTimer = false; static Stopwatch startupStopwatch; /// /// Defines how many seconds PUN keeps the connection, after Unity's OnApplicationPause(true) call. Default: 60 seconds. /// /// /// It's best practice to disconnect inactive apps/connections after a while but to also allow users to take calls, etc.. /// We think a reasonable backgroung timeout is 60 seconds. /// /// To handle the timeout, implement: OnDisconnectedFromPhoton(), as usual. /// Your application will "notice" the background disconnect when it becomes active again (running the Update() loop). /// /// If you need to separate this case from others, you need to track if the app was in the background /// (there is no special callback by PUN). /// /// A value below 0.1 seconds will disable this timeout (careful: connections can be kept indefinitely). /// /// /// Info: /// PUN is running a "fallback thread" to send ACKs to the server, even when Unity is not calling Update() regularly. /// This helps keeping the connection while loading scenes and assets and when the app is in the background. /// /// Note: /// Some platforms (e.g. iOS) don't allow to keep a connection while the app is in background. /// In those cases, this value does not change anything, the app immediately loses connection in background. /// /// Unity's OnApplicationPause() callback is broken in some exports (Android) of some Unity versions. /// Make sure OnApplicationPause() gets the callbacks you'd expect on the platform you target! /// Check PhotonHandler.OnApplicationPause(bool pause), to see the implementation. /// public static float BackgroundTimeout = 60.0f; /// /// Are we the master client? /// public static bool isMasterClient { get { if (offlineMode) { return true; } else { return networkingPeer.mMasterClientId == player.ID; } } } /// Is true while being in a room (connectionStateDetailed == ClientState.Joined). /// /// Many actions can only be executed in a room, like Instantiate or Leave, etc. /// You can join a room in offline mode, too. /// public static bool inRoom { get { // in offline mode, you can be in a room too and connectionStateDetailed then returns Joined like on online mode! return connectionStateDetailed == ClientState.Joined; } } /// /// True if we are in a room (client) and NOT the room's masterclient /// public static bool isNonMasterClientInRoom { get { return !isMasterClient && room != null; } } /// /// The count of players currently looking for a room (available on MasterServer in 5sec intervals). /// public static int countOfPlayersOnMaster { get { return networkingPeer.PlayersOnMasterCount; } } /// /// Count of users currently playing your app in some room (sent every 5sec by Master Server). Use playerList.Count to get the count of players in the room you're in! /// public static int countOfPlayersInRooms { get { return networkingPeer.PlayersInRoomsCount; } } /// /// The count of players currently using this application (available on MasterServer in 5sec intervals). /// public static int countOfPlayers { get { return networkingPeer.PlayersInRoomsCount + networkingPeer.PlayersOnMasterCount; } } /// /// The count of rooms currently in use (available on MasterServer in 5sec intervals). /// /// /// While inside the lobby you can also check the count of listed rooms as: PhotonNetwork.GetRoomList().Length. /// Since PUN v1.25 this is only based on the statistic event Photon sends (counting all rooms). /// public static int countOfRooms { get { return networkingPeer.RoomsCount; } } /// /// Enables or disables the collection of statistics about this client's traffic. /// /// /// If you encounter issues with clients, the traffic stats are a good starting point to find solutions. /// Only with enabled stats, you can use GetVitalStats /// public static bool NetworkStatisticsEnabled { get { return networkingPeer.TrafficStatsEnabled; } set { networkingPeer.TrafficStatsEnabled = value; } } /// /// Count of commands that got repeated (due to local repeat-timing before an ACK was received). /// /// /// If this value increases a lot, there is a good chance that a timeout disconnect will happen due to bad conditions. /// public static int ResentReliableCommands { get { return networkingPeer.ResentReliableCommands; } } /// Crc checks can be useful to detect and avoid issues with broken datagrams. Can be enabled while not connected. public static bool CrcCheckEnabled { get { return networkingPeer.CrcEnabled; } set { if (!connected && !connecting) { networkingPeer.CrcEnabled = value; } else { Debug.Log("Can't change CrcCheckEnabled while being connected. CrcCheckEnabled stays " + networkingPeer.CrcEnabled); } } } /// If CrcCheckEnabled, this counts the incoming packages that don't have a valid CRC checksum and got rejected. public static int PacketLossByCrcCheck { get { return networkingPeer.PacketLossByCrc; } } /// Defines the number of times a reliable message can be resent before not getting an ACK for it will trigger a disconnect. Default: 5. /// Less resends mean quicker disconnects, while more can lead to much more lag without helping. Min: 3. Max: 10. public static int MaxResendsBeforeDisconnect { get { return networkingPeer.SentCountAllowance; } set { if (value < 3) value = 3; if (value > 10) value = 10; networkingPeer.SentCountAllowance = value; } } /// In case of network loss, reliable messages can be repeated quickly up to 3 times. /// /// When reliable messages get lost more than once, subsequent repeats are delayed a bit /// to allow the network to recover.
/// With this option, the repeats 2 and 3 can be sped up. This can help avoid timeouts but /// also it increases the speed in which gaps are closed.
/// When you set this, increase PhotonNetwork.MaxResendsBeforeDisconnect to 6 or 7. ///
public static int QuickResends { get { return networkingPeer.QuickResendAttempts; } set { if (value < 0) value = 0; if (value > 3) value = 3; networkingPeer.QuickResendAttempts = (byte)value; } } /// /// Defines the delegate usable in OnEventCall. /// /// Any eventCode < 200 will be forwarded to your delegate(s). /// The code assigend to the incoming event. /// The content the sender put into the event. /// The ID of the player who sent the event. It might be 0, if the "room" sent the event. public delegate void EventCallback(byte eventCode, object content, int senderId); /// Register your RaiseEvent handling methods here by using "+=". /// Any eventCode < 200 will be forwarded to your delegate(s). /// public static EventCallback OnEventCall; internal static int lastUsedViewSubId = 0; // each player only needs to remember it's own (!) last used subId to speed up assignment internal static int lastUsedViewSubIdStatic = 0; // per room, the master is able to instantiate GOs. the subId for this must be unique too internal static List manuallyAllocatedViewIds = new List(); /// /// Static constructor used for basic setup. /// static PhotonNetwork() { #if UNITY_EDITOR if (PhotonServerSettings == null) { // create PhotonServerSettings CreateSettings(); } if (!EditorApplication.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode) { //Debug.Log(string.Format("PhotonNetwork.ctor() Not playing {0} {1}", UnityEditor.EditorApplication.isPlaying, UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)); return; } // This can happen when you recompile a script IN play made // This helps to surpress some errors, but will not fix breaking PhotonHandler[] photonHandlers = GameObject.FindObjectsOfType(typeof(PhotonHandler)) as PhotonHandler[]; if (photonHandlers != null && photonHandlers.Length > 0) { Debug.LogWarning("Unity recompiled. Connection gets closed and replaced. You can connect as 'new' client."); foreach (PhotonHandler photonHandler in photonHandlers) { //Debug.Log("Handler: " + photonHandler + " photonHandler.gameObject: " + photonHandler.gameObject); photonHandler.gameObject.hideFlags = 0; GameObject.DestroyImmediate(photonHandler.gameObject); Component.DestroyImmediate(photonHandler); } } #endif if (PhotonServerSettings != null) { Application.runInBackground = PhotonServerSettings.RunInBackground; } // Set up a MonoBehaviour to run Photon, and hide it GameObject photonGO = new GameObject(); photonMono = (PhotonHandler)photonGO.AddComponent(); photonGO.name = "PhotonMono"; photonGO.hideFlags = HideFlags.HideInHierarchy; // Set up the NetworkingPeer and use protocol of PhotonServerSettings ConnectionProtocol protocol = PhotonNetwork.PhotonServerSettings.Protocol; networkingPeer = new NetworkingPeer(string.Empty, protocol); networkingPeer.QuickResendAttempts = 2; networkingPeer.SentCountAllowance = 7; #if UNITY_XBOXONE Debug.Log("UNITY_XBOXONE is defined: Using AuthMode 'AuthOnceWss' and EncryptionMode 'DatagramEncryption'."); if (!PhotonPeer.NativeDatagramEncrypt) { Debug.LogError("XB1 builds need a Photon3Unity3d.dll which uses the native PhotonEncryptorPlugin. This dll does not!"); } networkingPeer.AuthMode = AuthModeOption.AuthOnceWss; networkingPeer.EncryptionMode = EncryptionMode.DatagramEncryption; #endif if (UsePreciseTimer) { Debug.Log("Using Stopwatch as precision timer for PUN."); startupStopwatch = new Stopwatch(); startupStopwatch.Start(); networkingPeer.LocalMsTimestampDelegate = () => (int)startupStopwatch.ElapsedMilliseconds; } // Local player CustomTypes.Register(); } /// /// While offline, the network protocol can be switched (which affects the ports you can use to connect). /// /// /// When you switch the protocol, make sure to also switch the port for the master server. Default ports are: /// TCP: 4530 /// UDP: 5055 /// /// This could look like this:
/// Connect(serverAddress, , appID, gameVersion) /// /// Or when you use ConnectUsingSettings(), the PORT in the settings can be switched like so:
/// PhotonNetwork.PhotonServerSettings.ServerPort = 4530; /// /// The current protocol can be read this way:
/// PhotonNetwork.networkingPeer.UsedProtocol /// /// This does not work with the native socket plugin of PUN+ on mobile! ///
/// Network protocol to use as low level connection. UDP is default. TCP is not available on all platforms (see remarks). public static void SwitchToProtocol(ConnectionProtocol cp) { // Debug.Log("SwitchToProtocol: " + cp + " PhotonNetwork.connected: " + PhotonNetwork.connected); networkingPeer.TransportProtocol = cp; } /// Connect to Photon as configured in the editor (saved in PhotonServerSettings file). /// /// This method will disable offlineMode (which won't destroy any instantiated GOs) and it /// will set isMessageQueueRunning to true. /// /// Your server configuration is created by the PUN Wizard and contains the AppId and /// region for Photon Cloud games and the server address if you host Photon yourself. /// These settings usually don't change often. /// /// To ignore the config file and connect anywhere call: PhotonNetwork.ConnectToMaster. /// /// To connect to the Photon Cloud, a valid AppId must be in the settings file (shown in the Photon Cloud Dashboard). /// https://www.photonengine.com/dashboard /// /// Connecting to the Photon Cloud might fail due to: /// - Invalid AppId (calls: OnFailedToConnectToPhoton(). check exact AppId value) /// - Network issues (calls: OnFailedToConnectToPhoton()) /// - Invalid region (calls: OnConnectionFail() with DisconnectCause.InvalidRegion) /// - Subscription CCU limit reached (calls: OnConnectionFail() with DisconnectCause.MaxCcuReached. also calls: OnPhotonMaxCccuReached()) /// /// More about the connection limitations: /// http://doc.exitgames.com/en/pun /// /// This client's version number. Users are separated from each other by gameversion (which allows you to make breaking changes). public static bool ConnectUsingSettings(string gameVersion) { if (networkingPeer.PeerState != PeerStateValue.Disconnected) { Debug.LogWarning("ConnectUsingSettings() failed. Can only connect while in state 'Disconnected'. Current state: " + networkingPeer.PeerState); return false; } if (PhotonServerSettings == null) { Debug.LogError("Can't connect: Loading settings failed. ServerSettings asset must be in any 'Resources' folder as: " + serverSettingsAssetFile); return false; } if (PhotonServerSettings.HostType == ServerSettings.HostingOption.NotSet) { Debug.LogError("You did not select a Hosting Type in your PhotonServerSettings. Please set it up or don't use ConnectUsingSettings()."); return false; } PhotonNetwork.logLevel = PhotonServerSettings.PunLogging; PhotonNetwork.networkingPeer.DebugOut = PhotonServerSettings.NetworkLogging; SwitchToProtocol(PhotonServerSettings.Protocol); networkingPeer.SetApp(PhotonServerSettings.AppID, gameVersion); if (PhotonServerSettings.HostType == ServerSettings.HostingOption.OfflineMode) { offlineMode = true; return true; } if (offlineMode) { // someone can set offlineMode in code and then call ConnectUsingSettings() with non-offline settings. Warning for that case: Debug.LogWarning("ConnectUsingSettings() disabled the offline mode. No longer offline."); } offlineMode = false; // Cleanup offline mode isMessageQueueRunning = true; networkingPeer.IsInitialConnect = true; if (PhotonServerSettings.HostType == ServerSettings.HostingOption.SelfHosted) { networkingPeer.IsUsingNameServer = false; networkingPeer.MasterServerAddress = (PhotonServerSettings.ServerPort == 0) ? PhotonServerSettings.ServerAddress : PhotonServerSettings.ServerAddress + ":" + PhotonServerSettings.ServerPort; return networkingPeer.Connect(networkingPeer.MasterServerAddress, ServerConnection.MasterServer); } if (PhotonServerSettings.HostType == ServerSettings.HostingOption.BestRegion) { return ConnectToBestCloudServer(gameVersion); } return networkingPeer.ConnectToRegionMaster(PhotonServerSettings.PreferredRegion); } /// Connect to a Photon Master Server by address, port, appID and game(client) version. /// /// To connect to the Photon Cloud, a valid AppId must be in the settings file (shown in the Photon Cloud Dashboard). /// https://www.photonengine.com/dashboard /// /// Connecting to the Photon Cloud might fail due to: /// - Invalid AppId (calls: OnFailedToConnectToPhoton(). check exact AppId value) /// - Network issues (calls: OnFailedToConnectToPhoton()) /// - Invalid region (calls: OnConnectionFail() with DisconnectCause.InvalidRegion) /// - Subscription CCU limit reached (calls: OnConnectionFail() with DisconnectCause.MaxCcuReached. also calls: OnPhotonMaxCccuReached()) /// /// More about the connection limitations: /// http://doc.exitgames.com/en/pun /// /// The server's address (either your own or Photon Cloud address). /// The server's port to connect to. /// Your application ID (Photon Cloud provides you with a GUID for your game). /// This client's version number. Users are separated by gameversion (which allows you to make breaking changes). public static bool ConnectToMaster(string masterServerAddress, int port, string appID, string gameVersion) { if (networkingPeer.PeerState != PeerStateValue.Disconnected) { Debug.LogWarning("ConnectToMaster() failed. Can only connect while in state 'Disconnected'. Current state: " + networkingPeer.PeerState); return false; } if (offlineMode) { offlineMode = false; // Cleanup offline mode Debug.LogWarning("ConnectToMaster() disabled the offline mode. No longer offline."); } if (!isMessageQueueRunning) { isMessageQueueRunning = true; Debug.LogWarning("ConnectToMaster() enabled isMessageQueueRunning. Needs to be able to dispatch incoming messages."); } networkingPeer.SetApp(appID, gameVersion); networkingPeer.IsUsingNameServer = false; networkingPeer.IsInitialConnect = true; networkingPeer.MasterServerAddress = (port == 0) ? masterServerAddress : masterServerAddress + ":" + port; return networkingPeer.Connect(networkingPeer.MasterServerAddress, ServerConnection.MasterServer); } /// Can be used to reconnect to the master server after a disconnect. /// /// After losing connection, you can use this to connect a client to the region Master Server again. /// Cache the room name you're in and use ReJoin(roomname) to return to a game. /// Common use case: Press the Lock Button on a iOS device and you get disconnected immediately. /// public static bool Reconnect() { if (string.IsNullOrEmpty(networkingPeer.MasterServerAddress)) { Debug.LogWarning("Reconnect() failed. It seems the client wasn't connected before?! Current state: " + networkingPeer.PeerState); return false; } if (networkingPeer.PeerState != PeerStateValue.Disconnected) { Debug.LogWarning("Reconnect() failed. Can only connect while in state 'Disconnected'. Current state: " + networkingPeer.PeerState); return false; } if (offlineMode) { offlineMode = false; // Cleanup offline mode Debug.LogWarning("Reconnect() disabled the offline mode. No longer offline."); } if (!isMessageQueueRunning) { isMessageQueueRunning = true; Debug.LogWarning("Reconnect() enabled isMessageQueueRunning. Needs to be able to dispatch incoming messages."); } networkingPeer.IsUsingNameServer = false; networkingPeer.IsInitialConnect = false; return networkingPeer.ReconnectToMaster(); } /// When the client lost connection during gameplay, this method attempts to reconnect and rejoin the room. /// /// This method re-connects directly to the game server which was hosting the room PUN was in before. /// If the room was shut down in the meantime, PUN will call OnPhotonJoinRoomFailed and return this client to the Master Server. /// /// Check the return value, if this client will attempt a reconnect and rejoin (if the conditions are met). /// If ReconnectAndRejoin returns false, you can still attempt a Reconnect and ReJoin. /// /// Similar to PhotonNetwork.ReJoin, this requires you to use unique IDs per player (the UserID). /// /// False, if there is no known room or game server to return to. Then, this client does not attempt the ReconnectAndRejoin. public static bool ReconnectAndRejoin() { if (networkingPeer.PeerState != PeerStateValue.Disconnected) { Debug.LogWarning("ReconnectAndRejoin() failed. Can only connect while in state 'Disconnected'. Current state: " + networkingPeer.PeerState); return false; } if (offlineMode) { offlineMode = false; // Cleanup offline mode Debug.LogWarning("ReconnectAndRejoin() disabled the offline mode. No longer offline."); } if (string.IsNullOrEmpty(networkingPeer.GameServerAddress)) { Debug.LogWarning("ReconnectAndRejoin() failed. It seems the client wasn't connected to a game server before (no address)."); return false; } if (networkingPeer.enterRoomParamsCache == null) { Debug.LogWarning("ReconnectAndRejoin() failed. It seems the client doesn't have any previous room to re-join."); return false; } if (!isMessageQueueRunning) { isMessageQueueRunning = true; Debug.LogWarning("ReconnectAndRejoin() enabled isMessageQueueRunning. Needs to be able to dispatch incoming messages."); } networkingPeer.IsUsingNameServer = false; networkingPeer.IsInitialConnect = false; return networkingPeer.ReconnectAndRejoin(); } /// /// Connect to the Photon Cloud region with the lowest ping (on platforms that support Unity's Ping). /// /// /// Will save the result of pinging all cloud servers in PlayerPrefs. Calling this the first time can take +-2 seconds. /// The ping result can be overridden via PhotonNetwork.OverrideBestCloudServer(..) /// This call can take up to 2 seconds if it is the first time you are using this, all cloud servers will be pinged to check for the best region. /// /// The PUN Setup Wizard stores your appID in a settings file and applies a server address/port. /// To connect to the Photon Cloud, a valid AppId must be in the settings file (shown in the Photon Cloud Dashboard). /// https://www.photonengine.com/dashboard /// /// Connecting to the Photon Cloud might fail due to: /// - Invalid AppId (calls: OnFailedToConnectToPhoton(). check exact AppId value) /// - Network issues (calls: OnFailedToConnectToPhoton()) /// - Invalid region (calls: OnConnectionFail() with DisconnectCause.InvalidRegion) /// - Subscription CCU limit reached (calls: OnConnectionFail() with DisconnectCause.MaxCcuReached. also calls: OnPhotonMaxCccuReached()) /// /// More about the connection limitations: /// http://doc.exitgames.com/en/pun /// /// This client's version number. Users are separated from each other by gameversion (which allows you to make breaking changes). /// If this client is going to connect to cloud server based on ping. Even if true, this does not guarantee a connection but the attempt is being made. public static bool ConnectToBestCloudServer(string gameVersion) { if (networkingPeer.PeerState != PeerStateValue.Disconnected) { Debug.LogWarning("ConnectToBestCloudServer() failed. Can only connect while in state 'Disconnected'. Current state: " + networkingPeer.PeerState); return false; } if (PhotonServerSettings == null) { Debug.LogError("Can't connect: Loading settings failed. ServerSettings asset must be in any 'Resources' folder as: " + PhotonNetwork.serverSettingsAssetFile); return false; } if (PhotonServerSettings.HostType == ServerSettings.HostingOption.OfflineMode) { return PhotonNetwork.ConnectUsingSettings(gameVersion); } networkingPeer.IsInitialConnect = true; networkingPeer.SetApp(PhotonServerSettings.AppID, gameVersion); CloudRegionCode bestFromPrefs = PhotonHandler.BestRegionCodeInPreferences; if (bestFromPrefs != CloudRegionCode.none) { Debug.Log("Best region found in PlayerPrefs. Connecting to: " + bestFromPrefs); return networkingPeer.ConnectToRegionMaster(bestFromPrefs); } bool couldConnect = PhotonNetwork.networkingPeer.ConnectToNameServer(); return couldConnect; } /// /// Connects to the Photon Cloud region of choice. /// public static bool ConnectToRegion(CloudRegionCode region, string gameVersion) { if (networkingPeer.PeerState != PeerStateValue.Disconnected) { Debug.LogWarning("ConnectToRegion() failed. Can only connect while in state 'Disconnected'. Current state: " + networkingPeer.PeerState); return false; } if (PhotonServerSettings == null) { Debug.LogError("Can't connect: ServerSettings asset must be in any 'Resources' folder as: " + PhotonNetwork.serverSettingsAssetFile); return false; } if (PhotonServerSettings.HostType == ServerSettings.HostingOption.OfflineMode) { return PhotonNetwork.ConnectUsingSettings(gameVersion); } networkingPeer.IsInitialConnect = true; networkingPeer.SetApp(PhotonServerSettings.AppID, gameVersion); if (region != CloudRegionCode.none) { Debug.Log("ConnectToRegion: " + region); return networkingPeer.ConnectToRegionMaster(region); } return false; } /// Overwrites the region that is used for ConnectToBestCloudServer(string gameVersion). /// /// This will overwrite the result of pinging all cloud servers.
/// Use this to allow your users to save a manually selected region in the player preferences.
/// Note: You can also use PhotonNetwork.ConnectToRegion to (temporarily) connect to a specific region. ///
public static void OverrideBestCloudServer(CloudRegionCode region) { PhotonHandler.BestRegionCodeInPreferences = region; } /// Pings all cloud servers again to find the one with best ping (currently). public static void RefreshCloudServerRating() { throw new NotImplementedException("not available at the moment"); } /// /// Resets the traffic stats and re-enables them. /// public static void NetworkStatisticsReset() { networkingPeer.TrafficStatsReset(); } /// /// Only available when NetworkStatisticsEnabled was used to gather some stats. /// /// A string with vital networking statistics. public static string NetworkStatisticsToString() { if (networkingPeer == null || offlineMode) { return "Offline or in OfflineMode. No VitalStats available."; } return networkingPeer.VitalStatsToString(false); } /// /// Used for compatibility with Unity networking only. Encryption is automatically initialized while connecting. /// [Obsolete("Used for compatibility with Unity networking only. Encryption is automatically initialized while connecting.")] public static void InitializeSecurity() { return; } /// /// Helper function which is called inside this class to erify if certain functions can be used (e.g. RPC when not connected) /// /// private static bool VerifyCanUseNetwork() { if (connected) { return true; } Debug.LogError("Cannot send messages when not connected. Either connect to Photon OR use offline mode!"); return false; } /// /// Makes this client disconnect from the photon server, a process that leaves any room and calls OnDisconnectedFromPhoton on completion. /// /// /// When you disconnect, the client will send a "disconnecting" message to the server. This speeds up leave/disconnect /// messages for players in the same room as you (otherwise the server would timeout this client's connection). /// When used in offlineMode, the state-change and event-call OnDisconnectedFromPhoton are immediate. /// Offline mode is set to false as well. /// Once disconnected, the client can connect again. Use ConnectUsingSettings. /// public static void Disconnect() { if (offlineMode) { offlineMode = false; offlineModeRoom = null; networkingPeer.State = ClientState.Disconnecting; networkingPeer.OnStatusChanged(StatusCode.Disconnect); return; } if (networkingPeer == null) { return; // Surpress error when quitting playmode in the editor } networkingPeer.Disconnect(); } /// /// Requests the rooms and online status for a list of friends and saves the result in PhotonNetwork.Friends. /// /// /// Works only on Master Server to find the rooms played by a selected list of users. /// /// The result will be stored in PhotonNetwork.Friends when available. /// That list is initialized on first use of OpFindFriends (before that, it is null). /// To refresh the list, call FindFriends again (in 5 seconds or 10 or 20). /// /// Users identify themselves by setting a unique userId in the PhotonNetwork.AuthValues. /// See remarks of AuthenticationValues for info about how this is set and used. /// /// The list of friends must be fetched from some other source (not provided by Photon). /// /// /// Internal: /// The server response includes 2 arrays of info (each index matching a friend from the request): /// ParameterCode.FindFriendsResponseOnlineList = bool[] of online states /// ParameterCode.FindFriendsResponseRoomIdList = string[] of room names (empty string if not in a room) /// /// Array of friend (make sure to use unique playerName or AuthValues). /// If the operation could be sent (requires connection, only one request is allowed at any time). Always false in offline mode. public static bool FindFriends(string[] friendsToFind) { if (networkingPeer == null || isOfflineMode) { return false; } return networkingPeer.OpFindFriends(friendsToFind); } /// /// Creates a room with given name but fails if this room(name) is existing already. Creates random name for roomName null. /// /// /// If you don't want to create a unique room-name, pass null or "" as name and the server will assign a roomName (a GUID as string). /// /// The created room is automatically placed in the currently used lobby (if any) or the default-lobby if you didn't explicitly join one. /// /// Call this only on the master server. /// Internally, the master will respond with a server-address (and roomName, if needed). Both are used internally /// to switch to the assigned game server and roomName. /// /// PhotonNetwork.autoCleanUpPlayerObjects will become this room's AutoCleanUp property and that's used by all clients that join this room. /// /// Unique name of the room to create. /// If the operation got queued and will be sent. public static bool CreateRoom(string roomName) { return CreateRoom(roomName, null, null, null); } /// /// Creates a room but fails if this room is existing already. Can only be called on Master Server. /// /// /// When successful, this calls the callbacks OnCreatedRoom and OnJoinedRoom (the latter, cause you join as first player). /// If the room can't be created (because it exists already), OnPhotonCreateRoomFailed gets called. /// /// If you don't want to create a unique room-name, pass null or "" as name and the server will assign a roomName (a GUID as string). /// /// Rooms can be created in any number of lobbies. Those don't have to exist before you create a room in them (they get /// auto-created on demand). Lobbies can be useful to split room lists on the server-side already. That can help keep the room /// lists short and manageable. /// If you set a typedLobby parameter, the room will be created in that lobby (no matter if you are active in any). /// If you don't set a typedLobby, the room is automatically placed in the currently active lobby (if any) or the /// default-lobby. /// /// Call this only on the master server. /// Internally, the master will respond with a server-address (and roomName, if needed). Both are used internally /// to switch to the assigned game server and roomName. /// /// PhotonNetwork.autoCleanUpPlayerObjects will become this room's autoCleanUp property and that's used by all clients that join this room. /// /// Unique name of the room to create. Pass null or "" to make the server generate a name. /// Common options for the room like MaxPlayers, initial custom room properties and similar. See RoomOptions type.. /// If null, the room is automatically created in the currently used lobby (which is "default" when you didn't join one explicitly). /// If the operation got queued and will be sent. public static bool CreateRoom(string roomName, RoomOptions roomOptions, TypedLobby typedLobby) { return CreateRoom(roomName, roomOptions, typedLobby, null); } /// /// Creates a room but fails if this room is existing already. Can only be called on Master Server. /// /// /// When successful, this calls the callbacks OnCreatedRoom and OnJoinedRoom (the latter, cause you join as first player). /// If the room can't be created (because it exists already), OnPhotonCreateRoomFailed gets called. /// /// If you don't want to create a unique room-name, pass null or "" as name and the server will assign a roomName (a GUID as string). /// /// Rooms can be created in any number of lobbies. Those don't have to exist before you create a room in them (they get /// auto-created on demand). Lobbies can be useful to split room lists on the server-side already. That can help keep the room /// lists short and manageable. /// If you set a typedLobby parameter, the room will be created in that lobby (no matter if you are active in any). /// If you don't set a typedLobby, the room is automatically placed in the currently active lobby (if any) or the /// default-lobby. /// /// Call this only on the master server. /// Internally, the master will respond with a server-address (and roomName, if needed). Both are used internally /// to switch to the assigned game server and roomName. /// /// PhotonNetwork.autoCleanUpPlayerObjects will become this room's autoCleanUp property and that's used by all clients that join this room. /// /// You can define an array of expectedUsers, to block player slots in the room for these users. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. /// /// Unique name of the room to create. Pass null or "" to make the server generate a name. /// Common options for the room like MaxPlayers, initial custom room properties and similar. See RoomOptions type.. /// If null, the room is automatically created in the currently used lobby (which is "default" when you didn't join one explicitly). /// Optional list of users (by UserId) who are expected to join this game and who you want to block a slot for. /// If the operation got queued and will be sent. public static bool CreateRoom(string roomName, RoomOptions roomOptions, TypedLobby typedLobby, string[] expectedUsers) { if (offlineMode) { if (offlineModeRoom != null) { Debug.LogError("CreateRoom failed. In offline mode you still have to leave a room to enter another."); return false; } EnterOfflineRoom(roomName, roomOptions, true); return true; } if (networkingPeer.Server != ServerConnection.MasterServer || !connectedAndReady) { Debug.LogError("CreateRoom failed. Client is not on Master Server or not yet ready to call operations. Wait for callback: OnJoinedLobby or OnConnectedToMaster."); return false; } typedLobby = typedLobby ?? ((networkingPeer.insideLobby) ? networkingPeer.lobby : null); // use given lobby, or active lobby (if any active) or none EnterRoomParams opParams = new EnterRoomParams(); opParams.RoomName = roomName; opParams.RoomOptions = roomOptions; opParams.Lobby = typedLobby; opParams.ExpectedUsers = expectedUsers; return networkingPeer.OpCreateGame(opParams); } /// Join room by roomname and on success calls OnJoinedRoom(). This is not affected by lobbies. /// /// On success, the method OnJoinedRoom() is called on any script. You can implement it to react to joining a room. /// /// JoinRoom fails if the room is either full or no longer available (it might become empty while you attempt to join). /// Implement OnPhotonJoinRoomFailed() to get a callback in error case. /// /// To join a room from the lobby's listing, use RoomInfo.Name as roomName here. /// Despite using multiple lobbies, a roomName is always "global" for your application and so you don't /// have to specify which lobby it's in. The Master Server will find the room. /// In the Photon Cloud, an application is defined by AppId, Game- and PUN-version. /// /// /// /// Unique name of the room to join. /// If the operation got queued and will be sent. public static bool JoinRoom(string roomName) { return JoinRoom(roomName, null); } /// Join room by roomname and on success calls OnJoinedRoom(). This is not affected by lobbies. /// /// On success, the method OnJoinedRoom() is called on any script. You can implement it to react to joining a room. /// /// JoinRoom fails if the room is either full or no longer available (it might become empty while you attempt to join). /// Implement OnPhotonJoinRoomFailed() to get a callback in error case. /// /// To join a room from the lobby's listing, use RoomInfo.Name as roomName here. /// Despite using multiple lobbies, a roomName is always "global" for your application and so you don't /// have to specify which lobby it's in. The Master Server will find the room. /// In the Photon Cloud, an application is defined by AppId, Game- and PUN-version. /// /// You can define an array of expectedUsers, to block player slots in the room for these users. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. /// /// /// /// Unique name of the room to join. /// Optional list of users (by UserId) who are expected to join this game and who you want to block a slot for. /// If the operation got queued and will be sent. public static bool JoinRoom(string roomName, string[] expectedUsers) { if (offlineMode) { if (offlineModeRoom != null) { Debug.LogError("JoinRoom failed. In offline mode you still have to leave a room to enter another."); return false; } EnterOfflineRoom(roomName, null, true); return true; } if (networkingPeer.Server != ServerConnection.MasterServer || !connectedAndReady) { Debug.LogError("JoinRoom failed. Client is not on Master Server or not yet ready to call operations. Wait for callback: OnJoinedLobby or OnConnectedToMaster."); return false; } if (string.IsNullOrEmpty(roomName)) { Debug.LogError("JoinRoom failed. A roomname is required. If you don't know one, how will you join?"); return false; } EnterRoomParams opParams = new EnterRoomParams(); opParams.RoomName = roomName; opParams.ExpectedUsers = expectedUsers; return networkingPeer.OpJoinRoom(opParams); } /// Lets you either join a named room or create it on the fly - you don't have to know if someone created the room already. /// /// This makes it easier for groups of players to get into the same room. Once the group /// exchanged a roomName, any player can call JoinOrCreateRoom and it doesn't matter who /// actually joins or creates the room. /// /// The parameters roomOptions and typedLobby are only used when the room actually gets created by this client. /// You know if this client created a room, if you get a callback OnCreatedRoom (before OnJoinedRoom gets called as well). /// /// Name of the room to join. Must be non null. /// Options for the room, in case it does not exist yet. Else these values are ignored. /// Lobby you want a new room to be listed in. Ignored if the room was existing and got joined. /// If the operation got queued and will be sent. public static bool JoinOrCreateRoom(string roomName, RoomOptions roomOptions, TypedLobby typedLobby) { return JoinOrCreateRoom(roomName, roomOptions, typedLobby, null); } /// Lets you either join a named room or create it on the fly - you don't have to know if someone created the room already. /// /// This makes it easier for groups of players to get into the same room. Once the group /// exchanged a roomName, any player can call JoinOrCreateRoom and it doesn't matter who /// actually joins or creates the room. /// /// The parameters roomOptions and typedLobby are only used when the room actually gets created by this client. /// You know if this client created a room, if you get a callback OnCreatedRoom (before OnJoinedRoom gets called as well). /// /// You can define an array of expectedUsers, to block player slots in the room for these users. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. /// /// Name of the room to join. Must be non null. /// Options for the room, in case it does not exist yet. Else these values are ignored. /// Lobby you want a new room to be listed in. Ignored if the room was existing and got joined. /// Optional list of users (by UserId) who are expected to join this game and who you want to block a slot for. /// If the operation got queued and will be sent. public static bool JoinOrCreateRoom(string roomName, RoomOptions roomOptions, TypedLobby typedLobby, string[] expectedUsers) { if (offlineMode) { if (offlineModeRoom != null) { Debug.LogError("JoinOrCreateRoom failed. In offline mode you still have to leave a room to enter another."); return false; } EnterOfflineRoom(roomName, roomOptions, true); // in offline mode, JoinOrCreateRoom assumes you create the room return true; } if (networkingPeer.Server != ServerConnection.MasterServer || !connectedAndReady) { Debug.LogError("JoinOrCreateRoom failed. Client is not on Master Server or not yet ready to call operations. Wait for callback: OnJoinedLobby or OnConnectedToMaster."); return false; } if (string.IsNullOrEmpty(roomName)) { Debug.LogError("JoinOrCreateRoom failed. A roomname is required. If you don't know one, how will you join?"); return false; } typedLobby = typedLobby ?? ((networkingPeer.insideLobby) ? networkingPeer.lobby : null); // use given lobby, or active lobby (if any active) or none EnterRoomParams opParams = new EnterRoomParams(); opParams.RoomName = roomName; opParams.RoomOptions = roomOptions; opParams.Lobby = typedLobby; opParams.CreateIfNotExists = true; opParams.PlayerProperties = player.CustomProperties; opParams.ExpectedUsers = expectedUsers; return networkingPeer.OpJoinRoom(opParams); } /// /// Joins any available room of the currently used lobby and fails if none is available. /// /// /// Rooms can be created in arbitrary lobbies which get created on demand. /// You can join rooms from any lobby without actually joining the lobby. /// Use the JoinRandomRoom overload with TypedLobby parameter. /// /// This method will only match rooms attached to one lobby! If you use many lobbies, you /// might have to repeat JoinRandomRoom, to find some fitting room. /// This method looks up a room in the currently active lobby or (if no lobby is joined) /// in the default lobby. /// /// If this fails, you can still create a room (and make this available for the next who uses JoinRandomRoom). /// Alternatively, try again in a moment. /// public static bool JoinRandomRoom() { return JoinRandomRoom(null, 0, MatchmakingMode.FillRoom, null, null); } /// /// Attempts to join an open room with fitting, custom properties but fails if none is currently available. /// /// /// Rooms can be created in arbitrary lobbies which get created on demand. /// You can join rooms from any lobby without actually joining the lobby. /// Use the JoinRandomRoom overload with TypedLobby parameter. /// /// This method will only match rooms attached to one lobby! If you use many lobbies, you /// might have to repeat JoinRandomRoom, to find some fitting room. /// This method looks up a room in the currently active lobby or (if no lobby is joined) /// in the default lobby. /// /// If this fails, you can still create a room (and make this available for the next who uses JoinRandomRoom). /// Alternatively, try again in a moment. /// /// Filters for rooms that match these custom properties (string keys and values). To ignore, pass null. /// Filters for a particular maxplayer setting. Use 0 to accept any maxPlayer value. /// If the operation got queued and will be sent. public static bool JoinRandomRoom(Hashtable expectedCustomRoomProperties, byte expectedMaxPlayers) { return JoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers, MatchmakingMode.FillRoom, null, null); } /// /// Attempts to join an open room with fitting, custom properties but fails if none is currently available. /// /// /// Rooms can be created in arbitrary lobbies which get created on demand. /// You can join rooms from any lobby without actually joining the lobby with this overload. /// /// This method will only match rooms attached to one lobby! If you use many lobbies, you /// might have to repeat JoinRandomRoom, to find some fitting room. /// This method looks up a room in the specified lobby or the currently active lobby (if none specified) /// or in the default lobby (if none active). /// /// If this fails, you can still create a room (and make this available for the next who uses JoinRandomRoom). /// Alternatively, try again in a moment. /// /// In offlineMode, a room will be created but no properties will be set and all parameters of this /// JoinRandomRoom call are ignored. The event/callback OnJoinedRoom gets called (see enum PhotonNetworkingMessage). /// /// You can define an array of expectedUsers, to block player slots in the room for these users. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. /// /// Filters for rooms that match these custom properties (string keys and values). To ignore, pass null. /// Filters for a particular maxplayer setting. Use 0 to accept any maxPlayer value. /// Selects one of the available matchmaking algorithms. See MatchmakingMode enum for options. /// The lobby in which you want to lookup a room. Pass null, to use the default lobby. This does not join that lobby and neither sets the lobby property. /// A filter-string for SQL-typed lobbies. /// Optional list of users (by UserId) who are expected to join this game and who you want to block a slot for. /// If the operation got queued and will be sent. public static bool JoinRandomRoom(Hashtable expectedCustomRoomProperties, byte expectedMaxPlayers, MatchmakingMode matchingType, TypedLobby typedLobby, string sqlLobbyFilter, string[] expectedUsers = null) { if (offlineMode) { if (offlineModeRoom != null) { Debug.LogError("JoinRandomRoom failed. In offline mode you still have to leave a room to enter another."); return false; } EnterOfflineRoom("offline room", null, true); return true; } if (networkingPeer.Server != ServerConnection.MasterServer || !connectedAndReady) { Debug.LogError("JoinRandomRoom failed. Client is not on Master Server or not yet ready to call operations. Wait for callback: OnJoinedLobby or OnConnectedToMaster."); return false; } typedLobby = typedLobby ?? ((networkingPeer.insideLobby) ? networkingPeer.lobby : null); // use given lobby, or active lobby (if any active) or none OpJoinRandomRoomParams opParams = new OpJoinRandomRoomParams(); opParams.ExpectedCustomRoomProperties = expectedCustomRoomProperties; opParams.ExpectedMaxPlayers = expectedMaxPlayers; opParams.MatchingType = matchingType; opParams.TypedLobby = typedLobby; opParams.SqlLobbyFilter = sqlLobbyFilter; opParams.ExpectedUsers = expectedUsers; return networkingPeer.OpJoinRandomRoom(opParams); } /// Can be used to return to a room after a disconnect and reconnect. /// /// After losing connection, you might be able to return to a room and continue playing, /// if the client is reconnecting fast enough. Use Reconnect() and this method. /// Cache the room name you're in and use ReJoin(roomname) to return to a game. /// /// Note: To be able to ReJoin any room, you need to use UserIDs! /// You also need to set RoomOptions.PlayerTtl. /// /// Important: Instantiate() and use of RPCs is not yet supported. /// The ownership rules of PhotonViews prevent a seamless return to a game. /// Use Custom Properties and RaiseEvent with event caching instead. /// /// Common use case: Press the Lock Button on a iOS device and you get disconnected immediately. /// public static bool ReJoinRoom(string roomName) { if (offlineMode) { Debug.LogError("ReJoinRoom failed due to offline mode."); return false; } if (networkingPeer.Server != ServerConnection.MasterServer || !connectedAndReady) { Debug.LogError("ReJoinRoom failed. Client is not on Master Server or not yet ready to call operations. Wait for callback: OnJoinedLobby or OnConnectedToMaster."); return false; } if (string.IsNullOrEmpty(roomName)) { Debug.LogError("ReJoinRoom failed. A roomname is required. If you don't know one, how will you join?"); return false; } EnterRoomParams opParams = new EnterRoomParams(); opParams.RoomName = roomName; opParams.RejoinOnly = true; opParams.PlayerProperties = player.CustomProperties; return networkingPeer.OpJoinRoom(opParams); } /// /// Internally used helper-method to setup an offline room, the numbers for actor and master-client and to do the callbacks. /// private static void EnterOfflineRoom(string roomName, RoomOptions roomOptions, bool createdRoom) { offlineModeRoom = new Room(roomName, roomOptions); networkingPeer.ChangeLocalID(1); offlineModeRoom.MasterClientId = 1; if (createdRoom) { NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnCreatedRoom); } NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnJoinedRoom); } /// On MasterServer this joins the default lobby which list rooms currently in use. /// /// The room list is sent and refreshed by the server. You can access this cached list by /// PhotonNetwork.GetRoomList(). /// /// Per room you should check if it's full or not before joining. Photon also lists rooms that are /// full, unless you close and hide them (room.open = false and room.visible = false). /// /// In best case, you make your clients join random games, as described here: /// http://doc.exitgames.com/en/realtime/current/reference/matchmaking-and-lobby /// /// /// You can show your current players and room count without joining a lobby (but you must /// be on the master server). Use: countOfPlayers, countOfPlayersOnMaster, countOfPlayersInRooms and /// countOfRooms. /// /// You can use more than one lobby to keep the room lists shorter. See JoinLobby(TypedLobby lobby). /// When creating new rooms, they will be "attached" to the currently used lobby or the default lobby. /// /// You can use JoinRandomRoom without being in a lobby! /// Set autoJoinLobby = false before you connect, to not join a lobby. In that case, the /// connect-workflow will call OnConnectedToMaster (if you implement it) when it's done. /// public static bool JoinLobby() { return JoinLobby(null); } /// On a Master Server you can join a lobby to get lists of available rooms. /// /// The room list is sent and refreshed by the server. You can access this cached list by /// PhotonNetwork.GetRoomList(). /// /// Any client can "make up" any lobby on the fly. Splitting rooms into multiple lobbies will /// keep each list shorter. However, having too many lists might ruin the matchmaking experience. /// /// In best case, you create a limited number of lobbies. For example, create a lobby per /// game-mode: "koth" for king of the hill and "ffa" for free for all, etc. /// /// There is no listing of lobbies at the moment. /// /// Sql-typed lobbies offer a different filtering model for random matchmaking. This might be more /// suited for skillbased-games. However, you will also need to follow the conventions for naming /// filterable properties in sql-lobbies! Both is explained in the matchmaking doc linked below. /// /// In best case, you make your clients join random games, as described here: /// http://confluence.exitgames.com/display/PTN/Op+JoinRandomGame /// /// /// Per room you should check if it's full or not before joining. Photon does list rooms that are /// full, unless you close and hide them (room.open = false and room.visible = false). /// /// You can show your games current players and room count without joining a lobby (but you must /// be on the master server). Use: countOfPlayers, countOfPlayersOnMaster, countOfPlayersInRooms and /// countOfRooms. /// /// When creating new rooms, they will be "attached" to the currently used lobby or the default lobby. /// /// You can use JoinRandomRoom without being in a lobby! /// Set autoJoinLobby = false before you connect, to not join a lobby. In that case, the /// connect-workflow will call OnConnectedToMaster (if you implement it) when it's done. /// /// A typed lobby to join (must have name and type). public static bool JoinLobby(TypedLobby typedLobby) { if (PhotonNetwork.connected && PhotonNetwork.Server == ServerConnection.MasterServer) { if (typedLobby == null) { typedLobby = TypedLobby.Default; } bool sending = networkingPeer.OpJoinLobby(typedLobby); if (sending) { networkingPeer.lobby = typedLobby; } return sending; } return false; } /// Leave a lobby to stop getting updates about available rooms. /// /// This does not reset PhotonNetwork.lobby! This allows you to join this particular lobby later /// easily. /// /// The values countOfPlayers, countOfPlayersOnMaster, countOfPlayersInRooms and countOfRooms /// are received even without being in a lobby. /// /// You can use JoinRandomRoom without being in a lobby. /// Use autoJoinLobby to not join a lobby when you connect. /// public static bool LeaveLobby() { if (PhotonNetwork.connected && PhotonNetwork.Server == ServerConnection.MasterServer) { return networkingPeer.OpLeaveLobby(); } return false; } /// Leave the current room and return to the Master Server where you can join or create rooms (see remarks). /// /// This will clean up all (network) GameObjects with a PhotonView, unless you changed autoCleanUp to false. /// Returns to the Master Server. /// /// In OfflineMode, the local "fake" room gets cleaned up and OnLeftRoom gets called immediately. /// public static bool LeaveRoom() { if (offlineMode) { offlineModeRoom = null; NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnLeftRoom); } else { if (room == null) { Debug.LogWarning("PhotonNetwork.room is null. You don't have to call LeaveRoom() when you're not in one. State: " + PhotonNetwork.connectionStateDetailed); } return networkingPeer.OpLeave(); } return true; } /// /// Gets currently known rooms as RoomInfo array. This is available and updated while in a lobby (check insideLobby). /// /// /// This list is a cached copy of the internal rooms list so it can be accessed each frame if needed. /// Per RoomInfo you can check if the room is full by comparing playerCount and MaxPlayers before you allow a join. /// /// The name of a room must be used to join it (via JoinRoom). /// /// Closed rooms are also listed by lobbies but they can't be joined. While in a room, any player can set /// Room.visible and Room.open to hide rooms from matchmaking and close them. /// /// RoomInfo[] of current rooms in lobby. public static RoomInfo[] GetRoomList() { if (offlineMode || networkingPeer == null) { return new RoomInfo[0]; } return networkingPeer.mGameListCopy; } /// /// Sets this (local) player's properties and synchronizes them to the other players (don't modify them directly). /// /// /// While in a room, your properties are synced with the other players. /// CreateRoom, JoinRoom and JoinRandomRoom will all apply your player's custom properties when you enter the room. /// The whole Hashtable will get sent. Minimize the traffic by setting only updated key/values. /// /// If the Hashtable is null, the custom properties will be cleared. /// Custom properties are never cleared automatically, so they carry over to the next room, if you don't change them. /// /// Don't set properties by modifying PhotonNetwork.player.customProperties! /// /// Only string-typed keys will be used from this hashtable. If null, custom properties are all deleted. public static void SetPlayerCustomProperties(Hashtable customProperties) { if (customProperties == null) { customProperties = new Hashtable(); foreach (object k in player.CustomProperties.Keys) { customProperties[(string)k] = null; } } if (room != null && room.IsLocalClientInside) { player.SetCustomProperties(customProperties); } else { player.InternalCacheProperties(customProperties); } } /// /// Locally removes Custom Properties of "this" player. Important: This does not synchronize the change! Useful when you switch rooms. /// /// /// Use this method with care. It can create inconsistencies of state between players! /// This only changes the player.customProperties locally. This can be useful to clear your /// Custom Properties between games (let's say they store which turn you made, kills, etc). /// /// SetPlayerCustomProperties() syncs and can be used to set values to null while in a room. /// That can be considered "removed" while in a room. /// /// If customPropertiesToDelete is null or has 0 entries, all Custom Properties are deleted (replaced with a new Hashtable). /// If you specify keys to remove, those will be removed from the Hashtable but other keys are unaffected. /// /// List of Custom Property keys to remove. See remarks. public static void RemovePlayerCustomProperties(string[] customPropertiesToDelete) { if (customPropertiesToDelete == null || customPropertiesToDelete.Length == 0 || player.CustomProperties == null) { player.CustomProperties = new Hashtable(); return; } // if a specific list of props should be deleted, we do that here for (int i = 0; i < customPropertiesToDelete.Length; i++) { string key = customPropertiesToDelete[i]; if (player.CustomProperties.ContainsKey(key)) { player.CustomProperties.Remove(key); } } } /// /// Sends fully customizable events in a room. Events consist of at least an EventCode (0..199) and can have content. /// /// /// To receive the events someone sends, register your handling method in PhotonNetwork.OnEventCall. /// /// Example: /// private void OnEventHandler(byte eventCode, object content, int senderId) /// { Debug.Log("OnEventHandler"); } /// /// PhotonNetwork.OnEventCall += this.OnEventHandler; /// /// With the senderId, you can look up the PhotonPlayer who sent the event. /// It is best practice to assign a eventCode for each different type of content and action. You have to cast the content. /// /// The eventContent is optional. To be able to send something, it must be a "serializable type", something that /// the client can turn into a byte[] basically. Most basic types and arrays of them are supported, including /// Unity's Vector2, Vector3, Quaternion. Transforms or classes some project defines are NOT supported! /// You can make your own class a "serializable type" by following the example in CustomTypes.cs. /// /// /// The RaiseEventOptions have some (less intuitive) combination rules: /// If you set targetActors (an array of PhotonPlayer.ID values), the receivers parameter gets ignored. /// When using event caching, the targetActors, receivers and interestGroup can't be used. Buffered events go to all. /// When using cachingOption removeFromRoomCache, the eventCode and content are actually not sent but used as filter. /// /// A byte identifying the type of event. You might want to use a code per action or to signal which content can be expected. Allowed: 0..199. /// Some serializable object like string, byte, integer, float (etc) and arrays of those. Hashtables with byte keys are good to send variable content. /// Makes sure this event reaches all players. It gets acknowledged, which requires bandwidth and it can't be skipped (might add lag in case of loss). /// Allows more complex usage of events. If null, RaiseEventOptions.Default will be used (which is fine). /// False if event could not be sent public static bool RaiseEvent(byte eventCode, object eventContent, bool sendReliable, RaiseEventOptions options) { if (!inRoom || eventCode >= 200) { Debug.LogWarning("RaiseEvent() failed. Your event is not being sent! Check if your are in a Room and the eventCode must be less than 200 (0..199)."); return false; } return networkingPeer.OpRaiseEvent(eventCode, eventContent, sendReliable, options); } /// /// Allocates a viewID that's valid for the current/local player. /// /// A viewID that can be used for a new PhotonView. public static int AllocateViewID() { int manualId = AllocateViewID(player.ID); manuallyAllocatedViewIds.Add(manualId); return manualId; } /// /// Enables the Master Client to allocate a viewID that is valid for scene objects. /// /// A viewID that can be used for a new PhotonView or -1 in case of an error. public static int AllocateSceneViewID() { if (!PhotonNetwork.isMasterClient) { Debug.LogError("Only the Master Client can AllocateSceneViewID(). Check PhotonNetwork.isMasterClient!"); return -1; } int manualId = AllocateViewID(0); manuallyAllocatedViewIds.Add(manualId); return manualId; } // use 0 for scene-targetPhotonView-ids // returns viewID (combined owner and sub id) private static int AllocateViewID(int ownerId) { if (ownerId == 0) { // we look up a fresh subId for the owner "room" (mind the "sub" in subId) int newSubId = lastUsedViewSubIdStatic; int newViewId; int ownerIdOffset = ownerId * MAX_VIEW_IDS; for (int i = 1; i < MAX_VIEW_IDS; i++) { newSubId = (newSubId + 1) % MAX_VIEW_IDS; if (newSubId == 0) { continue; // avoid using subID 0 } newViewId = newSubId + ownerIdOffset; if (!networkingPeer.photonViewList.ContainsKey(newViewId)) { lastUsedViewSubIdStatic = newSubId; return newViewId; } } // this is the error case: we didn't find any (!) free subId for this user throw new Exception(string.Format("AllocateViewID() failed. Room (user {0}) is out of 'scene' viewIDs. It seems all available are in use.", ownerId)); } else { // we look up a fresh SUBid for the owner int newSubId = lastUsedViewSubId; int newViewId; int ownerIdOffset = ownerId * MAX_VIEW_IDS; for (int i = 1; i < MAX_VIEW_IDS; i++) { newSubId = (newSubId + 1) % MAX_VIEW_IDS; if (newSubId == 0) { continue; // avoid using subID 0 } newViewId = newSubId + ownerIdOffset; if (!networkingPeer.photonViewList.ContainsKey(newViewId) && !manuallyAllocatedViewIds.Contains(newViewId)) { lastUsedViewSubId = newSubId; return newViewId; } } throw new Exception(string.Format("AllocateViewID() failed. User {0} is out of subIds, as all viewIDs are used.", ownerId)); } } private static int[] AllocateSceneViewIDs(int countOfNewViews) { int[] viewIDs = new int[countOfNewViews]; for (int view = 0; view < countOfNewViews; view++) { viewIDs[view] = AllocateViewID(0); } return viewIDs; } /// /// Unregister a viewID (of manually instantiated and destroyed networked objects). /// /// A viewID manually allocated by this player. public static void UnAllocateViewID(int viewID) { manuallyAllocatedViewIds.Remove(viewID); if (networkingPeer.photonViewList.ContainsKey(viewID)) { Debug.LogWarning(string.Format("UnAllocateViewID() should be called after the PhotonView was destroyed (GameObject.Destroy()). ViewID: {0} still found in: {1}", viewID, networkingPeer.photonViewList[viewID])); } } /// /// Instantiate a prefab over the network. This prefab needs to be located in the root of a "Resources" folder. /// /// /// Instead of using prefabs in the Resources folder, you can manually Instantiate and assign PhotonViews. See doc. /// /// Name of the prefab to instantiate. /// Position Vector3 to apply on instantiation. /// Rotation Quaternion to apply on instantiation. /// The group for this PhotonView. /// The new instance of a GameObject with initialized PhotonView. public static GameObject Instantiate(string prefabName, Vector3 position, Quaternion rotation, int group) { return Instantiate(prefabName, position, rotation, group, null); } /// /// Instantiate a prefab over the network. This prefab needs to be located in the root of a "Resources" folder. /// /// Instead of using prefabs in the Resources folder, you can manually Instantiate and assign PhotonViews. See doc. /// Name of the prefab to instantiate. /// Position Vector3 to apply on instantiation. /// Rotation Quaternion to apply on instantiation. /// The group for this PhotonView. /// Optional instantiation data. This will be saved to it's PhotonView.instantiationData. /// The new instance of a GameObject with initialized PhotonView. public static GameObject Instantiate(string prefabName, Vector3 position, Quaternion rotation, int group, object[] data) { if (!connected || (InstantiateInRoomOnly && !inRoom)) { Debug.LogError("Failed to Instantiate prefab: " + prefabName + ". Client should be in a room. Current connectionStateDetailed: " + PhotonNetwork.connectionStateDetailed); return null; } GameObject prefabGo; if (!UsePrefabCache || !PrefabCache.TryGetValue(prefabName, out prefabGo)) { prefabGo = (GameObject)Resources.Load(prefabName, typeof(GameObject)); if (UsePrefabCache) { PrefabCache.Add(prefabName, prefabGo); } } if (prefabGo == null) { Debug.LogError("Failed to Instantiate prefab: " + prefabName + ". Verify the Prefab is in a Resources folder (and not in a subfolder)"); return null; } // a scene object instantiated with network visibility has to contain a PhotonView if (prefabGo.GetComponent() == null) { Debug.LogError("Failed to Instantiate prefab:" + prefabName + ". Prefab must have a PhotonView component."); return null; } Component[] views = (Component[])prefabGo.GetPhotonViewsInChildren(); int[] viewIDs = new int[views.Length]; for (int i = 0; i < viewIDs.Length; i++) { //Debug.Log("Instantiate prefabName: " + prefabName + " player.ID: " + player.ID); viewIDs[i] = AllocateViewID(player.ID); } // Send to others, create info Hashtable instantiateEvent = networkingPeer.SendInstantiate(prefabName, position, rotation, group, viewIDs, data, false); // Instantiate the GO locally (but the same way as if it was done via event). This will also cache the instantiationId return networkingPeer.DoInstantiate(instantiateEvent, networkingPeer.LocalPlayer, prefabGo); } /// /// Instantiate a scene-owned prefab over the network. The PhotonViews will be controllable by the MasterClient. This prefab needs to be located in the root of a "Resources" folder. /// /// /// Only the master client can Instantiate scene objects. /// Instead of using prefabs in the Resources folder, you can manually Instantiate and assign PhotonViews. See doc. /// /// Name of the prefab to instantiate. /// Position Vector3 to apply on instantiation. /// Rotation Quaternion to apply on instantiation. /// The group for this PhotonView. /// Optional instantiation data. This will be saved to it's PhotonView.instantiationData. /// The new instance of a GameObject with initialized PhotonView. public static GameObject InstantiateSceneObject(string prefabName, Vector3 position, Quaternion rotation, int group, object[] data) { if (!connected || (InstantiateInRoomOnly && !inRoom)) { Debug.LogError("Failed to InstantiateSceneObject prefab: " + prefabName + ". Client should be in a room. Current connectionStateDetailed: " + PhotonNetwork.connectionStateDetailed); return null; } if (!isMasterClient) { Debug.LogError("Failed to InstantiateSceneObject prefab: " + prefabName + ". Client is not the MasterClient in this room."); return null; } GameObject prefabGo; if (!UsePrefabCache || !PrefabCache.TryGetValue(prefabName, out prefabGo)) { prefabGo = (GameObject)Resources.Load(prefabName, typeof(GameObject)); if (UsePrefabCache) { PrefabCache.Add(prefabName, prefabGo); } } if (prefabGo == null) { Debug.LogError("Failed to InstantiateSceneObject prefab: " + prefabName + ". Verify the Prefab is in a Resources folder (and not in a subfolder)"); return null; } // a scene object instantiated with network visibility has to contain a PhotonView if (prefabGo.GetComponent() == null) { Debug.LogError("Failed to InstantiateSceneObject prefab:" + prefabName + ". Prefab must have a PhotonView component."); return null; } Component[] views = (Component[])prefabGo.GetPhotonViewsInChildren(); int[] viewIDs = AllocateSceneViewIDs(views.Length); if (viewIDs == null) { Debug.LogError("Failed to InstantiateSceneObject prefab: " + prefabName + ". No ViewIDs are free to use. Max is: " + MAX_VIEW_IDS); return null; } // Send to others, create info Hashtable instantiateEvent = networkingPeer.SendInstantiate(prefabName, position, rotation, group, viewIDs, data, true); // Instantiate the GO locally (but the same way as if it was done via event). This will also cache the instantiationId return networkingPeer.DoInstantiate(instantiateEvent, networkingPeer.LocalPlayer, prefabGo); } /// /// The current roundtrip time to the photon server. /// /// Roundtrip time (to server and back). public static int GetPing() { return networkingPeer.RoundTripTime; } /// Refreshes the server timestamp (async operation, takes a roundtrip). /// Can be useful if a bad connection made the timestamp unusable or imprecise. public static void FetchServerTimestamp() { if (networkingPeer != null) { networkingPeer.FetchServerTimestamp(); } } /// /// Can be used to immediately send the RPCs and Instantiates just called, so they are on their way to the other players. /// /// /// This could be useful if you do a RPC to load a level and then load it yourself. /// While loading, no RPCs are sent to others, so this would delay the "load" RPC. /// You can send the RPC to "others", use this method, disable the message queue /// (by isMessageQueueRunning) and then load. /// public static void SendOutgoingCommands() { if (!VerifyCanUseNetwork()) { return; } while (networkingPeer.SendOutgoingCommands()) { } } /// Request a client to disconnect (KICK). Only the master client can do this /// Only the target player gets this event. That player will disconnect automatically, which is what the others will notice, too. /// The PhotonPlayer to kick. public static bool CloseConnection(PhotonPlayer kickPlayer) { if (!VerifyCanUseNetwork()) { return false; } if (!player.IsMasterClient) { Debug.LogError("CloseConnection: Only the masterclient can kick another player."); return false; } if (kickPlayer == null) { Debug.LogError("CloseConnection: No such player connected!"); return false; } RaiseEventOptions options = new RaiseEventOptions() { TargetActors = new int[] { kickPlayer.ID } }; return networkingPeer.OpRaiseEvent(PunEvent.CloseConnection, null, true, options); } /// /// Asks the server to assign another player as Master Client of your current room. /// /// /// RPCs and RaiseEvent have the option to send messages only to the Master Client of a room. /// SetMasterClient affects which client gets those messages. /// /// This method calls an operation on the server to set a new Master Client, which takes a roundtrip. /// In case of success, this client and the others get the new Master Client from the server. /// /// SetMasterClient tells the server which current Master Client should be replaced with the new one. /// It will fail, if anything switches the Master Client moments earlier. There is no callback for this /// error. All clients should get the new Master Client assigned by the server anyways. /// /// See also: PhotonNetwork.masterClient /// /// On v3 servers: /// The ReceiverGroup.MasterClient (usable in RPCs) is not affected by this (still points to lowest player.ID in room). /// Avoid using this enum value (and send to a specific player instead). /// /// If the current Master Client leaves, PUN will detect a new one by "lowest player ID". Implement OnMasterClientSwitched /// to get a callback in this case. The PUN-selected Master Client might assign a new one. /// /// Make sure you don't create an endless loop of Master-assigning! When selecting a custom Master Client, all clients /// should point to the same player, no matter who actually assigns this player. /// /// Locally the Master Client is immediately switched, while remote clients get an event. This means the game /// is tempoarily without Master Client like when a current Master Client leaves. /// /// When switching the Master Client manually, keep in mind that this user might leave and not do it's work, just like /// any Master Client. /// /// /// The player to become the next Master Client. /// False when this operation couldn't be done. Must be in a room (not in offlineMode). public static bool SetMasterClient(PhotonPlayer masterClientPlayer) { if (!inRoom || !VerifyCanUseNetwork() || offlineMode) { if (logLevel == PhotonLogLevel.Informational) Debug.Log("Can not SetMasterClient(). Not in room or in offlineMode."); return false; } if (room.serverSideMasterClient) { Hashtable newProps = new Hashtable() { { GamePropertyKey.MasterClientId, masterClientPlayer.ID } }; Hashtable prevProps = new Hashtable() { { GamePropertyKey.MasterClientId, networkingPeer.mMasterClientId } }; return networkingPeer.OpSetPropertiesOfRoom(newProps, expectedProperties: prevProps, webForward: false); } else { if (!isMasterClient) { return false; } return networkingPeer.SetMasterClient(masterClientPlayer.ID, true); } } /// /// Network-Destroy the GameObject associated with the PhotonView, unless the PhotonView is static or not under this client's control. /// /// /// Destroying a networked GameObject while in a Room includes: /// - Removal of the Instantiate call from the server's room buffer. /// - Removing RPCs buffered for PhotonViews that got created indirectly with the PhotonNetwork.Instantiate call. /// - Sending a message to other clients to remove the GameObject also (affected by network lag). /// /// Usually, when you leave a room, the GOs get destroyed automatically. /// If you have to destroy a GO while not in a room, the Destroy is only done locally. /// /// Destroying networked objects works only if they got created with PhotonNetwork.Instantiate(). /// Objects loaded with a scene are ignored, no matter if they have PhotonView components. /// /// The GameObject must be under this client's control: /// - Instantiated and owned by this client. /// - Instantiated objects of players who left the room are controlled by the Master Client. /// - Scene-owned game objects are controlled by the Master Client. /// - GameObject can be destroyed while client is not in a room. /// /// Nothing. Check error debug log for any issues. public static void Destroy(PhotonView targetView) { if (targetView != null) { networkingPeer.RemoveInstantiatedGO(targetView.gameObject, !inRoom); } else { Debug.LogError("Destroy(targetPhotonView) failed, cause targetPhotonView is null."); } } /// /// Network-Destroy the GameObject, unless it is static or not under this client's control. /// /// /// Destroying a networked GameObject includes: /// - Removal of the Instantiate call from the server's room buffer. /// - Removing RPCs buffered for PhotonViews that got created indirectly with the PhotonNetwork.Instantiate call. /// - Sending a message to other clients to remove the GameObject also (affected by network lag). /// /// Usually, when you leave a room, the GOs get destroyed automatically. /// If you have to destroy a GO while not in a room, the Destroy is only done locally. /// /// Destroying networked objects works only if they got created with PhotonNetwork.Instantiate(). /// Objects loaded with a scene are ignored, no matter if they have PhotonView components. /// /// The GameObject must be under this client's control: /// - Instantiated and owned by this client. /// - Instantiated objects of players who left the room are controlled by the Master Client. /// - Scene-owned game objects are controlled by the Master Client. /// - GameObject can be destroyed while client is not in a room. /// /// Nothing. Check error debug log for any issues. public static void Destroy(GameObject targetGo) { networkingPeer.RemoveInstantiatedGO(targetGo, !inRoom); } /// /// Network-Destroy all GameObjects, PhotonViews and their RPCs of targetPlayer. Can only be called on local player (for "self") or Master Client (for anyone). /// /// /// Destroying a networked GameObject includes: /// - Removal of the Instantiate call from the server's room buffer. /// - Removing RPCs buffered for PhotonViews that got created indirectly with the PhotonNetwork.Instantiate call. /// - Sending a message to other clients to remove the GameObject also (affected by network lag). /// /// Destroying networked objects works only if they got created with PhotonNetwork.Instantiate(). /// Objects loaded with a scene are ignored, no matter if they have PhotonView components. /// /// Nothing. Check error debug log for any issues. public static void DestroyPlayerObjects(PhotonPlayer targetPlayer) { if (player == null) { Debug.LogError("DestroyPlayerObjects() failed, cause parameter 'targetPlayer' was null."); } DestroyPlayerObjects(targetPlayer.ID); } /// /// Network-Destroy all GameObjects, PhotonViews and their RPCs of this player (by ID). Can only be called on local player (for "self") or Master Client (for anyone). /// /// /// Destroying a networked GameObject includes: /// - Removal of the Instantiate call from the server's room buffer. /// - Removing RPCs buffered for PhotonViews that got created indirectly with the PhotonNetwork.Instantiate call. /// - Sending a message to other clients to remove the GameObject also (affected by network lag). /// /// Destroying networked objects works only if they got created with PhotonNetwork.Instantiate(). /// Objects loaded with a scene are ignored, no matter if they have PhotonView components. /// /// Nothing. Check error debug log for any issues. public static void DestroyPlayerObjects(int targetPlayerId) { if (!VerifyCanUseNetwork()) { return; } if (player.IsMasterClient || targetPlayerId == player.ID) { networkingPeer.DestroyPlayerObjects(targetPlayerId, false); } else { Debug.LogError("DestroyPlayerObjects() failed, cause players can only destroy their own GameObjects. A Master Client can destroy anyone's. This is master: " + PhotonNetwork.isMasterClient); } } /// /// Network-Destroy all GameObjects, PhotonViews and their RPCs in the room. Removes anything buffered from the server. Can only be called by Master Client (for anyone). /// /// /// Can only be called by Master Client (for anyone). /// Unlike the Destroy methods, this will remove anything from the server's room buffer. If your game /// buffers anything beyond Instantiate and RPC calls, that will be cleaned as well from server. /// /// Destroying all includes: /// - Remove anything from the server's room buffer (Instantiate, RPCs, anything buffered). /// - Sending a message to other clients to destroy everything locally, too (affected by network lag). /// /// Destroying networked objects works only if they got created with PhotonNetwork.Instantiate(). /// Objects loaded with a scene are ignored, no matter if they have PhotonView components. /// /// Nothing. Check error debug log for any issues. public static void DestroyAll() { if (isMasterClient) { networkingPeer.DestroyAll(false); } else { Debug.LogError("Couldn't call DestroyAll() as only the master client is allowed to call this."); } } /// /// Remove all buffered RPCs from server that were sent by targetPlayer. Can only be called on local player (for "self") or Master Client (for anyone). /// /// /// This method requires either: /// - This is the targetPlayer's client. /// - This client is the Master Client (can remove any PhotonPlayer's RPCs). /// /// If the targetPlayer calls RPCs at the same time that this is called, /// network lag will determine if those get buffered or cleared like the rest. /// /// This player's buffered RPCs get removed from server buffer. public static void RemoveRPCs(PhotonPlayer targetPlayer) { if (!VerifyCanUseNetwork()) { return; } if (!targetPlayer.IsLocal && !isMasterClient) { Debug.LogError("Error; Only the MasterClient can call RemoveRPCs for other players."); return; } networkingPeer.OpCleanRpcBuffer(targetPlayer.ID); } /// /// Remove all buffered RPCs from server that were sent via targetPhotonView. The Master Client and the owner of the targetPhotonView may call this. /// /// /// This method requires either: /// - The targetPhotonView is owned by this client (Instantiated by it). /// - This client is the Master Client (can remove any PhotonView's RPCs). /// /// RPCs buffered for this PhotonView get removed from server buffer. public static void RemoveRPCs(PhotonView targetPhotonView) { if (!VerifyCanUseNetwork()) { return; } networkingPeer.CleanRpcBufferIfMine(targetPhotonView); } /// /// Remove all buffered RPCs from server that were sent in the targetGroup, if this is the Master Client or if this controls the individual PhotonView. /// /// /// This method requires either: /// - This client is the Master Client (can remove any RPCs per group). /// - Any other client: each PhotonView is checked if it is under this client's control. Only those RPCs are removed. /// /// Interest group that gets all RPCs removed. public static void RemoveRPCsInGroup(int targetGroup) { if (!VerifyCanUseNetwork()) { return; } networkingPeer.RemoveRPCsInGroup(targetGroup); } /// /// Internal to send an RPC on given PhotonView. Do not call this directly but use: PhotonView.RPC! /// internal static void RPC(PhotonView view, string methodName, PhotonTargets target, bool encrypt, params object[] parameters) { if (!VerifyCanUseNetwork()) { return; } if (room == null) { Debug.LogWarning("RPCs can only be sent in rooms. Call of \"" + methodName + "\" gets executed locally only, if at all."); return; } if (networkingPeer != null) { if (PhotonNetwork.room.serverSideMasterClient) { networkingPeer.RPC(view, methodName, target, null, encrypt, parameters); } else { if (PhotonNetwork.networkingPeer.hasSwitchedMC && target == PhotonTargets.MasterClient) { networkingPeer.RPC(view, methodName, PhotonTargets.Others, PhotonNetwork.masterClient, encrypt, parameters); } else { networkingPeer.RPC(view, methodName, target, null, encrypt, parameters); } } } else { Debug.LogWarning("Could not execute RPC " + methodName + ". Possible scene loading in progress?"); } } /// /// Internal to send an RPC on given PhotonView. Do not call this directly but use: PhotonView.RPC! /// internal static void RPC(PhotonView view, string methodName, PhotonPlayer targetPlayer, bool encrpyt, params object[] parameters) { if (!VerifyCanUseNetwork()) { return; } if (room == null) { Debug.LogWarning("RPCs can only be sent in rooms. Call of \"" + methodName + "\" gets executed locally only, if at all."); return; } if (player == null) { Debug.LogError("RPC can't be sent to target PhotonPlayer being null! Did not send \"" + methodName + "\" call."); } if (networkingPeer != null) { networkingPeer.RPC(view, methodName, PhotonTargets.Others, targetPlayer, encrpyt, parameters); } else { Debug.LogWarning("Could not execute RPC " + methodName + ". Possible scene loading in progress?"); } } /// /// Populates SendMonoMessageTargets with currently existing GameObjects that have a Component of type. /// /// If null, this will use SendMonoMessageTargets as component-type (MonoBehaviour by default). public static void CacheSendMonoMessageTargets(Type type) { if (type == null) type = SendMonoMessageTargetType; PhotonNetwork.SendMonoMessageTargets = FindGameObjectsWithComponent(type); } /// Finds the GameObjects with Components of a specific type (using FindObjectsOfType). /// Type must be a Component /// HashSet with GameObjects that have a specific type of Component. public static HashSet FindGameObjectsWithComponent(Type type) { HashSet objectsWithComponent = new HashSet(); Component[] targetComponents = (Component[]) GameObject.FindObjectsOfType(type); for (int index = 0; index < targetComponents.Length; index++) { if (targetComponents[index] != null) { objectsWithComponent.Add(targetComponents[index].gameObject); } } return objectsWithComponent; } /// /// Enable/disable receiving on given group (applied to PhotonViews) /// /// The interest group to affect. /// Sets if receiving from group to enabled (or not). public static void SetReceivingEnabled(int group, bool enabled) { if (!VerifyCanUseNetwork()) { return; } networkingPeer.SetReceivingEnabled(group, enabled); } /// /// Enable/disable receiving on given groups (applied to PhotonViews) /// /// The interest groups to enable (or null). /// The interest groups to disable (or null). public static void SetReceivingEnabled(int[] enableGroups, int[] disableGroups) { if (!VerifyCanUseNetwork()) { return; } networkingPeer.SetReceivingEnabled(enableGroups, disableGroups); } /// /// Enable/disable sending on given group (applied to PhotonViews) /// /// The interest group to affect. /// Sets if sending to group is enabled (or not). public static void SetSendingEnabled(int group, bool enabled) { if (!VerifyCanUseNetwork()) { return; } networkingPeer.SetSendingEnabled(group, enabled); } /// /// Enable/disable sending on given groups (applied to PhotonViews) /// /// The interest groups to enable sending on (or null). /// The interest groups to disable sending on (or null). public static void SetSendingEnabled(int[] enableGroups, int[] disableGroups) { if (!VerifyCanUseNetwork()) { return; } networkingPeer.SetSendingEnabled(enableGroups, disableGroups); } /// /// Sets level prefix for PhotonViews instantiated later on. Don't set it if you need only one! /// /// /// Important: If you don't use multiple level prefixes, simply don't set this value. The /// default value is optimized out of the traffic. /// /// This won't affect existing PhotonViews (they can't be changed yet for existing PhotonViews). /// /// Messages sent with a different level prefix will be received but not executed. This affects /// RPCs, Instantiates and synchronization. /// /// Be aware that PUN never resets this value, you'll have to do so yourself. /// /// Max value is short.MaxValue = 32767 public static void SetLevelPrefix(short prefix) { if (!VerifyCanUseNetwork()) { return; } networkingPeer.SetLevelPrefix(prefix); } /// Wraps loading a level to pause the network mesage-queue. Optionally syncs the loaded level in a room. /// /// To sync the loaded level in a room, set PhotonNetwork.automaticallySyncScene to true. /// The Master Client of a room will then sync the loaded level with every other player in the room. /// /// While loading levels, it makes sense to not dispatch messages received by other players. /// This method takes care of that by setting PhotonNetwork.isMessageQueueRunning = false and enabling /// the queue when the level was loaded. /// /// You should make sure you don't fire RPCs before you load another scene (which doesn't contain /// the same GameObjects and PhotonViews). You can call this in OnJoinedRoom. /// /// This uses Application.LoadLevel. /// /// /// Number of the level to load. When using level numbers, make sure they are identical on all clients. /// public static void LoadLevel(int levelNumber) { networkingPeer.SetLevelInPropsIfSynced(levelNumber); PhotonNetwork.isMessageQueueRunning = false; networkingPeer.loadingLevelAndPausedNetwork = true; SceneManager.LoadScene(levelNumber); } /// Wraps loading a level to pause the network mesage-queue. Optionally syncs the loaded level in a room. /// /// While loading levels, it makes sense to not dispatch messages received by other players. /// This method takes care of that by setting PhotonNetwork.isMessageQueueRunning = false and enabling /// the queue when the level was loaded. /// /// To sync the loaded level in a room, set PhotonNetwork.automaticallySyncScene to true. /// The Master Client of a room will then sync the loaded level with every other player in the room. /// /// You should make sure you don't fire RPCs before you load another scene (which doesn't contain /// the same GameObjects and PhotonViews). You can call this in OnJoinedRoom. /// /// This uses Application.LoadLevel. /// /// /// Name of the level to load. Make sure it's available to all clients in the same room. /// public static void LoadLevel(string levelName) { networkingPeer.SetLevelInPropsIfSynced(levelName); PhotonNetwork.isMessageQueueRunning = false; networkingPeer.loadingLevelAndPausedNetwork = true; SceneManager.LoadScene(levelName); } /// /// This operation makes Photon call your custom web-service by name (path) with the given parameters. /// /// /// This is a server-side feature which must be setup in the Photon Cloud Dashboard prior to use.
/// See the Turnbased Feature Overview for a short intro.
/// http://doc.photonengine.com/en/turnbased/current/getting-started/feature-overview ///
/// The Parameters will be converted into JSon format, so make sure your parameters are compatible. /// /// See PhotonNetworkingMessage.OnWebRpcResponse on how to get a response. /// /// It's important to understand that the OperationResponse only tells if the WebRPC could be called. /// The content of the response contains any values your web-service sent and the error/success code. /// In case the web-service failed, an error code and a debug message are usually inside the /// OperationResponse. /// /// The class WebRpcResponse is a helper-class that extracts the most valuable content from the WebRPC /// response. ///
/// /// Example callback implementation:
    ///
    /// public void OnWebRpcResponse(OperationResponse response)
    /// {
    ///     WebRpcResponse webResponse = new WebRpcResponse(operationResponse);
    ///     if (webResponse.ReturnCode != 0) { //...
    ///     }
    ///
    ///     switch (webResponse.Name) { //...
    ///     }
    ///     // and so on
    /// }
///
public static bool WebRpc(string name, object parameters) { return networkingPeer.WebRpc(name, parameters); } [Conditional("UNITY_EDITOR")] public static void CreateSettings() { PhotonNetwork.PhotonServerSettings = (ServerSettings)Resources.Load(PhotonNetwork.serverSettingsAssetFile, typeof(ServerSettings)); if (PhotonNetwork.PhotonServerSettings != null) { return; } // find out if ServerSettings can be instantiated (existing script check) ScriptableObject serverSettingTest = ScriptableObject.CreateInstance("ServerSettings"); if (serverSettingTest == null) { Debug.LogError("missing settings script"); return; } UnityEngine.Object.DestroyImmediate(serverSettingTest); // if still not loaded, create one if (PhotonNetwork.PhotonServerSettings == null) { string settingsPath = Path.GetDirectoryName(PhotonNetwork.serverSettingsAssetPath); if (!Directory.Exists(settingsPath)) { Directory.CreateDirectory(settingsPath); AssetDatabase.ImportAsset(settingsPath); } PhotonNetwork.PhotonServerSettings = (ServerSettings)ScriptableObject.CreateInstance("ServerSettings"); if (PhotonNetwork.PhotonServerSettings != null) { AssetDatabase.CreateAsset(PhotonNetwork.PhotonServerSettings, PhotonNetwork.serverSettingsAssetPath); } else { Debug.LogError("PUN failed creating a settings file. ScriptableObject.CreateInstance(\"ServerSettings\") returned null. Will try again later."); } } } /// /// Internally used by Editor scripts, called on Hierarchy change (includes scene save) to remove surplus hidden PhotonHandlers. /// public static void InternalCleanPhotonMonoFromSceneIfStuck() { PhotonHandler[] photonHandlers = GameObject.FindObjectsOfType(typeof(PhotonHandler)) as PhotonHandler[]; if (photonHandlers != null && photonHandlers.Length > 0) { Debug.Log("Cleaning up hidden PhotonHandler instances in scene. Please save it. This is not an issue."); foreach (PhotonHandler photonHandler in photonHandlers) { // Debug.Log("Removing Handler: " + photonHandler + " photonHandler.gameObject: " + photonHandler.gameObject); photonHandler.gameObject.hideFlags = 0; if (photonHandler.gameObject != null && photonHandler.gameObject.name == "PhotonMono") { GameObject.DestroyImmediate(photonHandler.gameObject); } Component.DestroyImmediate(photonHandler); } } } }