国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

2D地形對象區域優化-矩形合并算法

2019-11-06 09:23:29
字體:
來源:轉載
供稿:網友
問題從何而來:《無限戰爭》是一款cocos2d+lua開發的2D橫版RTS游戲,核心玩法是玩家在同一張地圖上排兵布陣進行博弈。在制作過程中,策劃希望游戲中能建立一種地形系統,在地圖加載時生成各種各樣的地形單位,這些地形單位可能沒有貼圖,但是一定擁有自己的作用和作用范圍。比如一片沼澤地,作戰單位進入后會有中毒效果(進入退出觸發buff系統)。比如一片雪地,作戰單位進入后會降低移速(進入退出觸發buff系統) 。比如一個大坑,不可行走區域(碰撞檢測) 。根據這個需求,程序首先想到的是將每個地形單位的數據表示成一個矩形列表,在地圖上生成對應的一個個矩形,并實現碰撞檢測。為了能讓策劃方便的指定不同尺寸的地形單位,我們在地形編輯器里繪制網格線框,網格的單元格(75*75像素)也就是地形單位的作用區域的最小單元。策劃創建并選定一個地形單位,就可以通過點亮網格單元格的方式來指定這個地形的作用區域。由此我們對每張地圖都能生成對應的一套地形單位列表和每個地形單位對應的作用區域的單元格列表。然后在引擎中,通過創建一個個單元格對象,來實現碰撞檢測和觸發對應邏輯。直到有一天,策劃覺得75*75像素太大了,特別是對不可行走區域來說碰撞的邊緣不圓滑。這樣我們不得不考慮將75*75縮成32*32的,這樣一來,網格線框顯得更加密集了,原來用幾個單元格就能表示的作用區域,現在需要更多。這無疑增加了引擎創建單位的數量。由于每個單元格的碰撞檢測都是獨立的,所以碰撞檢測的開銷也會隨之增長。我們不得不在地圖信息的邏輯里,引入一種算法,將可以合并在一起的小單元格合并成一個大的矩形,這樣一個地形單位對應的作用區域矩形列表的數目也會減少,從而減少上訴的開銷。圖例1. 一個雪地單位以及其對應的作用區域(黃色部分)我們想要實現什么:拿到一個單元格列表,在最短的時間內,將相鄰的單元格列表拼接,得到一個矩形數目最少(最優解)的矩形列表。給地形建立網格后,可以對單元格進行編號,并得到單元格有多少行多少列,根據單位32像素,很容易通過編號計算出它的位置。所以ipO是單元格列表{id, id, id, id, ...}->算法->矩形列表{     {x, y, wdith, height},     {x, y, wdith, height},     {x, y, wdith, height},     ...,}圖例2. 根據圖例1繪制的測試數據,每個單元格上對應的數據是它的編號和(行,列)圖例3. 算法執行后得到的最優解的圖形顯示如果我們把每個矩形當成一個Block,每個Block的表示方法為(左下角單元格行列)-(右上角單元格行列),那么輸出結果如下[LUA-PRint] ===Block:(12,8)-(12,19)[LUA-print] ===Block:(13,17)-(13,17)[LUA-print] ===Block:(13,12)-(13,15)[LUA-print] ===Block:(8,12)-(8,20)[LUA-print] ===Block:(9,9)-(11,19)[LUA-print] ===Block:(7,13)-(7,19)[LUA-print] ===Block:(10,20)-(10,20)[LUA-print] ===Block:(6,8)-(7,9)[LUA-print] ===Block:(10,7)-(10,7)[LUA-print] ====Calculator:recordResult 9[LUA-print] 72  0.025000000000034邏輯是lua實現的,其中單元格數量75,耗時25毫秒,最優結果為9個矩形。臨時的解決方案:筆者比較才疏學淺,到現在也不太清楚這種算法的專業說法應該叫什么,只能根據字面意思稱為是“矩形合并算法 ”。也可能是這個關鍵詞不太專業,所以網上一番搜索并沒有什么有用的信息。無可奈何之下,筆者根據所學知識,自己研究了一套計算思路(算是一種暴力搜索吧)。因為邏輯是在地形編輯器保存時進行計算,所以對算法的時間復雜度沒有過多的優化。在此拋磚引玉一下,若有算法大神走過路過,希望能指點迷津。求一個解的情況大致如下:a. 首先我們拿到一個矩形,要看看它能不能變成一個更大的矩形,也就是說要從上下左右四個方向上去判斷是否存在單元格能去擴大它。b. 如果這個矩形越擴越大,余下的單元格就越少,得到的解越接近最優解。c. 如果這個矩形四個方向上都不能擴大了,那就把這個矩形放入矩形列表,從余下的單元格隨便抽出一個單元格來,將這個單元格當成矩形,進行步驟a的操作d. 如果沒有單元格剩余了,那么我們就得到了一個解(不一定是最有解),而且得到了這個解的矩形數目。初看,這個算法肯定是逃不過一種樹形結構的遍歷的。上下左右加上不合并,一共五種情況的子節點遍歷方向。為什么會有不合并這種情況?假設我們選中的當前需擴大的單元格是圖例中377號單元格,你就懂了。圖例4. 遍歷樹形圖所以,我們得到這個樹形圖的節點結構如下:矩形數目count_of_rectangle矩形列表(坐標、尺寸的列表,注意這里的矩形是單元格合并之后的) rectangle_list = {     {x, y, wdith, height},     {x, y, wdith, height},     {x, y, wdith, height},     ...,}矩形列表包含的(已使用)單元格列表used_grids = {id, id, id, id, ...}當前待擴展矩形rectangle = {x, y, width, height}關于遍歷與剪枝:上面我們知道了需要遍歷,那是深度優先遍歷,還是廣度優先遍歷呢?因為分支有5個之多,答案無意是前者。因為分支這么多,我們需要設計一些剪枝策略,來避免不必要的遍歷。首先,想到的就是盡快求得一個解,以這個解的矩形數目為限制進行剪枝。在算法運行過程中,得到一個解時,記錄下來,minResult_當求得其他的解result_,一直沒有觸發這個剪枝,則更新記錄下的解,這樣遍歷停止時,我們就能得到最優解minResult_。廣度優先遍歷,一方面要維護一個子節點隊列,隨著遍歷的(樹)層級原來越深,子節點隊列中的元素會越來越多,拿到第一個解的速度絕對是比深度優先遍歷要慢的。其次,避免當前帶擴展矩形的重復計算。我們每個節點是對應一個當前帶擴展矩形的(如果沒有,則這個節點表示是一個解),向下遍歷即是對該矩形四個方向的擴展或者不合并。這5個情況已經包含了對該矩形處理的所有情況了,如果再次遍歷得到這個矩形,則不需要往下遍歷了。所以我們在算法進行的過程中就要記錄下來已經計算過哪些矩形了,矩形Block的數據為{x, y, width, height},為了方便記錄,我們同時需要記錄下來對應的(左下角單元格id,右上角單元格id)假設head = 左下角單元格idtail = 右上角單元格idlua中可以通過這種格式進行記錄和判斷marked_ = {     [head][tail] = true,     [head][tail] = true,     [head][tail] = true,     [head][tail] = true,     ...,}其他問題:減少數據的拷貝(如何回溯和數據結構設計)有深度優先遍歷、有剪枝,就會想我們如何去存數據。特別是節點信息中的那個“矩形列表包含的(已使用)單元格列表 ”,筆者不希望,每往下一層遍歷,就要拷貝一次父節點的已使用單元格,然后再往里面塞我們擴展矩形后新增的已使用的單元格。同理,還有矩形列表的拷貝。拷貝,無疑會拖慢算法的速度。對于一個解result_的核心數據,筆者是用Block棧來表示的,棧的top表示這個解的矩形數目,棧里的Block對象表示矩形列表(具體數據),棧頂的Block即當前帶擴展矩形因為算法已經設計成一種遞歸形式了,所以通過壓棧、出棧,很方便的實現向下遍歷和回溯。    self.result_:push(block)    self:perm(depth, grids, nil)    self.result_:pop()然后是矩形列表包含的(已使用)單元格列表,我們反過來用剩余單元格來表示。感謝排列組合算法的啟發,我設計了一個數據結構ArrayX(有序數組 )用來表示單元格列表grids。ArrayX本身會維護成員變量length_來表示它的元素個數,特殊在它的remove/removeAt函數、recover函數。remove/removeAt :將指定元素與數組末尾的元素交換,并且length_自減1。(實際上數據還是在的)recover:將數組恢復成指定長度。(這樣數據又回來了)最后對ArrayX遍歷和取數據的時候,判斷索引不要超過length_即可    local length = grids:length()    ...                if iter and not self:isMarked(head, tail) then -- 剪枝,矩形是否被遍歷過                    canMerge = true                    for i, id in iter do                        if grids:exist(id) then                            grids:remove(id)     -- 剩余單元格列表中中移除對應方向擴展使用掉的單元格                        else                            canMerge = false                            break                        end                    end                end                if canMerge then                    self:mark(head, tail)                    local newBlock = Block:create(head, tail)                    self:perm(depth, grids, newBlock)                end                grids:recover(length)5個擴展情況的優先級,不合并時如何選取下一個單元格?首先我們當然是希望能擴展的,如果不合并放在最前面,得到的第一個解就是單元格的數量,用這個解來剪枝并沒有什么意義。因為cocos是笛卡爾坐標系(x向右,y向上),而且我們單元格編號和行、列號也遵循這個規則,所以我們的遍歷的方向是 右 、上、 左、下。SEARCH_DIRECT = {    { "lineRight",    true,   },    { "lineUp",       true,   },    { "lineLeft",     false,  },    { "lineDown",     false,  },}...            for _, node in ipairs(SEARCH_DIRECT) do注意這里其實有順序的然后關于這個順序,其實可以引入一種貪心算法的思想,先計算四個方向上擴展需要消耗的單元格數量(也就是矩形的長、寬),進行排序,然后選擇數量多的那個方向擴展。即,每次擴展消耗的單元格越多,剩余的單元格就越少,越快得到一個解。然后不合并時如何選取下一個單元格,筆者這里其實是偷懶的做法,直接取剩余單元格列表的第一個            local id = grids:item(1)自己的疑問:直到截稿之日,筆者對“矩形合并算法”這種說法還是心存疑惑。這方面的算法應該早就出現,不至于Google上一無所獲。如果有人知道這個問題更專業的叫法,還望醍醐灌頂。這里只是提供一種暴力遍歷來解決問題(其實是很挫的),或許有一種更好的算法等待著筆者去挖掘。因為想這個問題越想越覺得其實它有最優子結構N個單元格 的解 = (N-M)個單元格的解  + M個單元格的解(0<M<N)特別是在處理不合并的情況的時候,那我繼續向下遍歷,不就是將剩余單元格列表放入算法迭代,然后與當前遍歷得到的Block列表合并么?奈何筆者思考了很久,還不太清楚怎么把動態規劃應用到這個問題上來。也許哪天想通了,再寫一份分享給大家吧。源碼參考:https://github.com/Ron2014/lua_rect_union本例是在cocos2d-x-3.13.1的kill pests的示例代碼的框架上修改的。具體邏輯參考以下文件:算法rect_union/models/Block.luarect_union/models/Calculator.lua界面rect_union/views/GridSprite.luarect_union/views/GridView.luarect_union/views/MainScene.lua數據結構util/type/array_x.luautil/type/stack.lua然后引擎中自己加了執行gm指令和reload腳本的小trick。因為和本文探討的問題沒有太大關聯,就不在這里贅述了。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 龙岩市| 鸡泽县| 陈巴尔虎旗| 高安市| 阿克| 澎湖县| 郧西县| 柳林县| 黄平县| 德钦县| 五常市| 方城县| 江阴市| 建德市| 南安市| 海盐县| 北安市| 龙川县| 丹东市| 怀安县| 富源县| 沐川县| 娱乐| 蚌埠市| 新竹县| 禹州市| 德阳市| 彰武县| 玉环县| 南昌市| 通州市| 金湖县| 碌曲县| 景洪市| 新龙县| 阜新市| 神农架林区| 屯留县| 金华市| 右玉县| 河北省|