欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > ET实现游戏中聊天系统逻辑思路(服务端)

ET实现游戏中聊天系统逻辑思路(服务端)

2024/10/24 8:31:42 来源:https://blog.csdn.net/qq_48512649/article/details/140821989  浏览:    关键词:ET实现游戏中聊天系统逻辑思路(服务端)

目录

一、准备工作

1.1 前言

1.2 完善聊天服务器

1.3 自定义聊天服消息

二、玩家登录聊天服 

2.1 Gate网关与聊天服通讯

2.2 保存玩家实体映射实例ID

三、处理聊天逻辑

3.1 发送聊天消息的定义

3.2 定义聊天记录组件 

3.3 编写发送聊天消息处理类

ET是一个游戏框架,用的编程语言是C#,游戏引擎是Unity,框架作者:熊猫  ET社区

聊天系统在各种网络游戏中也是必备的功能,我们可以通过系统进行世界聊天、私聊、发红包、组队等功能,实现游戏的社交属性。


(网上找的聊天框图片)

下面我将以我的开发经验记录用ET框架实现聊天系统的过程。

一、准备工作

1.1 前言

前几篇文章里实现的功能(商城、邮件)都是在Map服务器中实现的,我们实现聊天功能要单独起一个聊天服务器ChatInfo,来建立玩家实体映射来实现聊天消息的通讯。

下面这篇文章介绍的是ET框架单独起服务器的流程:

ET框架新起一个服务及实现服务之间的消息通讯_et startsceneconfig-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_48512649/article/details/136732262

按照流程新起一个聊天服务器ChatInfo


1.2 完善聊天服务器

创建组件ChatInfoUnitsComponent用来管理所有玩家映射实体,并把此组件挂载到聊天服中

namespace ET.Server
{[ComponentOf(typeof(Scene))]public class ChatInfoUnitsComponent : Entity,IAwake,IDestroy{public Dictionary<long, ChatInfoUnit> ChatInfoUnitsDict = new Dictionary<long, ChatInfoUnit>();}
}

