任务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.zip (8.51KB)

大厅预制体增加了更多信息的显示与设置更多信息的按钮

增加了 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前端项目看到本节的效果了。

课外练习:我们看下面的设置用户信息界面,能不能在这里调用到大厅界面上修改前的用户信息内容呢?

 

posted @ 2023-02-03 17:03  Domefy  阅读(96)  评论(0)    收藏  举报