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

环境准备

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

流程图如下

image

角色登录与创建

协议

//角色登录请求
message PlayerLoginReq {
	string PlayerID = 1;
	string Password = 2;
}
//角色登录响应
message PlayerLoginRsp {
	int32 Result = 1;
	string Reason = 2;
	PlayerSyncData PlayerData = 3;
}
//角色数据
message PlayerSaveData {
	string PlayerID = 1;
	string Password = 2;
	bytes Name = 3;
	int32 Money=4;
}
//角色创建请求
message PlayerCreateReq {
	string PlayerID = 1;
	string Password = 2;
	bytes Name = 3;
}
//角色创建响应
message PlayerCreateRsp {
	int32 Result = 1;
	string PlayerID = 2;
	bytes Name = 3;
	string Reason = 4;
}
//角色信息请求
message PlayerInfoReq{
	string PlayerID=1;
}
//角色信息响应
message PlayerInfoRsp{
	PlayerSaveData PlayerData=1;
	PlayerBagData PlayerBagData=2;
	int32 Result = 3;
	string Reason = 4;
}

客户端

通过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
当客户端接收到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);
        }
    }

角色信息请求和响应

    //发送请求角色信息
    public void SendPlayerInfoReq(string playerId)
    {
        Debug.LogFormat("Send PlayerInfo Request:[id:{0}]", playerId);
        PlayerInfoReq req = new PlayerInfoReq();
        req.PlayerID = playerId;
        Network.Instance().SendMsg((int)CLIENT_CMD.ClientPlayerinfoReq, req);
    }

    //接受角色信息响应
    public void OnPlayerInfo(int cmd, IMessage msg)
    {
        PlayerInfoRsp rsp = msg as PlayerInfoRsp;
        Debug.LogFormat("PlayInfoRsp:[Result:{0} Reason:{1}]", rsp.Result, rsp.Reason);

        if (rsp.Result == 0)
        {
            Debug.Log("加载角色数据完成...");
            Player.Instance().Name = rsp.PlayerData.Name.ToStringUtf8();
            Player.Instance().Password = rsp.PlayerData.Password;
            Player.Instance().Money = rsp.PlayerData.Money;
            OnPlayerInfoLoad.Invoke();

            if (rsp.PlayerBagData != null)
            {
                foreach (BagItem item in rsp.PlayerBagData.BagItem)
                {
                    Player.Instance().AddBagItem(item.ItemId,item.Count);
                }
                Debug.Log("加载背包数据完成...");
            }
        }
        else
        {
            Debug.LogFormat("加载角色数据失败...[Result:{0} Reason:{1}]",rsp.Result, rsp.Reason);
        }
    }

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

public class Player : Singleton<Player>
{
    private string playerID; //角色id
    private string password; //密码
    private string name; //角色姓名
    private int money; //角色金钱
    public Dictionary<int, int> bag = new Dictionary<int, int>(); //背包 k:道具id v:数量

    public UnityAction OnMoneyChange; //让ui注册对金钱变化的回调
    public UnityAction OnBagChange; //让ui注册对背包道具变化的回调

