unity基于網絡從登錄到背包與商店系統(tǒng)的簡單實例
環(huán)境準備
客戶端:unity2021
服務端:vs2019
工具:protobuf、cJson、libuv
流程圖如下

角色登錄與創(chuàng)建
協(xié)議
查看代碼
客戶端
通過UI點擊事件作為驅動邏輯發(fā)送網絡請求,接受請求的時候也作出相應回調
? ?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; //角色創(chuàng)建對UI回調
? ?public UnityAction OnBuySuccess; //購買物品時回調
? ?public UnityAction<int,string> OnBuyFail; //購買失敗的回調
? ?public UnityAction OnPlayerInfoLoad; //角色信息加載完成的回調
以角色登錄舉例 一個發(fā)送一個接受邏輯。角色登錄成功會再向服務器發(fā)送一個角色信息的請求
? ?//發(fā)送登錄請求
? ?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);
? ? ? ?}
? ?}
角色信息請求和響應
查看代碼
玩家實體唯一,用單例的方式表示
查看代碼
服務端
服務端的網絡層客戶端發(fā)來的網絡包后通過解析Proto協(xié)議,根據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;
此外創(chuàng)建的時候要將角色存儲下來
登錄的時候要將角色讀取出來存到角色容器
查看代碼
為了讓數據持久化,用文件方式存取數據
// 把玩家數據保存到文件中,成功返回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;
};
背包與商城
配置表
客戶端和服務端都持有一個配置表數據,記錄關于存放在商城和背包的道具的靜態(tài)信息。雙方只需要傳遞道具的id就可以在各自的配置表中找出道具的具體信息
json配置表

客戶端
客戶端背包商城邏輯

客戶端和服務端執(zhí)行相應的讀取操作,存儲到相應的實體中去
客戶端利用字典的結構進行存儲
查看代碼
發(fā)送購買請求和接收購買請求
//發(fā)送角色購買請求
? ?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);
? ? ? ?}
? ?}
協(xié)議結構如下:
查看代碼
購買成功后一個是會修改角色的金錢數據和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;
};
服務端維護了一個玩家背包的存儲數據
相應的存取操作
查看代碼
協(xié)議結構如下
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