00003 不思议迷宫.0006:客户端的操作如何反应到服务器?
00003 不思議迷宮.0006:客戶端的操作如何反應到服務器?
玩家點擊手機屏幕,根據點到內容的不同而執行不同的操作,比如切換畫面或者場景、播放動畫或聲音、發送數據等等。我現在所關心的是點到物品,比如主界面中的海怪觸手、漂流瓶、罐子等等,還有地牢中神龍許愿點擊99次礦物后才出現的鉆石。
我在主界面的創建代碼中未能找到海怪觸手、漂流瓶、罐子之類的相關代碼(可能有,但被我忽略了;也可能是確實沒有,它們都是動態創建的),所以研究下地牢中的物品撿取吧。
地牢界面的入口代碼為/src/game/ui/form/dungeon/UIDungeonMain.luac。打開瞅瞅,發現代碼很長,那如何快速找到我們所關心的物品撿取事件呢?其實辦法很簡單,只要搜索就行。
所謂物品撿取事件,其實也就是屏幕點擊事件,只不過點擊到的是物品。在cocos2dx中,點擊事件需要通過addTouchEventListener、addClickEventListener之類的函數進行注冊。查找結果讓我有點意外:
-- 注冊點擊事件
functionUIDungeonMain:registerTouchEvent()
??? -- 卷軸按鈕
??? local btn_Magic =findChildByName(self.node, "CT2/juanzhou");
??? local function onMagicOnClick(sender,eventType)
??????? if eventType == ccui.TouchEventType.endedthen
???????????AudioM.playFx("button_spell");
?
??????????? if ME.user.forbidToOpenMagicUI andnot DungeonGuideM.isGuideFinished() then
??????????????? -- 禁止打開魔法書界面
??????????????? return;
??????????? end
?
??????????? -- 打開魔法書界面
??????????? self:showMagicScrollUI();
??????? end
??? end
??? AddTouchEventListener(btn_Magic,onMagicOnClick);
?
??? -- 寶物按鈕
??? local btnTreasure =findChildByName(self.node, "CT2/baowu");
??? ……
??? AddTouchEventListener(btnTreasure,onTreasureOnClick);
?
??? -- 英雄格子
?? ?localbgHero = findChildByName(self.node, "CT2/bg3");
??? ……
??? AddTouchEventListener(bgHero,onBgHeroOnClick);
?
??? -- 施法選擇目標背景
??? local screen_bg =self.node:getChildByName("select_target_bg")
??? ……
??? screen_bg:addTouchEventListener(onOnClick);
?
? ??-- 稱號冒泡點擊事件
??? local careerBubble =findChildByName(self.node, "CT2/career_bubble/bg");
??? ……
???careerBubble:addTouchEventListener(onBubbleClick);
end
和撿取物品一點關系都沒有!
好吧,那就換個思路。想想,既然是撿取物品,那相關的函數的函數名或者函數代碼中總應當出現item、equipment這些字樣吧?這次確實找到了:
??? -- 注冊撿取物品的處理函數
???EventMgr.register("UIDungeonMain", event.PICK_UP_ITEM,function(params)
???????self.grids[params.pos]:onPickUp(params.bonus, params.newBonus,
???????????params.isItemVisible, params.noAlert, nil, params.borrowGrid);
??????? localpos = params.pos;
??????? localtype = params.type;
?
??????? -- 如果是拾取地圖
??????? if type== GRID_TYPE_MAP then
??????????? -- 判斷鄰格是否開啟
???????????local adjoinGrids = DungeonM.getAdjoinGrids(pos);
??????????? fori = 1, #adjoinGrids do
???????????????local targetPos = adjoinGrids[i];
???????????????local ok = DungeonM.canOpenGrid(targetPos);
???????????????if ok == GRID_OPEN_OK then
???????????????????self.grids[targetPos]:gotoVisible();
???????????????end
??????????? end
??????? end
?
???????self:whenPickUpItem(params);
??????? -- 更新界面UI
???????--self:updateUI();
??? end);
拾取地圖這什么鬼?先不管它,還是研究下self.grids[params.pos]:onPickUp和self:whenPickUpItem。
找到self.grids的賦值處,確定self.grids[params.pos]的類型:
??? -- 生成格子
??? self.grids ={}
??? for i = 1,GRID_ROW_NUM do
??????? for j =1, GRID_COL_NUM do
??????????? ……
???????????local grid = UIGrid.create(……);
??????????? ……
???????????self.grids[index] = grid;
??????????? ……
??????? end
??????? ……
??? end
是UIGrid.create。再看看UIGrid.onPickUp:
-- 道具撿取回調函數
function UIGrid:onPickUp(bonus, newBonus,isItemVisible, noAlert, curNum, borrowGrid)
??? ……
??? if bonus[1]== 1 then
??????? -- 物品
??????? localitemId = bonus[2];
??????? localnum = bonus[3];
?
??????? localitemName = ItemM.query(itemId, "name");
??????? ……
?
??????? localitemsFlyIn;
??????? localdelayFly = false
??????? ……
???????itemsFlyIn = function()
??????????? -- 飛入效果
???????????local fileName = ItemM.query(itemId, "icon");
???????????local iconPath = getItemIconPath(fileName);
??????????? ifSpellM.isSpell(itemId) then -- 卷軸
??????????????? ……
???????????elseif EquipM.isEquipment(itemId) then -- 寶物
??????????????? ……
???????????elseif DragonWishM.isWishItem(itemId) then -- 龍珠
??????????????? ……
???????????elseif SkyResourceM.query(itemId) then -- 天空物資
??????????????? ……
???????????elseif PropertyM.isProperty(itemId) then?? -- 道具
???????????????AudioM.playFx("pick_goods");
???????????????pickupPropertyEffect(self, UIDungeonMgr.getCurLevel():getEquipNode(),iconPath, itemId, num);
??????????? else-- 其他
???????????????if not noAlert then
???????????????????str = itemName;
???????????????end
???????????????local itemBar = uiCurLevel:getFreeItemBar();
???????????????local iconNode = itemBar:getChildByName("icon");
???????????????local textNode = itemBar:getChildByName("num");
???????????????local total = (nil == curNum) and ItemM.getAmount(ME.user, itemId) orcurNum;
???????????????gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, num);
???????????????AudioM.playFx("pick_goods");
??????????? end
???? ???end
?
??????? -- 不需要延遲,直接播放飛的效果
??????? if notdelayFly then
???????????itemsFlyIn();
??????? end
??????? ……
??? end
?
??? ……
end
僅僅是播放相關特效,并不涉及物品數量的處理。那么物品數量的處理在self:whenPickUpItem中?
-- 拾取物品的回調
function UIDungeonMain:whenPickUpItem(args)
??? local dungeonId= DungeonM.getDungeonId();
??? local layer= DungeonM.currentLayer();
?
??? for _,taskInfo in ipairs(DailyTaskM.getTaskList()) do
??????? iftaskInfo.dungeon_id == dungeonId and taskInfo.floor == layer then
??????????? ……
??????? end
??? end
end
真是失望,還是沒有。只能看看event.PICK_UP_ITEM,是在哪里被觸發的了。
在src目錄中搜索包含“PICK_UP_ITEM”的文件,然后發現了DungeonM.luac中內容:
-- 拾取物品
function pickUp(pos)
??? ……
?
??? -- 格子
??? local grids= getCurrentDungeon();
??? local grid =grids[pos];
??? local bonus= grid.bonus;
?
??? ……
??? -- 獎勵清除
??? grid.bonus =nil;
??? grid.picked= true;
?
??? ……
?
??? -- 增加行為
??? -- 這一句必須放在doBonus之前,以保證命令的先后順序(doBonus中也會觸發事件)
??? addAction({ ["cmd"] = "pick_item",["pos"] = pos, })
?
??? localresult;
??? -- 如果是立即使用的道具,那么使用掉
??? -- 應該在ProertyM里面做的,而且已經有現成的功能(配置auto_use便可)。by panyl
??? if bonus[1]== 1 and type(bonus[2]) == "number" then
??????? ……
??? else
??????? -- 獎勵
??????? result = BonusM.doBonus(bonus, "pick_item");
??? end
?
??? -- !!!!!!!!!!!!!!!!!!!!!!!!
??? --? 這里事件必須在獎勵之后做。因為拾取第一顆龍珠時,會去抽取許愿選項,
??? -- 需要抽取隨機數,等等,如果不放在獎勵之后就會導致先后順序問題,而且拾取龍珠/抽取許愿還不在同一個回合中
??? -- 一個回合事件
???EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos,["isDelay"] = true });
?
??? -- 事件
??? EventMgr.fire(event.PICK_UP_ITEM, {["bonus"]= bonus, ["pos"] = pos, ["newBonus"] = result,["type"] = grid.type, ["class"] = grid.class, });
?
??? -- 嘗試完成成就:獲得競技場對手物品
???EventMgr.fire(event.GET_ARENA_ENEMY_ITEM, {["bonus"] = bonus,["pos"] = pos, });
?
???Profiler.funcEnd("pickUp");
??? return true,true;
end
“EventMgr.fire(event.PICK_UP_ITEM”是我們尋找的內容,但它不重要,因為我們已經知道它只干界面上的事。我們需要弄明白在它之前發生了啥。閱讀了代碼之后,有兩句話引起了我們的注意:
??? addAction({ ["cmd"] = "pick_item",["pos"] = pos, })
??? result = BonusM.doBonus(bonus, "pick_item");
在addAction的參數中,我看到了“cmd”,這個詞和網絡相關,看起來需要重點關注。
-- 地牢探索行為
function addAction(action, delay)
??? ……
??? -- 加入延時action
??? if delaythen
???????table.insert(delayAction, action);
??????? return;
??? end
??? ……
??? -- 第一個字節存放id,第二個字節pos,接著四個字節存放操作數據,最后兩個字節存放操作累計次數,共8個字節
??? localactionId = DungeonActionM.query(action.cmd, "id");
??? localpos? = action.pos or 0;
??? local data =action.data or 0;
?
??? local num =#actionCache;
?
??? -- 看下是否需要合并,如果前六個字節一樣(id、pos、data)則需要合并次數
??? if isSame…… then
??????? combine(……)
??????? return;
??? end
?
??? -- 新插入的一個操作
??? -- 8個字節
??? local buf =Buffer.create(8);
?
??? -- id
???Buffer.set8(buf, 1, actionId);
?
??? -- pos
???Buffer.set16(buf, 2, pos);
?
??? -- data
???Buffer.set32(buf, 4, data);
?
??? -- times
???Buffer.set16(buf, 8, (action["times"] or 1));
?
???table.insert(actionCache, buf);
?
???addDelayAction();
end
addAction函數將參數作了轉化,然后保存在actionCache中,最后調用了一次addDelayAction,這是為毛?
function addDelayAction()
??? localtoAction = delayAction;
??? delayAction= {};
?
??? for _,action in pairs(toAction) do
???????addAction(action);
??? end
end
在addAction函數中,如果指定了第二個參數為true,則第一個參數action將被保存到緩存而非actionCache中,緩存的內容直到下次調用addAction(x, false)時才會被保存到actionCache中。
actionCache在sync函數中是被使用:
-- 同步所有操作
function sync()
??? ……
??? -- 如果有緩存的操作
???Operation.cmd_dungeon_action(dungeonContainer.identify, actionCache);
?
??? -- 清空緩存
?? ?actionCache = {};
??? ……
end
應當是發送給服務器端的,網絡流程和上一章中的數據驗證應該是一樣的,不過我們還是驗證一下。Operation.cmd_dungeon_action:
function Operation.cmd_dungeon_action(identify,actions)
??? -- 當前層
??? local layer= DungeonM.currentLayer();
?
??? -- 最后的屬性
???DungeonLogM.collectFinalData(layer);
?
??? -- 行為隊列是空的
??? if #actions<= 0 then
??????? return;
??? end
?
??? -- 把所有操作都連接成一個buffer
??? local buf =Buffer.create(0);
??? for _,action in pairs(actions) do
??????? buf =Buffer.append(buf, action);
??? end
?
??? -- 獲取隨機數游標
??? local randomCursor= RandomFactoryM.packRandomCursor();
?
??? -- 等待應答id(一個唯一的id)
??? local authId= os.time();
?
??? local v = {
???????["identify"]??? =identify,
???????["auth_id"]???? =authId,
???????["layer"]?????? = layer,
???????["cursor"]????? =randomCursor.value,
???????["args"]??????? =buf.value,
???????["attrib"]????? =SimpleEncryptM.collectAttribCoding(ME.user),
??? };
?
??? -- 等待隊列
???DungeonServiceM.addWaitSync("CMD_DUNGEON_ACTION", v);
end
actions被轉換到連續的空間中,然后和其他一些參數一起,傳遞給DungeonServiceM.addWaitSync。其中有個參數["attrib"]????? =SimpleEncryptM.collectAttribCoding(ME.user),顯得可疑。
DungeonServiceM.addWaitSync函數:
-- 緩存同步操作消息,如果沒有收到服務器的應答就重發
function addWaitSync(cmd, msg,only_one)
??? local id = msg.auth_id;
??? msg.only_one = only_one;
??? queue[id] = { cmd, msg, };
?
??? -- 先發一次
??? reSendMsg();
?
??? -- 定時重連
???ScheduleM.createScheme("DungeonServiceM", reSendMsg,SYNC_MSG_REPOST_TIME, true)
end
reSendMsg函數:
-- 重發消息
function reSendMsg()
??? local keys =table.keys(queue);
??? ……
??? -- 如果消息已經空了或者已經離開游戲了
??? if notME.isInGame or (#keys <= 0) then
??????? -- 移除定時器
???????clearMsgQue();
?
??????? return;
??? end
?
??? -- 如果網絡沒連接上就不管了
??? ……
?
??? -- 不能同步
??? ……
?
??? -- 發送每條同步消息,按照id排序
???table.sort(keys);
??? for _, id inpairs(keys) do
??????? ifqueue[id] then
???????????local cmd = queue[id][1];
???????????local v?? = queue[id][2];
???????????SyncM.addMessage(cmd, v);
??????? end
?
??????? -- 如果是僅發一次,刪除
??????? ifqueue[id][2].only_one then
???????????queue[id] = nil;
??????? end
??? end
?
??? SyncM.startSync();
end
在Operation.cmd_dungeon_action中,傳遞了authId = os.time();該值在DungeonServiceM.addWaitSync中被用作queue的key。在reSendMsg中又根據key對queue進行了排序。簡單說,queue是按照時間先后的順序進行排序的。之后,就按照順序依次傳遞給SyncM.addMessage。
SyncM.addMessage僅將數據存到內部緩存:
-- 添加一條同步消息
function addMessage(cmd, para)
???table.insert(todoMessages, { cmd, para });
end
在reSendMsg函數的最后,調用SyncM.startSync進行真正的同步操作:
-- 開始進行同步
function startSync()
??? -- 判斷
??? if notcanSync() then
??????? return;
??? end
?
??? local socket= require "socket";
??? lastTime =socket.gettime();
?
??? -- 將todoMessages的消息內容剪切出來
??? messages =table.append(messages, todoMessages);
??? todoMessages= {};
??? if(#messages == 0) then
??????? -- 沒有任何數據需要同步
??????? lastTime= -999;
??????? return;
??? end
?
??? -- 先鎖住
??? locked =true;
?
??? -- 發送消息給服務器,開始同步
???trace("SyncM", "開始進行同步,版本號(%d),消息數量:%d", sync, #messages);
??? Communicate.send("CMD_START_SYNC", { sync =sync });
??? for _, v inipairs(messages) do
??????? Communicate.send(v[1], v[2]);
??? end
??? Communicate.send("CMD_END_SYNC", { sync =sync });
end
最后是幾個Communicate.send調用。它的詳情,在前面介紹過,此處不再贅述。
同步完成后還有一個CMD_END_SYNC回調:
-- 同步完畢
function endSync(success)
??? success =iif(success == nil, true, success);
???trace("SyncM", "同步完成,當前同步版本號:%d",ME.user.dbase:query("sync", 0));
??? sync =ME.user.dbase:query("sync", 0);
??? messages ={};
??? if (notsuccess) then
?????? ?-- 同步失敗了后續的東西也不需要再同步了,直接丟棄以服務器為準
???????todoMessages = {};
??? end
??? ……
??? -- 解鎖
??? locked =false;
??? ……
end
在服務器接收到CMD_START_SYNC之后,就開始處理我們的各種點擊事件了(在(x,y)位置撿取物品z)。
DungeonM.pickUp函數中的另一個惹人注意的語句“result =BonusM.doBonus(bonus, "pick_item");”我們已經無需理會了。它做的應當是客戶端的邏輯,有興趣的可以自行驗證。
總結
以上是生活随笔為你收集整理的00003 不思议迷宫.0006:客户端的操作如何反应到服务器?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 斐波那契数列求法
- 下一篇: 最大子矩阵(普通和01)