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:公会赛礼包种类
- 聊天消息
- MessageContent:聊天内容
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
时,表示第一次查询公会数量不足所需要的数量。
/// <summary>
/// 获取公会信息列表。
/// </summary>
/// <param name="num">需要获取的公会数量。</param>
/// <param name="randomNum">随机数,用于查询。</param>
/// <param name="call">完成操作时要调用的可选回调动作。</param>
/// <param name="isGreater">是否获取大于随机数的公会。</param>
private void GetTeamInfoList(int num, float randomNum, Action call = null, bool isGreater = true)
{
Query teamQuery;
// 根据isGreater的值,设置查询条件
if (isGreater)
{
teamQuery = db.Collection($"/{databaseName}/TeamList/Teams")
.WhereGreaterThan("Random", randomNum)
.WhereEqualTo("IsOpen", true)
.OrderBy("Random").Limit(num);
}
else
{
teamQuery = db.Collection($"/{databaseName}/TeamList/Teams")
.WhereLessThanOrEqualTo("Random", randomNum)
.WhereEqualTo("IsOpen", true)
.OrderBy("Random").Limit(num);
}
// 异步获取查询结果
teamQuery.GetSnapshotAsync().ContinueWithOnMainThread((getTask) =>
{
// 如果任务失败或被取消,调用回调函数并返回
if (getTask.IsFaulted || getTask.IsCanceled)
{
call?.Invoke();
}
else
{
// 如果isGreater为真,清空randomTeamInfos
if (isGreater)
{
if (randomTeamInfos == null)
{
randomTeamInfos = new Dictionary<string, TeamInfo>();
}
randomTeamInfos.Clear();
}
// 遍历查询结果,将公会信息添加到randomTeamInfos
foreach (var doc in getTask.Result.Documents)
{
var teamInfo = doc.ConvertTo<TeamInfo>();
randomTeamInfos[teamInfo.TeamId] = teamInfo;
}
// 如果获取的公会数量小于需要的数量,再次调用GetTeamInfoList获取剩余的公会信息
if (randomTeamInfos.Count < num && randomNum != 0)
{
GetTeamInfoList(num - randomTeamInfos.Count, randomNum, call, false);
}
else
{
// 否则,将randomTeamInfos转换为Json字符串并保存,设置获取公会列表的日期,设置正在获取公会列表的标志为false,调用回调函数,触发公会列表信息获取事件
string teamListJson = SerializeTools.DicToJson(randomTeamInfos);
GameManager.PlayerData.SetString(Constant.PlayerData.TeamInfoListJson, teamListJson);
GameManager.PlayerData.SetDateTime(Constant.PlayerData.MergeTeamListDate, DateTime.Today);
GameManager.DataNode.SetData("IsGettingTeamList", false);
call?.Invoke();
call = null;
GameManager.Event.Fire(this, TeamListInfoGotEventArgs.Create(true));
}
}
});
}
考虑到 Firestore 计费是通过读写次数计费,为了节约数据库费用,这里没有设置一个公会信息列表的监听器,并且限制了公会列表的拉取频率。该策略导致用户客户端的数据不是最新的,但是数据库访问量得到了显著降低。修改后的获取公会列表的实现如下:
/// <summary>
/// 检查并加载公会信息列表。
/// </summary>
/// <param name="callback">完成操作时要调用的可选回调动作。</param>
public void CheckAndLoadTeamInfoList(Action callback = null)
{
// 如果玩家的当前等级低于开放公会所需的等级,
// 或者玩家没有登录,或者玩家已经有一个公会,调用回调并返回。
if (GameManager.PlayerData.NowLevel < GameManager.Firebase.GetLong(Constant.RemoteConfig.Openlevel_Team, 21)
|| GameManager.PlayerData.LoginType == 0 || GameManager.PlayerData.HasKey(Constant.PlayerData.MyTeamID))
{
callback?.Invoke();
return;
}
// 如果正在获取公会列表,调用回调并返回。
if (GameManager.DataNode.GetData("IsGettingTeamList", false))
{
callback?.Invoke();
return;
}
// 设置正在获取公会列表的标志。
GameManager.DataNode.SetData("IsGettingTeamList", true);
// 获取上次合并公会列表的日期。
DateTime time =
GameManager.PlayerData.GetDateTime(Constant.PlayerData.MergeTeamListDate, Constant.GameConfig.DateTimeMin);
// 如果当前日期在上次合并日期之后的3天以上,获取公会信息列表。
if (DateTime.Now > time.AddDays(3))
{
float randomNum = Random.Range(0f, 100f);
GetTeamInfoList(20, randomNum, callback);
}
else
{
// 否则,从玩家的数据中获取公会信息列表。
string json = GameManager.PlayerData.GetString(Constant.PlayerData.TeamInfoListJson, "");
randomTeamInfos = SerializeTools.DicFromJson<string, TeamInfo>(json);
// 如果公会信息列表为空或者少于20个公会,获取公会信息列表。
if (randomTeamInfos == null || randomTeamInfos.Count < 20)
{
randomTeamInfos = new Dictionary<string, TeamInfo>();
float randomNum = Random.Range(0f, 100f);
GetTeamInfoList(20, randomNum, callback);
}
else
{
// 否则,设置公会列表不在获取中的标志,
// 触发已获取公会列表信息的事件,并调用回调。
GameManager.DataNode.SetData("IsGettingTeamList", false);
GameManager.Event.Fire(this, TeamListInfoGotEventArgs.Create(true));
callback?.Invoke();
}
}
}
3.3 公会管理
- 公会信息查询:用户查询特定公会的信息。
/// <summary>
/// 获取指定公会的信息。
/// </summary>
/// <param name="teamId">公会的ID。</param>
/// <param name="teamAction">获取公会信息后的回调函数。</param>
public void GetTeamInfo(string teamId, Action<TeamInfo> teamAction)
{
// 如果玩家未登录,直接返回。
if (GameManager.PlayerData.LoginType == 0)
{
teamAction?.Invoke(null);
return;
}
// 获取玩家的UUID。
string uuid = GameManager.PlayerData.GetString(Constant.PlayerData.UserUID, "");
// 如果UUID为空,直接返回。
if (string.IsNullOrEmpty(uuid))
{
teamAction?.Invoke(null);
return;
}
// 获取指定公会的文档引用。
DocumentReference teamRef = db.Collection($"/{databaseName}/TeamList/Teams").Document(teamId);
TeamInfo teamInfo = null;
// 异步获取公会信息。
teamRef.GetSnapshotAsync().ContinueWithOnMainThread((getTask) =>
{
// 如果任务被取消或失败,记录警告并调用回调函数。
if (getTask.IsCanceled || getTask.IsFaulted)
{
Log.Warning("Get TeamInfo Failed.Exception:{0}",
getTask.Exception != null ? getTask.Exception.ToString() : string.Empty);
teamAction?.Invoke(teamInfo);
}
else
{
// 如果任务成功,将获取的文档转换为公会信息,并调用回调函数。
var snapshot = getTask.Result;
teamInfo = snapshot.ConvertTo<TeamInfo>();
teamAction?.Invoke(teamInfo);
}
});
}
同时获取公会和成员信息:
/// <summary>
/// 获取指定公会的信息和成员列表。
/// </summary>
/// <param name="teamId">公会的ID。</param>
/// <param name="teamAction">获取公会信息后的回调函数。</param>
public void GetTeamInfoAndMembers(string teamId, Action<TeamInfo> teamAction)
{
// 如果玩家未登录,直接返回。
if (GameManager.PlayerData.LoginType == 0)
{
teamAction?.Invoke(null);
return;
}
// 获取玩家的UUID。
string uuid = GameManager.PlayerData.GetString(Constant.PlayerData.UserUID, "");
// 获取指定公会的文档引用。
DocumentReference teamRef = db.Collection($"/{databaseName}/TeamList/Teams").Document(teamId);
TeamInfo teamInfo = null;
// 异步获取公会信息。
teamRef.GetSnapshotAsync().ContinueWithOnMainThread((getTask) =>
{
// 如果任务被取消或失败,记录警告并调用回调函数。
if (getTask.IsCanceled || getTask.IsFaulted)
{
Log.Warning("Get TeamInfo Failed.Exception:{0}",
getTask.Exception != null ? getTask.Exception.ToString() : string.Empty);
teamAction?.Invoke(teamInfo);
}
else
{
// 如果任务成功,将获取的文档转换为公会信息,并调用回调函数。
var snapshot = getTask.Result;
teamInfo = snapshot.ConvertTo<TeamInfo>();
if (teamInfo == null)
{
teamAction?.Invoke(null);
return;
}
// 如果获取的公会ID与我的公会ID相同,更新我的公会信息。
if (teamInfo.TeamId == myTeam.TeamId)
{
myTeam = teamInfo;
GameManager.PlayerData.SetString(Constant.PlayerData.MyTeamName, myTeam.TeamName);
GameManager.PlayerData.SetInt(Constant.PlayerData.MyTeamIconID, myTeam.IconIndex);
GameManager.PlayerData.SetString(Constant.PlayerData.MyTeamID, myTeam.TeamId);
}
// 获取公会成员列表的引用。
CollectionReference teamMembersRef = teamRef.Collection("MemberList");
// 按照"CurrentLevel"字段排序并异步获取成员列表。
teamMembersRef.OrderBy("CurrentLevel").GetSnapshotAsync().ContinueWithOnMainThread((getMemberTask) =>
{
// 如果任务被取消或失败,记录警告并调用回调函数。
if (getMemberTask.IsCanceled || getMemberTask.IsFaulted)
{
Log.Warning("Get MemberList Failed");
teamAction?.Invoke(teamInfo);
}
else
{
// 如果任务成功,遍历查询结果,将成员信息添加到公会信息中,并调用回调函数。
var querySnapShot = getMemberTask.Result;
foreach (DocumentSnapshot documentSnapshot in querySnapShot.Documents)
{
TeamMemberInfo member = documentSnapshot.ConvertTo<TeamMemberInfo>();
if (!documentSnapshot.ContainsField("IsRoyalPassActivated"))
{
member.IsRoyalPassActivated = false;
}
// 如果成员的UUID与我的UUID相同,更新我的成员信息。
if (member.UUID == myMemberInfo.UUID)
{
myMemberInfo.IsCoLeader = member.IsCoLeader;
GameManager.PlayerData.SetBool(Constant.PlayerData.IsCoLeader, member.IsCoLeader);
myMemberInfo.IsLeader = member.IsLeader;
GameManager.PlayerData.SetBool(Constant.PlayerData.IsLeader, member.IsLeader);
myMemberInfo.JoinInTime = member.JoinInTime;
GameManager.PlayerData.SetDateTime(Constant.PlayerData.JoinInTimestamp,
member.JoinInTime.ToDateTime().ToLocalTime());
myMemberInfo.HelpNum = member.HelpNum;
myMemberInfo.IsMute = member.IsMute;
GameManager.PlayerData.SetBool(Constant.PlayerData.IsMute, member.IsMute);
myMemberInfo.PeekRankScore =
GameManager.PlayerData.GetBool(Constant.PlayerData.PeekRankInited)
? GameManager.PlayerData.GetInt(Constant.PlayerData.PeekRankScore)
: 0;
member.PeekRankScore = myMemberInfo.PeekRankScore;
}
teamInfo.TeamMembers.Add(member);
}
teamAction?.Invoke(teamInfo);
}
});
}
});
}
- 加入公会:用户能够搜索加入现有公开公会。
/// <summary>
/// 使用公会ID加入一个公会
/// </summary>
/// <param name="teamId">要加入的公会的ID</param>
/// <param name="isFinished">加入公会操作完成后的回调函数</param>
/// <param name="isRobot">是否为机器人,默认为false</param>
public void JoinATeamWithTeamID(string teamId, Action<bool> isFinished = null, bool isRobot = false)
{
// 如果玩家未登录,直接返回
if (GameManager.PlayerData.LoginType == 0)
{
isFinished?.Invoke(false);
return;
}
// 获取玩家的UUID
string uuid = GameManager.PlayerData.GetString(Constant.PlayerData.UserUID, "");
// 获取指定公会的文档引用
DocumentReference documentReference = db.Collection($"/{databaseName}/TeamList/Teams").Document(teamId);
// 异步获取公会信息
documentReference.GetSnapshotAsync().ContinueWithOnMainThread((getTask) =>
{
// 如果任务失败或被取消,记录警告并调用回调函数
if (getTask.IsFaulted || getTask.IsCanceled)
{
Log.Warning("加入公会失败");
isFinished?.Invoke(false);
}
else
{
// 如果任务成功,将获取的文档转换为公会信息
myTeam = getTask.Result.ConvertTo<TeamInfo>();
// 如果公会信息为空,调用回调函数并返回
if (myTeam == null)
{
myTeam = new TeamInfo();
isFinished?.Invoke(false);
return;
}
// 更新我的公会信息,并保存到玩家数据中
myTeamInfoGotFromFirestore = true;
GameManager.PlayerData.SetString(Constant.PlayerData.MyTeamName, myTeam.TeamName);
GameManager.PlayerData.SetInt(Constant.PlayerData.MyTeamIconID, myTeam.IconIndex);
GameManager.PlayerData.SetString(Constant.PlayerData.MyTeamID, teamId);
Log.Info(GameManager.PlayerData.GetString(Constant.PlayerData.MyTeamID));
// 获取当前时间戳,并保存到玩家数据中
Timestamp timestamp = Timestamp.GetCurrentTimestamp();
GameManager.PlayerData.SetDateTime(Constant.PlayerData.JoinInTimestamp,
timestamp.ToDateTime().ToLocalTime());
// 创建一个新的公会成员信息,并设置加入时间
myMemberInfo = new TeamMemberInfo();
myMemberInfo.JoinInTime = timestamp;
// 检查并保存当前等级
GameManager.DataSave.CheckCurrentLevelAndSave();
// 触发有公会状态改变的事件
GameManager.Event.Fire(this, HasTeamStatusChangedEventArgs.Create(true));
Log.Info("HasTeamStatusChangedEventArgs Count " +
GameManager.Event.Count(HasTeamStatusChangedEventArgs.EventId));
// 触发我的公会信息获取的事件
GameManager.Event.Fire(this, MyTeamInfoGotEventArgs.Create());
// 发送加入通知
SendJoinInNotify(teamId);
// 设置监听器
SetListeners();
// 更新我的数据到公会
UpdateMyDataToTeamAsync(myMemberInfo, true, isFinished);
}
});
}
- 创建公会:允许用户创建新公会,生成唯一 ID 并初始化公会属性。
/// <summary>
/// 创建一个新的公会
/// </summary>
/// <param name="myTeamInfo">新公会的信息</param>
/// <param name="isFinished">创建公会操作完成后的回调函数</param>
public void CreateATeam(TeamInfo myTeamInfo, Action<bool> isFinished = null)
{
// 生成一个新的GUID作为公会ID
Guid guid = Guid.NewGuid();
myTeamInfo.TeamId = guid.ToString();
// 设置公会的随机数为-1
myTeamInfo.Random = -1;
// 设置公会的成员数量为1
myTeamInfo.MemberNum = 1;
// 获取新公会的文档引用
DocumentReference myTeamRef = db.Collection($"/{databaseName}/TeamList/Teams").Document(myTeamInfo.TeamId);
// 异步设置新公会的信息
myTeamRef.SetAsync(myTeamInfo, SetOptions.MergeAll).ContinueWithOnMainThread((task) =>
{
// 如果任务被取消或失败,记录警告并调用回调函数
if (task.IsCanceled || task.IsFaulted)
{
Log.Warning("CreateFailed");
isFinished?.Invoke(false);
}
else if (task.IsCompleted)
{
// 如果任务成功,设置玩家为公会领导,保存公会ID,更新我的公会信息,并加入新公会
GameManager.PlayerData.SetBool(Constant.PlayerData.IsLeader, true);
GameManager.PlayerData.SetString(Constant.PlayerData.MyTeamID, myTeamInfo.TeamId);
myTeam = myTeamInfo;
JoinATeamWithTeamID(guid.ToString(), isFinished);
}
});
}
- 离开公会:允许成员离开公会,将他们的数据从公会成员列表中移除。
/// <summary>
/// 离开当前公会
/// </summary>
/// <param name="callback">离开公会操作完成后的回调函数</param>
public void LeaveCurrentTeam(Action callback = null)
{
// 记录离开公会的信息
Log.Info("LeaveCurrentTeam" + GameManager.PlayerData.GetString(Constant.PlayerData.MyTeamID));
// 检查玩家是否有公会ID
var hasTeamId = GameManager.PlayerData.HasKey(Constant.PlayerData.MyTeamID);
string teamId = myTeam.TeamId;
Log.Info("LeaveCurrentTeam HasTeamID" + hasTeamId);
// 如果玩家未登录或没有公会ID,直接返回
if (GameManager.PlayerData.LoginType == 0 || !hasTeamId)
{
callback?.Invoke();
return;
}
// 获取当前公会的文档引用
DocumentReference myTeamRef = db.Collection($"/{databaseName}/TeamList/Teams").Document(myTeam.TeamId);
// 获取当前成员的文档引用
DocumentReference myMemberRef = db.Collection($"/{databaseName}/TeamList/Teams/{myTeam.TeamId}/MemberList")
.Document(myMemberInfo.UUID);
// 异步删除当前成员的文档
myMemberRef.DeleteAsync().ContinueWithOnMainThread((deltask) =>
{
// 如果任务完成
if (deltask.IsCompleted)
{
// 如果玩家是公会领导,更改公会领导
if (GameManager.DataNode.GetData("IsTeamLeaderBefore", false))
{
ChangeLeader(myTeamRef);
GameManager.DataNode.SetData("IsTeamLeaderBefore", false);
}
// 关闭所有我在退出公会时的消息
CloseAllMyMessagesOnQuitTeam(teamId);
// 创建一个离开通知的文档引用
DocumentReference leaveMsgRef =
db.Collection($"/{databaseName}/TeamList/Teams/{teamId}/MessageList").Document();
// 创建一个离开通知的消息
TeamMessage leaveMessage = new TeamMessage(TeamMessageType.QuitNotify);
leaveMessage.MessageID = leaveMsgRef.Id;
// 异步设置离开通知的消息
leaveMsgRef.SetAsync(leaveMessage);
// 记录离开公会后的公会信息列表的数量
Log.Info("LeaveTeam" + randomTeamInfos.Count);
// 检查并加载公会信息列表
CheckAndLoadTeamInfoList(callback);
}
});
}
- 更新公会信息:会长修改公会信息直接上传,公会成员数量以及总分数用 function 自动更新。
/// <summary>
/// 更新公会信息
/// </summary>
public void UpdateTeamInfo()
{
// 如果玩家未登录或没有公会ID,直接返回
if (GameManager.PlayerData.LoginType == 0 || !GameManager.PlayerData.HasKey(Constant.PlayerData.MyTeamID))
{
return;
}
// 获取当前公会的文档引用
DocumentReference teamRef = db.Collection($"/{databaseName}/TeamList/Teams").Document(myTeam.TeamId);
// 运行一个异步事务
db.RunTransactionAsync(trans =>
{
// 获取公会的快照
return teamRef.GetSnapshotAsync().ContinueWithOnMainThread(continuation =>
{
// 如果任务被取消或失败,返回false
if (continuation.IsFaulted || continuation.IsCanceled)
{
return false;
}
// 如果任务成功,获取文档快照
DocumentSnapshot snapshot = continuation.Result;
// 将文档快照转换为公会信息
TeamInfo team = snapshot.ConvertTo<TeamInfo>();
// 更新我的公会成员数量
myTeam.MemberNum = team.MemberNum;
// 创建一个字典来存储要更新的数据
Dictionary<string, object> data = new Dictionary<string, object>();
// 遍历我的公会的每个属性,将属性名称和对应的值添加到数据字典中
foreach (var item in myTeam.GetType().GetProperties())
{
data.Add(item.Name, item.GetValue(myTeam));
}
// 更新公会引用的数据
trans.Update(teamRef, data);
// 返回true表示任务成功
return true;
});
});
}
3.4 成员管理
- 移除成员:移除公会成员,直接移除数据库中的用户数据,并且关闭用户的 message,用户本地客户端 listener 监听到事件之后,自动执行退出操作。
/// <summary>
/// 通过UUID踢出公会成员
/// </summary>
/// <param name="teamComponent">公会组件</param>
/// <param name="memberInfo">要踢出的公会成员信息</param>
public static void KickMemberByUUID(this TeamComponent teamComponent, TeamMemberInfo memberInfo)
{
// 如果当前用户既不是公会领导也不是副领导,则直接返回
if (!teamComponent.myMemberInfo.IsCoLeader && !teamComponent.myMemberInfo.IsLeader)
{
return;
}
// 获取当前公会的文档引用
DocumentReference myTeamRef = teamComponent.db.Collection("/Team/TeamList/Teams").Document(teamComponent.myTeam.TeamId);
// 生成一个随机数
float randomNum = Random.Range(0f, 100f);
// 运行一个异步事务
teamComponent.db.RunTransactionAsync((transaction) =>
{
// 获取公会的快照
return transaction.GetSnapshotAsync(myTeamRef).ContinueWithOnMainThread((getTask) =>
{
// 如果任务完成
if (getTask.IsCompleted)
{
// 获取文档快照
DocumentSnapshot snapshot = getTask.Result;
// 创建一个字典来存储要更新的数据
Dictionary<string, object> updates = new Dictionary<string, object>
{
{ "Random", randomNum }
};
// 更新公会引用的数据
transaction.Update(myTeamRef, updates);
// 返回true表示任务成功
return true;
}
else
{
// 返回false表示任务失败
return false;
}
});
}).ContinueWithOnMainThread((transactionTask)=>
{
// 如果事务成功
if (transactionTask.Result)
{
// 获取当前成员的文档引用
DocumentReference memberDoc = myTeamRef.Collection("MemberList").Document(memberInfo.UUID);
// 异步删除当前成员的文档
memberDoc.DeleteAsync().ContinueWithOnMainThread(task=>
{
// 触发公会成员列表改变的事件
GameManager.Event.Fire(teamComponent, TeamMemberListChangedEventArgs.Create(teamComponent.myTeam.TeamId));
// 发送踢出通知
SendKickOutMessage(teamComponent, memberInfo);
// 关闭所有该成员的生命请求和皇家通行证通知
CloseAllSomeonesRequests(teamComponent, memberInfo.UUID,TeamMessageType.RequestLives);
CloseAllSomeonesRequests(teamComponent, memberInfo.UUID,TeamMessageType.RoyalPassNotify);
// 删除公会排名成员数据
GameManager.Activity.TeamRankManager.DeleteTeamRankMemberData(memberInfo.UUID);
});
// 通过UUID设置Firestore公会ID
SetFirestoreTeamIDByUUID(teamComponent, memberInfo.UUID);
}
else
{
// 触发公会成员列表改变的事件
GameManager.Event.Fire(teamComponent, TeamMemberListChangedEventArgs.Create(teamComponent.myTeam.TeamId));
}
});
}
// 监听自己的公会信息
public void SetMyMemberInfoListener()
{
if (GameManager.PlayerData.LoginType == 0 || !GameManager.PlayerData.HasKey(Constant.PlayerData.MyTeamID))
{
return;
}
myTeam.TeamId = GameManager.PlayerData.GetString(Constant.PlayerData.MyTeamID);
CollectionReference memberCollectionRef = db.Collection($"/{databaseName}/TeamList/Teams/{myTeam.TeamId}/MemberList");
Query query = memberCollectionRef.WhereEqualTo("UUID", myMemberInfo.UUID);
if (MyMemberInfoListener != null)
{
MyMemberInfoListener.Stop();
MyMemberInfoListener.Dispose();
}
MyMemberInfoListener = query.Listen((callback) =>
{
foreach (var change in callback.GetChanges())
{
if (change.ChangeType == DocumentChange.Type.Modified)
{
var doc = change.Document.ConvertTo<TeamMemberInfo>();
myMemberInfo = doc;
if (GameManager.PlayerData.GetBool(Constant.PlayerData.IsMute) != doc.IsMute)
{
GameManager.PlayerData.SetBool(Constant.PlayerData.IsMute, doc.IsMute);
GameManager.Event.Fire(this, TeamMemberMuteChangedEventArgs.Create(doc.IsMute));
}
}
//被踢了
else if (change.ChangeType == DocumentChange.Type.Removed)
{
if (myMemberInfo.IsLeader)
{
GameManager.DataNode.SetData("IsTeamLeaderBefore", true);
}
DeleteTeamKeys();
}
}
});
}
//用户上线后先检查自己是否被踢了
public void CheckIfKicked(Action<bool> callback)
{
if (GameManager.PlayerData.LoginType == 0 || !GameManager.PlayerData.HasKey(Constant.PlayerData.MyTeamID))
{
callback?.Invoke(true);
return;
}
myTeam.TeamId = GameManager.PlayerData.GetString(Constant.PlayerData.MyTeamID);
CollectionReference myDocRef = db.Collection($"/{databaseName}/TeamList/Teams/{myTeam.TeamId}/MemberList");
Query query = myDocRef.WhereEqualTo("UUID", myMemberInfo.UUID);
query.GetSnapshotAsync().ContinueWithOnMainThread((task) =>
{
if (task.IsFaulted || task.IsCanceled)
{
Log.Warning("CheckTask is Canceled or Faulted");
callback?.Invoke(true);
}
else if (task.IsCompleted)
{
if (task.Result.Count > 0)
{
callback?.Invoke(false);
}
else
{
callback?.Invoke(true);
}
}
});
}
3.5 消息和通知
目前允许在公会内发送的消息类型都写在了 TeamMessageType
中,其中包括了生命请求、加入通知、退出通知、踢人通知、通行证消息、公会战礼包消息、聊天消息,其中加入通知、退出通知、踢人通知等只有会长能看见。消息的实时发送与接受用的是 Firestore 的监听器系统,每一个成员加入公会时都会设置好监听器,当出现消息更改或者公会信息更改时,监听器会自动获取相应的消息并且在客户端做对应的操作,下面是一些监听器的例子:
/// <summary>
/// 设置普通消息监听器
/// </summary>
public void SetMessageListener()
{
// 如果玩家未登录或没有公会ID,直接返回
if (GameManager.PlayerData.LoginType == 0 || !GameManager.PlayerData.HasKey(Constant.PlayerData.MyTeamID))
{
return;
}
// 获取公会ID
myTeam.TeamId = GameManager.PlayerData.GetString(Constant.PlayerData.MyTeamID);
// 获取消息集合的引用
CollectionReference msgCollectionRef =
db.Collection($"/{databaseName}/TeamList/Teams/{myTeam.TeamId}/MessageList");
// 如果已经存在监听器,停止并销毁它
if (AllMessageListener != null)
{
AllMessageListener.Stop();
AllMessageListener.Dispose();
}
// 定义一个列表,包含之前不需要的消息类型
List<object> unUsed = new List<object>() { 1, 2, 3, 4, 5 };
// 创建查询,获取之前不需要的消息类型,按时间戳降序排序,最多获取20条
Query query = msgCollectionRef
.WhereIn("MessageType", unUsed.AsEnumerable())
.OrderByDescending("Timestamp")
.Limit(20);
// 设置监听器,当查询结果发生变化时触发
AllMessageListener = query.Listen((callback) =>
{
// 设置监听器状态为已设置
listenerSet = true;
// 如果没有获取到任何消息,检查网络连接
if (callback.GetChanges().Count() == 0)
{
// 如果网络不可达,显示网络重试界面
if (!GameManager.Network.IsNetworkReachable)
{
var uiForm = UIComponent.Instance.GetUIForm("TeamBaseMenu", "Area4");
if (uiForm != null)
{
uiForm.GetComponent<TeamBaseMenu>().ShowNetworkLoadingMenu();
}
}
}
else
{
// 如果获取到了消息,设置网络连接状态为已连接
TeamNetworkConnection = true;
}
// 遍历所有的变化
foreach (var change in callback.GetChanges().Reverse())
{
// 打印变化的类型
Log.Info("Changed " + change.ChangeType.ToString());
// 如果消息类型超出枚举的范围,跳过这条消息
if (change.Document.GetValue<int>("MessageType") > (Enum.GetValues(typeof(TeamMessageType)).Length - 1))
{
continue;
}
// 根据变化的类型进行不同的操作
if (change.ChangeType == DocumentChange.Type.Added)
{
// 如果是新增的消息,将其添加到消息列表中
var doc = change.Document.ConvertTo<TeamMessage>();
teamMessageList.Add(doc);
// 如果消息是由当前用户请求的,将其添加到用户的消息列表中
if (doc.RequesterUUID == GameManager.PlayerData.GetString(Constant.PlayerData.UserUID))
{
myTeamMessageList.Add(doc);
}
// 触发新消息获取事件
GameManager.Event.Fire(this, NewTeamMessageGotEventArgs.Create(doc));
}
else if (change.ChangeType == DocumentChange.Type.Modified)
{
// 如果是修改的消息,更新消息列表中的对应消息
var doc = change.Document.ConvertTo<TeamMessage>();
for (int i = 0; i < teamMessageList.Count; i++)
{
var message = teamMessageList[i];
if (message.MessageID == doc.MessageID)
{
teamMessageList[i] = doc;
}
}
// 触发消息修改事件
GameManager.Event.Fire(this,
TeamMessageModifiedEventArgs.Create(change.Document.ConvertTo<TeamMessage>()));
}
else if (change.ChangeType == DocumentChange.Type.Removed)
{
// 如果是删除的消息,从消息列表中移除对应的消息
var doc = change.Document.ConvertTo<TeamMessage>();
for (int i = 0; i < teamMessageList.Count; i++)
{
var message = teamMessageList[i];
if (message.MessageID == doc.MessageID)
{
teamMessageList.RemoveAt(i);
}
}
// 触发消息移除事件
GameManager.Event.Fire(this, RemoveTeamMessageEventArgs.Create(doc));
}
}
});
}
下面是聊天的消息监听器:
/// <summary>
/// 设置聊天消息监听器
/// </summary>
public void SetChatMessageListeners()
{
// 获取我的公会ID
myTeam.TeamId = GameManager.PlayerData.GetString(Constant.PlayerData.MyTeamID);
// 获取消息集合的引用
CollectionReference msgCollectionRef =
db.Collection($"/{databaseName}/TeamList/Teams/{myTeam.TeamId}/MessageList");
// 如果已经存在监听器,停止并销毁它
if (ChatMessageListener != null)
{
ChatMessageListener.Stop();
ChatMessageListener.Dispose();
}
// 获取加入时间的前7天
var joininTime = myMemberInfo.JoinInTime.ToDateTime().AddDays(-7);
var timestamp = Timestamp.FromDateTime(joininTime);
// 创建查询,获取最近7天的聊天消息,按时间戳降序排序,最多获取30条
Query query = msgCollectionRef
.WhereEqualTo("MessageType", 8)
.OrderByDescending("Timestamp")
.Limit(30);
// 设置监听器,当查询结果发生变化时触发
ChatMessageListener = query.Listen((callback) =>
{
// 设置监听器状态为已设置
listenerSet = true;
// 遍历所有的变化
foreach (var change in callback.GetChanges().Reverse())
{
// 打印变化的类型
Log.Info("Changed " + change.ChangeType.ToString());
// 如果消息已被删除,跳过这条消息
if (deletedMessageList.Contains(change.Document.ConvertTo<TeamMessage>().MessageID))
{
continue;
}
// 根据变化的类型进行不同的操作
if (change.ChangeType == DocumentChange.Type.Added)
{
// 如果是新增的消息,将其添加到消息列表中
var doc = change.Document.ConvertTo<TeamMessage>();
teamMessageList.Add(doc);
// 如果消息是由当前用户发送的,将其添加到用户的消息列表中
if (doc.RequesterUUID == GameManager.PlayerData.GetString(Constant.PlayerData.UserUID))
{
myTeamMessageList.Add(doc);
}
// 触发新消息获取事件
GameManager.Event.Fire(this, NewTeamMessageGotEventArgs.Create(doc));
}
else if (change.ChangeType == DocumentChange.Type.Modified)
{
// 如果是修改的消息,更新消息列表中的对应消息
var doc = change.Document.ConvertTo<TeamMessage>();
for (int i = 0; i < teamMessageList.Count; i++)
{
var message = teamMessageList[i];
if (message.MessageID == doc.MessageID)
{
teamMessageList[i] = doc;
}
}
// 触发消息修改事件
GameManager.Event.Fire(this,
TeamMessageModifiedEventArgs.Create(change.Document.ConvertTo<TeamMessage>()));
}
else if (change.ChangeType == DocumentChange.Type.Removed)
{
// 如果是删除的消息,从消息列表中移除对应的消息
var doc = change.Document.ConvertTo<TeamMessage>();
for (int i = 0; i < teamMessageList.Count; i++)
{
var message = teamMessageList[i];
if (message.MessageID == doc.MessageID)
{
teamMessageList.RemoveAt(i);
}
}
// 触发消息移除事件
GameManager.Event.Fire(this, RemoveTeamMessageEventArgs.Create(doc));
}
}
});
}
最后是生命请求的监听器:
/// <summary>
/// 设置生命请求消息监听器
/// </summary>
public void SetLifeMessageListeners()
{
// 获取我的公会ID
myTeam.TeamId = GameManager.PlayerData.GetString(Constant.PlayerData.MyTeamID);
// 获取消息集合的引用
CollectionReference msgCollectionRef =
db.Collection($"/{databaseName}/TeamList/Teams/{myTeam.TeamId}/MessageList");
// 如果已经存在监听器,停止并销毁它
if (LifeMessageListener != null)
{
LifeMessageListener.Stop();
LifeMessageListener.Dispose();
}
// 获取加入时间的前7天
var joininTime = myMemberInfo.JoinInTime.ToDateTime().AddDays(-7);
var timestamp = Timestamp.FromDateTime(joininTime);
// 创建查询,获取最近7天的生命请求消息,按时间戳降序排序,最多获取20条
Query query = msgCollectionRef
.WhereEqualTo("MessageType", 0)
.OrderByDescending("Timestamp")
.Limit(20);
// 设置监听器,当查询结果发生变化时触发
LifeMessageListener = query.Listen((callback) =>
{
// 设置监听器状态为已设置
listenerSet = true;
// 遍历所有的变化
foreach (var change in callback.GetChanges().Reverse())
{
// 打印变化的类型
Log.Info("Changed " + change.ChangeType.ToString());
// 如果消息类型超出枚举的范围,跳过这条消息
if (change.Document.GetValue<int>("MessageType") > (Enum.GetValues(typeof(TeamMessageType)).Length - 1))
{
continue;
}
// 根据变化的类型进行不同的操作
if (change.ChangeType == DocumentChange.Type.Added)
{
// 如果是新增的消息,将其添加到消息列表中
var doc = change.Document.ConvertTo<TeamMessage>();
teamMessageList.Add(doc);
// 如果消息是由当前用户请求的,将其添加到用户的消息列表中
if (doc.RequesterUUID == GameManager.PlayerData.GetString(Constant.PlayerData.UserUID))
{
myTeamMessageList.Add(doc);
// 如果消息是生命请求,并且发送者名字列表不为空,添加生命
if (doc.MessageType == TeamMessageType.RequestLives && doc.SenderNames.Count > 0)
{
foreach (var providerName in doc.SenderNames)
{
FreeLifeStringPrasing.Instance.AchieveFreeLife(providerName);
}
doc.SenderNames.Clear();
ClearSenderNames(doc.MessageID);
}
}
// 如果不是自己请求的生命,并且消息是打开状态,且消息类型是生命请求
List<string> messages = PlayerPrefsX.GetStringArray(Constant.PlayerData.MessageIDList).ToList();
if (doc.RequesterUUID != myMemberInfo.UUID && doc.IsAccess &&
doc.MessageType == TeamMessageType.RequestLives)
{
// 如果发送者UUID列表不包含我的UUID,并且消息ID列表不包含这条消息的ID
if (!doc.SenderUUIDs.Contains(myMemberInfo.UUID) && !messages.Contains(doc.MessageID))
{
// 将这条消息的ID添加到未帮助的消息列表中
unHelpedMessageList.Add(doc.MessageID);
// 触发生命请求数量改变事件
GameManager.Event.Fire(this,
LifeRequestNumChangedEventArgs.Create(unHelpedMessageList.Count));
}
}
// 触发新公会消息获取事件
GameManager.Event.Fire(this, NewTeamMessageGotEventArgs.Create(doc));
}
// 如果是修改的消息,更新消息列表中的对应消息
else if (change.ChangeType == DocumentChange.Type.Modified)
{
// 触发公会消息修改事件
GameManager.Event.Fire(this,
TeamMessageModifiedEventArgs.Create(change.Document.ConvertTo<TeamMessage>()));
var doc = change.Document.ConvertTo<TeamMessage>();
// 如果未帮助的消息列表包含这条消息的ID,并且消息是关闭状态
if (unHelpedMessageList.Contains(doc.MessageID) && !doc.IsAccess)
{
// 从未帮助的消息列表中移除这条消息的ID
unHelpedMessageList.Remove(doc.MessageID);
// 触发生命请求数量改变事件
GameManager.Event.Fire(this, LifeRequestNumChangedEventArgs.Create(unHelpedMessageList.Count));
}
// 在公会消息列表中找到这条消息,并更新它
for (int i = 0; i < teamMessageList.Count; i++)
{
var message = teamMessageList[i];
if (message.MessageID == doc.MessageID)
{
teamMessageList[i] = doc;
}
}
// 将生命添加到免费生命中
AddLifeToFreeLives(doc);
}
// 如果是删除的消息,从消息列表中移除对应的消息
else if (change.ChangeType == DocumentChange.Type.Removed)
{
var doc = change.Document.ConvertTo<TeamMessage>();
for (int i = 0; i < teamMessageList.Count; i++)
{
var message = teamMessageList[i];
if (message.MessageID == doc.MessageID)
{
teamMessageList.RemoveAt(i);
}
}
// 触发消息移除事件
GameManager.Event.Fire(this, RemoveTeamMessageEventArgs.Create(doc));
}
}
});
}
还有部分如公会礼包、通行证的监听器都类似,就不一一列举。
注意事项:Firestore 在做如下操作之前,需要现在网页上建立复杂索引:
Query query = msgCollectionRef
.WhereIn("MessageType", unUsed.AsEnumerable())
.OrderByDescending("Timestamp")
.Limit(20);
具体的建立方式有两种:
- 执行一下,等编辑器报错没有索引,然后错误信息里有个很长的链接,复制到浏览器打开,就会自动简历索引(推荐)。
- 手动根据自己的参数简历索引。
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 实现:
/* eslint-disable */
// 引入 Firebase Cloud Functions SDK 以创建 Cloud Functions 和设置触发器。
const functions = require("firebase-functions");
// 引入 Firebase Admin SDK 以访问 Firestore。
const admin = require("firebase-admin");
admin.initializeApp();
// 当在 "/Team/TeamList/Teams/{teamID}" 路径下创建文档时,触发此函数。
exports.sumTeamCountOnCreate = functions.firestore
.document("/Team/TeamList/Teams/{teamID}")
.onCreate((snap, context) => {
// 获取集合引用
const collectionRef = admin
.firestore()
.collection("/Team/TeamList/Teams");
// 查询 Random 字段大于等于 0 的文档,并统计数量
return collectionRef
.where("Random", ">=", 0)
.get()
.then((snapshot) => {
var Count = snapshot.size;
// 将统计的数量设置到父文档中
return collectionRef.parent.set({ Count }, { merge: true });
});
});
// 当在 "/Team/TeamList/Teams/{teamID}" 路径下删除文档时,触发此函数。
exports.sumTeamCountOnDelete = functions.firestore
.document("/Team/TeamList/Teams/{teamID}")
.onDelete((snap, context) => {
const collectionRef = admin
.firestore()
.collection("/Team/TeamList/Teams");
// 查询 Random 字段大于等于 0 的文档,并统计数量
return collectionRef
.where("Random", ">=", 0)
.get()
.then((snapshot) => {
var Count = snapshot.size;
// 将统计的数量设置到父文档中
return collectionRef.parent.set({ Count }, { merge: true });
});
});
// 当在 "/Team/TeamList/Teams/{teamID}/MemberList/{memberID}" 路径下创建文档时,触发此函数。
exports.addTeamMemberCount = functions.firestore
.document("/Team/TeamList/Teams/{teamID}/MemberList/{memberID}")
.onCreate((snap, context) => {
// 获取父文档的所有子文档,并统计数量
return snap.ref.parent.get().then((snapshot) => {
var MemberNum = snapshot.size;
// 将统计的数量设置到父文档中
return snap.ref.parent.parent.set({ MemberNum }, { merge: true });
});
});
// 当在 "/Team/TeamList/Teams/{teamID}/MemberList/{memberID}" 路径下删除文档时,触发此函数。
exports.delTeamMemberCount = functions.firestore
.document("/Team/TeamList/Teams/{teamID}/MemberList/{memberID}")
.onDelete((snap, context) => {
// 获取父文档的所有子文档,并统计数量
return snap.ref.parent.get().then((snapshot) => {
var MemberNum = snapshot.size;
// 将统计的数量设置到父文档中
return snap.ref.parent.parent.set({ MemberNum }, { merge: true });
});
});
// 当在 "/Team/TeamList/Teams/{teamID}" 路径下更新文档时,触发此函数。
exports.checkAndDelTeam = functions.firestore
.document("/Team/TeamList/Teams/{teamID}")
.onUpdate((snap, context) => {
// 获取更新后的 MemberNum 字段
const memberNum = snap.after.data().MemberNum;
// 如果 MemberNum 为 0,则删除该文档
if (memberNum == 0) {
return snap.after.ref.delete();
} else {
return;
}
});
// 通过 HTTP 调用此函数,获取包含指定公会名称的所有公会
exports.getTeamSearched = functions.https.onCall(async (data, context) => {
// 获取传入的公会名称
const original = data.teamName;
// 获取所有公会
const teamList = await admin
.firestore()
.collection("/Team/TeamList/Teams")
.get();
const ans = {};
// 遍历所有公会,如果公会名称包含传入的公会名称,则添加到结果中
teamList.forEach((element) => {
const name = element.data().TeamName;
if (name.toUpperCase().indexOf(original.toUpperCase()) >= 0) {
ans[element.data().TeamId] = element.data();
}
});
// 返回结果
return ans;
});
- 客户端搜索公会:直接请求
getTeamSearched
函数,具体实现如下:
/// <summary>
/// 从服务器搜索公会
/// </summary>
/// <param name="name">要搜索的公会名称</param>
/// <param name="callback">搜索完成后的回调函数</param>
public void SearchTeamFromServer(string name, Action<bool> callback = null)
{
// 清空搜索公会信息列表
searchTeamInfos.Clear();
// 获取Firebase函数的默认实例
FirebaseFunctions functions = FirebaseFunctions.DefaultInstance;
// 创建一个字典来存储要传递给函数的数据
Dictionary<string, object> data = new Dictionary<string, object>();
data["teamName"] = name;
// 调用名为"getTeamSearched"的函数,并将数据异步传递给它
var function = functions.GetHttpsCallable("getTeamSearched").CallAsync(data).ContinueWithOnMainThread((task) =>
{
// 如果任务被取消或失败
if (task.IsCanceled || task.IsFaulted)
{
// 遍历所有内部异常
foreach (var inner in task.Exception.InnerExceptions)
{
// 如果内部异常是FunctionsException类型
if (inner is FunctionsException)
{
// 获取并打印错误代码
var e = (FunctionsException)inner;
var code = e.ErrorCode;
Log.Info(code);
}
}
// 调用回调函数,传递false表示搜索失败
callback?.Invoke(false);
}
else
{
// 如果任务成功,获取函数返回的数据
IDictionary idic = (IDictionary)task.Result.Data;
// 遍历返回的数据
foreach (var key in idic.Keys)
{
// 将每个数据项转换为公会信息,并添加到搜索公会信息列表中
TeamInfo team = new TeamInfo((IDictionary)idic[key]);
searchTeamInfos[key.ToString()] = team;
}
// 调用回调函数,传递true表示搜索成功
callback?.Invoke(true);
}
});
}
5 客户端
客户端 UI 无特殊难点,着重要说的是消息页面的实现。目前 Royal 中,消息页面使用的是 ScrollArea ,根据消息类型的不同分别实现了 TeamMessagePanelManager
、RoyalPassMessagePanelManager
、TeamChatMessagePanelManager
、TeamTextMessage
、TeamRankPackageMessagePanel
,统一用 TeamMainMessagesManager
进行管理。
当 TeamMainMessagesManager
初始化时,获取所有已有的 TeamMessageList
生成 ScrollArea
,并且设置一个 NewTeamMessageGotEventArgs
事件接收器。
接收到 NewTeamMessageGotEventArgs
事件后,会封装一个 TeamMessageProcessor
发送给 MessageQueryManager
。然后在 MessageQueryManager
中依次进行处理。
TeamMessageProcessor
有以下几种处理类型:
public enum TeamMessageProcessType
{
DoValue,
ShowPanel,
HidePanel,
HideRoyalPassMessagePanel,
HideTeamRankPackageMessagePanel,
RemovePanel,
ShowProfanePanel,
}
分别由不同的组件接受到事件后发出的请求,设置这样一个 MessageQueryManager
主要是为了防止一下执行很多动画。
不过最后看下来这个系统有点多此一举,可以考虑优化或者删除掉。