    public string PlayerID { get => playerID; set => playerID = value; }
    public string Password { get => password; set => password = value; }
    public string Name { get => name; set => name = value; }
    public int Money 
    {
        get => money;
        set
        {
            money = value;
            if (OnMoneyChange != null)
            {
                OnMoneyChange.Invoke();
            }
        }
    }

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

服务端

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

// 处理一个完成的消息包
bool _OnPackHandle(uv_tcp_t* client, Packet* pack) {
    bool result = false;
    int len = 0;
    // todo 处理收到的数据包
    fprintf(stdout, "OnPackHandle: cmd:%d, len:%d, client:%llu\n", pack->cmd, pack->len, (uint64_t)client);
    switch (pack->cmd) {
        case CLIENT_PING:           // 处理客户端的ping
        {
            fprintf(stdout, "client ping, client:%llu\n", (uint64_t)client);
            len = encode(s_send_buff, SERVER_PONG, nullptr, 0);
            sendData((uv_stream_t*)client, s_send_buff, len);
            break;
        }
        case CLIENT_ADD_REQ:       // 处理客户端发起的一个加法计算请求
        {
            AddReq req;
            req.ParseFromArray(pack->data, pack->len);
            if (!_OnAdd(client, &req)) {
                goto Exit0;
            }
            break;
        }
        case CLIENT_LOGIN_REQ:
        {
            PlayerLoginReq req;
            req.ParseFromArray(pack->data, pack->len);
            g_playerMgr.player_login(client, &req);
            break;
        }
        case CLIENT_CREATE_REQ:
        {
            PlayerCreateReq req;
            req.ParseFromArray(pack->data, pack->len);
            g_playerMgr.player_create(client, &req);
            break;
        }
        case CLIENT_ANNOUNCE_REQ:
        {
            g_playerMgr.announce_request(client);
            break;
        }
        case CLIENT_PLAYERINFO_REQ:
        {
            PlayerInfoReq req;
            req.ParseFromArray(pack->data, pack->len);
            g_playerMgr.playerInfo_request(client, &req);
            break;
        }
        case CLIENT_SHOPBUY_REQ:
        {
            ShopBuyReq req;
            req.ParseFromArray(pack->data, pack->len);
            g_itemMgr.item_buy(client, &req);
            break;
        }

        default:
            fprintf(stderr, "invalid cmd:%d\n", pack->cmd);
            return false;
    }
    result = true;
Exit0:
    return result;
}

角色信息请求的处理逻辑

//客户端拉取角色信息
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;

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

bool PlayerMgr::player_login(uv_tcp_t* client, const PlayerLoginReq* req) {
    bool result = false;
    Player* player = nullptr;
    PlayerLoginRsp rsp;
    PlayerSaveData playerData;

    fprintf(stdout, "player login request\n");

    // 1. 查看玩家是否已经在游戏中,如果在游戏中登录失败
    player = find_player(req->playerid());
    if (player != nullptr) {
        rsp.set_result(-1);
        rsp.set_reason("player has login");
        goto Exit1;
    }

    // 2. 从文件中读取玩家数据并展开
    if (!_load_player(req->playerid(), &playerData)) {
        rsp.set_result(-2);
        rsp.set_reason("player not exist");
        goto Exit1;
    }

    // 3. 检验玩家登录的用户名密码与文件中的是否匹配
    if (playerData.password() != req->password()) {
        rsp.set_result(-3);
        rsp.set_reason("invalid password");
        goto Exit1;
    }

    // 4. 创建玩家对象,并构造
    player = new Player;
    if (player == nullptr) {
        goto Exit0;
    }
    player->Name = playerData.name();
    player->PlayerID = playerData.playerid();
    player->Password = playerData.password();
    player->Money = playerData.money();

    // 5. 把玩家对象加入m_playerMap中进行管理
    m_playerMap.insert(pair<uv_tcp_t*, Player*>(client, player));

    // 6. 应答客户端登录结果
    {
        rsp.set_result(0);
        PlayerSyncData* sync = rsp.mutable_playerdata();
        sync->set_name(player->Name);
    }
Exit1:
    {
        string r = rsp.SerializeAsString();
        int len = encode(s_send_buff, SERVER_LOGIN_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文件

//保存 文件名、数据、长度
int save(const char* name, const char* data, int len) {
    int result = -1;
    FILE * fp = nullptr;
    char path[512] = {0};

    sprintf(path, "%s/%s", SAVE_PATH, name);
    fp = fopen(path, "wb"); //二进制写入 没有文件会新建
    if (fp == nullptr) {
        result = -1;
        goto Exit0;
    }
    result = (int)fwrite(data, 1, len, fp);
    if (result != len) {
        result = -2;
        goto Exit0;
    }
    result = 0;
Exit0:
    if (fp != nullptr) {
        fclose(fp);
        fp = nullptr;
    }
    return result;
}

Load文件

//读取 文件名 数据 长度
int load(const char* name, char* data, int size) {
    int result = -1;
    FILE * fp = nullptr;
    char path[512] = {0};
    int fileSize = 0;

    sprintf(path, "%s/%s", SAVE_PATH, name);
    fp = fopen(path, "rb");
    if (fp == nullptr) {
        result = -1;
        goto Exit0;
    }
    fseek(fp, 0, SEEK_END); //指针移动到末尾 偏移量 文件末尾
    fileSize = ftell(fp); //求出文件字节数
    if (size < fileSize) {
        result = -2;
        goto Exit0;
    }
    rewind(fp); //指针移动会开头

    result = (int)fread(data, 1, fileSize, fp); //每次读一个字节读fileSize次
    if (result != fileSize) {
        result = -3;
        goto Exit0;
    }

Exit0:
    if (fp != nullptr) {
        fclose(fp);
        fp = nullptr;
    }
    return result;
}

Player实体数据

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

背包与商城

流程简单表示如下:

  • 客户端由ui控件监听购买点击事件,向Uservice层传递购买的物品id和数量
  • Uservice使用BuyRequest协议结构进行数据封装,调用网络层发送包体给服务端
  • 服务端进行粘包处理,当解析到buyRequest请求时,将请求的包体进行相应处理
  • 首先根据请求中的道具id信息读取商店配置表中的道具价格信息,再根据角色id读取用文件存储角色的金钱信息
  • 当金钱足够时,读取到用文件存储的角色背包信息,添加相应道具和数量重新存一遍
  • 当处理完请求后,发送buyResponse给客户端
  • 客户端接收到响应后,修改角色数据和ui的信息

配置表

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


客户端

客户端背包商城逻辑
image

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

[Serializable]
public class ItemInfos
{
    public List<Item> items;
}

[Serializable]
public class Item
{
    public int id;
    public string name;
    public string introduce;
    public int price;
    public string iconName;
}

public class ItemManager : Singleton<ItemManager>
{
    Dictionary<int, Item> ItemDic = new Dictionary<int, Item>();
    Dictionary<string, Sprite> spriteDic = new Dictionary<string, Sprite>(); //icon图集

    public void Init()
    {
        InitItems();
    }

    void InitItems()
    {
        TextAsset itemConfig = ResourceManager.Instance().Load<TextAsset>("ItemConfig");
        if (itemConfig != null)
        {
            string str = itemConfig.text.Replace("\n", "").Replace("\r", "").Replace("\t", "");
            ItemInfos itemInfos = JsonUtility.FromJson<ItemInfos>(str);
            //添加到管理器字典
            foreach(Item item in itemInfos.items)
            {
                if (!ItemDic.ContainsKey(item.id))
                {
                    ItemDic.Add(item.id, item);
                }
            }
        }

        //获取Icon图标
        Sprite[] sprites = Resources.LoadAll<Sprite>("UI/Icon/itemIcon");

        //添加到管理器图片字典
        foreach (Sprite sp in sprites)
        {
            if(!spriteDic.ContainsKey(sp.name))
                spriteDic.Add(sp.name, sp);
        }
    }

    //获取图片
    public bool TryGetSprite(string name,out Sprite sprite)
    {
        bool value=false;
        if (spriteDic.TryGetValue(name,out sprite))
        {
            value = true;
        }
        return value;
    }

    public bool TryGetSprite(int itemId,out Sprite sprite)
    {
        bool value = false;
        string name = ItemDic[itemId].iconName;
        if (spriteDic.TryGetValue(name, out sprite))
        {
            value = true;
        }
        return value;
    }

    public List<Item> GetItems()
    {
        List<Item> items = new List<Item>();
        foreach (Item item in ItemDic.Values)
        {
            items.Add(item);
        }
        return items;
    }

    public Item GetItem(int id)
    {
        return ItemDic[id];
    }
}

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

//发送角色购买请求
    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);
        }

    }

协议结构如下:

message ShopBuyReq{
	string PlayerID=1;
	int32 ItemId=2;
	int32 Count=3;
}

message ShopBuyRsp{
	int32 Result = 1;
	string Reason = 2;
}

购买成功后一个是会修改角色的金钱数据和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;
};

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

