Photon Unity Networking 实时多人在线游戏开发解决方案

作者:禅与计算机程序设计艺术

1.简介

2019年,由英特尔、Facebook等公司联合举办的GDC大会上宣布了Unity Technologies将推出一个新品牌——Unity Game Development Platform(UGDP)。这个平台将包括对虚幻引擎4、Unreal Engine 4和原生Unity引擎的支持。在这个平台基础上,Unity Technologies推出了实时的多人在线网络游戏服务Photon Services,其具有以下优点:

  • 支持Windows/Mac/Linux/IOS/Android
  • 完全免费,无论项目规模大小
  • 可扩展性强,可以支持百万级并发用户
  • 支持WebGL/HTML5/PWA
  • 可以快速部署到云端或本地服务器
    基于这些优势,Unity Technologies开发人员开发了一套完整的Photon SDK,该SDK主要提供以下功能:
  • 游戏服务器和客户端之间的通信
  • 用户角色的同步
  • 物理引擎
  • 声音引擎
  • 好友系统
  • 分组系统
  • 多语言支持
  • 聊天系统
  • 数据存储
    UGDP的目标是成为游戏领域的全面云端开发平台。GDC大会上发布的视频也指出,未来越来越多的游戏创作者将采用这个平台进行开发。Photon Unity Networking是基于Photon Services开发的一套完整的实时多人在线游戏开发解决方案。你可以用它快速地搭建起自己的在线虚拟现实游戏。
    为了更加细化Photon Services,本文将按照如下顺序逐步阐述Photon Services的相关特性和功能。Photon Services的主要特色包含:
  • 服务模式:Photon Services提供了云端托管的服务模型,客户只需支付服务器运行成本,即可获得良好的服务体验;
  • 开发门槛低:Photon Services提供了丰富的API,简单易懂,开发者可以轻松实现自己的游戏逻辑;
  • 超高并发:Photon Services支持单机百万级并发,同时还提供了分布式集群架构,可以满足大型游戏团队的业务需求;
  • 沉浸式游戏体验:Photon Services提供沉浸式的游戏体验,包括跨平台支持(Windows/Mac/Linux/IOS/Android)、WebGL/HTML5/PWA、即时互动聊天和多语言功能;
  • 数据安全:Photon Services提供了数据存储和传输加密功能,保证数据的完整性、私密性和可用性;
  • 免费试用:Photon Services提供了免费试用版本,开发者可以在不收取任何费用的情况下体验Photon Services的所有功能;
    接下来,我将详细描述Photon Services的每一个功能模块。

2.基本概念术语说明

1.用户角色管理(Authentication and Authorization)

在Photon Services中,每个用户都被分配一个唯一的ID。当一个用户连接到服务器时,服务器通过身份验证过程确保用户的有效身份。身份验证完成后,服务器将给予用户一串令牌,作为标识符,用来区分不同的设备。这个过程通常称为“认证授权”。
通过身份验证和授权机制,服务器能够判断每个用户是否有权限访问某些资源。每个客户端都需要向服务器发送一个请求,声明自己所属的用户身份,并且要附带相应的令牌。如果服务器确认令牌正确,则允许用户访问资源。
服务器的身份验证和授权模块包含两个子模块:

  1. 用户注册:当用户第一次登录时,需要先创建一个账户,然后才能正常玩游戏。用户注册一般包含两方面的内容:用户名、密码、邮箱地址、手机号码。用户名、密码用于识别和认证用户身份,邮箱和手机号码可用于找回密码或者验证用户身份。
  2. 用户登录:用户可以通过用户名和密码登录到Photon Server,完成身份验证。服务器验证用户名和密码后,生成一个令牌,发给客户端,作为标识符,以便区别不同的设备。
    用户登录后的身份验证信息会保存在服务器的一个临时数据库中,直到用户注销。

2.角色同步(Synchronization of User Roles)

