<1>不少人都玩過炸彈人或者泡泡堂,以前作了這個玩法,記錄一下關鍵實現緩存
<2>直接上Lua代碼服務器
1.維護每一個格子上的物體 好比:BUFF類,寶箱,草箱,木箱,鐵箱,炸彈,小怪...ui
用一個結構維護一個格子31*31=961,進入場景建立,離開釋放掉this
每一個結構裏面聲明一張map緩存格子裏面全部的物體 <物體類型,map<物體uid,true>>spa
2.計算炸彈爆炸所影響的格子(長寬不等的十字形)server
炸彈有一個威力等級Force,計算規則爲 Force*2+1 = 同一軸上所影響的格子對象
可是碰到鐵箱子是沒法穿透的,全部這個爆炸的十字形多是長寬不等的blog
BombermanMgr = { } local _isInit = false local rowNum = 31 local colNum = 31 local cellSize = 2 -- -22 30 對應 -21.945 29.925 要麼場景作偏移 要麼起始X Y作偏移 -0.055 0.075 -- 場景作偏移 local startX = -31 local startY = -31 local function create_self_param(self) if _isInit == ture then return end -- 導入依賴的文件 -- 初始化 self:__init_self_param() end local function create(self) create_self_param(self) end -- 初始化成員 function BombermanMgr:__init_self_param() self.boxPool = nil --緩存961格子信息 BoxData self.dropPool = { } --緩存掉落物 entity self.memberPool = { } --玩家信息 PPTMemberData self.rankPool = { }--玩家信息 PPTMemberRankData self.rankMap = { }--玩家排名 PPTMemberRankData self.skillPool = nil --玩家技能信息 self.mainRoleId = 0 --排行數據 self.todayReqTime = -1 --上次拉取時間 self.yestodayReqTime = -1 --上次拉取時間 self.todayLst = { } --今天數據 self.yestodayLst = { }--昨天數據 self.todayMine = nil --本身今天 self.yestodayMine = nil --本身昨天 self.myRespawnRoleId = nil --出生點模型 self.followId = -1 --跟隨id self.lastAlertTime = -1--上次彈出提示時間 self.bombEffectTimeMap = { }--格子爆炸特效上次釋放時間 self.audioTimeMap = { }--音效上次播放時間 --self.combineLst = ListLong() --須要合併的對象 self.combineRoot = nil --合併根節點 self.combineNum = 0 --無需清理 self.infoHandler = nil self.rankRewardLst = nil end function BombermanMgr:getMainPlayerId() return self.mainRoleId end function BombermanMgr:getMaxIndex() return rowNum*colNum end --技能信息 --新實體協議下發 初始化技能 根據實體中的寵物id獲取充能次數... function BombermanMgr:initSkill(petId) self.skillPool = { } local pptCfg = BombermanUtils:getPetConfigById(petId) if not pptCfg then Logger:logError("沒有讀取到PptPetConfig id ",petId) return end local boomData = PPTSkillData(PPTSkillType.boom,PPTBombForceIcon[1],BombermanConst.bombCooldown,pptCfg.bomCharge) self.skillPool[PPTSkillType.boom] = boomData local speedData = PPTSkillData(PPTSkillType.speed,BombermanConst.speedIcon,BombermanConst.speedCooldown,pptCfg.speedRune) self.skillPool[PPTSkillType.speed] = speedData local superData = PPTSkillData(PPTSkillType.super,BombermanConst.superIcon,BombermanConst.superCooldown,pptCfg.snvincibleRune) self.skillPool[PPTSkillType.super] = superData local hpData = PPTSkillData(PPTSkillType.hp,BombermanConst.hpIcon,BombermanConst.hpCooldown,pptCfg.life) self.skillPool[PPTSkillType.hp] = hpData end function BombermanMgr:getSkillLst() return self.skillPool end --計數數據更新 function BombermanMgr:updateSkill(skillType,count,force) local dt = self.skillPool[skillType] if dt then if force then dt:setForceLevel(force) end if count then dt:onCountChange(count) end end end function BombermanMgr:addSkillCount(skillType) local dt = self.skillPool[skillType] if dt then dt:addChargeNum(1) end end --配置靜態盒子 --2次for循環 中間少了的固定格子寫死 function BombermanMgr:initBox() self.boxPool = { } --把固定格子建立出來 local ignore = { } ignore[419] = true ignore[479] = true ignore[481] = true ignore[483] = true ignore[543] = true for i = 1,15 do local num = 31+2*i for k = 1,15 do local num2 = num+31*(k-1)*2 if ignore[num2] == nil then local data = BoxData(BoxType.metalBox,MathUtils:getUniqueID()) self.boxPool[num2] = data end end end --建立剩餘的格子 默認爲空格子 for i = 1,rowNum*colNum do if self.boxPool[i] == nil then local data = BoxData(BoxType.emptyBox,MathUtils:getUniqueID()) self.boxPool[i] = data end end end --隱射一個實體類型 local entityMap = { } entityMap[BoxType.bombBox] = LBoxHinderBombEntity local function getClass(type) local class = entityMap[type] if class == nil then class = LBoxHinderEntity end return class end --建立靜態實體 function BombermanMgr:createBox(msg) local index = msg.gridId local id = msg.livingId local btype = msg.type local masterId = msg.playerId local modelId = BoxType2Model[btype] if btype == BoxType.bombBox then modelId = masterId<=0 and BombermanConst.bossBombModelId or modelId --音效:炸彈建立 if masterId*10 == self:getMainPlayerId() then self:playAudio(BombermanConst.bombCastAudioId, nil) end end local pos = self:getBoxCenterPos(index) --data local roleData = LRoleData(id, RoleType.BoxHinder, true) roleData:changeAttr(RoleAttr.SkinId,modelId) roleData:changeAttr(RoleAttr.Fight,index) roleData:changeAttr(RoleAttr.Exp,btype) roleData:changeAttr(RoleAttr.MasterId,masterId) --roleData:changeAttr(RoleAttr.MountId,msg.blood) --roleData:changeAttr(RoleAttr.MagicWeaponId,msg.maxBlood) roleData:changeAttr(RoleAttr.isActive,true) roleData:changeAttr(RoleAttr.InitPosition, pos) roleData:setFull() roleData:attach() --建立 if StaticEntityMgr:getRoleData(id) ~= nil then return end local class = getClass(btype) local staticEntity = class(id, RoleType.BoxHinder, roleData , nil) if staticEntity then if self.boxPool[index] then self.boxPool[index]:addType(btype,id) end StaticEntityMgr:addRoleData(roleData) StaticEntityMgr:addRole(staticEntity) staticEntity:create() end end --移除觸發器實體 function BombermanMgr:removeBox(msg) local id = msg.livingId local index = msg.gridId local btype = msg.type if self.boxPool[index] then self.boxPool[index]:removeType(btype,id) end StaticEntityMgr:removeRole(id) StaticEntityMgr:removeRoleData(id) --移除的盒子須要有特效 則播放特效 self:playEffect(btype,index) end --有的盒子銷燬須要播放特效 function BombermanMgr:playEffect(btype,idx) if BoxType2ExplodeEffect[btype] ~= nil then local pos = self:getBoxCenterPos(idx) EffectMgr:createEffect(BoxType2ExplodeEffect[btype], pos, nil) end --寶箱移除 播放音效 if btype == BoxType.treasureBox then self:playAudio(BombermanConst.openTreasureAudioId, nil) end end --協議更新盒子 function BombermanMgr:updateBox(msg) if self.boxPool == nil then self:initBox() end local index = msg.gridId local btype = msg.type local uid = msg.livingId local isAdd = msg.blood > 0 --再也不使用狀態字段 blood就能夠標記這個狀態了 if self.boxPool[index] then if isAdd then self:createBox(msg) self.boxPool[index]:addType(btype,uid) else self:removeBox(msg) self.boxPool[index]:removeType(btype,uid) end end end --怪物移動 --在格子上添加移除 function BombermanMgr:simpleAddType(index,btype,uid) local boxData = self:getBoxData(index) if boxData then boxData:addType(btype,uid) end end function BombermanMgr:simpleRemoveType(index,btype,uid) local boxData = self:getBoxData(index) if boxData then boxData:removeType(btype,uid) end end --是否能夠移動到目標位置 function BombermanMgr:canMoveTo(nowIndex,targetPos) local index = self:getCellIndex(targetPos) --若是玩家在當前格子 能夠移動 if index == nowIndex then return true end --沒有找到格子 沒法移動 if self.boxPool[index] == nil then return true end --最後返回目標格子沒有阻擋物 return not self.boxPool[index]:getIsHinder() end function BombermanMgr:canMoveToByPos(nowPos,targetPos) local index = self:getCellIndex(nowPos) return self:canMoveTo(index,targetPos) end --炸彈爆炸全部格子 --服務器推 爆炸點 炸彈等級 客戶端計算出在哪些格子實例化炸彈 function BombermanMgr:getExplodeIndexs(index,level) local lst = { } local dirMap = { }--4個方向的數量 dirMap.top = 0 dirMap.bottom = 0 dirMap.left = 0 dirMap.right = 0 lst[1] = index local row = self:getIndexRow(index) --local col = self:getIndexCol(index) local boxData = self:getBoxData(index) if boxData == nil then return lst end local unlockTop = true local unlockBottom = true local unlockLeft = true local unlockRight = true for i = 1,level do local top = index-rowNum*i local bottom = index+rowNum*i local left = index-i local right = index+i --驗證是否合理 --top不能<=0 bottom不能>31 left right 驗證同一行既可 --公共條件 有的(鐵格子)無需放炸彈 被鐵格子擋住了 也沒法繼續延伸釋放炸彈 if unlockTop and top > 0 then unlockTop,canAdd = self:canPlaceExplode(top,level) if unlockTop then dirMap.top = dirMap.top + 1 end if canAdd then table.insert(lst,top) end end if unlockBottom and bottom <= rowNum*colNum then unlockBottom,canAdd = self:canPlaceExplode(bottom,level) if unlockBottom then dirMap.bottom = dirMap.bottom + 1 end if canAdd then table.insert(lst,bottom) end end --left right if unlockLeft and self:getIndexRow(left) == row then unlockLeft,canAdd = self:canPlaceExplode(left,level) if unlockLeft then dirMap.left = dirMap.left + 1 end if canAdd then table.insert(lst,left) end end if unlockRight and self:getIndexRow(right) == row then unlockRight,canAdd = self:canPlaceExplode(right,level) if unlockRight then dirMap.right = dirMap.right + 1 end if canAdd then table.insert(lst,right) end end end return lst,dirMap end --格子是否能夠放置炸彈爆炸物 --return 1是否被阻擋 2是否能夠爆炸 function BombermanMgr:canPlaceExplode(index,level) local boxData = self:getBoxData(index) if boxData == nil then return true,true end if boxData:hasType(BoxType.metalBox) then return false,false elseif boxData:hasType(BoxType.grassBox) then return false,true--level>=3 elseif boxData:hasType(BoxType.woodBox) then return false,true--level>=1 end return true,true --return not boxData:hasType(BoxType.metalBox) and not boxData:hasType(BoxType.grassBox) and not boxData:hasType(BoxType.woodBox) end --根據index獲取行列 function BombermanMgr:getIndexRow(index) return math.ceil(index/rowNum) end function BombermanMgr:getIndexCol(index) local col = index%rowNum return col == 0 and rowNum or col end --獲取格子數據 function BombermanMgr:getBoxData(index) return self.boxPool[index] end --根據位置獲取格子index function BombermanMgr:getCellIndex(pos) local x = pos.x local y = pos.z local rowIndex = math.ceil((x - startX)/cellSize) local colIndex = math.ceil((y - startY)/cellSize) return (colIndex-1)*rowNum+rowIndex end --獲取格子對應的座標(取格子中心) function BombermanMgr:getBoxCenterPos(index) local row = self:getIndexRow(index) local col = self:getIndexCol(index) local x = startX+(col-1)*cellSize+(cellSize/2) --x是遞加 local y = startY+(row-1)*cellSize+(cellSize/2) --y是遞加 return Vector3(x,0,y) end --獲取當前格子九宮格 function BombermanMgr:getSudokuBoxs(index) local lst = self:getRoundIndex(index) local boxLst = { } for k,v in pairs(lst) do local data = self:getBoxData(k) if data then table.insert(boxLst,data) end end return boxLst end --獲取格子周圍全部格子 function BombermanMgr:getRoundIndex(index) local lst = {} --top 沒有上 r1r2r3 --bottom沒有下 r6r7r8 --left沒有左r1r4r6 --right沒有右r3r5r8 local isTop = index<=rowNum local isBottom = index > (colNum-1)*rowNum local isLeft = index%rowNum == 1 local isRight = index%rowNum == 0 local filter = {} local r1 = index - rowNum - 1 local r2 = index - rowNum local r3 = index - rowNum + 1 local r4 = index - 1 local r5 = index + 1 local r6 = index + rowNum - 1 local r7 = index + rowNum local r8 = index + rowNum + 1 if isTop then filter[r1] = true filter[r2] = true filter[r3] = true end if isBottom then filter[r6] = true filter[r7] = true filter[r8] = true end if isLeft then filter[r1] = true filter[r4] = true filter[r6] = true end if isRight then filter[r3] = true filter[r5] = true filter[r8] = true end local result = {r1,r2,r3,r4,r5,r6,r7,r8} for i =1,#result do if filter[result[i]] == nil then lst[result[i]] = true end end lst[index] = true return lst end function BombermanMgr:isVaildMove(nowIndex,targetPos) local index = self:getCellIndex(targetPos) return index == nowIndex+1 or index == nowIndex-1 or index == nowIndex+rowNum*1 or index == nowIndex+rowNum-1 end --動態修改層級 --若是玩家當前格子有阻礙物 --設置當前阻礙物爲role層 不與玩家發生碰撞 --在玩家更換格子以後 把層級設置回來 function BombermanMgr:changeLayer(index,layerName) local boxData = self:getBoxData(index) if boxData and boxData:getIsHinder() then for k,v in pairs(BoxHinderType) do local map = boxData:getTypeLst(k) if map then for m,n in pairs(map) do --m = uid n = true if n == true then EntityUtils:changeStaticRoleLayer(m, LayerMask.NameToLayer(layerName)) end end end end end end --阻礙物加載完成 function BombermanMgr:checkLayer(hinderIndex,uid) local playerIndex = -1 local role = EntityMgr:getRole(self:getMainPlayerId()) if role then playerIndex = role:getCellIndex() end if hinderIndex == playerIndex then --self:changeLayer(hinderIndex,'Role') EntityUtils:changeStaticRoleLayer(uid, LayerMask.NameToLayer('Role')) end end -------------------------------如下緩存玩家信息--------------------------------- function BombermanMgr:addInfoListener(handler) self.infoHandler = handler--不須要清理 end function BombermanMgr:invokeInfoHandler() if self.infoHandler then self.infoHandler() end end --PPTMemberData (uid,name,serverName,job,transferJob) function BombermanMgr:addMember(id,msg) if self.memberPool[id] == nil then local dt = PPTMemberData(id,msg.nick_name,msg.serverName,msg.tempId,msg.lifeCount) self.memberPool[id] = dt self:invokeInfoHandler() end end function BombermanMgr:updateMemberHp(id,hp) local dt = self.memberPool[id] if dt then dt:setHp(hp) end self:invokeInfoHandler() end function BombermanMgr:getMemberLst() return self.memberPool end --rankPool function BombermanMgr:addRank(id,msg) if self.rankMap[id] == nil then local dt = PPTMemberRankData(id,msg.nick_name,msg.serverName) table.insert(self.rankPool,dt) --lst 排序 self.rankMap[id] = dt --map 快速查找 self:invokeInfoHandler() end end function BombermanMgr:updateRank(id,score) local dt = self.rankMap[id] if dt then dt:setScore(score) end table.sort(self.rankPool,function(m,n) return m:getScore()>n:getScore() end) self:invokeInfoHandler() end function BombermanMgr:getRankLst() return self.rankPool end function BombermanMgr:getScore(id) return self.rankMap[id] and self.rankMap[id]:getScore() or 0 end --如下排行榜相關 PPTRankData:__init(id,rank,nickName,score) function BombermanMgr:addTodayLst(msg) self.todayLst = { } local lst = msg.allReports if lst and #lst>0 then for i = 1,#lst do local rsp = lst[i] local dt = PPTRankData(rsp.livingId,rsp.rank,rsp.nickName,rsp.toalScore) table.insert(self.todayLst,dt) end end table.sort(self.todayLst,function(m,n) return m:getRank()<n:getRank() end) local rsp = msg.meInfos self.todayMine = PPTRankData(rsp.livingId,rsp.rank,rsp.nickName,rsp.toalScore) end function BombermanMgr:addYesTodayLst(msg) self.yestodayLst = { } local lst = msg.allReports if lst and #lst>0 then for i = 1,#lst do local rsp = lst[i] local dt = PPTRankData(rsp.livingId,rsp.rank,rsp.nickName,rsp.toalScore) table.insert(self.yestodayLst,dt) end end table.sort(self.yestodayLst,function(m,n) return m:getRank()<n:getRank() end) local rsp = msg.meInfos self.yestodayMine = PPTRankData(rsp.livingId,rsp.rank,rsp.nickName,rsp.toalScore) end function BombermanMgr:getTodayLst() return self.todayLst,self.todayMine end function BombermanMgr:getYesTodayLst() return self.yestodayLst,self.yestodayMine end function BombermanMgr:canReqToday() return TimerMgr:getServerTime() - self.todayReqTime > 60*1000--60S才能拉取 end function BombermanMgr:canReqYesToday() return TimerMgr:getServerTime() - self.yestodayReqTime > 60*1000--60S才能拉取 end function BombermanMgr:resetReqToday() self.todayReqTime = TimerMgr:getServerTime() end function BombermanMgr:resetReqYesToday() self.yestodayReqTime = TimerMgr:getServerTime() end function BombermanMgr:getRankRewards() if self.rankRewardLst == nil then self.rankRewardLst = {} local list = RewardConfigMgr:getRewardConfigList(RewardType.PPTAllRank) if list then for i = 1, #list do local data = DemonAwardData(list[i]) self.rankRewardLst[#self.rankRewardLst + 1] = data end else Logger:logError("can not find RewardConfig type =", RewardType.PPTAllRank) end end return self.rankRewardLst end --出生點模型 BombermanConst.myRespawnModeltId function BombermanMgr:checkMyRespawnModel(idx) if not self.myRespawnRoleId then local pos = self:getBoxCenterPos(idx) --data self.myRespawnRoleId = MathUtils:getUniqueID() local roleData = LRoleData(self.myRespawnRoleId, RoleType.BoxHinder, true) roleData:changeAttr(RoleAttr.SkinId,BombermanConst.myRespawnModeltId) roleData:changeAttr(RoleAttr.isActive,true) roleData:changeAttr(RoleAttr.InitPosition, pos) roleData:setFull() roleData:attach() --建立 local staticEntity = LBoxHinderEntity(self.myRespawnRoleId, RoleType.BoxHinder, roleData , nil) if staticEntity then StaticEntityMgr:addRoleData(roleData) StaticEntityMgr:addRole(staticEntity) staticEntity:create() end end end --是否能夠釋放 function BombermanMgr:canCastBomb(isAlert) local role = EntityMgr:getRole(self:getMainPlayerId()) if role then local roleIndex = role:getCellIndex() local boxData = self:getBoxData(roleIndex) if boxData and boxData:hasType(BoxType.bombBox) then if isAlert then --1000ms才能彈出1次提示 if TimerMgr:getServerTime() - self.lastAlertTime > 800 then self.lastAlertTime = TimerMgr:getServerTime() UIUtils:floatAlertByKey(BombermanLang.thisBoxHasBombStr) end end return false end end return true end function BombermanMgr:isFollowPlayer(id) return id == self.followId end --格子是否能夠實例化爆炸特效 function BombermanMgr:canPlayCellEffect(index,time) if not self.bombEffectTimeMap[index] or time - self.bombEffectTimeMap[index] > 500 then self.bombEffectTimeMap[index] = time return true end return false end --接口 index1是否在index2的 14*14範圍內 用於播放音效 function BombermanMgr:indexIn14Range(mine,target) local mineRow = self:getIndexRow(mine) local mineCol = self:getIndexCol(mine) local tarRow = self:getIndexRow(target) local tarCol = self:getIndexCol(target) return math.abs(tarRow-mineRow) <= 7 and math.abs(tarCol-mineCol) <= 7 end --封裝一層 音效播放 function BombermanMgr:playAudio(audioId, callBack) if audioId and audioId > 0 and self:canPlayAudioTime(audioId) then AudioMgr:playAudio(audioId, callBack) end end function BombermanMgr:canPlayAudioTime(audioId) local time = TimerMgr:getServerTime() local mill = AudioIdMinMill[audioId] and AudioIdMinMill[audioId] or 0 if not self.audioTimeMap[audioId] or time - self.audioTimeMap[audioId] > mill then self.audioTimeMap[audioId] = time return true end return false end --合併 function BombermanMgr:addStaticId(id) --table.insert(self.combineLst,id) self.combineLst:Add(id) if self.combineLst.Count>=1 then --combine EntityUtils:combineStatic(self.combineLst) self.combineLst:Clear() end end ---[[ function BombermanMgr:addToStatic(id) if self.combineRoot == nil or not Helper:goIsHas(self.combineRoot) then self.combineRoot = GameObject("combineRoot") end EntityUtils:setStaticParent(id,self.combineRoot) self.combineNum = self.combineNum + 1 if self.combineNum >= 15 then if self.combineRoot ~= nil and Helper:goIsHas(self.combineRoot) then --Logger:logError("combineRoot") StaticBatchingUtility.Combine(self.combineRoot) self.combineNum = 0 end end end --]] -- 清理數據 function BombermanMgr:clearSelf() self.boxPool = nil self.mainRoleId = 0 self.dropPool = { } --緩存掉落物 BUFF self.memberPool = { } --玩家信息 PPTMemberData self.rankPool = { }--玩家信息 PPTMemberRankData self.rankMap = { }--玩家排名 PPTMemberRankData self.todayReqTime = -1 --上次拉取時間 self.yestodayReqTime = -1 --上次拉取時間 self.todayLst = { } --今天數據 self.yestodayLst = { }--昨天數據 self.todayMine = nil --本身今天 self.yestodayMine = nil --本身昨天 self.myRespawnRoleId = nil --出生點模型 self.followId = -1 --跟隨id self.lastAlertTime = -1--上次彈出提示時間 self.bombEffectTimeMap = { }--格子爆炸特效上次釋放時間 self.audioTimeMap = { }--音效上次播放時間 --self.combineLst = ListLong() self.combineNum = 0 end ---------如下段落實現一些須要的定義方法-------end------------------------- -- 自動初始化 create(BombermanMgr)
<3>代碼備註排序
--[[ 實體建立: 5,6號協議主角仍是會建立(進入不會推) 在進入這個場景的時候,建立完畢炸彈人實體(繼承LRole),相機進行跟隨 同時關閉全部原先RootUI,搖桿從新建立一個新的(複用預設,class重寫),技能UI也作新的(所有重寫) 頭像 任務UI都重作 位置同步: 客戶端A搖桿拖動開始同步 中止移動也同步一次 客戶端A每次預測當前方向0.1秒位置,預測位置發送給服務器,服務器進行廣播 客戶端A每次移動都檢測是否碰撞到阻擋格子 客戶端B接到同步協議,進行插值移動 怪物位置同步: 服務器每次推送怪物目標格子 客戶端把每一個格子位置加入到怪物的目標位置列表 客戶端進行平滑插值 速度會根據列表中的個數進行加速度 碰撞器: 碰撞器使用Unity自帶碰撞 若是玩家卡到格子裏面 那麼格子裏面的物體碰撞層都會設置成不與玩家碰撞 觸發器: 不適用Unity觸發器 在Lua這邊維護觸發 主角每次改變格子 都會檢測觸發(檢測當前格子和九宮格,盒子生成也須要與玩家所在格子檢測) BUFF: buff存在於實體身上(對應關係備註在BombermanConst) buff服務器只告訴結束時間戳 客戶端本身維護BUFF結束 沒法使用以前的buff組件 實體獲取buff起定時器 每100ms檢查buff是否結束 使用setCountDown定時器會有後臺運行的問題(後臺運行定時器暫停) 主角buff定時器每次都拋出消息給UI 技能: 技能分爲3中類型 1炸彈(能夠充能) 2符文(使用一層少一層) 3血量(死亡一次少一次) 技能數據都存在於實體身上(對應關係備註在BombermanConst) 1炸彈充能 有最大充能次數(會動態更改) 2符文 3血量 其餘: 1.進入PPT場景主動拉取全部信息(否則會出現還沒加載完場景 協議來了建立實體 實體又立刻被銷燬) 2.進入PPT場景 服務器仍是會推其餘玩家的6號協議(服務器通用處理) 客戶端PPT玩家實體的id = id*10 取實體的 更新實體都須要*10 3.玩家實體建立協議與怪物實體建立協議不一樣(怪物實體用的是Box協議 客戶端轉實體) --]]