【Unity工具,简单应用】Photon + PUN 2,做一个简单多人在线聊天室
创始人
2025-06-01 12:06:58
0

【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().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 = res;obj.GetComponent().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 = 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 = 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。猜测可以通过转移所有权的方式保留聊天记录。

相关内容

热门资讯

最新或2023(历届)小学我们...  活动目的:以怀念革命先烈,怀念所有为中华民族的振兴献出宝贵生命的英雄,引发学生对英雄人物的敬意,学...
电影《百团大战》观后感1000...   【篇一】  今日有幸观看了影片《百团大战》的公映前媒体看片会,影片之后是主创见面会。影片耗资巨大...
神勇投弹手观后感400字(优秀...  【篇一】  今天是国庆节,我看了一部抗战电影——《神勇投弹手》。  电影中男主角陈傻子想要娶哑姑,...
最新或2023(历届)关于我们...  1. 两名小主持讲话  陈琥颖-------火红的九月,是理想绽放的九月,是希望放飞的九月,….....
最新或2023(历届)我们是共...  我们是共产主义接班人  我们是共产主义接班人,  继承革命先辈的光荣传统,  爱祖国,爱人民,  ...
婚礼司仪主持的台词大全 婚礼司... 下面有请今天的双方主婚人到前面就座  有请咱们今天亲爱的娘家客人到前面就座  有请证婚人、介绍人到前...
最新或2023(历届)春节放假...  春节是中国最大的传统节日,在外拼搏了一年的人们都从各地赶回家里与亲人团圆。短短的团聚时光,聊着这一...
《为了这片土地》电影观后感 为...   【篇一】  《为了这片土地》是一部主旋律电影,讲述了王桂兰三十多年如一日扎根农村,以改变家乡贫穷...
银行合规警示教育观后感(精选)... 【篇一】  在国有商业银行的改革和发展取得显著成绩的同时,基层机构案件濒发的问题却始终没有得到有效的...
影片《作风建设永远在路上》观后... 【篇一】  由中纪委宣传部与央视联合摄制的专题片《作风建设永远在路上》,在央视黄金时段强档推出。12...
最新或2023(历届)公司联欢...  男:尊敬的各位领导、各位来宾,  女:亲爱的同事们  合:大家下午好!  男:光阴似箭,岁月如梭,...
最新或2023(历届)度表彰大...  各位领导,各位同事,大家下午好:  今天我们欢聚一堂,在这里隆重召开一年一度的总结表彰大会。召开大...
年度表彰大会主持词大全 公司年...  各位领导,各位同事,大家下午好:  今天我们欢聚一堂,在这里隆重召开一年一度的总结表彰大会。召开大...
最新或2023(历届)六一儿童... 六一儿童节主持词开场白:  主持人甲:弹去五月的风尘,迎来六月的阳光。  主持人乙:我们的心儿像怒放...
最新或2023(历届)公司知识...  尊敬的各位领导、各位来宾、参赛选手们:大家晚上好!  还有两天就是我们的传统节日——中秋节了。我们...
最新或2023(历届)组织工作... 同志们:  现在开会。  这次会议的主要任务是,传达全市组织工作会议精神,总结专年工作,部署今年任务...
最新或2023(历届)大学毕业... 尊敬的各位领导,各位同学:  你们好!(敬礼,或是鞠躬)  昨天,你们怀着希望和期待,迈进了xx学院...
最新或2023(历届)消夏晚会... 尊敬的各位领导,各位来宾:  乙:xx村的父老乡亲们  合:大家好!  甲:又是一个郁郁葱葱的盛夏,...
公司知识竞赛主持词范文 党史知...  尊敬的各位领导、各位来宾、参赛选手们:大家晚上好!  还有两天就是我们的传统节日——中秋节了。我们...
老师工作失职检讨书范文 说老师... 一、我的专业素质太低。  我在大学所学的内容只是本专业的内容,但却没有想到,在登上讲台以后,我还要面...