Photon——聊天室
Lite Lobby Chat Demo Lite大厅的聊天演示
This demo shows some of the things you can do with Photon’s “Lite Lobby” Application, purely from the client side. No server programming is involved here. The client is written in C# and uses the Unity Engine (which also has a free edition) as framework and GUI.
这个演示展示了一些你能用Photon的“Lite Lobby” 应用程序做的东西,纯粹从客户端的角度来说。这里没有涉及服务器编程。客户端是用c#编写的,并使用统一的引擎 (这也有一个免费版)作为框架和GUI。
The Lite Lobby Chatroom Demo part of the Unity Client SDK v6.2.0 (and up).
Get the SDK and demo here
.
Lite Lobby 聊天室演示部分,统一客户端使用SDK v6 2 0(和 )。
Content 内容
- Quick Walkthrough 快速演练
- Development Toolkit 开发工具包
- The Lobby 大厅
- The Chatroom 聊天室
- Using Properties for Usernames 使用户名作为属性
- Sending and Receiving Chat Messages 发送和接收聊天信息
- Lite Lobby Chat Demo Live Lite Lobby聊天室演示
Quick Walkthrough 快速演练
First off, let’s go through the screens. When you start, the demo will automatically connect to Photon on the local machine. If this fails (and whenever connection is lost), it will display an error message.
首先,让我们浏览一下屏幕。当你开始,演示将自动连接到本地机器上的Photon。 如果失败了,它将显示一条错误消息。
You will be asked to enter a name and “Proceed to Lobby”. A short text explains “debug mode” keys to control multiple clients. This will come in handy in a minute. Enter the lobby. As no one else is on your server, you should create a room. Enter a name and click “Enter”. The next screen is a simple chat: Messages are entered at the bottom, users listed on the right side and there is a button to leave. Now, more users would be nice.
你将被要求输入一个名称和“Proceed to Lobby”。 一个短文本提供“调试模式”的key来控制多个客户端。这将在一分钟内派上用场。进入大厅。因为没有人是在你的服务器上, 您应该创建一个房间。输入一个名称,然后单击“进入”。接下来的屏幕是一个简单的聊天:在底部输入消息,用户列表在右侧,还有一个离开按钮。现在,用户越多越好。
Of course, you could start the demo several times to get more people in the chat. To make things easier, you can instantiate a chat client by pressing insert and switch between the simulated clients with Page Up and page down key.
当然,你可以开始演示几次,让更多的人在聊天。为了让事情简单,你可以实例化一个聊天客户端,通过插入和切换模拟客户页面。
Press: insert, then page down. You will see the name input screen once more. Keep in mind: now the input controls another client. Choose another name and press the button and now you will see a button. The lobby will create a button for each room that’s in use. You can join that room or create another.
按:插入,然后下一页。您将看到名字输入屏幕越来越多。记住:现在输入控制其他的客户端。选择另一个名称和按下按钮,现在你会看到一个按钮。大厅将为每个房间创建一个按钮。你可以加入这个房间或创建另一个。
Development Toolkit 开发工具包
In this demo (and in general) things get easier to develop if you don’t have to run a game many times to test multiplayer. For that reason, we will create clients that maintain their connection and state but not their input. Input is something we do in a central place and apply to an active client.
在这个演示里(一般来说)事情变得更容易发展,如果你不要运行游戏的多人多次测试。出于这个原因,我们将创建客户端,让他们保持连接和状态但不是他们输入的。输入是我们在一个中心位置和适用于一个活跃的客户端。
The PhotonClient is the lowest level we can automate a client. This can be re-used in other applications and will simply keep a state and connection. Extending it is the ChatPhotonClient, which adds chat-related states, a few methods and most of all, it handles our custom chat events.
这个PhotonClient是最低级的可以自动化的客户端。这可以被重用在其他应用程序和将简单地保持状态和连接。ChatPhotonClient扩展了它,从而增加了聊天相关的状态,一些方法和最重要的是,它处理我们定制的聊天事件。
To make this run in Unity, we need to attach the ChatGUI script and an initial ChatPhotonClient to any game object. We used the empty object “PhotonScripts”. The public values of the initial ChatPhotonClient let us edit the server before we start the demo. While running, we can check each client’s state, name, room and control if we want it’s debug out in the console. This setup makes things transparent and we might even save a few debugging sessions.
为了让这个运行在Unity,我们需要将ChatGUI脚本和一个最初的ChatPhotonClient放入任何的游戏对象。我们使用了空对象“PhotonScripts”。在我们演示开始前初始化ChatPhotonClient公共的值让我们编辑这服务器。在运行时,我们可以检查每个客户端的状态、名称、房间和控制,如果我们希望在控制台上调试它。这种设置使得事情更透明,我们甚至可能节省几个调试会话。
Missing in the client library is a special peer class for Lite Lobby, which is more or less treated like any game you would develop. To fill the gap, we implement a LiteLobbyPeer class, which is used instead of the LitePeer. Once you create your own operations for your game, you should also create a fitting “myGamePeer” class.
对于大厅来说在客户端库中缺少的是一个特殊的peer类,这是或多或少被对待像任何你会开发的游戏。填补这种差距,我们实现了一个LiteLobbyPeer类,它是用来代替 LitePeer的。一旦你为你自己的游戏创建你自己的操作,你还应该创建一个恰当的“myGamePeer”类。
The Lobby 大厅
The Lite Lobby Application defines two types of rooms: games and lobbies. But there is only one operation to join a room and there is no parameter to choose. The secret is that Lite Lobby creates a lobby when the roomname to join ends on “_lobby” and a game room in any other case. An optional parameter can be used to tell a game room to update a certain lobby. Read about the server side on page:
Lite Lobby Concepts
.
Lite Lobby 应用程序定义了两种类型的房间:游戏和游戏大厅。但只有一个操作来加入一个房间,没有参数选择。这个是因为Lite大厅创建一个大厅同时roomname加入“_lobby”的最后,和创建一个游戏房间在任何其他情况下。一个可选的参数可用于告诉一个游戏房间更新一个确定的大厅。阅读服务器端页面: Lite大厅概念 。
The additional features of Lite Lobby are currently not implemented by the client library. This where our LiteLobbyPeer class comes to use. It defined the needed operation- and event-codes and the codes for parameters and event-content already.
通过客户端库,Lite大厅的额外特性是目前尚未实现的。这里我们的LiteLobbyPeer类将拿来使用。它已经定义了所需的操作、事件代码、代码参数、事件内容。
As all rooms are created on the fly, it’s up to the client to create lobbies, too. This demo uses a single lobby called “chat_lobby” but you could also add a selection-screen for a list of lobbies. As Lite Lobby does not support listing of lobbies out of the box, you could either add a hard-coded list to select or change the server code to support it.
因为所有的房间都是动态创建的,促使它的客户端也去创建大厅。该示例使用单个大厅称为“聊天大厅”,但是你还可以添加一个选择屏幕列表的大厅。因为Lite Lobby不支持大厅清单,你可以补充一个硬编码的列表,选择或更改服务器代码来支持它。
The Chatroom 聊天室
We already noticed that the operation
Join
creates lobbies and games alike. When we create a room, we might set a lobby to report to. LiteLobbyPeer implements this version of operation
Join
as
OpJoinFromLobby. It wraps up the required parameters and even allows us to set actor properties (but not room properties, which we don’t use in this demo anyways).
我们已经注意到这个
Join操作创建大厅和游戏。当我们创建一个房间,我们可能会设置一个大厅报告。LiteLobbyPeer实现了这个版本的
Join操作作为OpJoinFromLobby。它包括了必需的参数,甚至允许我们设置actor的属性。
public
virtual
short
OpJoinFromLobby(
string
gameName,
string
lobbyName, Hashtable actorProperties,
bool
broadcastActorProperties)
{
if
(
this
.DebugOut >= DebugLevel.ALL)
{
this
.Listener.DebugReturn(DebugLevel.ALL, String.Format(
"OpJoin({0}/{1})"
, gameName, lobbyName));
}
// All operations get their parameters as key-value set (a Hashtable)
Hashtable opParameters =
new
Hashtable();
opParameters[(
byte
)LiteLobbyOpKey.RoomName] = gameName;
opParameters[(
byte
)LiteLobbyOpKey.LobbyName] = lobbyName;
if
(actorProperties !=
null
)
{
opParameters[(
byte
)LiteOpKey.ActorProperties] = actorProperties;
if
(broadcastActorProperties)
{
opParameters[(
byte
)LiteOpKey.Broadcast] = broadcastActorProperties;
}
}
return
OpCustom((
byte
)LiteLobbyOpCode.Join, opParameters,
true
);
}
Inside the chatroom, we want to know who is there and we want to send messages to everyone in the room.
在聊天室,我们想知道有谁和我们想要发送信息给房间里的每一个人。
Using Properties for Usernames 使用户名作为属性
In the Lite Lobby application, properties are key-value sets you can use to describe rooms and actors. This demo uses actor properties to store usernames. Each client sets its name in the
Join
operation and uses broadcast to send the new name to the others.
在Lite大厅应用程序中,属性是一个键-值集,你可以使用它来描述房间和玩家。该示例使用玩家属性来存储用户名。每个客户端设置它的名称在加入操作和使用广播发送新名称到其他人那。
public
void
JoinRoomFromLobby(
string
roomName)
{
this
.RoomName = roomName;
this
.ChatState = ChatStateOption.JoiningChatRoom;
this
.ActorProperties =
new
Hashtable();
Hashtable props =
new
Hashtable() { { ChatActorProperties.Name,
this
.UserName } };
this
.Peer.OpJoinFromLobby(
this
.RoomName,
this
.LobbyName, props,
true
);
}
A new user won’t get a join event for players who were in the room before, so we use
GetProperties
to get everyone’s name. It’s result is the complete property set at the time being. From now on, we just add properties when someone joins or remove them on leave.
一个新用户不会得到房间里其他玩家之前的加入操作信息,所以我们使用GetProperties获得每个人的名字。它的结果是在任何时候都有完整的属性集。从现在起,我们只是添加属性在有人加入或离开时移除它们。
switch
(eventCode)
{
// this client or any other joined the room (lobbies will not send this event but chat rooms do)
case
(
byte
)LiteEventCode.Join:
this
.DebugReturn(SupportClass.HashtableToString(photonEvent));
// update the list of actor numbers in room
this
.ActorNumbersInRoom = (
int
[])photonEvent[(
byte
)LiteOpKey.ActorList];
// update the list of actorProperties if any were set on join
Hashtable actorProps = photonEvent[(
byte
)LiteOpKey.ActorProperties]
as
Hashtable;
if
(actorProps !=
null
)
{
this
.ActorProperties[originatingActorNr] = actorProps;
}
break
;
// some other user left this room - remove his data
case
(
byte
)LiteEventCode.Leave:
// update the list of actor numbers in room
this
.ActorNumbersInRoom = (
int
[])photonEvent[(
byte
)LiteOpKey.ActorList];
// update the list of actorProperties we cache
if
(
this
.ActorProperties.ContainsKey(originatingActorNr))
{
this
.ActorProperties.Remove(originatingActorNr);
}
break
;
Sending and Receiving Chat Messages 发送和接收聊天信息
The Lite Lobby Application gives us rooms and events and a way to raise them. In our simple chat demo we don’t need the server to understand what the clients are talking about.
Lite应用提供给我们房间、事件和一个方法去触发他们。在我们的简单聊天演示中我们不需要服务器来理解客户正在谈论什么。
Custom events are defined by the client and handled by the clients. The server just passes them on when a client calls
RaiseEvent. In our case, we select a free event code and add in our text line.
通过客户端定义和处理自定义事件。服务器只是传递他们当客户端调用RaiseEvent的时候。在我们的例子中,我们选择一个事件代码并添加在我们的文本行中。
public
void
SendChatMessage(
string
line)
{
Hashtable chatEvent =
new
Hashtable();
// the custom event's data. content we want to send
chatEvent.Add((
byte
)ChatEventKey.TextLine, line);
// add some content
this
.Peer.OpRaiseEvent((
byte
)ChatEventCode.Message, chatEvent,
true
);
// call raiseEvent with our content and a event code
// because Photon won't send this event back to this client, we will add the chat line locally
this
.ChatLines.AppendLine(String.Format(
"{0}: {1}"
,
this
.UserName, line));
}
Lite Lobby will create an event from our data and adds the origin to it. Other users in a room will receive the event and know who sent it. To keep things lean, this is done by the
actorNumbers.
Lite大厅将创建一个事件来自我们的数据并添加它。其他用户在一个房间里将接收到事件和知道是谁发送来的。保持精简,这是通过actorNumbers可以做的。
Our sent text line is the data of our custom event. We fetch the name and put the line to the chat buffer.
我们发送的文本行是我们自定义事件的数据。我们取这个名字并把它放入聊天缓冲区。
Lite Lobby Chat Demo Live Lite Lobby聊天演示
This is a live version of the Lite Lobby Chat Demo from the Unity Client SDK (v6.2.0 and up).If this page is loaded more than once, you will see a list of rooms in use and chat users inside those.
这是一个可用的Lite大厅聊天演示来自Unity客户端SDK(v6 2 0)。如果这个页面被加载不止一次,您将看到一列个房间列表被使用和聊天的用户在那里面。
An App From Scratch 一个应用程序从头开始
This tutorial will try to help you understand how to build an application from scratch.(aka “Blank Server Tutorial”)
本教程将试图帮助您理解如何从头构建应用程序(又名空白服务器教程)
Build a Simple Chat Server From the Scratch In 10 Minutes
10分钟从头开始构建一个简单的聊天服务器
Hint: This tutorial is thought as a first step in understanding the basics of the main concepts in Photon Application and Peer. For most developers that at some point will use rooms we recommend to start with with an application that inherits from Lite.Application.
提示:本教程是作为理解Photon应用和Peer中的主要概念的第一步。对于大多数开发人员来说,当使用房间时,我们建议应用程序继承自Lite应用程序。
- Download and unzip the SDK
- Create a new class library project ‘ChatServer’
- Add references to ExitGamesLibs.dll, Photon.SocketServer.dll and PhotonHostRuntimeInterfaces.dll
- Create a new class ‘ChatServer’ and inherit from ‘Photon.SocketServer.ApplicationBase’:
- 下载并解压缩SDK
- 创建一个新的类库项目”ChatServer”
- 添加引用ExitGamesLibs.dll, Photon.SocketServer.dll and PhotonHostRuntimeInterfaces.dll
- 创建一个新类的ChatServer和继承 Photon.SocketServer.ApplicationBase
using
Photon.SocketServer;
public
class
ChatServer : ApplicationBase
{
protected
override
PeerBase CreatePeer(InitRequest initRequest)
{
}
protected
override
void
Setup()
{
}
protected
override
void
TearDown()
{
}
}
- Create a new class ‘ChatPeer’ and inherit from ‘Photon.SocketServer.PeerBase’:
- 创建一个新的类ChatPeer并继承自Photon.SocketServer.PeerBase
using
Photon.SocketServer;
using
PhotonHostRuntimeInterfaces;
public
class
ChatPeer : PeerBase
{
public
ChatPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer)
:
base
(protocol, unmanagedPeer)
{
}
protected
override
void
OnDisconnect()
{
}
protected
override
void
OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
{
}
}
- Return a new instance of ChatPeer at ChatServer.CreatePeer:
- 在ChatServer.CreatePeer返回一个新的ChatPeer实例
protected
override
PeerBase CreatePeer(InitRequest initRequest)
{
return
new
ChatPeer(initRequest.Protocol, initRequest.PhotonPeer);
}
- The server configuration looks like this:
- 服务器配置文件如下:
<
Applications
Default
=
"ChatServer"
>
<
Application
Name
=
"ChatServer"
BaseDirectory
=
"ChatServer"
Assembly
=
"ChatServer"
Type
=
"ChatServer"
/>
</
Applications
>
This config requires that the server binaries are located under ‘deploy/ChatServer/bin’ and that class ChatServer does not belong to a namespace.
这种配置要求服务器二进制文件位于下 deploy/ChatServer/bin,这类ChatServer不属于一个名称空间。
- Create a new console project for a chat client
- Add reference to PhotoDotNet.dll to the new project
- Client code:
- 创建一个新的控制台项目作为聊天客户端
- 添加引用PhotoDotNet.dll
- 客户端代码:
using
System;
using
System.Collections.Generic;
using
System.Text;
using
ExitGames.Client.Photon;
public
class
ChatClient : IPhotonPeerListener
{
private
bool
connected;
public
static
void
Main()
{
var
client =
new
ChatClient();
var
peer =
new
PhotonPeer(client,
true
);
// connect
client.connected =
false
;
peer.Connect(
"127.0.0.1:4530"
,
"ChatServer"
);
while
(!client.connected)
{
peer.Service();
}
var
buffer =
new
StringBuilder();
while
(
true
)
{
peer.Service();
// read input
if
(Console.KeyAvailable)
{
ConsoleKeyInfo key = Console.ReadKey();
if
(key.Key != ConsoleKey.Enter)
{
// store input
buffer.Append(key.KeyChar);
}
else
{
// send to server
var
parameters =
new
Dictionary<
byte
,
object
> { { 1, buffer.ToString() } };
peer.OpCustom(1, parameters,
true
);
buffer.Length = 0;
}
}
}
}
public
void
DebugReturn(DebugLevel level,
string
message)
{
Console.WriteLine(level +
": "
+ message);
}
public
void
OnEvent(EventData eventData)
{
Console.WriteLine(
"Event: "
+ eventData.Code);
if
(eventData.Code == 1)
{
Console.WriteLine(
"Chat: "
+ eventData.Parameters[1]);
}
}
public
void
OnOperationResponse(OperationResponse operationResponse)
{
Console.WriteLine(
"Response: "
+ operationResponse.OperationCode);
}
public
void
OnStatusChanged(StatusCode statusCode)
{
if
(statusCode == StatusCode.Connect)
{
this
.connected =
true
;
}
else
{
Console.WriteLine(
"Status: "
+ statusCode);
}
}
}
- If we now start the server the client will be able to connect and to send text messages, but the server logic to process these text messages is still missing. To verify that the message was received we answer with an OperationResponse at ChatPeer.OnOperationRequest:
- 如果我们开始这服务器,客户端将可能去连接并发送文本信息,但是服务器处理这些消息仍然会有丢失。为了验证这些信息是被接收到了,我们会在ChatPeer.OnOperationRequest响应一个OperationResponse:
protected
override
void
OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
{
var
response =
new
OperationResponse(operationRequest.OperationCode);
this
.SendOperationResponse(response, sendParameters);
}
The chat client should now print the event code and the chat message.
这聊天客户端会打印事件代码和聊天信息
- Next thing we want to do is to receive the chat messages on other clients. We implement a publish/subscribe pattern:
- 接下来我们要做的就是在其他客户端接收聊天信息。我们实现了一个发布/订阅模式:
using
System;
using
Photon.SocketServer;
using
PhotonHostRuntimeInterfaces;
public
class
ChatPeer : PeerBase
{
private
static
readonly
object
syncRoot =
new
object
();
public
ChatPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer)
:
base
(protocol, unmanagedPeer)
{
lock
(syncRoot)
{
BroadcastMessage +=
this
.OnBroadcastMessage;
}
}
private
static
event
Action<ChatPeer, EventData, SendParameters> BroadcastMessage;
protected
override
void
OnDisconnect()
{
lock
(syncRoot)
{
BroadcastMessage -=
this
.OnBroadcastMessage;
}
}
protected
override
void
OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
{
var
@
event
=
new
EventData(1) { Parameters = operationRequest.Parameters };
lock
(syncRoot)
{
BroadcastMessage(
this
, @
event
, sendParameters);
}
var
response =
new
OperationResponse(operationRequest.OperationCode);
this
.SendOperationResponse(response, sendParameters);
}
private
void
OnBroadcastMessage(ChatPeer peer, EventData @
event
, SendParameters sendParameters)
{
if
(peer !=
this
)
{
this
.SendEvent(@
event
, sendParameters);
}
}
}
If you now start two clients they should be able to exchange messages.
如果你现在开始运行2个客户端,他们可以交换信息。
Adding Operations 添加操作
In many cases, just sending events is not enough for a game. If you want to provide authorization, persistency or game-specific Operations, it’s time to extend Lite (or LiteLobby).This page shows two ways to implement new Operations and how to use them. Our sample Operation expects a message from the client, checks it and provides return values.
在许多情况下,只是发送事件是不够的,对于一个游戏。如果你想要提供授权、持续性或游戏操作,它是需要扩展Lite(或LiteLobby)。这个页面显示了两个方法来实现新业务和如何使用他们。我们的示例操作预计消息来自客户端,检查它并提供返回值。
Server-Side, Simple Variant 服务端,简单的变体
Let’s start on the server side, in the Lite Application. This variant is not very elegant but simple.
让我们在服务端开始,在Lite应用中,这变体不是很优雅但是很简单
//Server SDK, LitePeer.cs
public
void
OnOperationRequest(OperationRequest request)
{
// handle operation here (check request.OperationCode)
switch
(request.OperationCode)
{
case
1:
{
var
message = (
string
)request.Params[100];
if
(message ==
"Hello World"
)
{
// received hello world, send an answer!
var
response =
new
OperationResponse(request, 0,
"OK"
,
new
Dictionary<
short
,
object
> { { 100,
"Hello yourself!"
} } );
this
.PhotonPeer.SendOperationResponse(response);
}
else
{
// received something else, send an error
var
response =
new
OperationResponse(request, 1,
"Don't understand, what are you saying?"
);
this
.PhotonPeer.SendOperationResponse(response);
}
break
;
}
}
}
|
The above code first checks the called OperationCode. In this case, it’s 1. In Photon, the OperationCode is a shortcut for the name/type of an Operation. We’ll see how the client call this below.
上面的代码首先检查OperationCode的调用。在这种情况下,它是1。在Photon,OperationCode是一个操作的名称或类型的快捷方式。下面我们将看到客户端是如何调用这个。
If Operation 1 is called, we check the request parameters. Here, parameter 100 is expected to be a string. Again, Photon only uses byte-typed parameters, to keep things lean during transfer.
如果操作1被调用,我们检查请求参数。在这里, 参数100预计是一个字符串。再一次,Photon只使用字节输入参数,为了保持精简传递。
The code then checks the message content and prepares a response. Both responses have a returnCode and a debug message. The returnCode 0 is a positive return, any other number (here: 1) could mean an error and needs to be handled by the client.
然后该代码检查消息内容和准备一个响应。两端的响应有一个returnCode和调试消息。这个returnCode 0是一个积极的返回值,任何其他号码(这里是:1)可能意味着一个错误和需要由客户端处理。
Our positive response also includes a return value. You could add more key-value pairs, but here we stick to 100, which is a string.
我们积极响应还包括一个返回值。你可以添加更多的键-值对,但是这里我们坚持到100,这是一个字符串。
This already is a complete implementation of an Operation. This is our convention for a new Operation, which must be carried over to the client to be used.
这已经是一个完整的操作实现。这是我们约定的新操作,必须被传递到客户端使用。
Client Side 客户端
Knowing the definition of above’s Operation, we can call it from the client side. This client code calls it on connect:
知道了上面操作的定义,我们可以从客户端调用。客户端代码调用它在连接时候:
public
void
PeerStatusCallback(
int
returnCode)
{
// handle peer status callback
switch
((ReturnCode)returnCode)
{
case
ReturnCode.Connect:
|