// 从文件中加载玩家数据,成功返回true,失败返回false
bool ItemMgr::_load_player_Items(string playerID,PlayerBagData *playerBagData) {
    // todo 从文件中读取数据,并反序列化到playerData中,参考load函数
    string strBag = playerID + "_Bag";
    int len = load(strBag.c_str(), s_send_buff, sizeof(s_send_buff));
    if (len >= 0) {
        playerBagData->ParseFromArray(s_send_buff, len);
        return true;
    }
    return false;
    }

// 把玩家数据保存到文件中,成功返回true,失败返回false
bool ItemMgr::_save_player_Items(const PlayerItem* playeritem) {
    // todo 把玩家数据序列化后存盘,参考save函数
    int retCode = 0;
    PlayerBagData playerData;

    //还原修改后的PlayerBagData结构
    map<int, int> items = playeritem->Items;
    map<int, int>::iterator iter;
    iter = items.begin();
    while (iter != items.end()) {
        BagItem *bagItem= playerData.add_bagitem();
        bagItem->set_itemid(iter->first);
        bagItem->set_count(iter->second);
        iter++;
    }
    string strBag = playeritem->PlayerID + "_Bag";

    retCode = save(strBag.c_str(), playerData.SerializeAsString().c_str(), playerData.ByteSize());
    if (retCode != 0)
        return false;
    else

协议结构如下

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;
}
posted @   fjnloo  阅读(234)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示