【Unity工具,简单应用】Photon + PUN 2,做一个简单多人在线聊天室
【Unity工具,简单应用】Photon + PUN 2,做一个简单多人聊天室
- 前置知识,安装,及简单UI
- 大厅
- 聊天室
- 简单同步
- 较复杂同步
- 自定义同步
- 最终效果
前置知识,安装,及简单UI
- 【Unity工具,简单学习】PUN 2,多人在线游戏开发,初步使用
- 需要有一定 UNITY 使用经验的开发者可以顺利阅读。
大厅
- 简单搭建一下大厅UI。
给Laucher节点一个Launcher脚本

Launcher脚本如下,具体功能看注释
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using Photon.Realtime;public class Launcher : MonoBehaviourPunCallbacks
{public string RoomName = "Room"; // 设置房间名,确保每次连接到同一个房间private string gameVersion = "1";[SerializeField]private const byte maxPlayersPerRoom = 4; // 设置单房间最多玩家数[SerializeField]private GameObject controlPanel;[SerializeField]private GameObject progressLabel;void Awake(){// Then let master server can use PhotonNetwork.LoadLevel()// Everyone will see the same levelPhotonNetwork.AutomaticallySyncScene = true; // 确保该变量为 true,否则无法同步progressLabel.SetActive(false);controlPanel.SetActive(true);}public void Connect(){progressLabel.SetActive(true);controlPanel.SetActive(false);if (PhotonNetwork.IsConnected) // 若已连接,则直接加入到房间{PhotonNetwork.JoinOrCreateRoom(RoomName, new RoomOptions() { MaxPlayers = maxPlayersPerRoom }, default);}else{PhotonNetwork.ConnectUsingSettings(); // 用 PhotonServerSettings 来连接PhotonNetwork.GameVersion = gameVersion;}}public override void OnJoinRandomFailed(short returnCode, string message) // 若加入随机房间失败{Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: 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(RoomName, new RoomOptions() { MaxPlayers = maxPlayersPerRoom });}public override void OnJoinedRoom() // 若加入了某房间,则加载聊天室场景,不要使用 UNITY的加载场景方法{Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");PhotonNetwork.LoadLevel("ChatingRoom");}public override void OnConnectedToMaster() // 运行ConnectUsingSettings()后会先连接到 Master节点,再创建或加载房间{Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN");PhotonNetwork.JoinOrCreateRoom(RoomName, new RoomOptions() { MaxPlayers = maxPlayersPerRoom }, default);}public override void OnDisconnected(DisconnectCause cause) // 若失去连接后{progressLabel.SetActive(false);controlPanel.SetActive(true);Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause);}}
- 需要注意的是
PhotonNetwork.JoinOrCreateRoom(RoomName, new RoomOptions() { MaxPlayers = maxPlayersPerRoom }, default);方法,若该房间名的房间不存在则创建,否则加载该房间。 - 在输入框中,把该字符串赋值给
PhotonNetwork.NickName即表示该玩家的名称,需非空
PhotonNetwork.NickName = defaultName;
聊天室
- 聊天室的UI如下
添加一个GameManagerPUN的物体并给予它对应的脚本组件

GameManagerPUN脚本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Photon.Pun;
using Photon.Realtime;
public class GameManagerPUN : MonoBehaviourPunCallbacks
{public string DialogueText = "DialogueText";[SerializeField]private InputField _inputField;public GameObject Content;void Start(){GameObject go = PhotonNetwork.Instantiate("Sphere", Vector3.zero, Quaternion.identity);go.GetComponent<ChangeRandomPosition>().change(); // 用 PhotonNetwork.Instantiate 创建一个物体,让它的位置随机变一下,代表显示该房间内的玩家个数}public override void OnLeftRoom(){SceneManager.LoadScene("LobbyScene");}public void LeaveRoom() // 退出按钮的监听器直接调用该代码即可调用 OnLeftRoom() 方法,退出大厅{PhotonNetwork.LeaveRoom();}public override void OnPlayerEnteredRoom(Player other) // 自动监听是否有玩家进入{Debug.LogFormat("OnPlayerEnteredRoom() {0}", other.NickName); // not seen if you're the player connectingif (PhotonNetwork.IsMasterClient){Debug.LogFormat("OnPlayerEnteredRoom IsMasterClient {0}", PhotonNetwork.IsMasterClient); // called before OnPlayerLeftRoom}}public override void OnPlayerLeftRoom(Player other) // 自动监听是否有玩家退出{Debug.LogFormat("OnPlayerLeftRoom() {0}", other.NickName); // seen when other disconnectsif (PhotonNetwork.IsMasterClient){Debug.LogFormat("OnPlayerLeftRoom IsMasterClient {0}", PhotonNetwork.IsMasterClient); // called before OnPlayerLeftRoom}}public void SendMessage() // 发送消息,记录发送消息者名称,与它发送的消息,然后创建一个UI物体,加载到滚动content中{string res = PhotonNetwork.NickName + " : " + _inputField.text;GameObject obj = PhotonNetwork.Instantiate(DialogueText, Vector3.zero, Quaternion.identity);obj.GetComponent<Text>().text = res;obj.GetComponent<SentenceAsync>().str = res;obj.transform.SetParent(Content.transform);_inputField.text = ""; // 发送玩后清空输入框}
}
- 同步文本信息脚本
SentenceAsync如下,需要实现IPunObservable接口的OnPhotonSerializeView方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using UnityEngine.UI;
public class SentenceAsync : MonoBehaviourPunCallbacks, IPunObservable
{public string str;public GameManagerPUN gm;public GameObject pa;void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){if (stream.IsWriting){stream.SendNext(str);}else{str = (string)stream.ReceiveNext();if (pa == null) // 用于在不同客户端的同步处理{pa = GameObject.Find("Content");this.gameObject.transform.SetParent(pa.transform);this.GetComponent<Text>().text = str;}}}
}
- 没错,该同步脚本纠结了我好几个小时,才成功处理对。为什么这么麻烦呢?
简单同步
- 同步的基本原理比较复杂, 所幸该
Photon + Pun2给我们封装的差不多了,我们只需要知道基础的内容即可 - 简单来说,假设有客户端
A,B,C,他们都连接服务器X
在客户端A处,玩家创建了一个球,若只是普通创建,则只有A处的玩家能看到该球。
若希望每个客户端都能看到该球,首先需要使用PhotonNetwork.Instantiate(str, Vector3, Quaternion)方法进行同步创建。注意创建物体通过给定它的字符串str,该预设体的路径必须在该PhotonUnityNetworking / Resources文件夹下:

- 第二步,指定该物体需要同步什么信息。对于该球,我们只需要同步它的位置信息即可。
物体若需要同步信息,则必须创建Photon View脚本。
物体若需要同步信息,则必须创建Photon View脚本。
物体若需要同步信息,则必须创建Photon View脚本。
重要的信息重复三次
物体若需要同步transform信息,则直接给它Photon Transform View脚本即可

- 这里,
Ownership表示该物体的所有权是否转移,比如A创建了该物体,是否允许B更改它的信息。这里还设计到主机与从机的区别。但这里我们不更改别的信息,设置 fixed` 即可 - 这里
Synchronization选择Unreliable On Change即可,Observable Search选择同步的观察信息,可以直接选择Auto Find Active或者设置Manual然后手动给予它需要同步信息的脚本即可。 - 能被观察到的脚本必须实现
IPunObservable接口,实现IPunObservable.OnPhotonSerializeView方法。
较复杂同步
- 我们搜索一下已经有的脚本,发现官方直接支持我们如下的同步脚本:

- 也就是说,若需要同步的观察信息只有
Animator, Rigidbody, Transform那么可以直接挂载相应的脚本进行同步。 - 那如果我们需要同步某数值信息,比如
int, float, string呢?该三种信息同步相应来说比较容易
仍然,我们需要实现IPunObservable接口,实现IPunObservable.OnPhotonSerializeView方法
void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){if (stream.IsWriting) // 若流正在写,我们发送接下来要同步的信息 string str{stream.SendNext(str);}else{str = (string)stream.ReceiveNext(); // 接受流的下一个输出,并转化为 (string) 类型if (pa == null) // (1){pa = GameObject.Find("Content");this.gameObject.transform.SetParent(pa.transform);this.GetComponent<Text>().text = str;}}}
- 我们发现,其他脚本都很好懂,那么
(1)处是什么意思呢?

这里每行对话物体,由于挂载在Canvas中,且挂载Text组件,所以必须为Rect Transform组件
尽管Rect Transform组件是继承自Transform组件,但是它的信息与Transform组件截然不同,所以官方提供的Photon Transform View不能使用(你挂载就会出bug,可以F12查看该脚本原码) - 难道我们去实现能同步
RectTransform或者Text信息的脚本?写起来有点麻烦…
但是我们转念一想,我们需要同步的信息只有 发送信息的字符串而已
所以我们只同步字符串。由于PhotonNetwork.Instantiate()方法已经为我们创建了该同步物体,所以别的组件都是创建出来的,只是其中的信息没有同步而已 - 我们还需要同步哪个信息?
RectTransform?不需要,由于我们使用ScrollView,自动处理它的各种坐标信息
Text?我们只需给予其Text.text = str即可,其他的字体啥信息都默认即可
还有一个隐藏的需要同步的信息 —— 该物体的父对象。父对象由于我们没有同步,所以它默认会创建在世界根节点的下面,不会显示在ScrollView下。
由于该ScrollView下面的那些默认物体是每个玩家一开始就是一样的,所以我们只需要单纯通过(1)指定,把自己挂载到该父对象上即可。
自定义同步
- 若需要自定义同步,由于
stream.ReceiveNext()强转类型只支持默认的那三个int, float, string,所以你想同步比如说Rect Transform或者XXXscript脚本,就需要去里面注册某物体类型 - 很麻烦,不怎么推荐,相当于指定某个类型的序列化和反序列化的规则,可以自己去搜集信息。
最终效果
- 我和室友,使用的是两台笔记本的两个客户端,在里面进行聊天

- 经过测试发现,若某玩家退出后,由该
owner通过PhotonNetwork.Instantiate创建的同步物体会自动Destroy。猜测可以通过转移所有权的方式保留聊天记录。
相关文章:
【Unity工具,简单应用】Photon + PUN 2,做一个简单多人在线聊天室
【Unity工具,简单应用】Photon PUN 2,做一个简单多人聊天室前置知识,安装,及简单UI大厅聊天室简单同步较复杂同步自定义同步最终效果前置知识,安装,及简单UI 【Unity工具,简单学习】PUN 2&…...
程序员增加收入实战 让小伙伴们都加个鸡腿
文章目录前言1️⃣一、发外包平台💁🏻♂️二、朋友介绍✍️三、打造自己的个人IP👋🏿四、混群拉单🤳🏿五、面试拉单💻六、技术顾问🦴七、开发个人项目总结:前言 程序员…...
GPIO四种输入和四种输出模式
GPIO的结构图如下所示: 最右端为I/O引脚,左端的器件位于芯片内部。I/O引脚并联了两个用于保护的二极管。 输入模式 从I/O引脚进来就遇到了两个开关和电阻,与VDD相连的为上拉电阻,与VSS相连的为下拉电阻。再连接到TTL施密特触发…...
ChatGPT能够改变时代吗?一点点思考
都知道ChatGPT的出现对整个世界产生了剧烈的影响,前不久出的ChatGPT4更是在ChatGPT3.5的基础上展现了更强的功能。比如说同一个问题,ChatGPT3.5还是乱答的,ChatGPT4已经能给出正确解了。当然这只能说明技术是进步的。 虽然如此,很…...
Markdown如何使用详细教程
目录 一、Markdown 标题 二、Markdown 段落 三、Markdown 字体 四、Markdown 分隔线 五、Markdown 列表 六、Markdown 引用 七、Markdown 代码 八、Markdown 链接 九、Markdown 图片 十、Markdown 表格 前言 当前许多网站都广泛使用 Markdown 来撰写博客,…...
HTML5庆祝生日蛋糕烟花特效
HTML5庆祝生日蛋糕烟花特效 <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>HTML5 Birthday Cake Fireworks</title><style>canvas {position: absolute;top: 0;left: 0;z-index: -1;}</style> </h…...
算法套路四——反转链表
算法套路四——反转链表 算法示例一:LeetCode206. 反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 初始化pre为空,cur为头指针 pre指针:记录当前结点的前一个结点 cur指针:记录当…...
多线程 (六) wait和notify
🎉🎉🎉点进来你就是我的人了 博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!人生格言:当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔🦾&am…...
React--》状态管理工具—Mobx的讲解与使用
目录 Mobx的讲解与使用 Mobx环境配置 Mobx的基本使用 Mobx计算属性的使用 Mobx监听属性的使用 Mobx处理异步的使用 Mobx的模块化 Mobx的讲解与使用 Mobx是一个可以和React良好配合的集中状态管理工具,mobx和react的关系相当于vuex和vue之间的关系࿰…...
有效的括号长按键入验证外星语词典字符的最短距离用栈实现队列
有效的括号来源:杭哥20. 有效的括号 - 力扣(LeetCode)bool isValid(char * s) {int szstrlen(s);char stack[sz];int k0;for (int i0;i<sz;i){if (s[i]( || s[i][ || s[i]{){stack[k]s[i];}else{if (k0){return false;}else if (s[i]} &am…...
《前端开发者的进阶之路》
前端作为Web开发的重要领域之一,不断地发展和演变着。除了基本的HTML、CSS、JavaScript技能,前端开发者需要掌握更多的进阶知识才能应对不断变化的需求。本文将介绍一些前端的进阶知识,帮助前端开发者进一步提高自己的技能水平。1.框架和库在…...
为什么说网络安全是风口行业?是IT行业最后的红利?
前言 “没有网络安全就没有国家安全”。当前,网络安全已被提升到国家战略的高度,成为影响国家安全、社会稳定至关重要的因素之一。 网络安全行业特点 1、就业薪资非常高,涨薪快 2021年猎聘网发布网络安全行业就业薪资行业最高人均33.77万&…...
使用shell 脚本,批量解压一批zip文件,解压后的文件放在以原zip文件名前10个字符的文件夹中的例子
#!/bin/bash for file in *.zip dofolder$(echo $file | cut -c 1-10)mkdir $folderunzip -q $file -d $folder doneecho "All zip files have been extracted." # 说明: # 1. for循环遍历当前目录下的所有zip文件 # 2. 使用cut命令提取zip文件名前10个字…...
01 | Msyql系统架构
目录MySQL系统架构连接器查询缓存分析器优化器执行器MySQL系统架构 大体来说,MySQL分为Server层和引擎层两部分。 Server层包含链接器、查询缓存、分析器、优化器和执行器,而引擎层负责的是数据的存储和读取,支持InnoDB、Myisam、Memory等多…...
Linux命令---设备管理
Linux setleds命令Linux setleds命令用来设定键盘上方三个 LED 的状态。在 Linux 中,每一个虚拟主控台都有独立的设定。语法setleds [-v] [-L] [-D] [-F] [{|-}num] [{|-}caps] [{|-}scroll]参数:-F:预设的选项,设定虚拟主控台的状…...
前端入门:HTML5+CSS3+JAAVASCRIPT
1、 初识HTML HTML:Hyper Text Markup Language(超文本标记语言) 。 超文本包括:文字、图片、音频、视频、动画等。 1.1、W3C标准 1.2、HTML基本结构 示例: <!-- DOCTYPE:告诉浏览器,我们要使用什么规划,这里是HTML --> …...
【头歌实验】课外作业一:开通ECS及使用Linux命令
文章目录一、完成下列实验并截图二、简要回答“课堂考核”内容三、在头歌、华为云或阿里云官网上,找出自己的课外学习资源,制定小组的课程学习计划、专业学习计划。四、习题1.10一、完成下列实验并截图 1、实验《ECS云服务器新手上路》 https://develo…...
CMSIS-RTOS2 RTX5移植到GD32L233
1、CMSIS-RTOS2是什么? 关于CMSIS-RTOS2的官方描述如下: CMSIS-RTOS v2 (CMSIS-RTOS2) 为基于 Arm Cortex 处理器的设备提供通用 RTOS 接口。它为需要RTOS功能的软件组件提供了一个标准化的API,因此为用户和软件行业带…...
[网络原理] 网络中的基本概念
人生,本就是苦乐参半,这样的生活才是丰富多彩. 文章目录前言1. IP地址2. 端口号3. 协议4. 五元组5. 协议分层6. OSI七层模型7. TCP/IP协议8. 封装和分用9. 客户端与服务端10. 请求与响应前言 本章开始,我们开启网络部分的知识大门. 1. IP地址 1.定义: IP地址主要用于表示网络…...
BeanPostProcessor原理分析
文章目录一、BeanPostProcessor的作用1. 源码2. 使用案例二、Spring生命周期中的BeanPostProcessor三、BeanPostProcessor对PostConstruct的支持四、BeanPostProcessor中的顺序性五、总结一、BeanPostProcessor的作用 BeanPostProcessor提供了初始化前后回调的方法,…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