当一个用户登录游戏客户端时,他需要在游戏服务器上找到其他玩家的位置、状态信息。这个过程称为“角色同步”。
Photon Services提供了一个角色同步框架,使得游戏客户端可以自动获取所有玩家的信息,包括位置、状态等。客户端只需要调用一个函数,就可以接收到所有玩家的最新状态。
每个玩家的最新状态都可以通过回调的方式返回给客户端。这样,客户端就可以根据玩家的位置、视野范围、角度等信息渲染出符合要求的虚拟世界。

3.物理引擎(Physics Engine)

角色同步的另一个重要功能就是实时响应服务器传来的物理事件。例如,服务器可能会广播一条消息,表示有某个玩家射击了一个物体,需要引擎计算和响应结果。
Photon Services的物理引擎是一个高性能的、完备的物理引擎库。它的设计目标是支持各种类型的游戏物理行为,包括刚体运动、碰撞检测、合力等。
物理引擎可以用于制作开放式世界,比如城市,也可以用于制作关卡内部,比如电子竞技比赛中的道具运动。

4.声音引擎(Sound Engine)

声音引擎负责处理各类游戏声音效果,包括背景音乐、音效、音轨切换、三维声效等。Photon Services的声音引擎使用OpenAL API实现,支持多种音频格式和编解码器。
声音引擎也可以用于在客户端播放本地的音效文件,提升游戏画面表现力。

5.好友系统(Friends System)

Photon Services提供了一个好友系统,可以让玩家之间建立联系,分享游戏里的数据和资源。服务器会记录每个玩家之间的关系,并把它们传送到客户端。客户端可以使用好友系统来搜索、加入和删除好友,邀请好友一起玩游戏。
对于游戏里的独特玩法,服务器也可以创建基于规则的“战队”,并将它们安排在一起进行比赛。每个队伍都有一个统一的管理者,由他来发起对局。

6.分组系统(Group System)

群组系统在游戏里可以用来组织和管理玩家。每个玩家都可以加入到多个群组中,并且每个群组里可以有多个成员。群组系统可以用于创建公共社交圈,让玩家之间更方便地进行协同工作。
群组系统还可以用于向游戏内广播通知,让整个社区都知道发生了什么事情。

7.多语言支持(Multi-Language Support)

Photon Services的多语言支持模块可以帮助游戏开发者提供不同语言版本的游戏。不同语言版本的游戏只需要在服务器端配置语言设置,客户端将自动切换显示对应的语言。

8.聊天系统(Chat System)

聊天系统是实时的沟通工具,可以让游戏里的玩家相互沟通。Photon Services的聊天系统使用户可以快速、直接地进行文字、语音、图文、表情等多种形式的沟通。
聊天系统还可以用来记录游戏里的历史数据,让玩家能够回顾过去的经历。

9.数据存储(Data Storage)

数据存储是个重要功能,它可以让游戏里的玩家保存一些自定义数据,例如存档、成绩、财产等。Photon Services提供了丰富的数据接口,包括文件上传下载、数据同步、数据查询等功能。
此外,游戏的开发者也可以从服务器获取一些统计数据,用于了解玩家的习惯和喜好。这些数据可以用来优化游戏的玩法。

扫描二维码关注公众号,回复: 16823848 查看本文章

3.核心算法原理及操作步骤

下面,我将详细阐述Photon Services的每一个功能模块,介绍其关键技术和核心算法原理,并给出详细的代码示例。

1.角色同步(Synchronization of User Roles)

角色同步是Photon Services最基础的功能。角色同步涉及到的算法有:

  1. 差异化同步(Differential Synchronization):服务器通过传输变化量而不是绝对值,减少网络流量,提升游戏性能;
  2. 漏斗式同步(Funnel Synchronization):服务器只传输角色的变化量,减少网络消耗;
  3. 平滑融合(Smoothing Fusion):服务器采用一种“时间平均”的方式,聚合各个客户端的角色数据,消除突变影响;

操作步骤:

  1. 客户端连接服务器;
  2. 服务器告诉客户端当前角色的初始状态;
  3. 客户端更新角色状态;
  4. 服务器计算角色的变化量;
  5. 服务器传输角色的变化量;
  6. 客户端接收角色的变化量;
  7. 客户端更新角色状态;
  8. 以此循环往复,直至两边角色同步完成。