ChatInfoUnit用来保存玩家在聊天服中的基本信息,(每个玩家对应一个ChatInfoUnit

namespace ET.Server
{[ChildOf(typeof(ChatInfoUnitsComponent))]public class ChatInfoUnit : Entity,IAwake,IDestroy{public long GateSessionActorId;public string Name;  //姓名public long UnitId;  public int Gender;  //性别public int Level;   //等级public int HeadImg;  //头像//私聊信息public Dictionary<long, List<PrivateTalk>> PrivateTalkInfo = new Dictionary<long, List<PrivateTalk>>();public int AreaId = 1;  //地区public int ServerId = 2;  //服务器IDpublic int Online = 0;    //是否在线}
}

1.3 自定义聊天服消息

ET框架网络通讯消息_et mongohelper.deserialize-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_48512649/article/details/134028530

聊天服中发送和接收消息我们进行自定义,用来针对玩家级别的消息转发

namespace ET
{// 不需要返回消息public interface IActorChatInfoMessage: IActorMessage{}public interface IActorChatInfoRequest: IActorRequest{}public interface IActorChatInfoResponse: IActorResponse{}
}

NetServerComponentOnReadEvent中判断,

如果消息类型是IActorChatInfoRequestIActorChatInfoMessage则进行处理

其中player.ChatInfoInstanceId会在2.2中讲解是如何保存的

 case IActorChatInfoRequest actorChatInfoRequest:{Player player = session.GetComponent<SessionPlayerComponent>().Player;if (player == null || player.IsDisposed || player.ChatInfoInstanceId == 0){break;}int rpcId          = actorChatInfoRequest.RpcId; // 这里要保存客户端的rpcIdlong instanceId    = session.InstanceId;IResponse iresponse = await ActorMessageSenderComponent.Instance.Call(player.ChatInfoInstanceId, actorChatInfoRequest);iresponse.RpcId     = rpcId;// session可能已经断开了,所以这里需要判断if (session.InstanceId == instanceId){session.Send(iresponse);}break;}case IActorChatInfoMessage actorChatInfoMessage:{Player player = session.GetComponent<SessionPlayerComponent>().Player;if (player == null || player.IsDisposed || player.ChatInfoInstanceId == 0){break;}ActorMessageSenderComponent.Instance.Send(player.ChatInfoInstanceId, actorChatInfoMessage);break;}            

二、玩家登录聊天服 

在玩家登录游戏的流程中会发送这样一条消息

G2C_EnterGame g2CEnterGame = (G2C_EnterGame)await gateSession.Call(new C2G_EnterGame() { });

在这个消息的处理类中要做的事:

  • 从数据库或缓存中加载出玩家Unit实体及其相关组件
  • 玩家Unit上线后的初始化操作
  • 玩家登录聊天服

我们重点看C2G_EnterGameHandler中玩家登录聊天服的实现

玩家登录聊天服的过程其实就是在聊天服生成玩家实体映射信息、获取并保存玩家实体映射信息的实例Id,有了实例Id后就可以与聊天服进行玩家级别的消息通讯了

2.1 Gate网关与聊天服通讯

private async ETTask<long> EnterWorldChatServer(Unit unit,int guildId)
{UserCacheProto1 ucp = UnitHelper.GetUserCacheForChat(unit,guildId);//读取配置表获取聊天服配置信息StartSceneConfig startSceneConfig = StartSceneConfigCategory.Instance.GetBySceneName(unit.DomainZone(), "ChatInfo");//网关服发送消息登录聊天服Chat2G_EnterChat chat2GEnterChat = (Chat2G_EnterChat)await ActorMessageSenderComponent.Instance.Call(startSceneConfig.InstanceId, new G2Chat_EnterChat(){UserInfo = ucp,GateSessionActorId = unit.GetComponent<UnitGateComponent>().GateSessionActorId});return chat2GEnterChat.ChatInfoUnitInstanceId;
}

网关服和聊天服通讯属于内部消息,所以Chat2G_EnterChat消息定义在InnerMessage.proto

//ResponseType Chat2G_EnterChat
message G2Chat_EnterChat // IActorRequest
{int32 RpcId = 90;int64 GateSessionActorId = 1;UserCacheProto1 UserInfo = 1;
}message Chat2G_EnterChat // IActorResponse
{int32 RpcId = 90;int32 Error = 91;string Message = 92;int64 ChatInfoUnitInstanceId = 1;
}//UserCacheProto1 用于玩家信息聊天传输的载体
//UserCacheProto1 定义在OuterMessage.proto中
message UserCacheProto1
{string Name = 1;int32 Gender = 2;int32 Level = 3;int32 HeadImg = 4;int64 UnitId = 5;
}

消息处理类

namespace ET.Server
{[ActorMessageHandler(SceneType.ChatInfo)][FriendOf(typeof(ChatInfoUnit))]public class G2Chat_EnterChatHandler : AMActorRpcHandler<Scene, G2Chat_EnterChat, Chat2G_EnterChat>{protected override async ETTask Run(Scene scene, G2Chat_EnterChat request, Chat2G_EnterChat response){//获取管理所有玩家映射实体的组件ChatInfoUnitsComponent chatInfoUnitsComponent = scene.GetComponent<ChatInfoUnitsComponent>();//获得具体的玩家映射信息ChatInfoUnit chatInfoUnit = chatInfoUnitsComponent.Get(request.UserInfo.UnitId);int flag = 0;if ( chatInfoUnit == null ){ chatInfoUnit  = chatInfoUnitsComponent.AddChildWithId<ChatInfoUnit>(request.UserInfo.UnitId);//挂载邮箱组件才会有消息处理能力(玩家级别的消息处理)chatInfoUnit.AddComponent<MailBoxComponent>();flag = 1;}chatInfoUnit.Logon(request.UserInfo, request.GateSessionActorId);if (flag == 1){chatInfoUnitsComponent.Add(chatInfoUnit);}response.ChatInfoUnitInstanceId = chatInfoUnit.InstanceId;await ETTask.CompletedTask;}}
}

管理所有玩家映射实体ChatInfoUnitsComponent组件的具体行为:ChatInfoUnitsComponentSystem

using System.Collections.Generic;namespace ET.Server
{public class ChatInfoUnitsComponentDestroy : DestroySystem<ChatInfoUnitsComponent>{protected override void Destroy(ChatInfoUnitsComponent self){foreach (var chatInfoUnit in self.ChatInfoUnitsDict.Values){chatInfoUnit?.Dispose();}}}[FriendOf(typeof(ChatInfoUnit))][FriendOf(typeof(ChatInfoUnitsComponent))][FriendOf(typeof(UserCacheInfo))]public static class ChatInfoUnitsComponentSystem{//从数据库中加载玩家的相关聊天信息public static async ETTask LoadInfo(this ChatInfoUnitsComponent self){var userCacheList = await  DBManagerComponent.Instance.GetZoneDB(self.DomainZone()).Query<UserCacheInfo>(d => true,collection:"UserCachesComponent");foreach ( UserCacheInfo userCache in userCacheList ){ChatInfoUnit chatInfoUnit = self.AddChildWithId<ChatInfoUnit>(userCache.UnitId);chatInfoUnit.AddComponent<MailBoxComponent>();chatInfoUnit.Name               = userCache.Name;chatInfoUnit.GateSessionActorId = 0;chatInfoUnit.UnitId = userCache.UnitId;chatInfoUnit.Gender = userCache.Gender;chatInfoUnit.Level = userCache.Level;chatInfoUnit.HeadImg = userCache.HeadImg;chatInfoUnit.TitleImg = userCache.TitleImg;chatInfoUnit.Online = 0;self.ChatInfoUnitsDict.Add(userCache.UnitId,chatInfoUnit);}}//将玩家实体映射信息添加到组件中public static void Add(this ChatInfoUnitsComponent self, ChatInfoUnit chatInfoUnit){if (self.ChatInfoUnitsDict.ContainsKey(chatInfoUnit.Id)){return;}self.ChatInfoUnitsDict.Add(chatInfoUnit.Id, chatInfoUnit);}//尝试从组件中获取玩家实体映射信息public static ChatInfoUnit Get(this ChatInfoUnitsComponent self, long id){self.ChatInfoUnitsDict.TryGetValue(id, out ChatInfoUnit chatInfoUnit);return chatInfoUnit;}//移除玩家实体映射信息public static void Remove(this ChatInfoUnitsComponent self, long id){if (self.ChatInfoUnitsDict.TryGetValue(id, out ChatInfoUnit chatInfoUnit)){self.ChatInfoUnitsDict.Remove(id);chatInfoUnit?.Dispose();}}public static void Leave(this ChatInfoUnitsComponent self, long id){if (self.ChatInfoUnitsDict.TryGetValue(id, out ChatInfoUnit chatInfoUnit)){self.Get(id).Leave();//chatInfoUnit?.Dispose();}}public static List<ChatInfoForList> GetChatInfo(this ChatInfoUnitsComponent self,int type, int index){List<ChatInfoForList> cil = new List<ChatInfoForList>();return cil;}}
}

2.2 保存玩家实体映射实例ID

经过Gate网关发送了G2Chat_EnterChat消息,我们可以看到把聊天服中玩家实体映射实例ID返回回来了。


玩家新增字段ChatInfoInstanceId用于保存聊天服玩家实体映射实例ID信息

 在C2G_EnterGameHandler这一消息处理类中进行保存:

player.ChatInfoInstanceId = await this.EnterWorldChatServer(unit,gi.GuildId); //登录聊天服

保存好ChatInfoInstanceId后我们就可以使用自定义聊天服消息进行通讯了。

三、处理聊天逻辑

 前边工作都做好后我们开始实现聊天相关的逻辑。

3.1 发送聊天消息的定义

我们都知道,当我们聊天的时候在输入框输入内容,点击发送,我们输入的内容就会出现在聊天框中,当别人发送消息时我们也会看到聊天框新弹出消息内容。


聊天过程:客户端 ——》聊天服,外部消息所以定义到OutMessage.proto

//ResponseType Chat2C_SendChatInfo
message C2Chat_SendChatInfo // IActorChatInfoRequest
{int32 RpcId         = 1;int32 ChatType = 2;   //聊天类型string Text = 3;     //聊天内容int64 UnitId = 4;   //玩家ID
}message Chat2C_SendChatInfo // IActorChatInfoResponse
{int32 RpcId    = 90;int32 Error    = 91;string Message = 92;
}message ChatInfoForList
{UserCacheProto1 UserInfo = 1;  //玩家实体映射信息ChatMessage2 ChatMessage = 2;  //封装的聊天信息
}message ChatMessage2
{int32 ChatType = 1;      //聊天类型string Text = 2;         //聊天内容int64 Timestamp = 3;     //发送时间
}

3.2 定义聊天记录组件 

namespace ET.Server
{[ComponentOf(typeof(Scene))]public class ChatInfoListsComponent : Entity,IAwake,IDestroy,IDeserialize,ITransfer,IUnitCache{[BsonIgnore]public Dictionary<long, ChatInfoList> ChatInfoListsDict = new Dictionary<long, ChatInfoList>();}
}
namespace ET.Server
{[ChildOf(typeof(ChatInfoListsComponent))]public class ChatInfoList: Entity,IAwake,IDestroy{public int Type = 0;[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]public Dictionary<long, ChatInfoForList> ChatInfos= new Dictionary<long, ChatInfoForList>();}
}

 3.3 编写发送聊天消息处理类

namespace ET.Server
{[FriendOf(typeof(ChatInfoUnitsComponent))][FriendOf(typeof(ChatInfoListsComponent))][FriendOf(typeof(ChatInfoUnit))][FriendOf(typeof(ChatInfoList))][ActorMessageHandler(SceneType.ChatInfo)]public class C2Chat_SendChatInfoHandler : AMActorRpcHandler<ChatInfoUnit, C2Chat_SendChatInfo, Chat2C_SendChatInfo>{protected override async ETTask Run(ChatInfoUnit chatInfoUnit, C2Chat_SendChatInfo request, Chat2C_SendChatInfo response){//如果聊天内容为空直接returnif (string.IsNullOrEmpty(request.Text)){response.Error = ErrorCode.ERR_ChatMessageEmpty;return;}if (request.ChatType == 4){response.Error = 99999999;return;}//私聊ChatInfoUnitsComponent chatInfoUnitsComponent = chatInfoUnit.DomainScene().GetComponent<ChatInfoUnitsComponent>();if (request.ChatType == 5){PrivateTalk pt = new PrivateTalk();pt.UnitId = chatInfoUnit.UnitId;pt.Text = request.Text;pt.TimeStamp = TimeHelper.ClientNow();chatInfoUnit.PrivateTalks(pt,request.UnitId);otherChatInfoUnit.PrivateTalks(pt,chatInfoUnit.UnitId);}//世界聊天else{ChatInfoListsComponent chatInfoListsComponent = chatInfoUnit.DomainScene().GetComponent<ChatInfoListsComponent>();UserCacheProto1 ucp = chatInfoUnit.ToMessage();ChatMessage2 cm = new ChatMessage2(){ChatType = request.ChatType,ChatDesType = request.ChatDesType,Text = request.Text,Timestamp = TimeHelper.ClientNow()};chatInfoListsComponent.Send(request.ChatType,ucp, cm,chatInfoUnitsComponent.ChatInfoUnitsDict,chatInfoUnit);}await ETTask.CompletedTask;}}
}

ChatInfoUnitSystem处理私聊信息 

public static void PrivateTalks(this ChatInfoUnit self, PrivateTalk pt, long UnitId)
{self.PrivateTalkInfo[UnitId].Add(pt);if (self.GateSessionActorId > 0 && self.Online == 1){ActorMessageSenderComponent.Instance.Send(self.GateSessionActorId, new Chat2C_NoticeChatInfo(){UserInfo  = self.GetParent<ChatInfoUnitsComponent>().ChatInfoUnitsDict[pt.UnitId].ToMessage(), ChatMessage = new  ChatMessage2(){ChatType= 5,ChatDesType=pt.ChatDesType,Text = pt.Text,Timestamp = pt.TimeStamp},});}}

ChatInfoListsComponentSystem处理世界聊天信息

public static void Send(this ChatInfoListsComponent self, int type , UserCacheProto1 ucp,ChatMessage2 cm , Dictionary<long, ChatInfoUnit> ChatInfoUnitsDict, ChatInfoUnit chatInfoUnit)
{ChatInfoList cil = self.ChatInfoListsDict[id];foreach (var otherUnit in ChatInfoUnitsDict){ActorMessageSenderComponent.Instance.Send(otherUnit.Value.GateSessionActorId, new Chat2C_NoticeChatInfo(){UserInfo =ucp, ChatMessage =cm,});}
}

Chat2C_NoticeChatInfo是服务器发送给客户端的一次性消息,由客户端来处理并展示聊天信息。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com