• 欢迎光临~

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

开发技术 开发技术 2022-07-31 次浏览

环境准备

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

流程图如下

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

角色登录与创建

协议

查看代码

//角色登录请求
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.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);
        }
    }

角色信息请求和响应

查看代码

    //发送请求角色信息
    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
{
    private string playerID; //角色id
    private string password; //密码
    private string name; //角色姓名
    private int money; //角色金钱
    public Dictionary bag = new Dictionary(); //背包 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:%llun", pack->cmd, pack->len, (uint64_t)client);
    switch (pack->cmd) {
        case CLIENT_PING:           // 处理客户端的ping
        {
            fprintf(stdout, "client ping, client:%llun", (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:%dn", 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 requestn");

    // 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 requestn");

// 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;
};

背包与商城

配置表

客户端和服务端都持有一个配置表数据,记录关于存放在商城和背包的道具的静态信息。双方只需要传递道具的id就可以在各自的配置表中找出道具的具体信息
json配置表
unity基于网络从登录到背包与商店系统的简单实例


客户端

客户端背包商城逻辑
unity基于网络从登录到背包与商店系统的简单实例

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

查看代码

[Serializable]
public class ItemInfos
{
    public List items;
}

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

public class ItemManager : Singleton
{
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 requestn");

    // 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 errorn");
        goto Exit0;
    }

    //6、保存玩家信息

    if (!g_playerMgr._save_player(player)) {
        fprintf(stdout, "save playerInfo errorn");
        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;
}
程序员灯塔
转载请注明原文链接:unity基于网络从登录到背包与商店系统的简单实例
喜欢 (0)