using Photon.Realtime;
using Photon.Pun;
public class MyCharacter : MonoBehaviourPun, IPunObservable
{
    
    
   private Vector3 _position = Vector3.zero;
   
   void Start()
   {
    
    
       if (photonView.IsMine)
       {
    
    
           // We are the local player, so we can request the other players via RPC call
       }
   }
   
   [PunRPC]
   void UpdateOtherPlayerPosition(Vector3 position)
   {
    
    
       this._position = position;
       transform.position = position;
   }
   
   void Update()
   {
    
    
       photonView.RPC("UpdateOtherPlayerPosition", RpcTarget.Others, transform.position);
       
       // Do some calculations based on new role state
      ...
   }
}

上面例子演示了如何使用角色同步。客户端脚本的Start()函数调用了photonView.IsMine条件,只有当客户端是主角色时才会执行,也就是说,只有本地玩家的角色信息才会被请求和同步。
更新角色状态的update()函数使用了photonView.RPC方法,通过远程过程调用(Remote Procedure Call),调用UpdateOtherPlayerPosition函数更新其他玩家的角色位置。这个函数通过远程调用,将自己的角色位置信息发送到其他客户端。
角色同步算法的实现部分使用了Vector3作为角色状态类型,但是实际上也可以使用其他类型,如Quaternion,Color等。另外,角色同步还可以利用PhotonView组件进行扩展,实现更复杂的场景中的角色同步。例如,角色可以拥有不同的移动方式、攻击能力、目标选择算法等,这些都可以用自定义属性来定义,然后服务器端将它们全部同步到客户端。

2.物理引擎(Physics Engine)

物理引擎是Photon Services的核心模块,也是很多网络游戏的基础模块。Photon Services的物理引擎是基于Bullet Physics API开发的,它可以模拟复杂的物理行为,如碰撞、弹簧、摩擦、阻尼等。
Bullet Physics API是专门为实时计算设计的高性能物理引擎,具有可靠的数学性能,适用于游戏、虚拟现实、机器人控制、交互式媒体等领域。

操作步骤:

  1. 创建刚体组件(Rigidbody Component);
  2. 将刚体添加到对象并赋予合理的质量、摩擦、阻力等属性;
  3. 为刚体添加一个碰撞体(Collision Shape Component);
  4. 使用相对位置(Relative Position)进行刚体位置和旋转同步;
  5. (可选)应用物理材料(Physical Materials);
  6. (可选)添加触发器(Triggers);
using UnityEngine;
using Photon.Pun;
public class MyCubeController : MonoBehaviourPun, IPunObservable
{
    
    
   Rigidbody _rigidbody;
   
   private float speed = 5f;
    void Start()
   {
    
    
       _rigidbody = GetComponent<Rigidbody>();
       
       if (!photonView.IsMine)
       {
    
    
           // We don't need to synchronize our movement, let's just move directly with interpolation
       }
   }
   
   void Update()
   {
    
    
       if (photonView.IsMine && Input.GetKey(KeyCode.W))
       {
    
    
           _rigidbody.velocity += transform.forward * Time.deltaTime * speed;
       }
       else if (photonView.IsMine && Input.GetKey(KeyCode.S))
       {
    
    
           _rigidbody.velocity -= transform.forward * Time.deltaTime * speed;
       }
       
       if (photonView.IsMine && Input.GetKey(KeyCode.A))
       {
    
    
           _rigidbody.velocity -= transform.right * Time.deltaTime * speed;
       }
       else if (photonView.IsMine && Input.GetKey(KeyCode.D))
       {
    
    
           _rigidbody.velocity += transform.right * Time.deltaTime * speed;
       }
       
       if (_rigidbody!= null)
       {
    
    
           _rigidbody.velocity = Vector3.ClampMagnitude(_rigidbody.velocity, speed);
           
           // Apply gravity force
           _rigidbody.AddForce(new Vector3(0, -1, 0), ForceMode.Acceleration);
       }
   }
    void FixedUpdate()
   {
    
    
       if (photonView.IsMine)
       {
    
    
           transform.position = _rigidbody.position;
           transform.rotation = _rigidbody.rotation;
       }
   }
   
