mirror of
https://github.com/FriendshipIsEpic/FiE-Game.git
synced 2024-12-01 09:27:59 +01:00
3220 lines
138 KiB
C#
3220 lines
138 KiB
C#
// --------------------------------------------------------------------------------------------------------------------
|
|
// <copyright file="PhotonNetwork.cs" company="Exit Games GmbH">
|
|
// Part of: Photon Unity Networking
|
|
// </copyright>
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
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
|
|
|
|
|
|
/// <summary>
|
|
/// The main class to use the PhotonNetwork plugin.
|
|
/// This class is static.
|
|
/// </summary>
|
|
/// \ingroup publicApi
|
|
public static class PhotonNetwork
|
|
{
|
|
/// <summary>Version number of PUN. Also used in GameVersion to separate client version from each other.</summary>
|
|
public const string versionPUN = "1.80";
|
|
|
|
/// <summary>Version string for your this build. Can be used to separate incompatible clients. Sent during connect.</summary>
|
|
/// <remarks>This is only sent when you connect so that is also the place you set it usually (e.g. in ConnectUsingSettings).</remarks>
|
|
public static string gameVersion { get; set; }
|
|
|
|
/// <summary>
|
|
/// This Monobehaviour allows Photon to run an Update loop.
|
|
/// </summary>
|
|
internal static readonly PhotonHandler photonMono;
|
|
|
|
/// <summary>
|
|
/// Photon peer class that implements LoadBalancing in PUN.
|
|
/// Primary use is internal (by PUN itself).
|
|
/// </summary>
|
|
internal static NetworkingPeer networkingPeer;
|
|
|
|
/// <summary>
|
|
/// The maximum number of assigned PhotonViews <i>per player</i> (or scene). See the [General Documentation](@ref general) topic "Limitations" on how to raise this limitation.
|
|
/// </summary>
|
|
public static readonly int MAX_VIEW_IDS = 1000; // VIEW & PLAYER LIMIT CAN BE EASILY CHANGED, SEE DOCS
|
|
|
|
|
|
/// <summary>Name of the PhotonServerSettings file (used to load and by PhotonEditor to save new files).</summary>
|
|
internal const string serverSettingsAssetFile = "PhotonServerSettings";
|
|
|
|
/// <summary>Path to the PhotonServerSettings file (used by PhotonEditor).</summary>
|
|
internal const string serverSettingsAssetPath = "Assets/Photon Unity Networking/Resources/" + PhotonNetwork.serverSettingsAssetFile + ".asset";
|
|
|
|
|
|
/// <summary>Serialized server settings, written by the Setup Wizard for use in ConnectUsingSettings.</summary>
|
|
public static ServerSettings PhotonServerSettings = (ServerSettings)Resources.Load(PhotonNetwork.serverSettingsAssetFile, typeof(ServerSettings));
|
|
|
|
/// <summary>Currently used server address (no matter if master or game server).</summary>
|
|
public static string ServerAddress { get { return (networkingPeer != null) ? networkingPeer.ServerAddress : "<not connected>"; } }
|
|
|
|
/// <summary>
|
|
/// False until you connected to Photon initially. True in offline mode, while connected to any server and even while switching servers.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// True when you called ConnectUsingSettings (or similar) until the low level connection to Photon gets established.
|
|
/// </summary>
|
|
public static bool connecting
|
|
{
|
|
get { return networkingPeer.IsInitialConnect && !offlineMode; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// A refined version of connected which is true only if your connection to the server is ready to accept operations like join, leave, etc.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simplified connection state
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detailed connection state (ignorant of PUN, so it can be "disconnected" while switching servers).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In OfflineMode, this is ClientState.Joined (after create/join) or it is ConnectedToMaster in all other cases.
|
|
/// </remarks>
|
|
public static ClientState connectionStateDetailed
|
|
{
|
|
get
|
|
{
|
|
if (offlineMode)
|
|
{
|
|
return (offlineModeRoom != null) ? ClientState.Joined : ClientState.ConnectedToMaster;
|
|
}
|
|
|
|
if (networkingPeer == null)
|
|
{
|
|
return ClientState.Disconnected;
|
|
}
|
|
|
|
return networkingPeer.State;
|
|
}
|
|
}
|
|
|
|
/// <summary>The server (type) this client is currently connected or connecting to.</summary>
|
|
/// <remarks>Photon uses 3 different roles of servers: Name Server, Master Server and Game Server.</remarks>
|
|
public static ServerConnection Server { get { return (PhotonNetwork.networkingPeer != null) ? PhotonNetwork.networkingPeer.Server : ServerConnection.NameServer; } }
|
|
|
|
/// <summary>
|
|
/// A user's authentication values used during connect.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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
|
|
/// </remarks>
|
|
public static AuthenticationValues AuthValues
|
|
{
|
|
get { return (networkingPeer != null) ? networkingPeer.AuthValues : null; }
|
|
set { if (networkingPeer != null) networkingPeer.AuthValues = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the room we're currently in. Null if we aren't in any room.
|
|
/// </summary>
|
|
public static Room room
|
|
{
|
|
get
|
|
{
|
|
if (isOfflineMode)
|
|
{
|
|
return offlineModeRoom;
|
|
}
|
|
|
|
return networkingPeer.CurrentRoom;
|
|
}
|
|
}
|
|
|
|
/// <summary>If true, Instantiate methods will check if you are in a room and fail if you are not.</summary>
|
|
/// <remarks>
|
|
/// Instantiating anything outside of a specific room is very likely to break things.
|
|
/// Turn this off only if you know what you do.</remarks>
|
|
public static bool InstantiateInRoomOnly = true;
|
|
|
|
/// <summary>
|
|
/// Network log level. Controls how verbose PUN is.
|
|
/// </summary>
|
|
public static PhotonLogLevel logLevel = PhotonLogLevel.ErrorsOnly;
|
|
|
|
/// <summary>
|
|
/// The local PhotonPlayer. Always available and represents this player.
|
|
/// CustomProperties can be set before entering a room and will be synced as well.
|
|
/// </summary>
|
|
public static PhotonPlayer player
|
|
{
|
|
get
|
|
{
|
|
if (networkingPeer == null)
|
|
{
|
|
return null; // Surpress ExitApplication errors
|
|
}
|
|
|
|
return networkingPeer.LocalPlayer;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Master Client of the current room or null (outside of rooms).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static PhotonPlayer masterClient
|
|
{
|
|
get
|
|
{
|
|
if (offlineMode)
|
|
{
|
|
return PhotonNetwork.player;
|
|
}
|
|
|
|
if (networkingPeer == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return networkingPeer.GetPlayerWithId(networkingPeer.mMasterClientId);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set to synchronize the player's nickname with everyone in the room(s) you enter. This sets PhotonNetwork.player.NickName.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The playerName is just a nickname and does not have to be unique or backed up with some account.<br/>
|
|
/// Set the value any time (e.g. before you connect) and it will be available to everyone you play with.<br/>
|
|
/// Access the names of players by: PhotonPlayer.NickName. <br/>
|
|
/// PhotonNetwork.otherPlayers is a list of other players - each contains the playerName the remote player set.
|
|
/// </remarks>
|
|
public static string playerName
|
|
{
|
|
get
|
|
{
|
|
return networkingPeer.PlayerName;
|
|
}
|
|
|
|
set
|
|
{
|
|
networkingPeer.PlayerName = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>The list of players in the current room, including the local player.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static PhotonPlayer[] playerList
|
|
{
|
|
get
|
|
{
|
|
if (networkingPeer == null)
|
|
return new PhotonPlayer[0];
|
|
|
|
return networkingPeer.mPlayerListCopy;
|
|
}
|
|
}
|
|
|
|
/// <summary>The list of players in the current room, excluding the local player.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static PhotonPlayer[] otherPlayers
|
|
{
|
|
get
|
|
{
|
|
if (networkingPeer == null)
|
|
return new PhotonPlayer[0];
|
|
|
|
return networkingPeer.mOtherPlayerListCopy;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read-only list of friends, their online status and the room they are in. Null until initialized by a FindFriends call.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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).
|
|
/// </remarks>
|
|
public static List<FriendInfo> Friends { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Age of friend list info (in milliseconds). It's 0 until a friend list is fetched.
|
|
/// </summary>
|
|
public static int FriendsListAge
|
|
{
|
|
get { return (networkingPeer != null) ? networkingPeer.FriendListAge : 0; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static float precisionForVectorSynchronization = 0.000099f;
|
|
|
|
/// <summary>
|
|
/// The minimum angle that a rotation needs to change before we send it via a PhotonView's OnSerialize/ObservingComponent.
|
|
/// </summary>
|
|
public static float precisionForQuaternionSynchronization = 1.0f;
|
|
|
|
/// <summary>
|
|
/// The minimum difference between floats before we send it via a PhotonView's OnSerialize/ObservingComponent.
|
|
/// </summary>
|
|
public static float precisionForFloatSynchronization = 0.01f;
|
|
|
|
/// <summary>
|
|
/// While enabled, the MonoBehaviours on which we call RPCs are cached, avoiding costly GetComponents<MonoBehaviour>() calls.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.).
|
|
/// </remarks>
|
|
public static bool UseRpcMonoBehaviourCache;
|
|
|
|
/// <summary>
|
|
/// While enabled (true), Instantiate uses PhotonNetwork.PrefabCache to keep game objects in memory (improving instantiation of the same prefab).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static bool UsePrefabCache = true;
|
|
|
|
/// <summary>
|
|
/// An Object Pool can be used to keep and reuse instantiated object instances. It replaced Unity's default Instantiate and Destroy methods.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// To use a GameObject pool, implement IPunPrefabPool and assign it here.
|
|
/// Prefabs are identified by name.
|
|
/// </remarks>
|
|
public static IPunPrefabPool PrefabPool { get { return networkingPeer.ObjectPool; } set { networkingPeer.ObjectPool = value; }}
|
|
|
|
/// <summary>
|
|
/// Keeps references to GameObjects for frequent instantiation (out of memory instead of loading the Resources).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// You should be able to modify the cache anytime you like, except while Instantiate is used. Best do it only in the main-Thread.
|
|
/// </remarks>
|
|
public static Dictionary<string, GameObject> PrefabCache = new Dictionary<string, GameObject>();
|
|
|
|
/// <summary>
|
|
/// If not null, this is the (exclusive) list of GameObjects that get called by PUN SendMonoMessage().
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static HashSet<GameObject> SendMonoMessageTargets;
|
|
|
|
|
|
/// <summary>
|
|
/// Defines which classes can contain PUN Callback implementations.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This provides the option to optimize your runtime for speed.<br/>
|
|
/// The more specific this Type is, the fewer classes will be checked with reflection for callback methods.
|
|
/// </remarks>
|
|
public static Type SendMonoMessageTargetType = typeof(MonoBehaviour);
|
|
|
|
/// <summary>
|
|
/// Can be used to skip starting RPCs as Coroutine, which can be a performance issue.
|
|
/// </summary>
|
|
public static bool StartRpcsAsCoroutine = true;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
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;
|
|
|
|
|
|
/// <summary>Only used in Unity Networking. In PUN, set the number of players in PhotonNetwork.CreateRoom.</summary>
|
|
[Obsolete("Used for compatibility with Unity networking only.")]
|
|
public static int maxConnections;
|
|
|
|
/// <summary>Defines if all clients in a room should load the same level as the Master Client (if that used PhotonNetwork.LoadLevel).</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static bool automaticallySyncScene
|
|
{
|
|
get
|
|
{
|
|
return _mAutomaticallySyncScene;
|
|
}
|
|
set
|
|
{
|
|
_mAutomaticallySyncScene = value;
|
|
if (_mAutomaticallySyncScene && room != null)
|
|
{
|
|
networkingPeer.LoadLevelIfSynced();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool _mAutomaticallySyncScene = false;
|
|
|
|
/// <summary>
|
|
/// This setting defines per room, if network-instantiated GameObjects (with PhotonView) get cleaned up when the creator of it leaves.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Set in PhotonServerSettings asset. Defines if the PhotonNetwork should join the "lobby" when connected to the Master server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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).
|
|
/// </remarks>
|
|
public static bool autoJoinLobby
|
|
{
|
|
get
|
|
{
|
|
return PhotonNetwork.PhotonServerSettings.JoinLobby;
|
|
}
|
|
set
|
|
{
|
|
PhotonNetwork.PhotonServerSettings.JoinLobby = value;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Set in PhotonServerSettings asset. Enable to get a list of active lobbies from the Master Server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static bool EnableLobbyStatistics
|
|
{
|
|
get
|
|
{
|
|
return PhotonNetwork.PhotonServerSettings.EnableLobbyStatistics;
|
|
}
|
|
set
|
|
{
|
|
PhotonNetwork.PhotonServerSettings.EnableLobbyStatistics = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// If turned on, the Master Server will provide information about active lobbies for this application.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static List<TypedLobbyInfo> 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; }
|
|
}
|
|
|
|
|
|
/// <summary>True while this client is in a lobby.</summary>
|
|
/// <remarks>
|
|
/// 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).
|
|
/// </remarks>
|
|
public static bool insideLobby
|
|
{
|
|
get
|
|
{
|
|
return networkingPeer.insideLobby;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The lobby that will be used when PUN joins a lobby or creates a game.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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)
|
|
/// </remarks>
|
|
public static TypedLobby lobby
|
|
{
|
|
get { return networkingPeer.lobby; }
|
|
set { networkingPeer.lobby = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines how many times per second PhotonNetwork should send a package. If you change
|
|
/// this, do not forget to also change 'sendRateOnSerialize'.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines how many times per second OnPhotonSerialize should be called on PhotonViews.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Choose this value in relation to PhotonNetwork.sendRate. OnPhotonSerialize will create updates and messages to be sent.<br/>
|
|
/// A lower rate takes up less performance but will cause more lag.
|
|
/// </remarks>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Can be used to pause dispatching of incoming evtents (RPCs, Instantiates and anything else incoming).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static bool isMessageQueueRunning
|
|
{
|
|
get
|
|
{
|
|
return m_isMessageQueueRunning;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value) PhotonHandler.StartFallbackSendAckThread();
|
|
networkingPeer.IsSendingOnlyAcks = !value;
|
|
m_isMessageQueueRunning = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>Backup for property isMessageQueueRunning.</summary>
|
|
private static bool m_isMessageQueueRunning = true;
|
|
|
|
/// <summary>
|
|
/// Used once per dispatch to limit unreliable commands per channel (so after a pause, many channels can still cause a lot of unreliable commands)
|
|
/// </summary>
|
|
public static int unreliableCommandsLimit
|
|
{
|
|
get
|
|
{
|
|
return networkingPeer.LimitOfUnreliableCommands;
|
|
}
|
|
|
|
set
|
|
{
|
|
networkingPeer.LimitOfUnreliableCommands = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Photon network time, synched with the server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// v1.55<br/>
|
|
/// 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).<br/>
|
|
/// This is not a DateTime!<br/>
|
|
///
|
|
/// Use this value with care: <br/>
|
|
/// It can start with any positive value.<br/>
|
|
/// It will "wrap around" from 4294967.295 to 0!
|
|
/// </remarks>
|
|
public static double time
|
|
{
|
|
get
|
|
{
|
|
uint u = (uint)ServerTimestamp;
|
|
double t = u;
|
|
return t / 1000;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The current server's millisecond timestamp.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static int ServerTimestamp
|
|
{
|
|
get
|
|
{
|
|
if (offlineMode)
|
|
{
|
|
if (UsePreciseTimer && startupStopwatch != null && startupStopwatch.IsRunning)
|
|
{
|
|
return (int)startupStopwatch.ElapsedMilliseconds;
|
|
}
|
|
return Environment.TickCount;
|
|
}
|
|
|
|
return networkingPeer.ServerTimeInMilliSeconds;
|
|
}
|
|
}
|
|
|
|
/// <summary>If true, PUN will use a Stopwatch to measure time since start/connect. This is more precise than the Environment.TickCount used by default.</summary>
|
|
private static bool UsePreciseTimer = false;
|
|
static Stopwatch startupStopwatch;
|
|
|
|
/// <summary>
|
|
/// Defines how many seconds PUN keeps the connection, after Unity's OnApplicationPause(true) call. Default: 60 seconds.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static float BackgroundTimeout = 60.0f;
|
|
|
|
/// <summary>
|
|
/// Are we the master client?
|
|
/// </summary>
|
|
public static bool isMasterClient
|
|
{
|
|
get
|
|
{
|
|
if (offlineMode)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return networkingPeer.mMasterClientId == player.ID;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Is true while being in a room (connectionStateDetailed == ClientState.Joined).</summary>
|
|
/// <remarks>
|
|
/// Many actions can only be executed in a room, like Instantiate or Leave, etc.
|
|
/// You can join a room in offline mode, too.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if we are in a room (client) and NOT the room's masterclient
|
|
/// </summary>
|
|
public static bool isNonMasterClientInRoom
|
|
{
|
|
get
|
|
{
|
|
return !isMasterClient && room != null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The count of players currently looking for a room (available on MasterServer in 5sec intervals).
|
|
/// </summary>
|
|
public static int countOfPlayersOnMaster
|
|
{
|
|
get
|
|
{
|
|
return networkingPeer.PlayersOnMasterCount;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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!
|
|
/// </summary>
|
|
public static int countOfPlayersInRooms
|
|
{
|
|
get
|
|
{
|
|
return networkingPeer.PlayersInRoomsCount;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The count of players currently using this application (available on MasterServer in 5sec intervals).
|
|
/// </summary>
|
|
public static int countOfPlayers
|
|
{
|
|
get
|
|
{
|
|
return networkingPeer.PlayersInRoomsCount + networkingPeer.PlayersOnMasterCount;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The count of rooms currently in use (available on MasterServer in 5sec intervals).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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).
|
|
/// </remarks>
|
|
public static int countOfRooms
|
|
{
|
|
get
|
|
{
|
|
return networkingPeer.RoomsCount;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables or disables the collection of statistics about this client's traffic.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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
|
|
/// </remarks>
|
|
public static bool NetworkStatisticsEnabled
|
|
{
|
|
get
|
|
{
|
|
return networkingPeer.TrafficStatsEnabled;
|
|
}
|
|
|
|
set
|
|
{
|
|
networkingPeer.TrafficStatsEnabled = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Count of commands that got repeated (due to local repeat-timing before an ACK was received).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If this value increases a lot, there is a good chance that a timeout disconnect will happen due to bad conditions.
|
|
/// </remarks>
|
|
public static int ResentReliableCommands
|
|
{
|
|
get { return networkingPeer.ResentReliableCommands; }
|
|
}
|
|
|
|
/// <summary>Crc checks can be useful to detect and avoid issues with broken datagrams. Can be enabled while not connected.</summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>If CrcCheckEnabled, this counts the incoming packages that don't have a valid CRC checksum and got rejected.</summary>
|
|
public static int PacketLossByCrcCheck
|
|
{
|
|
get { return networkingPeer.PacketLossByCrc; }
|
|
}
|
|
|
|
/// <summary>Defines the number of times a reliable message can be resent before not getting an ACK for it will trigger a disconnect. Default: 5.</summary>
|
|
/// <remarks>Less resends mean quicker disconnects, while more can lead to much more lag without helping. Min: 3. Max: 10.</remarks>
|
|
public static int MaxResendsBeforeDisconnect
|
|
{
|
|
get { return networkingPeer.SentCountAllowance; }
|
|
set
|
|
{
|
|
if (value < 3) value = 3;
|
|
if (value > 10) value = 10;
|
|
networkingPeer.SentCountAllowance = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>In case of network loss, reliable messages can be repeated quickly up to 3 times.</summary>
|
|
/// <remarks>
|
|
/// When reliable messages get lost more than once, subsequent repeats are delayed a bit
|
|
/// to allow the network to recover.<br/>
|
|
/// 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.<br/>
|
|
/// When you set this, increase PhotonNetwork.MaxResendsBeforeDisconnect to 6 or 7.
|
|
/// </remarks>
|
|
public static int QuickResends
|
|
{
|
|
get { return networkingPeer.QuickResendAttempts; }
|
|
set
|
|
{
|
|
if (value < 0) value = 0;
|
|
if (value > 3) value = 3;
|
|
networkingPeer.QuickResendAttempts = (byte)value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines the delegate usable in OnEventCall.
|
|
/// </summary>
|
|
/// <remarks>Any eventCode < 200 will be forwarded to your delegate(s).</remarks>
|
|
/// <param name="eventCode">The code assigend to the incoming event.</param>
|
|
/// <param name="content">The content the sender put into the event.</param>
|
|
/// <param name="senderId">The ID of the player who sent the event. It might be 0, if the "room" sent the event.</param>
|
|
public delegate void EventCallback(byte eventCode, object content, int senderId);
|
|
|
|
/// <summary>Register your RaiseEvent handling methods here by using "+=".</summary>
|
|
/// <remarks>Any eventCode < 200 will be forwarded to your delegate(s).</remarks>
|
|
/// <see cref="RaiseEvent"/>
|
|
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<int> manuallyAllocatedViewIds = new List<int>();
|
|
|
|
/// <summary>
|
|
/// Static constructor used for basic setup.
|
|
/// </summary>
|
|
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<PhotonHandler>();
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// While offline, the network protocol can be switched (which affects the ports you can use to connect).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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:<br/>
|
|
/// Connect(serverAddress, <udpport|tcpport>, appID, gameVersion)
|
|
///
|
|
/// Or when you use ConnectUsingSettings(), the PORT in the settings can be switched like so:<br/>
|
|
/// PhotonNetwork.PhotonServerSettings.ServerPort = 4530;
|
|
///
|
|
/// The current protocol can be read this way:<br/>
|
|
/// PhotonNetwork.networkingPeer.UsedProtocol
|
|
///
|
|
/// This does not work with the native socket plugin of PUN+ on mobile!
|
|
/// </remarks>
|
|
/// <param name="cp">Network protocol to use as low level connection. UDP is default. TCP is not available on all platforms (see remarks).</param>
|
|
public static void SwitchToProtocol(ConnectionProtocol cp)
|
|
{
|
|
// Debug.Log("SwitchToProtocol: " + cp + " PhotonNetwork.connected: " + PhotonNetwork.connected);
|
|
networkingPeer.TransportProtocol = cp;
|
|
}
|
|
|
|
|
|
/// <summary>Connect to Photon as configured in the editor (saved in PhotonServerSettings file).</summary>
|
|
/// <remarks>
|
|
/// 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
|
|
/// </remarks>
|
|
/// <param name="gameVersion">This client's version number. Users are separated from each other by gameversion (which allows you to make breaking changes).</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>Connect to a Photon Master Server by address, port, appID and game(client) version.</summary>
|
|
/// <remarks>
|
|
/// 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
|
|
/// </remarks>
|
|
/// <param name="masterServerAddress">The server's address (either your own or Photon Cloud address).</param>
|
|
/// <param name="port">The server's port to connect to.</param>
|
|
/// <param name="appID">Your application ID (Photon Cloud provides you with a GUID for your game).</param>
|
|
/// <param name="gameVersion">This client's version number. Users are separated by gameversion (which allows you to make breaking changes).</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>Can be used to reconnect to the master server after a disconnect.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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();
|
|
}
|
|
|
|
|
|
/// <summary>When the client lost connection during gameplay, this method attempts to reconnect and rejoin the room.</summary>
|
|
/// <remarks>
|
|
/// 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).
|
|
/// </remarks>
|
|
/// <returns>False, if there is no known room or game server to return to. Then, this client does not attempt the ReconnectAndRejoin.</returns>
|
|
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();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Connect to the Photon Cloud region with the lowest ping (on platforms that support Unity's Ping).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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
|
|
/// </remarks>
|
|
/// <param name="gameVersion">This client's version number. Users are separated from each other by gameversion (which allows you to make breaking changes).</param>
|
|
/// <returns>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.</returns>
|
|
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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Connects to the Photon Cloud region of choice.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Overwrites the region that is used for ConnectToBestCloudServer(string gameVersion).</summary>
|
|
/// <remarks>
|
|
/// This will overwrite the result of pinging all cloud servers.<br/>
|
|
/// Use this to allow your users to save a manually selected region in the player preferences.<br/>
|
|
/// Note: You can also use PhotonNetwork.ConnectToRegion to (temporarily) connect to a specific region.
|
|
/// </remarks>
|
|
public static void OverrideBestCloudServer(CloudRegionCode region)
|
|
{
|
|
PhotonHandler.BestRegionCodeInPreferences = region;
|
|
}
|
|
|
|
/// <summary>Pings all cloud servers again to find the one with best ping (currently).</summary>
|
|
public static void RefreshCloudServerRating()
|
|
{
|
|
throw new NotImplementedException("not available at the moment");
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Resets the traffic stats and re-enables them.
|
|
/// </summary>
|
|
public static void NetworkStatisticsReset()
|
|
{
|
|
networkingPeer.TrafficStatsReset();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Only available when NetworkStatisticsEnabled was used to gather some stats.
|
|
/// </summary>
|
|
/// <returns>A string with vital networking statistics.</returns>
|
|
public static string NetworkStatisticsToString()
|
|
{
|
|
if (networkingPeer == null || offlineMode)
|
|
{
|
|
return "Offline or in OfflineMode. No VitalStats available.";
|
|
}
|
|
|
|
return networkingPeer.VitalStatsToString(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for compatibility with Unity networking only. Encryption is automatically initialized while connecting.
|
|
/// </summary>
|
|
[Obsolete("Used for compatibility with Unity networking only. Encryption is automatically initialized while connecting.")]
|
|
public static void InitializeSecurity()
|
|
{
|
|
return;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function which is called inside this class to erify if certain functions can be used (e.g. RPC when not connected)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Makes this client disconnect from the photon server, a process that leaves any room and calls OnDisconnectedFromPhoton on completion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requests the rooms and online status for a list of friends and saves the result in PhotonNetwork.Friends.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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)
|
|
/// </remarks>
|
|
/// <param name="friendsToFind">Array of friend (make sure to use unique playerName or AuthValues).</param>
|
|
/// <returns>If the operation could be sent (requires connection, only one request is allowed at any time). Always false in offline mode.</returns>
|
|
public static bool FindFriends(string[] friendsToFind)
|
|
{
|
|
if (networkingPeer == null || isOfflineMode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return networkingPeer.OpFindFriends(friendsToFind);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Creates a room with given name but fails if this room(name) is existing already. Creates random name for roomName null.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="roomName">Unique name of the room to create.</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
public static bool CreateRoom(string roomName)
|
|
{
|
|
return CreateRoom(roomName, null, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a room but fails if this room is existing already. Can only be called on Master Server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="roomName">Unique name of the room to create. Pass null or "" to make the server generate a name.</param>
|
|
/// <param name="roomOptions">Common options for the room like MaxPlayers, initial custom room properties and similar. See RoomOptions type..</param>
|
|
/// <param name="typedLobby">If null, the room is automatically created in the currently used lobby (which is "default" when you didn't join one explicitly).</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
public static bool CreateRoom(string roomName, RoomOptions roomOptions, TypedLobby typedLobby)
|
|
{
|
|
return CreateRoom(roomName, roomOptions, typedLobby, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a room but fails if this room is existing already. Can only be called on Master Server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="roomName">Unique name of the room to create. Pass null or "" to make the server generate a name.</param>
|
|
/// <param name="roomOptions">Common options for the room like MaxPlayers, initial custom room properties and similar. See RoomOptions type..</param>
|
|
/// <param name="typedLobby">If null, the room is automatically created in the currently used lobby (which is "default" when you didn't join one explicitly).</param>
|
|
/// <param name="expectedUsers">Optional list of users (by UserId) who are expected to join this game and who you want to block a slot for.</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
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);
|
|
}
|
|
|
|
|
|
/// <summary>Join room by roomname and on success calls OnJoinedRoom(). This is not affected by lobbies.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <see cref="PhotonNetworkingMessage.OnPhotonJoinRoomFailed"/>
|
|
/// <see cref="PhotonNetworkingMessage.OnJoinedRoom"/>
|
|
/// <param name="roomName">Unique name of the room to join.</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
public static bool JoinRoom(string roomName)
|
|
{
|
|
return JoinRoom(roomName, null);
|
|
}
|
|
|
|
/// <summary>Join room by roomname and on success calls OnJoinedRoom(). This is not affected by lobbies.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <see cref="PhotonNetworkingMessage.OnPhotonJoinRoomFailed"/>
|
|
/// <see cref="PhotonNetworkingMessage.OnJoinedRoom"/>
|
|
/// <param name="roomName">Unique name of the room to join.</param>
|
|
/// <param name="expectedUsers">Optional list of users (by UserId) who are expected to join this game and who you want to block a slot for.</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
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);
|
|
}
|
|
|
|
|
|
/// <summary>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.</summary>
|
|
/// <remarks>
|
|
/// 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).
|
|
/// </remarks>
|
|
/// <param name="roomName">Name of the room to join. Must be non null.</param>
|
|
/// <param name="roomOptions">Options for the room, in case it does not exist yet. Else these values are ignored.</param>
|
|
/// <param name="typedLobby">Lobby you want a new room to be listed in. Ignored if the room was existing and got joined.</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
public static bool JoinOrCreateRoom(string roomName, RoomOptions roomOptions, TypedLobby typedLobby)
|
|
{
|
|
return JoinOrCreateRoom(roomName, roomOptions, typedLobby, null);
|
|
}
|
|
|
|
/// <summary>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.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="roomName">Name of the room to join. Must be non null.</param>
|
|
/// <param name="roomOptions">Options for the room, in case it does not exist yet. Else these values are ignored.</param>
|
|
/// <param name="typedLobby">Lobby you want a new room to be listed in. Ignored if the room was existing and got joined.</param>
|
|
/// <param name="expectedUsers">Optional list of users (by UserId) who are expected to join this game and who you want to block a slot for.</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Joins any available room of the currently used lobby and fails if none is available.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static bool JoinRandomRoom()
|
|
{
|
|
return JoinRandomRoom(null, 0, MatchmakingMode.FillRoom, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to join an open room with fitting, custom properties but fails if none is currently available.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="expectedCustomRoomProperties">Filters for rooms that match these custom properties (string keys and values). To ignore, pass null.</param>
|
|
/// <param name="expectedMaxPlayers">Filters for a particular maxplayer setting. Use 0 to accept any maxPlayer value.</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
public static bool JoinRandomRoom(Hashtable expectedCustomRoomProperties, byte expectedMaxPlayers)
|
|
{
|
|
return JoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers, MatchmakingMode.FillRoom, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to join an open room with fitting, custom properties but fails if none is currently available.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="expectedCustomRoomProperties">Filters for rooms that match these custom properties (string keys and values). To ignore, pass null.</param>
|
|
/// <param name="expectedMaxPlayers">Filters for a particular maxplayer setting. Use 0 to accept any maxPlayer value.</param>
|
|
/// <param name="matchingType">Selects one of the available matchmaking algorithms. See MatchmakingMode enum for options.</param>
|
|
/// <param name="typedLobby">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.</param>
|
|
/// <param name="sqlLobbyFilter">A filter-string for SQL-typed lobbies.</param>
|
|
/// <param name="expectedUsers">Optional list of users (by UserId) who are expected to join this game and who you want to block a slot for.</param>
|
|
/// <returns>If the operation got queued and will be sent.</returns>
|
|
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);
|
|
}
|
|
|
|
|
|
/// <summary>Can be used to return to a room after a disconnect and reconnect.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
///
|
|
/// <b>Important: Instantiate() and use of RPCs is not yet supported.</b>
|
|
/// 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.
|
|
/// </remarks>
|
|
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);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Internally used helper-method to setup an offline room, the numbers for actor and master-client and to do the callbacks.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>On MasterServer this joins the default lobby which list rooms currently in use.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static bool JoinLobby()
|
|
{
|
|
return JoinLobby(null);
|
|
}
|
|
|
|
/// <summary>On a Master Server you can join a lobby to get lists of available rooms.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="typedLobby">A typed lobby to join (must have name and type).</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Leave a lobby to stop getting updates about available rooms.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static bool LeaveLobby()
|
|
{
|
|
if (PhotonNetwork.connected && PhotonNetwork.Server == ServerConnection.MasterServer)
|
|
{
|
|
return networkingPeer.OpLeaveLobby();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>Leave the current room and return to the Master Server where you can join or create rooms (see remarks).</summary>
|
|
/// <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.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets currently known rooms as RoomInfo array. This is available and updated while in a lobby (check insideLobby).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns>RoomInfo[] of current rooms in lobby.</returns>
|
|
public static RoomInfo[] GetRoomList()
|
|
{
|
|
if (offlineMode || networkingPeer == null)
|
|
{
|
|
return new RoomInfo[0];
|
|
}
|
|
|
|
return networkingPeer.mGameListCopy;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets this (local) player's properties and synchronizes them to the other players (don't modify them directly).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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!
|
|
/// </remarks>
|
|
/// <param name="customProperties">Only string-typed keys will be used from this hashtable. If null, custom properties are all deleted.</param>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Locally removes Custom Properties of "this" player. Important: This does not synchronize the change! Useful when you switch rooms.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="customPropertiesToDelete">List of Custom Property keys to remove. See remarks.</param>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends fully customizable events in a room. Events consist of at least an EventCode (0..199) and can have content.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="eventCode">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.</param>
|
|
/// <param name="eventContent">Some serializable object like string, byte, integer, float (etc) and arrays of those. Hashtables with byte keys are good to send variable content.</param>
|
|
/// <param name="sendReliable">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).</param>
|
|
/// <param name="options">Allows more complex usage of events. If null, RaiseEventOptions.Default will be used (which is fine).</param>
|
|
/// <returns>False if event could not be sent</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates a viewID that's valid for the current/local player.
|
|
/// </summary>
|
|
/// <returns>A viewID that can be used for a new PhotonView.</returns>
|
|
public static int AllocateViewID()
|
|
{
|
|
int manualId = AllocateViewID(player.ID);
|
|
manuallyAllocatedViewIds.Add(manualId);
|
|
return manualId;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Enables the Master Client to allocate a viewID that is valid for scene objects.
|
|
/// </summary>
|
|
/// <returns>A viewID that can be used for a new PhotonView or -1 in case of an error.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregister a viewID (of manually instantiated and destroyed networked objects).
|
|
/// </summary>
|
|
/// <param name="viewID">A viewID manually allocated by this player.</param>
|
|
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]));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiate a prefab over the network. This prefab needs to be located in the root of a "Resources" folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Instead of using prefabs in the Resources folder, you can manually Instantiate and assign PhotonViews. See doc.
|
|
/// </remarks>
|
|
/// <param name="prefabName">Name of the prefab to instantiate.</param>
|
|
/// <param name="position">Position Vector3 to apply on instantiation.</param>
|
|
/// <param name="rotation">Rotation Quaternion to apply on instantiation.</param>
|
|
/// <param name="group">The group for this PhotonView.</param>
|
|
/// <returns>The new instance of a GameObject with initialized PhotonView.</returns>
|
|
public static GameObject Instantiate(string prefabName, Vector3 position, Quaternion rotation, int group)
|
|
{
|
|
return Instantiate(prefabName, position, rotation, group, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiate a prefab over the network. This prefab needs to be located in the root of a "Resources" folder.
|
|
/// </summary>
|
|
/// <remarks>Instead of using prefabs in the Resources folder, you can manually Instantiate and assign PhotonViews. See doc.</remarks>
|
|
/// <param name="prefabName">Name of the prefab to instantiate.</param>
|
|
/// <param name="position">Position Vector3 to apply on instantiation.</param>
|
|
/// <param name="rotation">Rotation Quaternion to apply on instantiation.</param>
|
|
/// <param name="group">The group for this PhotonView.</param>
|
|
/// <param name="data">Optional instantiation data. This will be saved to it's PhotonView.instantiationData.</param>
|
|
/// <returns>The new instance of a GameObject with initialized PhotonView.</returns>
|
|
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<PhotonView>() == 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);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="prefabName">Name of the prefab to instantiate.</param>
|
|
/// <param name="position">Position Vector3 to apply on instantiation.</param>
|
|
/// <param name="rotation">Rotation Quaternion to apply on instantiation.</param>
|
|
/// <param name="group">The group for this PhotonView.</param>
|
|
/// <param name="data">Optional instantiation data. This will be saved to it's PhotonView.instantiationData.</param>
|
|
/// <returns>The new instance of a GameObject with initialized PhotonView.</returns>
|
|
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<PhotonView>() == 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The current roundtrip time to the photon server.
|
|
/// </summary>
|
|
/// <returns>Roundtrip time (to server and back).</returns>
|
|
public static int GetPing()
|
|
{
|
|
return networkingPeer.RoundTripTime;
|
|
}
|
|
|
|
/// <summary>Refreshes the server timestamp (async operation, takes a roundtrip).</summary>
|
|
/// <remarks>Can be useful if a bad connection made the timestamp unusable or imprecise.</remarks>
|
|
public static void FetchServerTimestamp()
|
|
{
|
|
if (networkingPeer != null)
|
|
{
|
|
networkingPeer.FetchServerTimestamp();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Can be used to immediately send the RPCs and Instantiates just called, so they are on their way to the other players.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public static void SendOutgoingCommands()
|
|
{
|
|
if (!VerifyCanUseNetwork())
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (networkingPeer.SendOutgoingCommands())
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>Request a client to disconnect (KICK). Only the master client can do this</summary>
|
|
/// <remarks>Only the target player gets this event. That player will disconnect automatically, which is what the others will notice, too.</remarks>
|
|
/// <param name="kickPlayer">The PhotonPlayer to kick.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asks the server to assign another player as Master Client of your current room.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
///
|
|
/// </remarks>
|
|
/// <param name="masterClientPlayer">The player to become the next Master Client.</param>
|
|
/// <returns>False when this operation couldn't be done. Must be in a room (not in offlineMode).</returns>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Network-Destroy the GameObject associated with the PhotonView, unless the PhotonView is static or not under this client's control.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns>Nothing. Check error debug log for any issues.</returns>
|
|
public static void Destroy(PhotonView targetView)
|
|
{
|
|
if (targetView != null)
|
|
{
|
|
networkingPeer.RemoveInstantiatedGO(targetView.gameObject, !inRoom);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Destroy(targetPhotonView) failed, cause targetPhotonView is null.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Network-Destroy the GameObject, unless it is static or not under this client's control.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns>Nothing. Check error debug log for any issues.</returns>
|
|
public static void Destroy(GameObject targetGo)
|
|
{
|
|
networkingPeer.RemoveInstantiatedGO(targetGo, !inRoom);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Network-Destroy all GameObjects, PhotonViews and their RPCs of targetPlayer. Can only be called on local player (for "self") or Master Client (for anyone).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns>Nothing. Check error debug log for any issues.</returns>
|
|
public static void DestroyPlayerObjects(PhotonPlayer targetPlayer)
|
|
{
|
|
if (player == null)
|
|
{
|
|
Debug.LogError("DestroyPlayerObjects() failed, cause parameter 'targetPlayer' was null.");
|
|
}
|
|
|
|
DestroyPlayerObjects(targetPlayer.ID);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns>Nothing. Check error debug log for any issues.</returns>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns>Nothing. Check error debug log for any issues.</returns>
|
|
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.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="targetPlayer">This player's buffered RPCs get removed from server buffer.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all buffered RPCs from server that were sent via targetPhotonView. The Master Client and the owner of the targetPhotonView may call this.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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).
|
|
/// </remarks>
|
|
/// <param name="targetPhotonView">RPCs buffered for this PhotonView get removed from server buffer.</param>
|
|
public static void RemoveRPCs(PhotonView targetPhotonView)
|
|
{
|
|
if (!VerifyCanUseNetwork())
|
|
{
|
|
return;
|
|
}
|
|
|
|
networkingPeer.CleanRpcBufferIfMine(targetPhotonView);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="targetGroup">Interest group that gets all RPCs removed.</param>
|
|
public static void RemoveRPCsInGroup(int targetGroup)
|
|
{
|
|
if (!VerifyCanUseNetwork())
|
|
{
|
|
return;
|
|
}
|
|
|
|
networkingPeer.RemoveRPCsInGroup(targetGroup);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal to send an RPC on given PhotonView. Do not call this directly but use: PhotonView.RPC!
|
|
/// </summary>
|
|
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?");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal to send an RPC on given PhotonView. Do not call this directly but use: PhotonView.RPC!
|
|
/// </summary>
|
|
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?");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates SendMonoMessageTargets with currently existing GameObjects that have a Component of type.
|
|
/// </summary>
|
|
/// <param name="type">If null, this will use SendMonoMessageTargets as component-type (MonoBehaviour by default).</param>
|
|
public static void CacheSendMonoMessageTargets(Type type)
|
|
{
|
|
if (type == null) type = SendMonoMessageTargetType;
|
|
PhotonNetwork.SendMonoMessageTargets = FindGameObjectsWithComponent(type);
|
|
}
|
|
|
|
/// <summary>Finds the GameObjects with Components of a specific type (using FindObjectsOfType).</summary>
|
|
/// <param name="type">Type must be a Component</param>
|
|
/// <returns>HashSet with GameObjects that have a specific type of Component.</returns>
|
|
public static HashSet<GameObject> FindGameObjectsWithComponent(Type type)
|
|
{
|
|
HashSet<GameObject> objectsWithComponent = new HashSet<GameObject>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable/disable receiving on given group (applied to PhotonViews)
|
|
/// </summary>
|
|
/// <param name="group">The interest group to affect.</param>
|
|
/// <param name="enabled">Sets if receiving from group to enabled (or not).</param>
|
|
public static void SetReceivingEnabled(int group, bool enabled)
|
|
{
|
|
if (!VerifyCanUseNetwork())
|
|
{
|
|
return;
|
|
}
|
|
networkingPeer.SetReceivingEnabled(group, enabled);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Enable/disable receiving on given groups (applied to PhotonViews)
|
|
/// </summary>
|
|
/// <param name="enableGroups">The interest groups to enable (or null).</param>
|
|
/// <param name="disableGroups">The interest groups to disable (or null).</param>
|
|
public static void SetReceivingEnabled(int[] enableGroups, int[] disableGroups)
|
|
{
|
|
if (!VerifyCanUseNetwork())
|
|
{
|
|
return;
|
|
}
|
|
networkingPeer.SetReceivingEnabled(enableGroups, disableGroups);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Enable/disable sending on given group (applied to PhotonViews)
|
|
/// </summary>
|
|
/// <param name="group">The interest group to affect.</param>
|
|
/// <param name="enabled">Sets if sending to group is enabled (or not).</param>
|
|
public static void SetSendingEnabled(int group, bool enabled)
|
|
{
|
|
if (!VerifyCanUseNetwork())
|
|
{
|
|
return;
|
|
}
|
|
|
|
networkingPeer.SetSendingEnabled(group, enabled);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Enable/disable sending on given groups (applied to PhotonViews)
|
|
/// </summary>
|
|
/// <param name="enableGroups">The interest groups to enable sending on (or null).</param>
|
|
/// <param name="disableGroups">The interest groups to disable sending on (or null).</param>
|
|
public static void SetSendingEnabled(int[] enableGroups, int[] disableGroups)
|
|
{
|
|
if (!VerifyCanUseNetwork())
|
|
{
|
|
return;
|
|
}
|
|
networkingPeer.SetSendingEnabled(enableGroups, disableGroups);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Sets level prefix for PhotonViews instantiated later on. Don't set it if you need only one!
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="prefix">Max value is short.MaxValue = 32767</param>
|
|
public static void SetLevelPrefix(short prefix)
|
|
{
|
|
if (!VerifyCanUseNetwork())
|
|
{
|
|
return;
|
|
}
|
|
|
|
networkingPeer.SetLevelPrefix(prefix);
|
|
}
|
|
|
|
/// <summary>Wraps loading a level to pause the network mesage-queue. Optionally syncs the loaded level in a room.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name='levelNumber'>
|
|
/// Number of the level to load. When using level numbers, make sure they are identical on all clients.
|
|
/// </param>
|
|
public static void LoadLevel(int levelNumber)
|
|
{
|
|
networkingPeer.SetLevelInPropsIfSynced(levelNumber);
|
|
|
|
PhotonNetwork.isMessageQueueRunning = false;
|
|
networkingPeer.loadingLevelAndPausedNetwork = true;
|
|
SceneManager.LoadScene(levelNumber);
|
|
}
|
|
|
|
/// <summary>Wraps loading a level to pause the network mesage-queue. Optionally syncs the loaded level in a room.</summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name='levelName'>
|
|
/// Name of the level to load. Make sure it's available to all clients in the same room.
|
|
/// </param>
|
|
public static void LoadLevel(string levelName)
|
|
{
|
|
networkingPeer.SetLevelInPropsIfSynced(levelName);
|
|
|
|
PhotonNetwork.isMessageQueueRunning = false;
|
|
networkingPeer.loadingLevelAndPausedNetwork = true;
|
|
SceneManager.LoadScene(levelName);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// This operation makes Photon call your custom web-service by name (path) with the given parameters.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is a server-side feature which must be setup in the Photon Cloud Dashboard prior to use.<br/>
|
|
/// See the Turnbased Feature Overview for a short intro.<br/>
|
|
/// http://doc.photonengine.com/en/turnbased/current/getting-started/feature-overview
|
|
///<br/>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// Example callback implementation:<pre>
|
|
///
|
|
/// public void OnWebRpcResponse(OperationResponse response)
|
|
/// {
|
|
/// WebRpcResponse webResponse = new WebRpcResponse(operationResponse);
|
|
/// if (webResponse.ReturnCode != 0) { //...
|
|
/// }
|
|
///
|
|
/// switch (webResponse.Name) { //...
|
|
/// }
|
|
/// // and so on
|
|
/// }</pre>
|
|
/// </example>
|
|
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.");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Internally used by Editor scripts, called on Hierarchy change (includes scene save) to remove surplus hidden PhotonHandlers.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|