【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提供了初始化前后回调的方法,…...
PingFangSC字体实战指南:跨平台字体解决方案的最佳实践
PingFangSC字体实战指南:跨平台字体解决方案的最佳实践 【免费下载链接】PingFangSC PingFangSC字体包文件、苹果平方字体文件,包含ttf和woff2格式 项目地址: https://gitcode.com/gh_mirrors/pi/PingFangSC 行业痛点诊断 场景导入:设…...
从ILSVRC2015_VID到SOT与MOT:这个经典数据集如何影响了今天的多目标跟踪算法?
ILSVRC2015_VID:计算机视觉领域的"罗塞塔石碑"如何重塑目标跟踪技术 当计算机视觉领域的学者们谈起目标跟踪算法的演进史,2015年是个绕不开的年份。那一年,ImageNet大规模视觉识别挑战赛(ILSVRC)首次引入视频…...
网络协议深度解析:从OSI七层模型到TCP/IP实战应用
1. OSI七层模型:网络世界的通用语言 第一次接触OSI七层模型时,我完全被那些专业术语搞晕了。直到后来在实际项目中调试网络问题,才真正理解这个模型的精妙之处。简单来说,OSI模型就像是一本网络通信的"使用说明书"&…...
低门槛AI视频生成新选择:opensora-hpcai本地部署与优化指南
低门槛AI视频生成新选择:opensora-hpcai本地部署与优化指南 【免费下载链接】opensora-hpcai-1_0_ms MindSpore implementation of OpenSora, an open-source project that aims to foster innovation, creativity, and inclusivity within the field of content cr…...
run-aspnetcore-microservices 购物车微服务:Redis分布式缓存与Grpc同步通信实现
run-aspnetcore-microservices 购物车微服务:Redis分布式缓存与Grpc同步通信实现 【免费下载链接】run-aspnetcore-microservices aspnetrun/run-aspnetcore-microservices: 是一个用于部署和运行 ASP.NET Core 微服务应用程序的开源项目,提供了一个简单…...
破解微信小程序video组件的限制:3种禁止拖动进度条的实战方案对比
微信小程序视频播放控制深度解析:3种禁止拖动进度条的工程化方案 在知识付费和在线教育类小程序中,视频内容的完整播放率直接影响知识传递效果。但微信小程序原生video组件的enable-progress-gesture属性仅能禁用触摸手势,无法真正阻止进度条…...
线程与进程的区别与联系:操作系统入门详解(含 Python 示例)
、先搞懂:进程与线程到底是什么?(通俗类比官方定义) 1.1 生活化类比:快速建立认知 如果把计算机的操作系统比作一个大型工厂: 进程:就是工厂里的一个个独立车间。每个车间有自己专属的生产资…...
Wan2.2-I2V-A14B在微信小程序开发中的应用:实时图片转视频功能实现
Wan2.2-I2V-A14B在微信小程序开发中的应用:实时图片转视频功能实现 1. 引言 "一张照片能变成视频吗?"这是很多社交类小程序用户常有的疑问。想象一下,用户在电商小程序上传商品图片后,系统自动生成一段展示视频&#…...
终极游戏画质升级指南:用OptiScaler解锁全显卡超采样自由
终极游戏画质升级指南:用OptiScaler解锁全显卡超采样自由 【免费下载链接】OptiScaler DLSS replacement for AMD/Intel/Nvidia cards with multiple upscalers (XeSS/FSR2/DLSS) 项目地址: https://gitcode.com/GitHub_Trending/op/OptiScaler OptiScaler是…...
libvirt 有哪些命令
除了 virsh 外,还有很多有意思的命令。virt-manager 用于打开 libvirt 交互的界面除了连接本地电脑,也可以访问远程电脑的 libvirtd 服务virt-clone 快速克隆一个虚拟机。在 virt-manager 界面上也集成了这个功能。如下图,就是这么简单快捷&a…...