   [PunRPC]
   void SetVelocity(Vector3 velocity)
   {
    
    
       _rigidbody.velocity = velocity;
   }
   
   void OnTriggerEnter(Collider collider)
   {
    
    
       if (collider.GetComponent<PhotonView>() == null || collider.GetComponent<PhotonView>().IsMine)
       {
    
    
           return;
       }
        Debug.LogFormat("{0} entered trigger from {1}", gameObject.name, collider.gameObject.name);
   }
}

上面例子演示了如何使用物理引擎。创建刚体组件后,将刚体赋予合理的质量、摩擦、阻力等属性。设置速度限制,防止角色跑得太快。处理角色的输入命令,并将它们转换为刚体的运动方向。将刚体的状态同步给服务器,并在FixedUpdate()函数里更新自己的transform。角色触发器(OnTriggerEnter())函数使用了远程过程调用(SetVelocity()),将刚体的速度信息发送到其他客户端。
物理引擎还可以用于处理复杂的物理场景,如交叉墙壁、楼梯、楼顶、房间隔离区域、触发器等,这些都需要高度的数学技巧才能实现精准模拟。

3.声音引擎(Sound Engine)

Photon Services声音引擎模块负责处理游戏声音,包括背景音乐、音效、音轨切换、三维声效等。Photon Services声音引擎使用OpenAL API实现,支持多种音频格式和编解码器。

操作步骤:

  1. 创建AudioSource组件;
  2. 设置音源类型、距离模型、音效混合等参数;
  3. 设置音效文件路径;
  4. 调整音效参数;
using Photon.Pun;
public class MusicManager : MonoBehaviourPun
{
    
    
   AudioSource audioSource;
    void Awake()
   {
    
    
       audioSource = GetComponent<AudioSource>();
   }
    [PunRPC]
   void PlayMusic(string name)
   {
    
    
       string path = Application.streamingAssetsPath + "/music/" + name;
       audioSource.PlayOneShot(Resources.Load<AudioClip>(path));
   }
    void OnJoinedRoom()
   {
    
    
       photonView.RPC("PlayMusic", RpcTarget.AllBuffered, "background_music");
   }
}

上面例子演示了如何使用声音引擎。创建AudioSource组件,设置音源类型、距离模型、音效混合等参数。为AudioSource设置音效文件路径,并调用PlayOneShot()方法播放音效。初始化的时候,调用photonView.RPC()函数将背景音乐信息发送给所有客户端。
声音引擎还可以用于实现音频的动态范围(DOA)计算、动态定位、环境音效、音效捕获、音效编辑等功能。

4.好友系统(Friends System)

好友系统是Photon Services的高级功能之一。它提供了一个独立于游戏之外的社交功能,可以让玩家和其他玩家建立联系,分享游戏里的数据和资源。

操作步骤:

  1. 创建好友列表;
  2. 获取好友列表;
  3. 添加、删除、查找好友;
  4. 设置好友别名;
using Photon.Pun;
public class FriendManager : MonoBehaviourPun
{
    
    
   static List<Friend> friendsList = new List<Friend>();
    public class Friend
   {
    
    
       public string username;
       public string alias;
        public Friend(string uName, string aName)
       {
    
    
           username = uName;
           alias = aName;
       }
   }
    public bool AddFriend(string username, string alias)
   {
    
    
       foreach (Friend f in friendsList)
       {
    
    
           if (f.username == username)
           {
    
    
               return false; // Already exists
           }
       }
        friendsList.Add(new Friend(username, alias));
       return true;
   }
    public bool RemoveFriend(string username)
   {
    
    
       for (int i = 0; i < friendsList.Count; ++i)
       {
    
    
           if (friendsList[i].username == username)
           {
    
    
               friendsList.RemoveAt(i);
               return true;
           }
       }
        return false;
   }
    public bool FindFriend(string searchText, out List<Friend> foundList)
   {
    
    
       foundList = new List<Friend>();
        foreach (Friend f in friendsList)
       {
    
    
           if (f.alias.ToLower().Contains(searchText.ToLower()))
           {
    
    
               foundList.Add(f);
           }
       }
        return foundList.Count > 0;
   }
}

