Photon——Hello World Part 2 第二部分
Hello World Part 2 第二部分
In part 1 we introduced some basic concepts of the client API: PhotonPeer, Service, Connect, and the listener/callback design. Building on the application of part 1 (initial connection setup) we will have a look at how to use this connection to create a simple chat, where the application joins a room and sends a “Hello World!” message to the other connected clients.
在第1部分我们介绍了一些客户端API的基本概念: PhotonPeer,服务,连接和侦听器/回调的设计。建立在应用程序的第1部分基础上(初始连接设置)我们将看看如何使用这个连接来创建一个简单的聊天,应用程序加入一个房间,发送一个“Hello World ! “消息给其他连接的客户端。
Content 内容
- Overview 概述
- Preparation: Refactoring the Main Flow 准备:重构主要的流程
- Operations and Events 操作和事件
- OnStatusChanged OnStatusChanged事件
- OnOperationResponse OnOperationResponse事件
- OnEvent OnEvent事件
- Final Demo Code 最后的演示代码
Overview 概述
We are going to look into two sets of concepts:
Operations are remote procedure calls with a request and a response. An operation request consists of an OperationCode and Parameters (a Dictionary<byte, object\> containing the parameters). To send operation requests to the server OpCustom of the peer instance is called. The operation result is returned to your application in the OnOperationResponse callback.Note:a) Sending the request and receiving the response are decoupled and fully asynchronous. b)the operation response is optional.Events are messages or notifications pushed to the client. When the peer receives an event the application is notified through the OnEvent callbackRooms group client connections or peers and facilitate the communication between peers. Peers can join a room and send events to all peers of the room or parts of them.OpJoin is the operation called by the client to join the room.OpRaiseEvent is the operation called to send events to other clients.
Note: Rooms, OpJoin and OpRaiseEvent are implemented in the Lite Application. The room concept is essential to many games because this is the approach many developers take - although it's not the only one - Photon is committed to an open architecture philosophy.
我们先看看两个概念:
操作是一个远程过程调用,由一个请求和一个响应组成。一个操作请求包含一个OperationCode 和 参数(一个字典<字节,对象\ >包含了参数)。 操作请求发送到服务器peer实例的OpCustom被调用。OnOperationResponse回调时操作的结果被返回给你的应用程序中。
注意:a)发送请求和接收响应是解耦和完全异步的。b)操作响应是可选的。事件是消息或通知被推送到客户端。当peer收到一个事件时,应用程序将通过OnEvent回调得到通知。房间组织客户端的连接或peer并方便peer之间的交流。peer可以加入一个房间和发送事件到房间内的所有peer或其中部分。客户端加入房间时OpJoin操作被调用,将事件发送给其他客户端OpRaiseEvent操作被调用。注意:在Lite应用程序中实现房间,OpJoin和OpRaiseEvent。这房间的概念是至关重要的许多游戏,因为这是很多开发人员采取的方法——尽管它不是只有一个——Photon致力于一个开放式体系结构的理论研究。
Preparation: Refactoring the Main Flow 准备:重构主要流程
We will start a new C#/Windows/Console project, name it Helloworld2, add a reference to PhotonDotNet.dll and copy the Program.cs of part 1:
我们将开始一个新的c# / Windows /控制台项目,命名它为Helloworld2,添加一个引用PhotonDotNet.dll和复制第1部分的Program.cs:
Next we’ll refactor the code in main as follows
- We will move the core flow into the “Program” instance method Run (line 13).
- Leaving a small stub of main just to create the instance and call Run (line 3).
- Peer is declared as an instance variable and initialized in the constructor of “Program” (line 6).
接下来我们将重构代码如下:
- 我们将把核心流程加入到“Program”实例的Run方法(第13行)。
- 留下一个Main的存根只是为了创建实例和调用Run (第3行)。
- Peer是在“Program”的构造函数里声明为一个实例变量并初始化(第6行)。
static void Main( string [] args) { new Program().Run(); } PhotonPeer peer; public Program() { peer = new PhotonPeer( this , ConnectionProtocol.Udp); } void Run() { if (peer.Connect( "localhost:5055" , "Lite" )) { do { //Console.WriteLine("."); peer.Service(); System.Threading.Thread.Sleep(500); } while (!Console.KeyAvailable); } else Console.WriteLine( "Unknown hostname!" ); Console.WriteLine( "Press any key to end program!" ); Console.ReadKey(); } |
Note: in line 19 we commented the "." output. Optionally you can replace it by Debug.Write("."); to make sure your loop is working. To see this check the output-tab in your Visual Studio.
注意:在第19行我们评论“.“的输出。选择你可以取代它通过调试写(“.”),确保你的循环工作。看到这检查你在Visual Studio的输出选项卡。
Operations and Events 操作和事件
Now we are done with the preparation work we can start to see how we send an operation request to the server.
现在我们已经完成了准备工作,我们可以开始看看我们是如何将一个操作发送请求到服务器。
The flow we are implementing once we initialized the connection:
-
- When status changes to connected: send the operation join request to join the room “MyRoomName”. (in OnStatusChanged)
- When receiving the result “join ok”: send the operation raise event request with the event code 101 and a “Hello World” message. (in OnOperationResponse)
当我们初始化连接时流程就会被实施:
-
- 当状态更改为连接:发送操作加入请求,加入房间”MyRoomName”。 (在 OnStatusChanged )
- 当接收到结果为“join ok”:发送操作触发事件的请求,事件代码101和一个“Hello World”消息。 (在 OnOperationResponse )
Note: usually you would define event codes centrally for your application in an enumeration for instance. Because we are using Lite features the codes 255-251 (Join, Leave, SetProperties, GetProperties) are predefined.
注意:通常你会定义事件代码集在应用程序的枚举实例中。例如, 因为我们使用Lite特性编码255 - 251(Join, Leave, SetProperties, GetProperties)是预定义的。
-
- When receiving an event with the code 101: print the received message to the console. (in OnEvent)
- 当收到一个事件代码101:打印接收到的消息到控制台里。 (在 OnEvent )
OnStatusChanged OnStatusChanged事件
To start with the implementation of the flow we described above the first change to the code we’ll be to add a switch for the statusCode we get notified with and a case block for the StatusCode.Connect to send an OperationRequest to join a room with the name “MyRoomName” (lines 6-11):
- where we create a hashtable opParams containing the parameters (lines 8+9).
- and call peer.OpCustom with the OperationCode and passing in the opParams Dictionary<byte, object\> (line 10).
首先实现上面描述的流程,我们的第一个要变化的代码是我们将添加一个当做开关的statusCode,我们得到的通知是StatusCode.Connect块发送一个OperationRequest加入一个叫做“MyRoomName”的房间 (行6-11):
- 我们创建一个哈希表包含参数opParams (第8行+ 9)。
- 和调用peer.OpCustom 与 OperationCode 并传递这个opParams字典<字节,对象\ >(第10行)。
public void OnStatusChanged(StatusCode statusCode) { Console.WriteLine( "\n---OnStatusChanged:" + statusCode); switch (statusCode) { case StatusCode.Connect: Console.WriteLine( "Calling OpJoin ..." ); Dictionary<Byte, Object>opParams = new Dictionary<Byte,Object>(); opParams[( byte )LiteOpKey.GameId] = "MyRoomName" ; peer.OpCustom(( byte )LiteOpCode.Join, opParams, true ); break ; default : break ; } } |
|
OnOperationResponse OnOperationResponse事件
For an easy way to print out readable operation codes we will define the following enum:
对于一个简单的方法来打印可读的操作码,我们将定义为以下的枚举:
enum OpCodeEnum : byte { Join = 255, Leave = 254, RaiseEvent = 253, SetProperties = 252, GetProperties = 251 } |
Next we change the OnOperationResponse callback as follows:
- We check the operationResponse.ReturnCode if it failed (not 0) we exit the method (lines 3,8).
- Add a switch-case for OperationCode (operationResponse.OperationCode) LiteOpCode.Join (lines 11,13)
- We create a RaiseEvent operation request (LiteOpCode.RaiseEvent). With the parameters LiteOpKey.Code = 101 and LiteOpKey.Data = “Hello World” (our message) (lines 18-21).
接下来,我们改变OnOperationResponse的回调如下:
- 我们检查 operationResponse.ReturnCode 如果它失败了(不是0) 我们退出方法(行3-8)。
- 添加一个开关例子为OperationCode ( operationResponse.OperationCode ) LiteOpCode.Join (行11-13)
- 我们创建一个RaiseEvent操作请求( LiteOpCode.RaiseEvent )。参数LiteOpKey.Code= 101和LiteOpKey.Data= " Hello World”(我们的消息)(第18 - 21行)。
public void OnOperationResponse(OperationResponse operationResponse) { if (operationResponse.ReturnCode == 0) Console.WriteLine( "\n---OnOperationResponse: OK - " + (OpCodeEnum)operationResponse.OperationCode + "(" + operationResponse.OperationCode + ")" ); else { Console.WriteLine( "\n---OnOperationResponse: NOK - " + (OpCodeEnum)operationResponse.OperationCode + "(" + operationResponse.OperationCode + ")\n ->ReturnCode=" + operationResponse.ReturnCode + " DebugMessage=" + operationResponse.DebugMessage); return ; } switch (operationResponse.OperationCode) { case ( byte )LiteOpCode.Join: int myActorNr = ( int )operationResponse.Parameters[LiteOpKey.ActorNr]; Console.WriteLine( " ->My PlayerNr (or ActorNr) is:" + myActorNr); Console.WriteLine( "Calling OpRaiseEvent ..." ); Dictionary< byte , object > opParams = new Dictionary< byte , object >(); opParams[LiteOpKey.Code] = ( byte )101; opParams[LiteOpKey.Data] = "Hello World!" ; peer.OpCustom(( byte )LiteOpCode.RaiseEvent, opParams, true ); break ; } } |
When you run this code the ouput will look like this:
当您运行这个代码,输出将看起来像这样:
C:\...\HelloWorld2\bin\Debug>HelloWorld2.exe---OnStatusChanged:ConnectCalling OpJoin ...---OnOperationResponse: OK - Join(90)->My PlayerNr (or ActorNr) is:1Calling OpRaiseEvent ...---OnOperationResponse: NOK - RaiseEvent(253)->ReturnCode=-1 DebugMessage=Wrong parameter type 245(RaiseEventRequest.Data): should be Hashtable but received String
As you can see the first operation (Join) returned OK. It also returned the actor number 1 (->My PlayerNr (or ActorNr) is:1). If you start a second Helloworld2.exe you will see a “2” instead. The next client would get a “3”.
正如您可以看到的第一个操作( 加入 )返回OK。它还返回1号的actor ( - >我的PlayerNr(或ActorNr)是:1 )。如果你开始运行第二个Helloworld2.exe,你将看到一个“2”。接下来的客户端将会得到一个“3”。
Note: starting and stopping clients might lead to higher actor No. values. The server recognizes the disconnect after a certain timeout and removes those peers. Usually, a client would send a disconnect on shutdown - which is the recommended way, so others "see" faster that a peer disconnected - a peer removed from the room triggers a leave event broadcasting it to all peers connected.
注:启动和停止客户端可能会导致更多的actor没有值。服务器断开后识别到某些超时并删除那些Peer。通常,客户端在关闭时会发送一个断开——这是被推荐的方式,所以其他人更快的“看到” peer断开连接——一个peer离开房间会触发一个离开事件并广播它给所有连接着的peer。
The second operation OpRaiseEvent failed. This is because the parameter Data is expected to be a hashtable. So we will change our code as follows (lines 10-13):
第二个操作OpRaiseEvent失败了。这是因为参数数据预计将是一个散列表。所以我们将改变我们的代码去遵循(第10行):
switch (operationResponse.OperationCode) { case LiteOpCode.Join: int myActorNr = ( int )operationResponse.Parameters[LiteOpKey.ActorNr]; Console.WriteLine( " ->My PlayerNr (or ActorNr) is:" + myActorNr); Console.WriteLine( "Calling OpRaiseEvent ..." ); Dictionary< byte , object > opParams = new Dictionary< byte , object >(); opParams[LiteOpKey.Code] = ( byte )101; //opParams[LiteOpKey.Data] = "Hello World!"; //<- returns an error, server expects a hashtable Hashtable evData = new Hashtable(); evData[( byte )1] = "Hello Wolrd!" ; opParams[LiteOpKey.Data] = evData; peer.OpCustom(( byte )LiteOpCode.RaiseEvent, opParams, true ); break ; } |
When running the code with the changes we just made you will notice the OnOperationResponse for RaiseEvent doesn’t appear anymore - this happens, because the server only sends a response in case of an error!
当运行代码发生变化时,我们只是让你注意到 OnOperationResponse 对于 RaiseEvent 不出现了——发生这一切是因为服务器在出错的情况下只发送一个响应!
Note: The key (byte)1 of evData has nothing to do with the parameter opParams[LiteOpKey.Code] beeing (byte)101(line 9).
Hint: It's recommended to use either byte or short keys in the evData hashtable, because events tend to be sent very often.
注意:Key(字节)1的evData没有参数opParams[LiteOpKey.Code]可用(字节)101(第9行)。
提示:这是推荐要么使用byte,要么使用短的Key在evData哈希表,这是因为事件往往会经常发送。
OnEvent OnEvent事件
For an easy way to print out readable operation codes we will define the following Enum:
用一个简单的方法来打印可读的操作码,我们将定义以下枚举:
enum EvCodeEnum : byte { Join = 255, Leave = 254, PropertiesChanged = 253 } |
The last change we’re making is to display the message we receive in the eventData when we receive the event with code = 101:
最后,当我们接收到事件eventData与code = 101的时候,在eventData里显示我们收到的消息:
public void OnEvent(EventData eventData) { Console.WriteLine( "\n---OnEvent: " + (EvCodeEnum)eventData.Code + "(" + eventData.Code + ")" ); switch (eventData.Code) { case 101: int sourceActorNr = ( int )eventData.Parameters[LiteEventKey.ActorNr]; Hashtable evData = (Hashtable)eventData.Parameters[LiteEventKey.Data]; Console.WriteLine( " ->Player" + sourceActorNr + " say's: " + evData[( byte )1]); break ; } } |
If you launch two clients you’ll now see the following:
如果您启动两个客户你现在将看到如下:
the first client: 第一个客户端
C:\...\HelloWorld2\bin\Debug>HelloWorld2.exe---OnStatusChanged:ConnectCalling OpJoin ...---OnOperationResponse: OK - Join(255)->My PlayerNr (or ActorNr) is:1Calling OpRaiseEvent ...---OnEvent: Join(255)->Player1 joined!->Total num players in room:1, Actornr List: 1,---OnEvent: Join(255)->Player2 joined!->Total num players in room:2, Actornr List: 1,2,---OnEvent: 101(101)->Player2 say's: Hello Wolrd!
the second client: 第二个客户端
C:\...\HelloWorld2\bin\Debug>HelloWorld2.exe---OnStatusChanged:ConnectCalling OpJoin ...---OnOperationResponse: OK - Join(255)->My PlayerNr (or ActorNr) is:2Calling OpRaiseEvent ...---OnEvent: Join(255)->Player2 joined!->Total num players in room:2, Actornr List: 1,2,
The first client receives 3 events:
- the join event triggered by its own join.
- the join event triggered by the joining of the second client.
- the 101 event the second client sent to all others in the room
第一个客户端接收3个事件:
- join事件被自己的加入所触发。
- join事件被第二个加入的客户端所触发。
- 101事件,第二个客户端发送给房间里的所有其他人。
all others is the default behavior of OpRaisEvent (for more details see SDK documentation of LitePeer).The second client only receives one join event (triggered by its own join).
所有其他人的事件触发是OpRaisEvent的默认行为 (更多细节 看到SDK文档的LitePeer)。第二个客户端只收到一个加入事件(被自己的加入触发)。
In this tutorial, we only used PhotonPeer. The client SDK also includes a LitePeer to ease the use of Lite features. It extends the PhotonPeer, so when joining a room with LitePeer you only need to call peer.OpJoin(“MyRoomName”). To see how this works just replace PhotonPeer by LitePeer. Another powerful Lite feature worth looking into is Properties. In Lite you can set room and player (or actor) properties. As an example you could set initial configuration values (e.g. the game map, difficulty, …) as room properties or the players nicknames as actor properties (for more details see the client SDK documentation).
在本教程中,我们只使用了PhotonPeer。客户端SDK还包括了一个LitePeer为了更方便的使用Lite。它扩展了PhotonPeer,所以当LitePeer加入一个房间,你只需要调用peer.OpJoin (“MyRoomName”)。接下来看看PhotonPeer接替LitePeer是如何工作的。另一个强大的Lite特性值得研究的是属性。在Lite可以设置房间和玩家的属性。作为一个示例,您可以设定初始配置值(例如游戏地图,困难,…)作为房间属性或玩家昵称作为玩家的属性(详情见客户端SDK文档)。
Final Demo Code 最后的演示代码
In the code that follows we’ve added a couple of lines to increase debug-output, which should help you to dig deeper into the details of how Photon and Lite work.
下列代码所示,我们已经添加了一对调试输出,这应该有助于您更深入的理解Photon和Lite的工作细节。
|