本文轉自http://blog.csdn.net/dinko321/article/details/46739563
?
項目里面需要加載一個很大的地圖,目測最少是4096x4096的分辨率。
?
? ? ? ? 先不考慮什么引擎最大支持多大的圖啊,大圖加載效率啊等等這些問題,光是4k x 4k的分辨率,ARGB8888,加載進去,就是64M的內存,這還只是一個背景。再來點其他七七八八的東西,輕松超過120,這個內存在某些設備上就已經很危險了。
?
? ? ? ? 為了實現這個目標,處理的方式大概有2種:
? ? ? ? 一、資源重用。也就是類似于tiled這種方法,把地圖上面的一些圖塊,反復使用,通過有限的紋理,來拼接出地圖。但是這種方法的確定是地圖會比較死板
? ? ? ? 二、分塊加載。就是把一個大圖切成若干小塊,每次只加載需要顯示的圖塊。看上去很美,現在主要說這個。
?
? ? ? ??先說切圖,肯定是切小方塊,按照二的冪的原則,一般有這么幾個備選:32、64、128、256、512。
? ? ? ? 用切的圖塊來鋪滿一個屏幕,不一定能恰好填滿,可能會多出一部分(少一部分就會留黑,肯定不行),從這個上面上來說,肯定是切的越小越好,因為這樣就算有浪費,最多也就浪費一個圖塊的寬度。但是圖切的太碎,會對渲染效率產生影響。從調試信息可以看到有個GLVerts,verts越多,顯示效率就越低。
? ? ? ? 但是如果切太大,內存又會有影響,比如我切個512的方塊,假設屏幕是960X640,那么極限情況下,最多會同時顯示4塊(請自行想象在田字格的中間放一個方框,方框就是屏幕),這樣就達不到節約內存的目的。
? ? ? ? 一般128或者256應該就差不多了。
? ? ? ? 然后把大圖切小,分別命名。這圖怎么切,當然是叫美工用PS切啊,命名,手動啊。。。當然這是開玩笑,你要真這么弄,美工不把你砍死。。。作為程序員,就是要會偷懶嘛,寫個程序就切了,python的。雖然我也不是很會python,只會基本語法。但是python庫多啊
?
[python]?view plaincopy
import?Image????import?sys????import?os.path????from??datetime??import??*??????import?random????import?time??????????IMAGE_PATH?=?"map.png"????xIndex?=?0??yIndex?=?0??cropSize?=?256??xNum?=?0??yNum?=?0??????im?=?Image.open(IMAGE_PATH)?pSize?=?im.size????xNum?=?pSize[0]/cropSize??yNum?=?pSize[1]/cropSize????print?"size??"?,xNum,'??',yNum????for?yIndex?in?range(yNum):??????for?xIndex?in?range(xNum):??????????print?"pic?:?"?,?xIndex?,?"_"?,?yIndex??????????box?=?(xIndex*cropSize,yIndex*cropSize,(xIndex+1)*cropSize,(yIndex+1)*cropSize)?????????region?=?im.crop(box)??????????name?=?"/Users/apple/Desktop/result/map%s_%s.png"?%?(xIndex,yNum-1-yIndex)??????????region.save(name)?????????????????????????print?int(time.time());???????? ? ? ? ? 這樣圖就切好了,名字也起好了,而且還不用和美工撕逼或者裝孫子。。。
?
?
? ? ? ??再說鋪磚,也就是把圖塊放在地圖上的方法。
? ? ? ? 這里會有2個需要區別的東西,一個是坐標,就是offset的那個坐標。一個是index,就是圖塊的index。因為按照上面的圖塊命名,坐標是map0_0 ,map0_1這樣的規則。
? ? ? ? 大概思路應該是這樣的,首先,獲取winSize,計算橫豎2個方向需要多少圖塊才能鋪滿,向上取整。
?
[cpp]?view plaincopy
xTileNum?=?winSize.width/tileSize;??yTileNum?=?winSize.height/tileSize;??xTileNum+=1;??yTileNum+=1;?? ? ? ? ? 然后,根據scrollView的offset,取得左下角那個圖塊的index,知道這一點應該用那一塊來鋪。
?
?
[cpp]?view plaincopy
Vec2?offset?=?view->getContentOffset();????offset.x?=?fabsf(offset.x);??offset.y?=?fabsf(offset.y);????int?xStartIdx?=?offset.x/tileSize;??int?yStartIdx?=?offset.y/tileSize;?? ? ? ? ? 然后,在橫方向,和豎方向上,鋪滿
?
?
[cpp]?view plaincopy
for?(int?i?=?0?;?i<xTileNum?;?i++)??{??????for?(int?j=0;?j<yTileNum;?j++)??????{??????????int?xIdx?=?xStartIdx+i;??????????int?yIdx?=?yStartIdx+j;????????????????????char?name[128];??????????sprintf(name,?"result/map%d_%d.png",xIdx,yIdx);??????????Sprite*?tile?=?Sprite::create(name);??????????tile->ignoreAnchorPointForPosition(true);??????????int?posX?=?tileSize*xIdx;??????????int?posY?=?tileSize*yIdx;??????????tile->setPosition(posX,posY);??????????contentLayer->addChild(tile);??????}??}?? ? ? ? ? 這樣,就鋪滿一屏幕了。
?
? ? ? ? 然后來說說滾動的時候的處理方法。
上圖很清楚,要補充的是綠色區域,要去掉的是左上的那2條,中間的是不用動的。這個地方能想出一萬種算法來做。我用的是一種簡單直接的,但是絕對不是效率最高的。因為最后你會發現這點效率完全沒有什么卵用。。。。 首先需要這么幾個東西
[cpp]?view plaincopy
#include?<algorithm>??#include?<unordered_map>??#include?<set>????using?namespace?std;????????unordered_map<string,?Sprite*>?curTiles;??set<string>?curKeys;??set<string>?newKeys;?? ? ? ? ? 其實想法和簡單,就是把每次加入的圖塊sprite,放入一個map,然后把移動過后,需要加入的圖塊,是全部的,不是新增的那一部分,放入一個map,然后對2個map取差集,就可以得出需要添加的部分,和需要刪除的部分。 但是為毛我只寫了一個map確用了2個set。。。因為我沒試過map能不能用stl的set_difference這個函數。。。大概就是這樣
[cpp]?view plaincopy
newKeys.clear();??for?(int?i?=?0?;?i<xTileNum?;?i++)??{??????for?(int?j=0;?j<yTileNum;?j++)??????{??????????int?xIdx?=?xStartIdx+i;??????????int?yIdx?=?yStartIdx+j;????????????????????if?(xIdx>15?||?yIdx>15)??????????{??????????????continue;??????????}????????????????????char?name[128];??????????sprintf(name,?"result/map%d_%d.png",xIdx,yIdx);????????????????????string?key?=?string(name);??????????newKeys.insert(key);??????}??}????set<string>?results;??set_difference(curKeys.begin(),?curKeys.end(),?newKeys.begin(),?newKeys.end(),?inserter(results,?results.begin()));??for?(auto?it=results.begin();?it!=results.end();?it++)??{??????curTiles[*it]->removeFromParent();??????curTiles.erase(*it);??}????results.clear();??set_difference(newKeys.begin(),?newKeys.end(),?curKeys.begin(),?curKeys.end(),?inserter(results,?results.begin()));??for?(auto?it=results.begin();?it!=results.end();?it++)??{??????Sprite*?tile?=?Sprite::create((*it).c_str());??????tile->ignoreAnchorPointForPosition(true);????????????int?xIdx?=?0;??????int?yIdx?=?0;????????????sscanf((*it).c_str(),?"result/map%d_%d.png",&xIdx,&yIdx);????????????int?posX?=?tileSize*xIdx;??????int?posY?=?tileSize*yIdx;????????tile->setPosition(posX,posY);??????contentLayer->addChild(tile);????????curTiles[*it]?=?tile;??}????curKeys?=?newKeys;??newKeys.clear();?? ? ? ? ? 然后還有最重要的一步
[cpp]?view plaincopy
Director::getInstance()->getTextureCache()->removeUnusedTextures();??
? ? ? ??再說效果。代碼這就寫完了,然后上甄姬測試吧。 然后跑起來之后,你就會發現,媽蛋,怎么還是這么卡的不要不要的。還不如全部加載了流暢。 看幾個性能參數,內存,效果很明顯,GLVerts,效果也很明顯。那為毛還這么卡,一定是我的替換算法寫的太渣了。然后在算法的起始和結尾輸出時間。發現基本可以說是秒過,完全沒有性能瓶頸。 控制變量法,其他不變,把圖塊sprite的紋理全部換成一個圖,再一跑,發現流暢無比,各項參數都是很牛B的。
綜上可知,應該是可推測。如果是繪制的問題,那么每次都需要繪制新的圖庫,都應該一樣的卡才對。 在2次測試中,唯一的區別,就是把每次都去讀取新的圖片紋理,換成了用緩存中已經存在的紋理。那么推測應該就是在用新紋理創建新的sprite這個地方卡住了。
[cpp]?view plaincopy
bool?Sprite::initWithFile(const?std::string&?filename)??{??????...?...??????Texture2D?*texture?=?Director::getInstance()->getTextureCache()->addImage(filename);??????...?...??}?? [cpp]?view plaincopy
Texture2D?*?TextureCache::addImage(const?std::string?&path)??{??????...?...??????image?=?new?(std::nothrow)?Image();??????...?...?????????}?? ? ? ? ? 從這里看,在創建新紋理的時候,用到了fileUtil,也就是說讀了文件,也就是說這個地方有IO,然而IO是很容易卡的。 下面是輸出時間:
[cpp]?view plaincopy
-------1--------??-------?time?stamp?:?1386769052??-------2--------??-------?time?stamp?:?1386769053??-------a--------??-------?time?stamp?:?1386769053??-------b--------??-------?time?stamp?:?1386769084??-------a--------??-------?time?stamp?:?1386769084??-------b--------??-------?time?stamp?:?1386769115??-------a--------??-------?time?stamp?:?1386769115??-------b--------??-------?time?stamp?:?1386769147??-------a--------??-------?time?stamp?:?1386769147??-------b--------??-------?time?stamp?:?1386769182??cocos2d:?TextureCache:?removing?unused?texture:?/private/var/mobile/Containers/Bundle/Application/C85C52CF-396B-4630-A4AE-11A412D8C060/Hello?iOS.app/result/map10_3.png??cocos2d:?TextureCache:?removing?unused?texture:?/private/var/mobile/Containers/Bundle/Application/C85C52CF-396B-4630-A4AE-11A412D8C060/Hello?iOS.app/result/map10_2.png??cocos2d:?TextureCache:?removing?unused?texture:?/private/var/mobile/Containers/Bundle/Application/C85C52CF-396B-4630-A4AE-11A412D8C060/Hello?iOS.app/result/map10_0.png??cocos2d:?TextureCache:?removing?unused?texture:?/private/var/mobile/Containers/Bundle/Application/C85C52CF-396B-4630-A4AE-11A412D8C060/Hello?iOS.app/result/map10_1.png??-------3--------??-------?time?stamp?:?1386769184?? ? ? ? ? 1是開始計算新圖塊的地方,2是開始添加新圖庫的地方。3是整個完成。a是創建圖庫sprite前,b是創建圖庫sprite之后。 整個過程耗時132ms,也就是說此時FPS只有10不到。其中最慢的幾個地方,都是在ab之間,差不多30ms左右。再次說明和之前那個效率不算太高的替換算法關系不大。。。
本文標題之所以跟了一個“初”,是我覺得這種方式如果優化一下是可行的,雖然我不知道怎么弄,有知道的麻煩通知我:dinko@126.com
?
? ? ? ? 所以我覺得這種方式暫時是不靠譜的,還是乖乖用tiled去拼,或者分層來做吧。
?
?
PS:
? ? ? ? 其實還有一種做法,類似google map和百度地圖那樣,在滾動的時候不加載,滾動停了之后再加載。但是人家那是APP啊,游戲這樣做你看效果如何。分分鐘刪游戲
轉載于:https://www.cnblogs.com/liuqing0328/p/4900903.html
總結
以上是生活随笔為你收集整理的cocos2dx 大地图分块加载的研究(初)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。