上面例子展示了好友系统的实现。首先,创建一个FriendManager类的静态变量friendsList,用于保存好友列表。Friend类用于封装好友的用户名和别名。AddFriend()函数用来添加新的好友,RemoveFriend()函数用来删除好友,FindFriend()函数用来搜索好友。
当客户端加入房间之后,可以通过调用photonView.RPC()函数将好友列表发送给所有客户端。其他客户端可以读取好友列表,并做出相应的处理。例如,客户端可以显示好友列表,让玩家可以选择某个好友进行交谈。

5.分组系统(Group System)

分组系统是Photon Services另一个高级功能。它提供了一个集中管理多个玩家的能力,可以让玩家组织起来,并一起合作解决问题。

操作步骤:

  1. 创建组;
  2. 加入组;
  3. 离开组;
  4. 查询组成员;
using Photon.Pun;
public class GroupManager : MonoBehaviourPun
{
    
    
   const byte MaxPlayersPerGroup = 4;
    Dictionary<byte, List<int>> groups = new Dictionary<byte, List<int>>();
    public bool CreateGroup(byte groupId)
   {
    
    
       if (groups.ContainsKey(groupId))
       {
    
    
           return false; // Already exists
       }
        groups.Add(groupId, new List<int>());
       return true;
   }
    public bool JoinGroup(byte groupId)
   {
    
    
       if (photonView.IsMine && groups.ContainsKey(groupId) && groups[groupId].Count >= MaxPlayersPerGroup)
       {
    
    
           return false; // Full
       }
        int playerId = photonView.OwnerActorNr;
        if (!groups.ContainsKey(groupId))
       {
    
    
           groups.Add(groupId, new List<int>());
       }
        groups[groupId].Add(playerId);
       return true;
   }
    public bool LeaveGroup(byte groupId)
   {
    
    
       if (!groups.ContainsKey(groupId))
       {
    
    
           return false; // Not in group
       }
        groups[groupId].Remove(photonView.OwnerActorNr);
       return true;
   }
    public bool QueryGroups()
   {
    
    
       SendGroupsToMaster();
        return true;
   }
    [PunRPC]
   void ReceiveGroups(Dictionary<byte, List<int>> receivedGroups)
   {
    
    
       groups = receivedGroups;
   }
    void SendGroupsToMaster()
   {
    
    
       photonView.Rpc("ReceiveGroups", RpcTarget.MasterClient, groups);
   }
}

上面例子展示了分组系统的实现。首先,创建一个MaxPlayersPerGroup常量,表示每个组最多可以容纳多少玩家。接着,创建一个字典groups,用来存储每个组里的玩家列表。
创建组CreateGroup()函数通过GroupId检查是否已经存在,如果不存在就创建一个新的空的组。加入组JoinGroup()函数查找指定组是否存在,并且查看组成员数量是否达到最大值。若可以加入则将自己的ActorNumber加入到组里的玩家列表。同样的,离开组LeaveGroup()函数通过ActorNumber移除自己所在的组。查询组QueryGroups()函数发送组信息给主机客户端。
当客户端加入房间后,会调用SendGroupsToMaster()函数将组信息发送给主机客户端。主机客户端接收到组信息后,会调用ReceiveGroups()函数更新组成员。所有客户端都可以调用QueryGroups()函数获取组信息。
分组系统还可以用于共享游戏内容、团队排位、决策协商等功能。

6.多语言支持(Multi-Language Support)

Photon Services的多语言支持模块可以提供不同语言版本的游戏。游戏开发者只需要在服务器端配置语言设置,客户端会自动切换显示对应的语言。

操作步骤:

  1. 配置服务器语言设置;
  2. 客户端自动切换语言;
