任务19:添加更多用户信息字段,修改和提交用户信息
先来看看这节需要做的是什么
回顾一下,我们之前做过
Register,实现了在登录注册页面新注册了用户保存到数据库中,这可以理解为是一种【新增数据】。
GetUserInfo,实现了在大厅显示了从服务端获取的登录用户的用户信息,这可以理解为是一种【查询数据】。
今天我们要做 SetUserInfo,将实现在大厅界面点击设置信息按钮,在弹出面板输入更多其它的用户信息并提交保存到数据库,这可以理解为是一种【修改数据】。
这样我们就学会了,向数据端发送请求,并实现对数据库的增,改,查。
服务端部分
首先我们要修改服务端的UserInfo数据实体
当我们增加或者减少数据实体的字段时,就要修改这个类 UserInfo.cs
\Server\ET.Core\Landlords\Entity\DB\UserInfo.cs
using MongoDB.Bson.Serialization.Attributes; using System.Collections.Generic; namespace ETModel { [ObjectSystem] public class UserInfoAwakeSystem : AwakeSystem<UserInfo, string> { public override void Awake(UserInfo self, string name) { self.Awake(name); } } /// <summary> /// 用户信息 /// </summary> public class UserInfo : Entity { //昵称 public string UserName { get; set; } //等级 public int Level { get; set; } //余额 public long Money { get; set; } //电话 public long Phone { get; set; } //邮箱 public string Email { get; set; } //性别 public string Sex { get; set; } //称号 public string Title { get; set; } public int LastPlay { get; set; } //public List<Ca> public void Awake(string name) { UserName = name; Level = 1; Money = 10000; Title = "贫农"; Email = ""; Sex = ""; LastPlay = 0; } } }
Awake方法中,是用户信息实体初始时,需要初始的默认数据。
这里要插播一下向谁请求的思路:
realm
当前端在发起登录请求时,是向realm服务请求的,realm服务中的处理Handler会负责用请求提供的账号与密码查询数据库,验证是不是已经注册过的有效用户和密码正确性。(记住realm只负责查询与验证用户的有效性,不会在内存存储任何用户对象)
当前端在发起注册请求时,也是向realm服务请求的,realm服务中的处理Handler会查询数据库验证用户名是不是可以注册(未重名),并创建一份AccountInfo实例,新增一份账号与密码数据存入数据库。然后还会再创建一份UserInfo实例,新增一份初始的用户数据到数据库。
gate
当我们在发起获取用户信息请求时,是向gate服务请求的,为什么呢?上面说过realm服务中是没在保存任何用户对象的,realm上只能登录验证用户,注册新用户。
Map
当用户进入到开始玩的房间,相当于角色扮演游戏进入到地图中,里面的实体,handler,组件就是在map服务上了。这个后面会学到。
而gate上我们前面课时学过,挂有SessionUserComponent组件,存着所有的登录用户对象的。只要是gate上的处理Handler中,可以这样获取到登录用户的对象。
//获取玩家对象 User user = session.GetComponent<SessionUserComponent>().User;
这样我们就能知道用户的UserID了,不就可以通过UserID查询数据了嘛。
然后我们来创建服务端收到修改用户数据时的处理Handler A1002_SetUserInfo_Handler.cs
\Server\Hotfix\Landlords\Handler\Gate\A1002_SetUserInfo_Handler.cs
先要判断请求session是不是有效并在线
//验证Session if (!GateHelper.SignSession(session)) { response.Error = ErrorCode.ERR_UserNotOnline; reply(); return; }
这里回顾下新增用户信息数据时的方法:
//生成玩家的用户信息对象 UserInfo newUser = ComponentFactory.CreateWithId<UserInfo, string>(newAccount.Id, request.Account); await dbProxy.Save(newUser);
新增数据时,创建一个UserInfo 的实例对象,然后直接Save就可以了。
而本节我们是修改某条已经存在的用户信息数据,方法是这样的:
//查询获得用户信息数据对象 UserInfo userInfo = await dbProxy.Query<UserInfo>(user.UserID); userInfo.Phone = request.Phone; userInfo.Email = request.Email; userInfo.Sex = request.Sex; await dbProxy.Save(userInfo);
也就是我们可以用UserID先查询到这条用户数据,然后提代更新的字段数据后,再Save就可以了。
完整的A1002_SetUserInfo_Handler.cs
using System; using System.Net; using ETModel; using System.Collections.Generic; using MongoDB.Bson; namespace ETHotfix { [MessageHandler(AppType.Gate)] public class A1002_SetUserInfo_Handler : AMRpcHandler<A1002_SetUserInfo_C2G, A1002_SetUserInfo_G2C> { protected override async ETTask Run(Session session, A1002_SetUserInfo_C2G request,A1002_SetUserInfo_G2C response, Action reply) { try { //验证Session if (!GateHelper.SignSession(session)) { response.Error = ErrorCode.ERR_UserNotOnline; reply(); return; } DBProxyComponent dbProxy = Game.Scene.GetComponent<DBProxyComponent>(); //获取User对象 User user = session.GetComponent<SessionUserComponent>().User; //查询获得用户信息数据对象 UserInfo userInfo = await dbProxy.Query<UserInfo>(user.UserID); userInfo.Phone = request.Phone; userInfo.Email = request.Email; userInfo.Sex = request.Sex; await dbProxy.Save(userInfo); response.Phone = userInfo.Phone; response.Email = userInfo.Email; response.Sex = userInfo.Sex; response.Title = userInfo.Title; reply(); await ETTask.CompletedTask; } catch (Exception e) { ReplyError(response, e, reply); } } } }
既然增加了更多用户字段,那之前的A1001_GetUserInfo_Handler也需要修改了
\Server\Hotfix\Landlords\Handler\Gate\A1001_GetUserInfo_Handler.cs
using System; using ETModel; namespace ETHotfix { //已知玩家UserID 尝试读取玩家资料 [MessageHandler(AppType.Gate)] public class A1001_GetUserInfo_Handler : AMRpcHandler<A1001_GetUserInfo_C2G, A1001_GetUserInfo_G2C> { protected override async ETTask Run(Session session, A1001_GetUserInfo_C2G request,A1001_GetUserInfo_G2C response,Action reply) { try { //验证Session if (!GateHelper.SignSession(session)) { response.Error = ErrorCode.ERR_UserNotOnline; reply(); return; } //获取玩家对象 User user = session.GetComponent<SessionUserComponent>().User; DBProxyComponent dbProxyComponent = Game.Scene.GetComponent<DBProxyComponent>(); UserInfo userInfo = await dbProxyComponent.Query<UserInfo>(user.UserID); response.UserName = userInfo.UserName; response.Money = userInfo.Money; response.Level = userInfo.Level; response.Phone = userInfo.Phone; response.Email = userInfo.Email; response.Sex = userInfo.Sex; response.Title = userInfo.Title; reply(); } catch (Exception e) { ReplyError(response, e, reply); } } } }
修改消息体类 HotfixMessage.cs
数据字段修改了,那请求的消息体也就需要修改了,按照上一节课的内容,我们来修改
HotfixMessage.proto文件,增加了A1002_SetUserInfo_C2G,A1002_SetUserInfo_G2C ,修改了A1001_GetUserInfo_G2C
课时有调整,可能你错过了重要的这课,不知道定义和自动生成消息体,消息指令,请前往这课学习 定义消息体字段,用protobuf工具生成消息体
//获取用户信息 message A1001_GetUserInfo_C2G // IRequest { int32 RpcId = 90; } //返回用户信息 message A1001_GetUserInfo_G2C // IResponse { int32 RpcId = 90; int32 Error = 91; string Message = 92; string UserName = 1; int32 Level = 2; int64 Money = 3; int64 Phone = 4; string Email = 5; string Sex = 6; string Title = 7; } //设置用户信息 message A1002_SetUserInfo_C2G // IRequest { int32 RpcId = 90; int64 Phone = 1; string Email = 2; string Sex = 3; string Title = 4; } //返回设置用户信息 message A1002_SetUserInfo_G2C // IResponse { int32 RpcId = 90; int32 Error = 91; string Message = 92; string UserName = 1; int32 Level = 2; int64 Money = 3; int64 Phone = 4; string Email = 5; string Sex = 6; string Title = 7; }
上次已定义的获取用户信息,返回用户信息的消息定义需要修改替换,不要直接复制过去添加在后面造成重复的定义,这样会无法正确生成消息文件,甚至造成unity卡死。
然后我们在Unity中点击Tools>Proto2CS工具,HotfixMessage.cs 就已经生成了修改的内容了。把它复制一份,拷贝到 服务端覆盖一下服务端的HotfixMessage.cs。
前端部分
再下来就是前端项目的开发了,可以下载新的LandLobby界面与SetUserInfo界面资源
大厅预制体增加了更多信息的显示与设置更多信息的按钮
增加了 SetUserInfo 预制体
UIEventType.cs 增加打开与移除设置用户信息界面的事件方法
增加了设置用户信息界面需要的EventType,FUIType,和打开与移除设置用户信息界面的事件方法。
\Assets\Model\Landlords\LandUI\UIEventType.cs
public static partial class LandUIType { public const string LandLogin = "LandLogin"; public const string LandLobby = "LandLobby"; public const string SetUserInfo = "SetUserInfo"; } public static partial class UIEventType { //斗地主EventIdType public const string LandInitSceneStart = "LandInitSceneStart"; public const string LandLoginFinish = "LandLoginFinish"; public const string LandInitLobby = "LandInitLobby"; public const string LandInitSetUserInfo = "LandInitSetUserInfo"; public const string LandSetUserInfoFinish = "LandSetUserInfoFinish"; }
//初始设置用户信息事件方法 [Event(UIEventType.LandInitSetUserInfo)] public class LandInitUserInfo_CreateSetUserInfo : AEvent { public override void Run() { Game.Scene.GetComponent<UIComponent>().Create(LandUIType.SetUserInfo); } } //登录完成移除登录界面事件方法 [Event(UIEventType.LandSetUserInfoFinish)] public class LandSetUserInfoFinish : AEvent { public override void Run() { Game.Scene.GetComponent<UIComponent>().Remove(LandUIType.SetUserInfo); } }
修改大厅UI组件中的显示更多用户信息的U绑定变量,增加SetUserInfo按钮与响应事件
\Assets\Model\Landlords\LandUI\LandLoggy\LandLobbyComponent.cs
可以看到里面有一个public void OnSetUserInfo() 方法,这个方法是当修改信息界面修改提交新的用户信息后,更新大厅界面上信息的内容。
using System; using ETModel; using UnityEngine; using UnityEngine.UI; namespace ETModel { [ObjectSystem] public class LandLobbyComponentAwakeSystem : AwakeSystem<LandLobbyComponent> { public override void Awake(LandLobbyComponent self) { self.Awake(); } } /// <summary> /// 大厅界面组件 /// </summary> public class LandLobbyComponent : Component { //提示文本 public Text prompt; //玩家名称 private Text name; //玩家金钱 private Text money; //玩家等级 private Text level; //电话 public Text phone; //邮箱 public Text email; //性别 public Text sex; //称号 public Text title; public bool isMatching; public async void Awake() { ReferenceCollector rc = this.GetParent<UI>().GameObject.GetComponent<ReferenceCollector>(); prompt = rc.Get<GameObject>("Prompt").GetComponent<Text>(); name = rc.Get<GameObject>("Name").GetComponent<Text>(); money = rc.Get<GameObject>("Money").GetComponent<Text>(); level = rc.Get<GameObject>("Level").GetComponent<Text>(); phone = rc.Get<GameObject>("Phone").GetComponent<Text>(); email = rc.Get<GameObject>("Email").GetComponent<Text>(); sex = rc.Get<GameObject>("Sex").GetComponent<Text>(); title = rc.Get<GameObject>("Title").GetComponent<Text>(); rc.Get<GameObject>("SetUserInfo").GetComponent<Button>().onClick.Add(OnSetUserInfo); //获取玩家数据 A1001_GetUserInfo_C2G GetUserInfo_Req = new A1001_GetUserInfo_C2G(); A1001_GetUserInfo_G2C GetUserInfo_Ack = (A1001_GetUserInfo_G2C)await SessionComponent.Instance.Session.Call(GetUserInfo_Req); //显示用户名和用户等级 name.text = GetUserInfo_Ack.UserName; money.text = GetUserInfo_Ack.Money.ToString(); level.text = GetUserInfo_Ack.Level.ToString(); phone.text = GetUserInfo_Ack.Phone.ToString(); email.text = GetUserInfo_Ack.Email; sex.text = GetUserInfo_Ack.Sex; title.text = GetUserInfo_Ack.Title; //添加进入房间匹配事件 //... //添加新的匹配目标 //... } public void OnSetUserInfo() { //加载设置用户信息界面 Game.EventSystem.Run(UIEventType.LandInitSetUserInfo); } public void UpdateUserInfo(A1002_SetUserInfo_G2C info){ phone.text = info.Phone.ToString(); email.text = info.Email; sex.text = info.Sex; } } }
最后到了我们本节前端的重头戏啦,设置用户信息的界面组件与工厂方法对象
创建 LandSetUserInfoComponent.cs类
\Assets\Model\Landlords\LandUI\LandSetUserInfo\LandSetUserInfoComponent.cs
using System; using ETModel; using UnityEngine; using UnityEngine.UI; namespace ETModel { [ObjectSystem] public class LandSetUserInfoComponentAwakeSystem : AwakeSystem<LandSetUserInfoComponent> { public override void Awake(LandSetUserInfoComponent self) { self.Awake(); } } /// <summary> /// 修改设置用户信息界面组件 /// </summary> public class LandSetUserInfoComponent : Component { //电话 public InputField phone; //邮箱 public InputField email; //性别 public InputField sex; public void Awake() { ReferenceCollector rc = this.GetParent<UI>().GameObject.GetComponent<ReferenceCollector>(); phone = rc.Get<GameObject>("Phone").GetComponent<InputField>(); email = rc.Get<GameObject>("Email").GetComponent<InputField>(); sex = rc.Get<GameObject>("Sex").GetComponent<InputField>(); rc.Get<GameObject>("Confirm").GetComponent<Button>().onClick.Add(OnConfirmUserInfo); } public async void OnConfirmUserInfo() { try { //发送设置用户信息消息 A1002_SetUserInfo_C2G SetUserInfo_Req = new A1002_SetUserInfo_C2G() { Phone = Int64.Parse(phone.text), Email = email.text, Sex = sex.text }; A1002_SetUserInfo_G2C SetUserInfo_Ack = (A1002_SetUserInfo_G2C)await SessionComponent.Instance.Session.Call(SetUserInfo_Req); //更新大厅界面上的用户信息 LandLobbyComponent lobbyComponent = Game.Scene.GetComponent<UIComponent>().Get(LandUIType.LandLobby).GetComponent<LandLobbyComponent>(); lobbyComponent.UpdateUserInfo(SetUserInfo_Ack); //移除用户信息设置界面 Game.EventSystem.Run(UIEventType.LandSetUserInfoFinish); } catch (Exception e) { Log.Error(e); } } } }
这里要理解一下,我们是如何在 LandSetUserInfoComponent界面组件中调用大厅界面组件LandLobbyComponent呢?
通过Game.Scene获取到全局的UIComponent对象,然后用对应的LandUIType就可以找到挂了LandLobbyComponent组件的UI对象,
再GetComponent<LandLobbyComponent>()就是大厅界面组件对象的实例啦!会跨UI组件调用了吧。
//更新大厅界面上的用户信息 LandLobbyComponent lobbyComponent = Game.Scene.GetComponent<UIComponent>().Get(LandUIType.LandLobby).GetComponent<LandLobbyComponent>(); lobbyComponent.UpdateUserInfo(SetUserInfo_Ack);
创建 LandSetUserInfoFactory.cs类
\Assets\Model\Landlords\LandUI\LandSetUserInfo\LandSetUserInfoFactory.cs
using ETModel; using System; using UnityEngine; namespace ETModel { [UIFactory(LandUIType.SetUserInfo)] public class LandSetUserInfoFactory : IUIFactory { public UI Create(Scene scene, string type, GameObject parent) { try { //加载AB包 ResourcesComponent resourcesComponent = Game.Scene.GetComponent<ResourcesComponent>(); resourcesComponent.LoadBundle($"{type}.unity3d"); //加载大厅界面预设并生成实例 GameObject bundleGameObject = (GameObject)resourcesComponent.GetAsset($"{type}.unity3d", $"{type}"); GameObject setUserInfo = UnityEngine.Object.Instantiate(bundleGameObject); //设置UI层级,只有UI摄像机可以渲染 setUserInfo.layer = LayerMask.NameToLayer(LayerNames.UI); UI ui = ComponentFactory.Create<UI, GameObject>(setUserInfo); ui.AddComponent<LandSetUserInfoComponent>(); return ui; } catch (Exception e) { Log.Error(e); return null; } } public void Remove(string type) { ETModel.Game.Scene.GetComponent<ResourcesComponent>().UnloadBundle($"{type}.unity3d"); } } }
现在我们可以运行服务端,然后Play前端项目看到本节的效果了。
课外练习:我们看下面的设置用户信息界面,能不能在这里调用到大厅界面上修改前的用户信息内容呢?