# 观前提醒
项目会在 GitHub 中开源,链接:https://github.com/Maikire/UnityGameDemo/tree/main/A simple web chat room
如果有什么问题或想法,欢迎各位在评论区留言。
# 网络聊天室
本章将制作一个基于 UDP 的网络聊天室,客户端与服务端都用 Unity 来实现(实际的开发中,服务端一定不是用 Unity 做的)
需求:
- 用户可以自由选择登录服务端或客户端。
- 客户端:输入名字、服务端的 IP 和端口后即可进行多人聊天。
- 服务端:输入服务端的 IP 和端口后即可开启服务,无法进行聊天,可以看到谁在线,以及他们的 IP 和端口
# 需求分析
- 使用 ClientSever 架构
- 向服务端发送数据时,不能发送自定义的类,只能发送基本的数据类型,例如:bool/int/float/string...
- 自定义协议:按照指定的格式发送数据和读取数据
具体内容
- 发送的消息可能有很多种,使用枚举区分
- 将消息的内容封装到一个类中,自定义协议,提供字节数组与对象之间的转换的功能
- 制作客户端网络服务类,实现接收和发送消息的功能,将网络部分与其他部分隔离
- 实现登录功能,通知服务端上线
- 制作客户端的脚本,实现基本的功能
- 制作服务端网络服务类,与客户端网络服务类相同
- 制作服务端的脚本,实现基本的功能
- 仅有三个类是最重要的:消息内容、客户端网络服务类、服务端网络服务类。其他的类都不是重点。
# 代码部分
# 消息类型
代码如下:
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 消息类型 | |
/// </summary> | |
public enum MessageType | |
{ | |
/// <summary> | |
/// 上线 | |
/// </summary> | |
OnLine, | |
/// <summary> | |
/// 下线 | |
/// </summary> | |
OffLine, | |
/// <summary> | |
/// 普通消息 | |
/// </summary> | |
General, | |
} | |
} |
# 消息内容
- 封装聊天信息
- 字节数组的转换:可以单独处理每个数据类型的转换,但是这样做比较麻烦。所以,本文使用内存流、二进制写入器、二进制读取器
代码如下:
using System; | |
using System.IO; | |
using System.Text; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 聊天信息 | |
/// </summary> | |
public class ChatMessage | |
{ | |
/// <summary> | |
/// 消息类型 | |
/// </summary> | |
public MessageType Type; | |
/// <summary> | |
/// 发送者 | |
/// </summary> | |
public string SenderName; | |
/// <summary> | |
/// 发送内容 | |
/// </summary> | |
public string Content; | |
/// <summary> | |
/// ChatMessage | |
/// </summary> | |
public ChatMessage() { } | |
/// <summary> | |
/// 赋值 | |
/// </summary> | |
/// <param name="messageType">messageType</param> | |
/// <param name="senderName">senderName</param> | |
/// <param name="content">content</param> | |
public ChatMessage(MessageType messageType, string senderName, string content) | |
{ | |
Type = messageType; | |
SenderName = senderName; | |
Content = content; | |
} | |
/// <summary> | |
/// object -> byte[] | |
/// </summary> | |
/// <returns></returns> | |
public byte[] ObjectToBytes() | |
{ | |
//string/int/bool... -- 二进制写入器 -- 内存流 --> byte [] | |
// 内存流 | |
using (MemoryStream stream = new MemoryStream()) | |
{ | |
// 二进制写入器 | |
// 属性 -> 内存流 | |
BinaryWriter writer = new BinaryWriter(stream); | |
WriteString(writer, Type.ToString()); | |
WriteString(writer, SenderName); | |
WriteString(writer, Content); | |
// 内存流 -> byte [] | |
return stream.ToArray(); | |
} | |
} | |
/// <summary> | |
/// 写入二进制数据 | |
/// </summary> | |
/// <param name="writer">writer</param> | |
/// <param name="str">string</param> | |
private void WriteString(BinaryWriter writer, string str) | |
{ | |
// 不用这个,因为这个只支持一种编码 | |
//writer.Write(str); | |
// 编码 | |
byte[] byte_Type = Encoding.UTF8.GetBytes(str); | |
// 写入长度 | |
writer.Write(byte_Type.Length); | |
// 写入内容 | |
writer.Write(byte_Type); | |
} | |
/// <summary> | |
/// byte[] -> object | |
/// </summary> | |
/// <param name="bytes">bytes</param> | |
/// <returns></returns> | |
public static ChatMessage BytesToObject(byte[] bytes) | |
{ | |
//byte [] -- 内存流 -- 二进制读取器 --> string/int/bool... | |
using (MemoryStream stream = new MemoryStream(bytes)) | |
{ | |
BinaryReader reader = new BinaryReader(stream); | |
MessageType type = (MessageType)Enum.Parse(typeof(MessageType), ReadString(reader)); | |
string senderName = ReadString(reader); | |
string content = ReadString(reader); | |
return new ChatMessage(type, senderName, content); | |
} | |
} | |
/// <summary> | |
/// 读取二进制数据 | |
/// </summary> | |
/// <param name="reader">reader</param> | |
/// <returns></returns> | |
private static string ReadString(BinaryReader reader) | |
{ | |
// 不用这个,因为这个只支持一种编码 | |
//reader.ReadString(); | |
// 读 4 个字节:标记了接下来要读取的长度 | |
int length = reader.ReadInt32(); | |
// 读指定的长度 | |
byte[] bytes = reader.ReadBytes(length); | |
// 转字符串 | |
return Encoding.UTF8.GetString(bytes); | |
} | |
} | |
} |
# 登录信息类
- 储存登录的信息,例如:IP、端口、名字
代码如下:
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 登录信息 | |
/// </summary> | |
public class LogInContent | |
{ | |
/// <summary> | |
/// Name | |
/// </summary> | |
public string Name; | |
/// <summary> | |
/// IP | |
/// </summary> | |
public string IP; | |
/// <summary> | |
/// Port | |
/// </summary> | |
public string Port; | |
} | |
} |
# 客户端网络服务类
- 这个类只需要一个,所以使用单例模式
- 使用 UDP 协议
- 创建一个线程,用于接收消息
- 使用事件处理消息的接收:接收到消息后触发事件并传递参数
- 辅助线程不能访问 Unity 的 API,需要使用线程交叉访问助手解决这个问题
事件参数类:
using System; | |
using System.Net; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 接收事件的信息类 | |
/// </summary> | |
public class ReceiveEventArgs : EventArgs | |
{ | |
/// <summary> | |
/// ChatMessage | |
/// </summary> | |
public ChatMessage Message; | |
/// <summary> | |
/// IPEndPoint | |
/// </summary> | |
public IPEndPoint SourceRemote; | |
public ReceiveEventArgs() { } | |
public ReceiveEventArgs(ChatMessage message, IPEndPoint sourceRemote) | |
{ | |
Message = message; | |
SourceRemote = sourceRemote; | |
} | |
} | |
} |
客户端网络服务类:
using Common; | |
using System; | |
using System.Net; | |
using System.Net.Sockets; | |
using System.Threading; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 客户端 UDP 网络服务 | |
/// </summary> | |
public class ClientUdpNetService : MonoSingleton<ClientUdpNetService> | |
{ | |
/// <summary> | |
/// 接收到消息后触发 | |
/// </summary> | |
public event EventHandler<ReceiveEventArgs> OnReceive; | |
/// <summary> | |
/// 登录信息 | |
/// </summary> | |
public LogInContent Content; | |
/// <summary> | |
/// UdpClient | |
/// </summary> | |
private UdpClient UDPClient; | |
/// <summary> | |
/// 客户端线程 | |
/// </summary> | |
private Thread ClientThread; | |
private void Start() | |
{ | |
ClientThread = new Thread(ReceiveMessage); | |
} | |
/// <summary> | |
/// 初始化 | |
/// 由登录窗口传递 IP 和端口 | |
/// </summary> | |
/// <param name="content"> 登录信息 & lt;/param> | |
public void Initialize(LogInContent content) | |
{ | |
Content = content; | |
// 随机分配端口 | |
UDPClient = new UdpClient(); | |
// 与服务端连接(仅仅是配置自身的 Socket) | |
// 单播:只能给服务器发数据 | |
UDPClient.Connect(IPAddress.Parse(Content.IP), int.Parse(Content.Port)); | |
// 开启线程 | |
ClientThread.Start(); | |
NotiyfSever(MessageType.OnLine); | |
} | |
private void NotiyfSever(MessageType type) | |
{ | |
if (Content == null) return; | |
SendMessage(new ChatMessage(type, Content.Name, string.Empty)); | |
} | |
/// <summary> | |
/// 发送数据 | |
/// </summary> | |
/// <param name="message"> 信息 & lt;/param> | |
public void SendMessage(ChatMessage message) | |
{ | |
byte[] bytes = message.ObjectToBytes(); | |
// 创建 Socket 对象时建立了链接,所以不能绑定终结点 | |
UDPClient.Send(bytes, bytes.Length); | |
} | |
/// <summary> | |
/// 接收消息 | |
/// </summary> | |
/// <param name="message"></param> | |
private void ReceiveMessage() | |
{ | |
while (true) | |
{ | |
// 任意 IP 地址,任意端口 | |
IPEndPoint sourceRemote = new IPEndPoint(IPAddress.Any, 0); | |
// 接收消息 | |
byte[] bytes = UDPClient.Receive(ref sourceRemote); | |
// 转化消息 | |
ChatMessage message = ChatMessage.BytesToObject(bytes); | |
// 事件参数 | |
ReceiveEventArgs args = new ReceiveEventArgs(message, sourceRemote); | |
// 在主线程中触发事件 | |
ThreadCrossHelper.Instance.ExecuteOnMainThread(() => | |
{ | |
OnReceive?.Invoke(this, args); | |
}); | |
} | |
} | |
private void OnApplicationQuit() | |
{ | |
NotiyfSever(MessageType.OffLine); | |
ClientThread?.Abort(); | |
UDPClient?.Dispose(); | |
} | |
} | |
} |
# 登录功能
- 登录界面分两部分制作,点击按钮切换
- 使用变换组件助手类寻找物体
- 注册按钮事件,切换场景
- 保存用户输入的信息
- 会涉及到服务端网络服务类的代码,它与客户端网络服务类的代码相似,具体内容会在下文中讲到
登录信息输入,分别挂载到物体 “ClientContent” 和 “SeverContent” 上
using Common; | |
using TMPro; | |
using UnityEngine; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 登录信息输入 | |
/// </summary> | |
public class LogInInput : MonoBehaviour | |
{ | |
/// <summary> | |
/// Input_Name | |
/// </summary> | |
private TMP_InputField Input_Name; | |
/// <summary> | |
/// Input_IP | |
/// </summary> | |
private TMP_InputField Input_IP; | |
/// <summary> | |
/// Input_Port | |
/// </summary> | |
private TMP_InputField Input_Port; | |
/// <summary> | |
/// 登录信息 | |
/// </summary> | |
public LogInContent Content | |
{ | |
get | |
{ | |
return new LogInContent() | |
{ | |
Name = Input_Name?.text, | |
IP = Input_IP.text, | |
Port = Input_Port.text, | |
}; | |
} | |
} | |
private void Awake() | |
{ | |
Input_Name = this.transform.FindChildByName("Input_Name")?.GetComponent<TMP_InputField>(); | |
Input_IP = this.transform.FindChildByName("Input_IP").GetComponent<TMP_InputField>(); | |
Input_Port = this.transform.FindChildByName("Input_Port").GetComponent<TMP_InputField>(); | |
} | |
} | |
} |
登录按钮的处理,挂载到物体 “Canvas” 上
using UnityEngine; | |
using UnityEngine.SceneManagement; | |
using UnityEngine.UI; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// LogInButton | |
/// </summary> | |
public class LogInButtons : MonoBehaviour | |
{ | |
/// <summary> | |
/// LogInType | |
/// </summary> | |
private enum LogInType | |
{ | |
/// <summary> | |
/// 客户端 | |
/// </summary> | |
Client, | |
/// <summary> | |
/// 服务端 | |
/// </summary> | |
Sever, | |
} | |
[Header("Client")] | |
[Tooltip("客户端登录")] | |
public Button Client; | |
[Tooltip("ClientContent")] | |
public GameObject ClientContent; | |
[Tooltip("ClientImage")] | |
private Image ClientImage; | |
[Header("Sever")] | |
[Tooltip("服务端登录")] | |
public Button Sever; | |
[Tooltip("SeverContent")] | |
public GameObject SeverContent; | |
[Tooltip("SeverImage")] | |
private Image SeverImage; | |
[Header("Enter")] | |
[Tooltip("登录按钮")] | |
public Button Enter; | |
/// <summary> | |
/// LogInType | |
/// </summary> | |
private LogInType LogType; | |
private void Awake() | |
{ | |
// 考虑到遍历物体的性能开销,所以不用代码找物体 | |
ClientImage = Client.GetComponent<Image>(); | |
SeverImage = Sever.GetComponent<Image>(); | |
} | |
private void Start() | |
{ | |
LogType = LogInType.Client; | |
Client.onClick.AddListener(OnClientButtonClick); | |
Sever.onClick.AddListener(OnServerButtonClick); | |
Enter.onClick.AddListener(OnEnterButtonClick); | |
} | |
/// <summary> | |
/// OnClientButtonClick | |
/// </summary> | |
private void OnClientButtonClick() | |
{ | |
ClientImage.color = Color.red; | |
ClientContent.SetActive(true); | |
SeverImage.color = Color.white; | |
SeverContent.SetActive(false); | |
LogType = LogInType.Client; | |
} | |
/// <summary> | |
/// OnServerButtonClick | |
/// </summary> | |
private void OnServerButtonClick() | |
{ | |
ClientImage.color = Color.white; | |
ClientContent.SetActive(false); | |
SeverImage.color = Color.red; | |
SeverContent.SetActive(true); | |
LogType = LogInType.Sever; | |
} | |
/// <summary> | |
/// OnEnterButtonClick | |
/// </summary> | |
private void OnEnterButtonClick() | |
{ | |
switch (LogType) | |
{ | |
case LogInType.Client: | |
GoClient(); | |
break; | |
case LogInType.Sever: | |
GoServer(); | |
break; | |
default: | |
break; | |
} | |
} | |
/// <summary> | |
/// GoClient | |
/// </summary> | |
private void GoClient() | |
{ | |
LogInInput logInInput = ClientContent.GetComponent<LogInInput>(); | |
ClientUdpNetService.Instance.Initialize(logInInput.Content); | |
SceneManager.LoadScene("ChatDemoClient", LoadSceneMode.Single); | |
} | |
/// <summary> | |
/// GoServer | |
/// </summary> | |
private void GoServer() | |
{ | |
LogInInput logInInput = SeverContent.GetComponent<LogInInput>(); | |
SeverUdpNetService.Instance.Initialize(logInInput.Content); | |
SceneManager.LoadScene("ChatDemoSever", LoadSceneMode.Single); | |
} | |
} | |
} |
# 客户端的脚本
- 制作 UI 界面
- 接收消息、发送消息、生成消息(预制件)
消息的拥有者
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 消息的拥有者 | |
/// </summary> | |
public enum MessageOwer | |
{ | |
My, | |
Other | |
} | |
} |
挂载到预制件上
using Common; | |
using System; | |
using TMPro; | |
using UnityEngine; | |
using UnityEngine.UI; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 客户端信息管理 | |
/// </summary> | |
public class ClientMessageManger : MonoBehaviour | |
{ | |
/// <summary> | |
/// 设置信息 | |
/// </summary> | |
/// <param name="message"> 信息 & lt;/param> | |
/// <param name="name"> 发送者 & lt;/param> | |
public void SetMessage(string message, string name) | |
{ | |
this.transform.FindChildByName("Message").GetComponent<TextMeshProUGUI>().text = message; | |
this.transform.FindChildByName("Name").GetComponent<TextMeshProUGUI>().text = name; | |
this.transform.FindChildByName("Time").GetComponent<TextMeshProUGUI>().text = DateTime.Now.ToString(); | |
} | |
} | |
} |
挂载到 “Content” 上
using UnityEngine; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 创建客户端消息 | |
/// </summary> | |
public class CreateClientMessage : MonoBehaviour | |
{ | |
[Tooltip("我的消息预制件")] | |
public GameObject MessagePrefab_My; | |
[Tooltip("其他人的消息预制件")] | |
public GameObject MessagePrefab_Other; | |
/// <summary> | |
/// 增加新消息 | |
/// </summary> | |
/// <param name="ower"> 消息属于谁 & lt;/param> | |
/// <param name="chatMessage"> 消息 & lt;/param> | |
public void AddMessage(MessageOwer ower, ChatMessage chatMessage) | |
{ | |
switch (ower) | |
{ | |
case MessageOwer.My: | |
Create(MessagePrefab_My, chatMessage); | |
break; | |
case MessageOwer.Other: | |
Create(MessagePrefab_Other, chatMessage); | |
break; | |
default: | |
break; | |
} | |
} | |
/// <summary> | |
/// 创建物体 | |
/// </summary> | |
/// <param name="prefab"> 预制件 & lt;/param> | |
/// <param name="chatMessage"> 信息 & lt;/param> | |
private void Create(GameObject prefab, ChatMessage chatMessage) | |
{ | |
GameObject obj = Instantiate(prefab, this.transform); | |
obj.GetComponent<ClientMessageManger>().SetMessage(chatMessage.Content, chatMessage.SenderName); | |
} | |
} | |
} |
挂载到 “Canvas” 上
using Common; | |
using TMPro; | |
using UnityEngine; | |
using UnityEngine.UI; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 客户端逻辑 | |
/// </summary> | |
public class ChatClient : MonoBehaviour | |
{ | |
[Tooltip("增加消息")] | |
public CreateClientMessage Create; | |
/// <summary> | |
/// SendButton | |
/// </summary> | |
private Button SendButton; | |
/// <summary> | |
/// 输入的信息 | |
/// </summary> | |
private TMP_InputField InputMessage; | |
private void Awake() | |
{ | |
SendButton = this.transform.FindChildByName("SendButton").GetComponent<Button>(); | |
InputMessage = this.transform.FindChildByName("InputMessage").GetComponent<TMP_InputField>(); | |
} | |
private void Update() | |
{ | |
if (Input.GetKey(KeyCode.LeftControl) && Input.GetKey(KeyCode.Return)) | |
{ | |
OnSendMessage(); | |
} | |
} | |
private void OnEnable() | |
{ | |
SendButton.onClick.AddListener(OnSendMessage); | |
ClientUdpNetService.Instance.OnReceive += OnReceiveMessage; | |
} | |
private void OnDisable() | |
{ | |
SendButton.onClick.RemoveListener(OnSendMessage); | |
ClientUdpNetService.Instance.OnReceive -= OnReceiveMessage; | |
} | |
/// <summary> | |
/// 按下发送消息按钮 | |
/// </summary> | |
private void OnSendMessage() | |
{ | |
if (InputMessage.text == string.Empty) return; | |
ChatMessage message = new ChatMessage() | |
{ | |
Type = MessageType.General, | |
SenderName = ClientUdpNetService.Instance.Content.Name, | |
Content = InputMessage.text | |
}; | |
Create.AddMessage(MessageOwer.My, message); | |
ClientUdpNetService.Instance.SendMessage(message); | |
InputMessage.text = string.Empty; | |
} | |
/// <summary> | |
/// 接收消息 | |
/// </summary> | |
/// <param name="sender"></param> | |
/// <param name="receive"></param> | |
private void OnReceiveMessage(object sender, ReceiveEventArgs receive) | |
{ | |
if (receive.Message.SenderName != ClientUdpNetService.Instance.Content.Name) | |
{ | |
Create.AddMessage(MessageOwer.Other, receive.Message); | |
} | |
} | |
} | |
} |
# 服务端网络服务类
- 需要分配 IP 和端口
- 使用列表储存所有已经上线的客户端
- 其他部分与客户端网络服务类类似
代码如下:
using Common; | |
using System; | |
using System.Collections.Generic; | |
using System.Net; | |
using System.Net.Sockets; | |
using System.Threading; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 服务端 UDP 网络服务 | |
/// </summary> | |
public class SeverUdpNetService : MonoSingleton<SeverUdpNetService> | |
{ | |
/// <summary> | |
/// 接收到消息后触发 | |
/// </summary> | |
public event EventHandler<ReceiveEventArgs> OnReceive; | |
/// <summary> | |
/// 登录信息 | |
/// </summary> | |
public LogInContent Content; | |
/// <summary> | |
/// UdpClient | |
/// </summary> | |
private UdpClient UDPClient; | |
/// <summary> | |
/// 服务端线程 | |
/// </summary> | |
private Thread SeverThread; | |
/// <summary> | |
/// 客户端列表 | |
/// </summary> | |
public List<IPEndPoint> ClientRemotes { get; private set; } | |
private void Start() | |
{ | |
ClientRemotes = new List<IPEndPoint>(); | |
SeverThread = new Thread(ReceiveMessage); | |
} | |
/// <summary> | |
/// 初始化 | |
/// 由登录窗口传递 IP 和端口 | |
/// </summary> | |
/// <param name="content"> 登录信息 & lt;/param> | |
public void Initialize(LogInContent content) | |
{ | |
Content = content; | |
// 分配端口 | |
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(Content.IP), int.Parse(Content.Port)); | |
UDPClient = new UdpClient(iPEndPoint); | |
// 开启线程 | |
SeverThread.Start(); | |
} | |
/// <summary> | |
/// 发送数据 | |
/// </summary> | |
/// <param name="message"> 信息 & lt;/param> | |
/// <param name="remote"> 目标终结点 & lt;/param> | |
public void SendMessage(ChatMessage message, IPEndPoint remote) | |
{ | |
byte[] bytes = message.ObjectToBytes(); | |
UDPClient.Send(bytes, bytes.Length, remote); | |
} | |
/// <summary> | |
/// 接收消息 | |
/// </summary> | |
private void ReceiveMessage() | |
{ | |
while (true) | |
{ | |
// 任意 IP 地址,任意端口 | |
IPEndPoint sourceRemote = new IPEndPoint(IPAddress.Any, 0); | |
// 接收消息 | |
byte[] bytes = UDPClient.Receive(ref sourceRemote); | |
// 转化消息 | |
ChatMessage message = ChatMessage.BytesToObject(bytes); | |
// 根据消息类型执行对应的逻辑 | |
MessageReceived(message, sourceRemote); | |
// 事件参数 | |
ReceiveEventArgs args = new ReceiveEventArgs(message, sourceRemote); | |
// 在主线程中触发事件 | |
ThreadCrossHelper.Instance.ExecuteOnMainThread(() => | |
{ | |
OnReceive?.Invoke(this, args); | |
}); | |
} | |
} | |
/// <summary> | |
/// 根据消息类型执行对应的逻辑 | |
/// </summary> | |
/// <param name="message"></param> | |
/// <param name="remote"></param> | |
private void MessageReceived(ChatMessage message, IPEndPoint remote) | |
{ | |
switch (message.Type) | |
{ | |
case MessageType.OnLine: | |
ClientRemotes.Add(remote); | |
break; | |
case MessageType.OffLine: | |
ClientRemotes.Remove(remote); | |
break; | |
case MessageType.General: | |
ClientRemotes.ForEach((item) => { SendMessage(message, item); }); | |
break; | |
default: | |
break; | |
} | |
} | |
private void OnApplicationQuit() | |
{ | |
SeverThread?.Abort(); | |
UDPClient?.Dispose(); | |
} | |
} | |
} |
# 服务端的脚本
- 制作 UI
- 接收消息、生成消息(预制件)
- 消息的预制件会频繁地创建和销毁,可以使用对象池
挂载到预制件上
using Common; | |
using System.Net; | |
using TMPro; | |
using UnityEngine; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 服务端信息管理 | |
/// </summary> | |
public class SeverMessageManger : MonoBehaviour | |
{ | |
/// <summary> | |
/// 设置信息 | |
/// </summary> | |
/// <param name="sourceRemote">source remote</param> | |
public void SetMessage(IPEndPoint sourceRemote) | |
{ | |
this.transform.FindChildByName("SourceIPInformation").GetComponent<TextMeshProUGUI>().text = sourceRemote.Address.ToString(); | |
this.transform.FindChildByName("SourcePortInformation").GetComponent<TextMeshProUGUI>().text = sourceRemote.Port.ToString(); | |
} | |
} | |
} |
挂载到 “Content” 上
using Common; | |
using System.Net; | |
using UnityEngine; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 创建服务端消息 | |
/// </summary> | |
public class CreateSeverMessage : MonoBehaviour | |
{ | |
[Tooltip("服务端消息预制件")] | |
public GameObject SeverMessage; | |
public void AddMessage(IPEndPoint sourceRemote) | |
{ | |
for (int i = 0; i < this.transform.childCount; i++) | |
{ | |
ObjectPool.Instance.RecoverGameObject(this.transform.GetChild(i).gameObject); | |
} | |
for (int i = 0; i < SeverUdpNetService.Instance.ClientRemotes.Count; i++) | |
{ | |
GameObject obj = ObjectPool.Instance.GetGameObject("SeverMessage", SeverMessage, this.transform); | |
obj.GetComponent<SeverMessageManger>().SetMessage(sourceRemote); | |
} | |
} | |
} | |
} |
挂载到 “SeverWindow” 上
using UnityEngine; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 服务端逻辑 | |
/// </summary> | |
public class ChatSever : MonoBehaviour | |
{ | |
[Tooltip("增加消息")] | |
public CreateSeverMessage Create; | |
private void OnEnable() | |
{ | |
SeverUdpNetService.Instance.OnReceive += OnReceiveMessage; | |
} | |
private void OnDisable() | |
{ | |
SeverUdpNetService.Instance.OnReceive -= OnReceiveMessage; | |
} | |
/// <summary> | |
/// 接收消息 | |
/// </summary> | |
/// <param name="sender"></param> | |
/// <param name="receive"></param> | |
private void OnReceiveMessage(object sender, ReceiveEventArgs receive) | |
{ | |
if (receive.Message.Type != MessageType.General) | |
{ | |
Create.AddMessage(receive.SourceRemote); | |
} | |
} | |
} | |
} |
# 切换场景不销毁物体
- 客户端网络服务类、服务端网络服务类、线程交叉访问助手类都挂载到同一个物体上
- 在切换场景后还需要使用这个物体,所以不能销毁
代码如下:
using UnityEngine; | |
namespace ChatDemo | |
{ | |
/// <summary> | |
/// 切换场景保留物体 | |
/// </summary> | |
public class KeepObject : MonoBehaviour | |
{ | |
private void Start() | |
{ | |
DontDestroyOnLoad(this.gameObject); | |
} | |
} | |
} |