using Photon.Pun;
public class LanguageManager : MonoBehaviourPun
{
    
    
   const byte DefaultLanguageCode = 0x01;
    public enum LanguageCodes : byte
   {
    
    
       English = 0x01,
       Spanish = 0x02,
       French = 0x03,
       German = 0x04,
       Italian = 0x05
   }
    byte languageCode;
    public bool ChangeLanguage(LanguageCodes langCode)
   {
    
    
       switch (langCode)
       {
    
    
           case LanguageCodes.English:
               languageCode = (byte) LanguageCodes.English;
               break;
           case LanguageCodes.Spanish:
               languageCode = (byte) LanguageCodes.Spanish;
               break;
           case LanguageCodes.French:
               languageCode = (byte) LanguageCodes.French;
               break;
           case LanguageCodes.German:
               languageCode = (byte) LanguageCodes.German;
               break;
           case LanguageCodes.Italian:
               languageCode = (byte) LanguageCodes.Italian;
               break;
       }
        SaveSettings();
       return true;
   }
    void LoadSettings()
   {
    
    
       // Read settings from file or database here
       languageCode = PlayerPrefs.GetInt("language", DefaultLanguageCode);
   }
    void SaveSettings()
   {
    
    
       // Write settings to file or database here
       PlayerPrefs.SetInt("language", languageCode);
   }
    void Start()
   {
    
    
       LoadSettings();
       photonView.RPC("ChangeLanguageRPC", RpcTarget.All, (LanguageCodes) languageCode);
   }
}

上面例子展示了多语言支持的实现。首先,定义了默认语言编码DefaultLanguageCode,以及支持的语言编码LanguageCodes。配置服务器语言设置的方法是修改语言配置文件,并同步给所有客户端。客户端启动时,调用LoadSettings()函数读取当前语言设置,并通过photonView.RPC()函数通知所有客户端改变语言。
客户端语言设置的切换过程是在photonView.RPC()函数中执行的。服务器向所有客户端发送“改变语言”指令(ChangeLanguageRPC),各客户端接收到该指令后,调用LanguageManager对象的ChangeLanguage()函数更新语言设置。保存设置的方法可以在这里实现。
虽然Photon Services没有提供文本翻译工具,但开发者可以通过类似工具进行本地化开发。

7.聊天系统(Chat System)

聊天系统是一个实时的沟通工具,可以让玩家在游戏里进行随意的文字、语音、图文、表情等形式的交流。

操作步骤:

  1. 创建聊天室;
  2. 加入聊天室;
  3. 离开聊天室;
  4. 发送消息;
using Photon.Pun;
public class ChatManager : MonoBehaviourPun
{
    
    
   const int MessageHistoryLength = 100;
    public struct ChatMessage
   {
    
    
       public string sender;
       public string message;
        public ChatMessage(string sName, string msg)
       {
    
    
           sender = sName;
           message = msg;
       }
   }
    Dictionary<int, List<ChatMessage>> chatRooms = new Dictionary<int, List<ChatMessage>>();
    public bool JoinChatRoom(int roomId)
   {
    
    
       if (chatRooms.ContainsKey(roomId))
       {
    
    
           return false; // Already joined
       }
        chatRooms.Add(roomId, new List<ChatMessage>());
       return true;
   }
    public bool LeaveChatRoom(int roomId)
   {
    
    
       if (!chatRooms.ContainsKey(roomId))
       {
    
    
           return false; // Not in room
       }
        chatRooms.Remove(roomId);
       return true;
   }
    public bool SendMessage(int roomId, string message)
   {
    
    
       if (!chatRooms.ContainsKey(roomId))
       {
    
    
           return false; // No such room
       }
        var history = chatRooms[roomId];
       if (history.Count > MessageHistoryLength)
       {
    
    
           history.RemoveRange(0, history.Count - MessageHistoryLength);
       }
        var msg = new ChatMessage(photonView.Owner.NickName, message);
       history.Add(msg);
        SendMessageToOtherClients(roomInfo.Id, msg);
        return true;
   }
    void SendMessageToOtherClients(int roomId, ChatMessage message)
   {
    
    
       foreach (KeyValuePair<int, GameObject> kv in PhotonNetwork.CurrentRoom.Players)
       {
    
    
           if (kv.Key!= photonView.owner.ActorNumber)
           {
    
    
               kv.Value.GetPhotonView(this).RPC("ReceiveMessage",
                   RpcTarget.OthersBufferedViaServer, roomId, message);
           }
       }
   }
    [PunRPC]
   void ReceiveMessage(int roomId, ChatMessage message)
   {
    
    
       var messages = chatRooms[roomId];
       if (messages.Count > MessageHistoryLength)
       {
    
    
           messages.RemoveRange(0, messages.Count - MessageHistoryLength);
       }
        messages.Add(message);
       RenderMessagesInUI(roomId);
   }
    void RenderMessagesInUI(int roomId)
   {
    
    
       // Show messages in UI here
   }
}

