连接服务器,房间访问和创建
我们首先来看这篇教程的核心,连接Photon Cloud服务器和加入房间,或者需要的话创建一个。
- 创建一个新的场景,保存为Launcher.unity
- 创建新的C#脚本Launcher
- 创建新的GameObject,命名为Launcher
- 把C#脚本添加到Launcher对象上
- 按照下面的内容编辑C#脚本
- 记得保存
using UnityEngine;
namespace Com.MyCompany.MyGame
{
public class Launcher : MonoBehaviour
{
#region Public Variables
#endregion
#region Private Variables
///
/// This client's version number. Users are separated from each other by gameversion (which allows you to make breaking changes).
///
string _gameVersion = "1";
#endregion
#region MonoBehaviour CallBacks
///
/// MonoBehaviour method called on GameObject by Unity during early initialization phase.
///
void Awake()
{
// #NotImportant
// Force Full LogLevel
PhotonNetwork.logLevel = PhotonLogLevel.Full;
// #Critical
// we don't join the lobby. There is no need to join a lobby to get the list of rooms.
PhotonNetwork.autoJoinLobby = false;
// #Critical
// this makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
PhotonNetwork.automaticallySyncScene = true;
}
///
/// MonoBehaviour method called on GameObject by Unity during initialization phase.
///
void Start()
{
Connect();
}
#endregion
#region Public Methods
///
/// Start the connection process.
/// - If already connected, we attempt joining a random room
/// - if not yet connected, Connect this application instance to Photon Cloud Network
///
public void Connect()
{
// we check if we are connected or not, we join if we are , else we initiate the connection to the server.
if (PhotonNetwork.connected)
{
// #Critical we need at this point to attempt joining a Random Room. If it fails, we'll get notified in OnPhotonRandomJoinFailed() and we'll create one.
PhotonNetwork.JoinRandomRoom();
}else{
// #Critical, we must first and foremost connect to Photon Online Server.
PhotonNetwork.ConnectUsingSettings(_gameVersion);
}
}
#endregion
}
}
让我们看看代码中都做了什么,先从Unity内容开始,然后再看PUN的内容。
-
Namespace
虽然不是强制性的,为脚本提供适当的命名空间可防止与其他Assets和开发人员发生冲突。 如果另一个开发者创建一个类Launcher怎么办? Unity编译器会报错,你们必须重命名这个的类。 如果冲突来自您从资源商店下载的Assets,这可能很棘手。 现在,Launcher类实际上是在Com.MyCompany.MyGame.launcher这个命名空间下,别人不太可能使用与我们这个完全相同的命名空间,因为你拥有这个域名,所以使用域名逆序作为命名空间,可以使您的工作安全,组织良好。 Com.MyCompany.MyGame应该被替换为你自己的逆序域名和游戏名称,应该遵从这个良好的约定。
-
MonoBehaviour类
注意,我们使用MonoBehaviour派生类,它可以将我们的类转换为Unity组件,然后可以放到GameObject或Prefab上。 扩展MonoBehaviour的类可以访问许多非常重要的方法和属性。 在这里,我们将使用两个回调方法,Awake()和Start()。
-
PhotonNetwork.logLevel
在开发过程中,特别是如果这是你第一次使用PUN时,我们将要求PUN在Unity控制台上尽可能多地记录日志,以便我们准确地知道发生了什么。 您将更加自信,并且当事情按预期工作时,将此LogLevel切换到Informational。
-
PhotonNetwork.autoJoinLobby
在Awake()期间,我们将PhotonNetwork.autoJoinLobby设置为false,因为我们不需要大厅功能,我们只需要获取现有房间的列表。 强制设置通常是一个好主意,因为在同一个项目中,你可能有另一个场景,实际上想要自动加入大厅,所以在同一个项目内,在不同的方法中间切换的话,这两个情况下都会没有问题。
-
PhotonNetwork.ConnectUsingSettings()
在Start()期间,使用我们的公共函数connect()连接到PUN ,调用了PhotonNetwork.ConnectUsingSettings()。 注意_gameVersion变量表示你的游戏版本。 您应该将其保留为“1”,直到您需要对已经处于活动状态的项目进行大的修改。 这里要记住的重要信息是,PhotonNetwork.ConnectUsingSettings()是您的游戏的联网和连接到Photon Cloud的起点。
-
PhotonNetwork.automaticallySyncScene
我们的游戏将有一个基于玩家数量可调整的竞技场,并确保加载的场景对每个连接的玩家是相同的,我们将利用Photon提供的非常方便的功能:PhotonNetwork.automaticallySyncScene。当该变量设置为true的时候,MasterClient可以调用PhotonNetwork.LoadLevel(),此时所有连接的玩家都会自动的加载同样的Level。
到这里,您可以保存启动场景并点击播放。 你应该在Unity控制台中看到好多条日志。特别是“已连接到masterserver”这条日志,表明我们现在已经连接并准备好加入一个房间。
编码时的良好习惯是总是测试失败的可能性。 这里我们假定计算机已连接到互联网,但如果计算机未连接到互联网会发生什么呢?让我们来看看。 关闭计算机的网络并启动场景。 你应该看到在Unity控制台错误“Connect() to 'ns.exitgames.com' failed:System.Net.Sockets.SocketException:No such host is known”。
理想情况下,我们的脚本应该知道这个问题,并对这些情况作出反应。并且无论什么情况或问题可能出现,都要能够积极响应。
我们现在处理这两种情况,并通知我们的Launcher脚本,我们到底有没有连接上PUN服务器。 这将是对PUN Callbacks的完美介绍。
PUN CallBacks
PUN的回调非常灵活,并提供了三种非常不同的实现。 让我们学习所有的三种方法,然后根据情况选择使用最适合的一个。
"magic" methods
当使用常规的MonoBehaviour的时候,可以简单创建一个私有方法:
void OnConnectedToMaster()
{
Debug.Log("DemoAnimator/Launcher: OnConnectedToMaster() was called by PUN");
}
这挺神奇的,因为任何MonoBehaviour可以实现该方法或任何其他消息从PUN。 它遵循与Unity发送给MonoBehaviours(如Awake()或Start())主要方法相同的原则。 但是我们不会使用这个,因为如果你拼错这些“Magic”的方法,不会通知你出了错误,所以这是一个非常快速和实际的实现,但只有当知道确切的每个方法的名称,并且你非常熟悉,非常在行调试技术,才能快速找到这些类型的问题。
使用IPunCallbacks和IPunObservable接口
PUN提供了两个C#接口,IPunCallbacks和IPunObservable,你可以在类中实现。
这是一个非常安全的方法,以确保一个类符合所有的接口,但强制开发人员实现所有的接口声明。 最好的脚本编辑器将使这个任务非常容易,如上面使用MonoDevelop时所示。 然而,脚本可能最终有很多不做任何事情的方法,但是为了编译器通过必须都实现。 所以你的脚本可以大量使用所有或大多数Pun特性。
后面的教程中,做数据序列化的时候还会使用到IPunObservable。
使用Photon.PunBehaviour
上一个部分,是我们将经常使用的技术,是最方便的一个。 我们还可以从Photon.PunBehaviour继承类,而不是创建一个从MonoBehaviour派生的类,因为它暴露了特定的属性和虚拟方法,供我们在方便时使用和覆盖。 这点非常实用,因为我们可以确定我们没有任何打字错误,我们不需要实现所有的方法。
注意:当重写方法时,大多数脚本编辑器将默认实现一个基类调用并自动填充,但在我们的例子中不需要,因此作为Photon.PunBehaviour的一般规则,不会调用基类方法。
注意:当重写方法时,另一个很大的好处是,您通过简单地悬停在方法名称上从帮助中获益。
好的,让我们实现以下OnConnectedToMaster()和OnDisconnectedFromPhoton()这两个PUN回调。
编辑Launcher脚本
-
把基类改成Photon.PunBehaviour
public class Launcher : Photon.PunBehaviour {
-
在类的末尾添加下面两个方法,为了清晰请写到这个区块Photon.PunBehaviour CallBacks里面
#region Photon.PunBehaviour CallBacks public override void OnConnectedToMaster() { Debug.Log("DemoAnimator/Launcher: OnConnectedToMaster() was called by PUN"); } public override void OnDisconnectedFromPhoton() { Debug.LogWarning("DemoAnimator/Launcher: OnDisconnectedFromPhoton() was called by PUN"); } #endregion
保存脚本
现在,如果我们在有或没有互联网的情况下启动这个场景,我们可以采取适当的步骤通知玩家是否进行下一步。我们将在下一节中讨论构建UI。现在我们将处理成功的连接:
因此,我们在OnConnectedToMaster()方法中追加以下调用:
// #Critical: The first we try to do is to join a potential existing room. If there is, good, else, we'll be called back with OnPhotonRandomJoinFailed()
PhotonNetwork.JoinRandomRoom();
正如注释所说,我们需要被告知尝试加入一个随机房间是否失败,在这种情况下,我们需要实际创建一个房间,所以我们在我们的脚本中实现OnPhotonRandomJoinFailed这个PUN回调。创建一个房间使用PhotonNetwork.CreateRoom(),你已经猜到,当我们成功地加入一个房间时使用PUN回调OnJoinedRoom()。
public override void OnPhotonRandomJoinFailed (object[] codeAndMsg)
{
Debug.Log("DemoAnimator/Launcher:OnPhotonRandomJoinFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom(null, new RoomOptions() {maxPlayers = 4}, null);");
// #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
PhotonNetwork.CreateRoom(null, new RoomOptions() { maxPlayers = 4 }, null);
}
public override void OnJoinedRoom()
{
Debug.Log("DemoAnimator/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");
}
现在如果你运行场景,并且你应该遵循连接到PUN连续的逻辑,尝试加入现有房间,否则创建房间并加入新创建的房间。
到这里,尽管我们现在已经介绍了连接和加入一个房间的关键方面,但是有一些事情不是很方便,迟早药解决它们。 这些不是真正与学习PUN有关,但从总体角度来说很重要。
在Unity Inspector中暴露变量
MonoBehaviours会自动将他们的公共属性暴露给Unity Inspector。这是Unity中一个非常重要的概念,在我们的例子中,我们将修改我们定义LogLevel的方式,并创建一个公共变量,以便我们可以在不触及代码本身的情况下进行设置。
///
/// The PUN loglevel.
///
public PhotonLogLevel Loglevel = PhotonLogLevel.Informational;
以及修改Awake():
// #NotImportant
// Force LogLevel
PhotonNetwork.logLevel = Loglevel;
所以,现在我们不强制脚本是一个特定类型的LogLevel,我们只需要在Unity Inspector中设置它,然后点击运行,不需要打开脚本,编辑它,保存它,等待Unity重新编译并最终运行。 这种方式更有效率和灵活性。
我们也会这样做,每个房间的玩家数量最多。 在代码中硬编码不是最好的做法,而是让它作为一个公共变量,以便我们以后可以确定和调整这个数字,而不需要重新编译。
在类声明的开头,Public Variables代码段中加入:
///
/// The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created.
///
[Tooltip("The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created")]
public byte MaxPlayersPerRoom = 4;
然后修改PhotonNetwork.CreateRoom()调用的地方来使用这个公共变量。
// #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
PhotonNetwork.CreateRoom(null, new RoomOptions() { maxPlayers = MaxPlayersPerRoom }, null);
在Unity Inspector中效果如下: