欢迎光临散文网 会员登陆 & 注册

unity基于网络从登录到背包与商店系统的简单实例

2022-08-05 13:43 作者:限量版范儿  | 我要投稿

环境准备

客户端:unity2021
服务端:vs2019
工具:protobuf、cJson、libuv

流程图如下

角色登录与创建

协议

查看代码

客户端

通过UI点击事件作为驱动逻辑发送网络请求,接受请求的时候也作出相应回调

   private void OnLoginClick()    {        //预先判断账号密码是否为空        if (string.IsNullOrEmpty(playerId.text))        {            Debug.Log("用户名为空");            return;        }        if (string.IsNullOrEmpty(password.text))        {            Debug.Log("密码为空");            return;        }        UserService.Instance().SendPlayerLogin(this.playerId.text, this.password.text);    }    private void OnLogin(int result,string reason)    {        //能够成功登录        if (result == 0)        {            SceneManager.LoadScene(1);            UserService.Instance().SendPlayerInfoReq(Player.Instance().PlayerID);        }        //登录失败        else        {            Debug.LogFormat("Login Fail [Result:{0} Reason:{1}]", result, reason);        }    }

UserService.cs
当客户端接收到Response,Service接收到该事件,并向UI层和其他数据管理模块进行回调
和NetWork的事件注册逻辑

   public UserService()    {        EventModule.Instance().AddNetEvent((int)SERVER_CMD.ServerLoginRsp, OnPlayerLogin);        EventModule.Instance().AddNetEvent((int)SERVER_CMD.ServerCreateRsp, OnPlayerCreate);        EventModule.Instance().AddNetEvent((int)SERVER_CMD.ServerShopbuyRsp, OnShopBuy);        EventModule.Instance().AddNetEvent((int)SERVER_CMD.ServerPlayerinfoRsp, OnPlayerInfo);    }    public void OnDispose()    {        EventModule.Instance().RemoveNetEvent((int)SERVER_CMD.ServerLoginRsp, OnPlayerLogin);        EventModule.Instance().RemoveNetEvent((int)SERVER_CMD.ServerCreateRsp, OnPlayerCreate);        EventModule.Instance().RemoveNetEvent((int)SERVER_CMD.ServerShopbuyRsp, OnShopBuy);        EventModule.Instance().RemoveNetEvent((int)SERVER_CMD.ServerPlayerinfoRsp, OnPlayerInfo);    }

让其他模块通过这里进行注册回调

   public UnityAction<int, string> OnLogin; //角色进入对UI回调    public UnityAction<int, string> OnCreate; //角色创建对UI回调    public UnityAction OnBuySuccess; //购买物品时回调    public UnityAction<int,string> OnBuyFail; //购买失败的回调    public UnityAction OnPlayerInfoLoad; //角色信息加载完成的回调

以角色登录举例 一个发送一个接受逻辑。角色登录成功会再向服务器发送一个角色信息的请求

   //发送登录请求    public void SendPlayerLogin(string playerId, string password)    {        Debug.LogFormat("Send Player Login Request:[id:{0} password:{1}]", playerId, password);        PlayerLoginReq req = new PlayerLoginReq();        req.PlayerID = playerId;        req.Password = password;        Player.Instance().PlayerID = playerId;        Network.Instance().SendMsg((int)CLIENT_CMD.ClientLoginReq, req);    }    //接受登录响应    public void OnPlayerLogin(int cmd, IMessage msg)    {        PlayerLoginRsp rsp = msg as PlayerLoginRsp;        Debug.LogFormat("OnPlayerLogin:[Result:{0} Reason:{1}]", rsp.Result, rsp.Reason);        if (this.OnLogin != null)        {            OnLogin.Invoke(rsp.Result, rsp.Reason);        }    }

角色信息请求和响应

查看代码




玩家实体唯一,用单例的方式表示

查看代码




服务端

服务端的网络层客户端发来的网络包后通过解析Proto协议,根据Proto头进行不同处理

查看代码






角色信息请求的处理逻辑

//客户端拉取角色信息 bool PlayerMgr::playerInfo_request(uv_tcp_t* client,const PlayerInfoReq* req) {    bool result = false;    Player* player = nullptr;    PlayerSaveData* playerData = new PlayerSaveData();    PlayerBagData* playerBag = new PlayerBagData();    PlayerInfoRsp rsp;    fprintf(stdout, "playerInfo request\n");    // 1. 查看玩家是否已经在游戏中,如果不在游戏中则返回失败    player = find_player(req->playerid());    if (player == nullptr) {        rsp.set_result(-1);        rsp.set_reason("player has not login");        player = nullptr;        goto Exit1;    }    // 2. 尝试从文件中加载玩家数据,如果未加载成功则返回失败    if (!_load_player(req->playerid(), playerData)) {        rsp.set_result(-2);        rsp.set_reason("player id exist");        goto Exit1;    }    // 3、尝试从文件中加载背包数据    if (g_itemMgr._load_player_Items(req->playerid(), playerBag))    {        rsp.set_allocated_playerbagdata(playerBag);    }    // 4. 应答客户端登录结果    rsp.set_allocated_playerdata(playerData);    rsp.set_result(0); Exit1:    {        string r = rsp.SerializeAsString();        int len = encode(s_send_buff, SERVER_PLAYERINFO_RSP, r.c_str(), (int)r.length());        sendData((uv_stream_t*)client, s_send_buff, len);    }    result = true; Exit0:    return result;

此外创建的时候要将角色存储下来
登录的时候要将角色读取出来存到角色容器

查看代码







为了让数据持久化,用文件方式存取数据

// 把玩家数据保存到文件中,成功返回true,失败返回false bool PlayerMgr::_save_player(const Player* player) {    // todo 把玩家数据序列化后存盘,参考save函数    int retCode = 0;    PlayerSaveData playerData;    playerData.set_password(player->Password);    playerData.set_name(player->Name);    playerData.set_playerid(player->PlayerID);    playerData.set_money(player->Money);    retCode = save(player->PlayerID.c_str(), playerData.SerializeAsString().c_str(), playerData.ByteSize());    if (retCode != 0)        return false;    else        return true; }// 从文件中加载玩家数据,用PlayerSaveData的方式读出 成功返回true,失败返回false bool PlayerMgr::_load_player(string playerID, PlayerSaveData* playerData) {    // todo 从文件中读取数据,并反序列化到playerData中,参考load函数    int len = load(playerID.c_str(), s_send_buff, sizeof(s_send_buff));    if (len >= 0) {        playerData->ParseFromArray(s_send_buff, len);        return true;    }    return false; }

Save文件

查看代码










Load文件

查看代码










Player实体数据

struct Player {    string PlayerID;    string Password;    string Name;    int Money; };

背包与商城

配置表

客户端和服务端都持有一个配置表数据,记录关于存放在商城和背包的道具的静态信息。双方只需要传递道具的id就可以在各自的配置表中找出道具的具体信息
json配置表

客户端

客户端背包商城逻辑

客户端和服务端执行相应的读取操作,存储到相应的实体中去
客户端利用字典的结构进行存储

查看代码






发送购买请求和接收购买请求

//发送角色购买请求    public void SendShopBuy(string playerId,int itemId,int count)    {        Debug.LogFormat("Send Shop Buy Request:[id:{0} itemId:{1} count:{2}]", playerId, itemId, count);        ShopBuyReq req = new ShopBuyReq();        req.PlayerID = playerId;        req.ItemId = itemId;        req.Count = count;        Network.Instance().SendMsg((int)CLIENT_CMD.ClientShopbuyReq, req);    }    //接受购买请求响应    public void OnShopBuy(int cmd, IMessage msg)    {        ShopBuyRsp rsp = msg as ShopBuyRsp;        Debug.LogFormat("ShopBuyRsp:[Result:{0} Reason:{1}]", rsp.Result, rsp.Reason);        if (rsp.Result == 0)        {            OnBuySuccess.Invoke();        }        else        {            OnBuyFail.Invoke(rsp.Result, rsp.Reason);            Debug.LogFormat("Buy Fail [Result:{0} Reason:{1}]", rsp.Result, rsp.Reason);        }    }

协议结构如下:

查看代码






购买成功后一个是会修改角色的金钱数据和ui上的金币数量,一个是修改背包的道具信息
ShopUI记录了购买时点击的商品id和数量,当购买成功后修改角色背包信息,同时对UIBag的信息进行了更新
添加Player中的道具

   public void AddBagItem(int itemId,int count)    {        if (!bag.ContainsKey(itemId))        {            bag.Add(itemId, count);        }        else        {            bag[itemId] += count;        }        if (OnBagChange != null)        {            OnBagChange.Invoke();        }    }

修改背包UI 重新刷新一遍

   void UpdateItem()    {        ClearItems();        this.money.text = Player.Instance().Money.ToString(); //修改金币数量        Dictionary<int, int> bag = Player.Instance().bag;        foreach (int id in bag.Keys)        {            Item item = ItemManager.Instance().GetItem(id);            GameObject go = Instantiate<GameObject>(ItemPrefab, itemGrid.transform);            Slot ui;            if (go.TryGetComponent<Slot>(out ui))            {                if (ui is UIBagItem)                {                    UIBagItem bagItem = ui as UIBagItem;                    Sprite sprite;                    ItemManager.Instance().TryGetSprite(item.iconName, out sprite);                    //将item信息赋值给ui                    bagItem.SetInfo(item, sprite);                    bagItem.count.text = string.Format("*{0}", bag[id]);                    bagItem.onClick.AddListener(OnSlotClick);                }            }        }    }

服务端

服务端也需要从配置表读取道具信息

查看代码



本地的实体结构如下

struct PlayerItem {    string PlayerID;    map<int, int> Items; }; struct Item {    int id;    string name;    string introduce;    int price;    string iconName; };

服务端维护了一个玩家背包的存储数据
相应的存取操作

查看代码





协议结构如下

message BagItem{ int32 ItemId=1; int32 Count=2; } message PlayerBagData{ repeated BagItem BagItem=2; }

接收到购买请求进行处理

bool ItemMgr::item_buy(uv_tcp_t* client, const ShopBuyReq* req) {    bool result = false;    Player* player = nullptr;    PlayerSaveData playerData;    ShopBuyRsp rsp;    PlayerBagData playerBagData;    PlayerItem* playerItem = new PlayerItem();    fprintf(stdout, "item buy request\n");    // 1. 查看玩家是否已经在游戏中,如果在游戏中登录失败    player = g_playerMgr.find_player(req->playerid());    if (player == nullptr) {        rsp.set_result(-1);        rsp.set_reason("player has not login");        goto Exit1;    }    //2、判断商店是否有该商品    if (m_storeMap.count(req->itemid()) == 0)    {        rsp.set_result(-2);        rsp.set_reason("store has not this item");        goto Exit1;    }    //3、读取当前玩家金钱数据 读取商店物品价格数据 判断是否足够购买    if (player->Money < m_storeMap[req->itemid()]->price * req->count())    {        rsp.set_result(-4);        rsp.set_reason("has not enough money");        goto Exit1;    }    player->Money -= m_storeMap[req->itemid()]->price * req->count();    // 4. 从文件中读取玩家背包数据并展开    playerItem->PlayerID = req->playerid();    if (_load_player_Items(playerItem->PlayerID, &playerBagData)) {        //如果存在玩家背包 用playerItem暂存        if (playerBagData.bagitem_size() > 0)        {            for (int i = 0; i < playerBagData.bagitem_size(); i++)            {                playerItem->Items.insert(make_pair(playerBagData.bagitem()[i].itemid(), playerBagData.bagitem()[i].count()));            }        }    }    //找到该道具 直接添加数量    if (playerItem->Items.count(req->itemid()) == 1)    {        playerItem->Items[req->itemid()] += req->count();    }    //没有找到 添加该道具    else    {        playerItem->Items.insert(make_pair(req->itemid(), req->count()));    }    //5、 保存背包信息    if (!_save_player_Items(playerItem))    {        fprintf(stdout, "save playerItems error\n");        goto Exit0;    }    //6、保存玩家信息    if (!g_playerMgr._save_player(player)) {        fprintf(stdout, "save playerInfo error\n");        goto Exit0;    }    // 7. 应答客户端登录结果    rsp.set_result(0); Exit1:    {        fprintf(stdout, "buy success [item:%d count:%d moneyLeft:%d]\n", req->itemid(), req->count(), player->Money);        string r = rsp.SerializeAsString();        int len = encode(s_send_buff, SERVER_SHOPBUY_RSP, r.c_str(), (int)r.length());        sendData((uv_stream_t*)client, s_send_buff, len);    }    result = true; Exit0:    return result; }

来源链接:https://www.dianjilingqu.com/460927.html


unity基于网络从登录到背包与商店系统的简单实例的评论 (共 条)

分享到微博请遵守国家法律