上面例子展示了聊天系统的实现。首先,创建一个ChatMessage结构,用于保存聊天消息的发送者和内容。定义了一个MessageHistoryLength常量,用于限制消息记录的最大长度。
创建聊天室JoinChatRoom()函数通过房间号roomId检查是否已经加入某个房间,如果没有则创建新的房间并加入到chatRooms字典中。离开聊天室LeaveChatRoom()函数通过roomId移除自己所在的房间。
发送消息SendMessage()函数通过roomId查找指定房间是否存在,并且向消息历史列表添加新的聊天消息。最后,调用SendMesssageToOtherClients()函数向其他客户端发送消息。客户端收到消息后,调用ReceiveMessage()函数更新消息历史列表,并调用RenderMessagesInUI()函数渲染到UI上。
聊天系统还可以用于私聊、频道讨论、游戏内聊天室等功能。

8.数据存储(Data Storage)

对于网络游戏来说,数据存储是非常重要的。它可以让玩家存储游戏数据,例如存档、成绩、设置、个人信息等。Photon Services提供的文件存储接口可以用来存储大型文件的容量,而且不需要额外付费。

操作步骤:

  1. 文件上传;
  2. 文件下载;
  3. 删除文件;
using Photon.Pun;
public class DataManager : MonoBehaviourPun
{
    
    
   public async Task UploadFile(byte[] data, string fileName)
   {
    
    
       var webRequest = UnityWebRequest.Put($"http://myserver.com/{
      
      fileName}", data);
       await webRequest.SendWebRequest();
        if (!webRequest.isHttpError &&!webRequest.isNetworkError)
       {
    
    
           Debug.Log("Upload successful!");
       }
       else
       {
    
    
           Debug.LogError("Upload failed! " + webRequest.error);
       }
   }
    public async Task DownloadFile(string fileName)
   {
    
    
       var webRequest = UnityWebRequest $"http://myserver.com/{
      
      fileName}";
       await webRequest.SendWebRequest();
        if (!webRequest.isHttpError &&!webRequest.isNetworkError)
       {
    
    
           var downloadedData = webRequest.downloadHandler.data;
           ProcessDownloadedData(downloadedData);
       }
       else
       {
    
    
           Debug.LogError("Download failed! " + webRequest.error);
       }
   }
    public bool DeleteFile(string fileName)
   {
    
    
       var filePath = Path.Combine(Application.persistentDataPath, fileName);
       File.Delete(filePath);
       return true;
   }
}

上面例子展示了数据存储的实现。文件上传UploadFile()函数通过UnityWebRequest对象异步发送HTTP PUT请求,并等待响应。文件下载DownloadFile()函数通过UnityWebRequest对象异步发送HTTP GET请求,并等待响应,并读取响应数据ProcessDownloadedData()函数用来处理下载的数据。删除文件DeleteFile()函数通过本地文件系统删除指定的文件。
文件存储接口还有很多可以扩展的地方,例如文件搜索、文件分享、版本控制、权限管理等。

猜你喜欢

转载自blog.csdn.net/universsky2015/article/details/132158072