1 概述
Firestore 和 Cloud Functions简介 中介绍了 Firestore 和 Cloud Functions 的基本用法,基于这两个系统,我们可以实现公会系统。
该公会系统允许在游戏或应用程序中创建、管理和互动公会。它使用 Firebase Firestore 作为数据存储和实时更新,确保了操作的高效和可扩展性。
2 基本数据结构
2.1 TeamInfo
TeamInfo.cs
文件详细定义了公会信息的数据结构,用于在 Firestore 数据库中存储和管理公会的基本信息。以下是对其主要内容的分析:
- 使用
[FirestoreData]
属性标记类,使其与 Firestore 文档对应。
- 包含多个
[FirestoreProperty]
属性,如 TeamName
(公会名称)、MemberNum
(成员数量)、IconIndex
(图标索引)、TeamDescription
(公会描述)、TeamScore
(公会得分)、TeamId
(公会 ID)、TeamRankSerialNumber
(公会战序列号)、IsAdvancedGroup
(是否为高级群组,公会战做区分)、IsOpen
(是否公开)、JoinInLevel
(加入等级)、CoLeaderNum
(副领导数量)和 Random
(随机数,用于获取随机序列)。这些属性反映了公会的基本信息和状态。
- 提供了两个构造函数:一个是无参数构造函数,用于创建新的
TeamInfo
实例并初始化一些默认值;另一个接受 IDictionary
参数,用于从 Firestore 文档或其他字典类型数据中创建 TeamInfo
实例。
这个类的设计允许灵活地处理公会信息的存储和检索,支持公会创建、成员管理和公会信息更新等功能。通过 Firestore 的实时数据库特性,公会信息的任何更改都可以快速同步到所有客户端,为用户提供即时更新的体验。
2.2 TeamMemberInfo
TeamMemberInfo.cs
定义了公会系统中成员的详细信息结构。它使用 Firestore 数据模型,包含成员的名称、等级、帮助次数、唯一标识符(UUID)、领导状态、加入时间、是否激活了通行证、是否被禁言、最后更新时间、最高排名分数和头像索引等属性。此类为公会成员提供了一个全面的数据结构,以支持在应用中对成员的各种操作和状态管理。
通过两种构造函数,TeamMemberInfo
可以直接从游戏管理器中获取玩家数据来初始化,或者通过从 Firestore 文档或其他字典类型数据中提取信息来初始化。这种设计使得在实际应用中,无论是新成员加入还是现有成员信息的更新,都能够灵活高效地处理。
2.3 TeamMessage
TeamMessage 内置了很多 MessageType,根据 MessageType 不同,TeamMessage 所用到的字段也不同。
一些通用的字段如下:
- RequesterUUID:发送这条消息的用户 UUID
- RequesterName:发送这条消息的用户昵称
- MessageID:消息 ID
- IsAccess:消息是否被关闭
- Timestamp:时间戳
- MessageType:消息类型
- IsRoyalPassActivated:发送消息的用户是否买了通行证
- 生命请求
- SenderUUIDs:赠送生命的用户 UUID 列表,防止一个人赠送多次
- SenderNames:赠送生命的用户昵称列表,收到生命的人本地需要赠送生命的人的名字
- CurrentLifeNum:当前赠送生命数量
- TotalLifeNum:总计生命数量
通过 FreeLifeStringPrasing 类来管理赠送过来的生命,当监听到自己发送的生命请求被赠送了生命时,就会触发
FreeLifeStringPrasing.Instance.AchieveFreeLife
给自己添加生命,然后调用 ClearSenderNames
将 SenderNames 清空。当最后一个人赠送完生命后,会将 IsAccess
置成 false
,该消息在消息列表隐藏。
- 加入通知:无特殊字段
- 退出通知:无特殊字段
- 踢出通知:无特殊字段
- 加入请求:未实现
- 加入请求批准通知:未实现
- 皇家通行证通知
本地维护了一个 messageIDList,防止重复领取。
- 公会赛礼包通知
- TeamRankPackageType:公会赛礼包种类
- 聊天消息
3 功能和接口
TeamComponent.cs
文件是公会系统的主要脚本,直接挂载在 GameManager.cs
下,其提供了大部分公会系统所用的接口,还有部分后续的补充接口写在了 TeamExtension.cs
下。
3.1 公会系统初始化
根据 TeamComponent.cs
文件的初始化部分,组件的初始化流程可以详细分析如下:
- 初始化 FirebaseFirestore 实例:通过
FirebaseFirestore.DefaultInstance
获取默认 Firestore 实例,用于后续的数据库操作。
- 初始化各种数据结构:包括
searchTeamInfos
、randomTeamInfos
、teamMessageList
、chatMessageList
、myTeamMessageList
、unHelpedMessageList
等,这些数据结构用于存储公会信息、消息等。
- 初始化
TeamMessageQueryManager
:用于管理公会消息查询。
- 初始化公会和成员信息:创建
myMemberInfo
和 myTeam
实例,用于存储当前用户的公会成员信息和公会信息。
- 设置默认网络 URL:通过
GameManager.Network.SetDefaultUrl
设置网络请求的默认 URL,该 URL 主要用于判断是否能连接上谷歌数据库。
- 初始化状态标志:包括
teamInfoInited
、myTeamInfoGotFromFirestore
等标志,用于跟踪公会信息的初始化状态。
- 获取公会 ID 并初始化公会信息:通过
GameManager.DataSave.GetTeamIdFromDatabase
异步获取公会 ID,然后调用 InitTeamInfo
方法进一步初始化公会信息,InitTeamInfo
会判断用户等级是否符合要求以及用户是否登录、公会 ID 是否为空等前置条件,然后执行不同的初始化操作:
- 如果用户未登录且用户等级不满足要求,则直接
return
。
- 如果用户已经加入了一个公会(即公会 ID 存在),则执行
GetTeamInfo
来初始化该用户的公会信息,包括从 Firestore 获取公会详细信息、成员列表等,并设置相关的状态标志,表示公会信息已经初始化完成。
- 如果用户没有加入任何公会(即公会 ID 不存在或为空),则删除本地公会信息并且拉取公会列表。
- 设置初始化完成标志:将
isInit
标志设置为 true
,表示组件已完成初始化。
3.2 公会列表管理
为了保证用户拉取的公会列表是随机列表,每个公会信息都设置了一个 Random
字段作为随机种子,每当公会信息(成员数量、成员分数、公会简介)变更时,会同时更改 Random
的值,范围 [0,100)
。获取用户列表主要通过 GetTeamInfoList
实现,该函数的具体实现如下:
- num:需要获取的公会数量。
- randomNum:用作查询条件的随机数。
- call:完成操作时的可选回调动作。
- isGreater:一个布尔值,表示是否只获取随机数大于randomNum的公会信息,默认为
True
,当它为 False
时,表示第一次查询公会数量不足所需要的数量。
考虑到 Firestore 计费是通过读写次数计费,为了节约数据库费用,这里没有设置一个公会信息列表的监听器,并且限制了公会列表的拉取频率。该策略导致用户客户端的数据不是最新的,但是数据库访问量得到了显著降低。修改后的获取公会列表的实现如下:
3.3 公会管理
同时获取公会和成员信息:
- 创建公会:允许用户创建新公会,生成唯一 ID 并初始化公会属性。
- 离开公会:允许成员离开公会,将他们的数据从公会成员列表中移除。
- 更新公会信息:会长修改公会信息直接上传,公会成员数量以及总分数用 function 自动更新。
3.4 成员管理
- 移除成员:移除公会成员,直接移除数据库中的用户数据,并且关闭用户的 message,用户本地客户端 listener 监听到事件之后,自动执行退出操作。
3.5 消息和通知
目前允许在公会内发送的消息类型都写在了 TeamMessageType
中,其中包括了生命请求、加入通知、退出通知、踢人通知、通行证消息、公会战礼包消息、聊天消息,其中加入通知、退出通知、踢人通知等只有会长能看见。消息的实时发送与接受用的是 Firestore 的监听器系统,每一个成员加入公会时都会设置好监听器,当出现消息更改或者公会信息更改时,监听器会自动获取相应的消息并且在客户端做对应的操作,下面是一些监听器的例子:
下面是聊天的消息监听器:
最后是生命请求的监听器:
还有部分如公会礼包、通行证的监听器都类似,就不一一列举。
注意事项:Firestore 在做如下操作之前,需要现在网页上建立复杂索引:
具体的建立方式有两种:
- 执行一下,等编辑器报错没有索引,然后错误信息里有个很长的链接,复制到浏览器打开,就会自动简历索引(推荐)。
- 手动根据自己的参数简历索引。
4 服务端自动化处理
由于目前的项目没有服务器,数据库的自动化处理依赖 Cloud Functions。Cloud Functions 的部署推荐使用本地 CLI 部署,具体的操作示例见 Cloud Functions。
Function 的部署不复杂,如果遇到 Eslint 的报错,可以在文档开头加上 /* eslint-disable */
来取消 Eslint 校验。
Cloud Function 的不便之处主要在测试与 Debug,Cloud Function 的 Debug 可以打日志,然后在控制台看输出。此外,还可以直接在本地实现 Node.js 在 Firestore 的增删改查 等功能无误后,在写成 Function 的形式上传到云端。
下面是当前 Royal 中的部分 Function 实现:
- 客户端搜索公会:直接请求
getTeamSearched
函数,具体实现如下:
5 客户端
客户端 UI 无特殊难点,着重要说的是消息页面的实现。目前 Royal 中,消息页面使用的是 ScrollArea ,根据消息类型的不同分别实现了 TeamMessagePanelManager
、RoyalPassMessagePanelManager
、TeamChatMessagePanelManager
、TeamTextMessage
、TeamRankPackageMessagePanel
,统一用 TeamMainMessagesManager
进行管理。
当 TeamMainMessagesManager
初始化时,获取所有已有的 TeamMessageList
生成 ScrollArea
,并且设置一个 NewTeamMessageGotEventArgs
事件接收器。
接收到 NewTeamMessageGotEventArgs
事件后,会封装一个 TeamMessageProcessor
发送给 MessageQueryManager
。然后在 MessageQueryManager
中依次进行处理。
TeamMessageProcessor
有以下几种处理类型:
分别由不同的组件接受到事件后发出的请求,设置这样一个 MessageQueryManager
主要是为了防止一下执行很多动画。
不过最后看下来这个系统有点多此一举,可以考虑优化或者删除掉。