flash游戏客户端设计2:地图实现

 

地图分为四部分:wall也就是墙;frontLayer就是地图上人可以站其后面的物体,比如雕塑;miniMap小地图;tileLayer地图。

这些数据全配置在地图模板表中,每个场景为一行record。

一、wall

wall就是地图中的不可行走区域。

在美术画好不可行走区域后,由地图工具读入。因为美术的图每个像素还有颜色信息存储的位数会更多,按照每个像素是否可行走写入wall文件,这样每个像素只要一位。二维的数据实际是存为一维的。

同时因为游戏中对不可行走区域的精度不需要达到像素级。所以我们可以将粒度扩大,5452*4000的我们将其粒度设为10*10。这样原来需要100位的只要1位来存储。

public class Wall{public var mapId : int;public var mapWidth : int;public var mapHeight : int;private var _stepX : int;private var _stepY : int;private var _itemList:Array;private var _byte : ByteArray;private var _byteCopy:ByteArray;private var _point:Point = new Point;public function Wall(){}public function get itemList():Array{return _itemList;}public function setData(byte:ByteArray) : void {//解压byte.uncompress();mapId = byte.readInt();mapWidth = byte.readInt();mapHeight = byte.readInt();_stepX = byte.readInt();_stepY = byte.readInt();_itemList = byte.readObject();_byte = new ByteArray();byte.readBytes(_byte,0,byte.bytesAvailable);// 不要忘记解压缩!_byte.uncompress();_byteCopy = new ByteArray;_byteCopy.writeBytes(_byte, 0, _byte.bytesAvailable);_barrierDic = new Dictionary;}public function clear() : void {mapId = -1;_byte = null;}public function canWalk( pixX:uint , pixY:uint ):Boolean {return !isWall(pixX,pixY);} public function isWall( pixX:uint , pixY:uint ):Boolean {if( pixX < 0 || pixX > mapWidth || pixY < 0 || pixY > mapHeight ){return true;}if( !_byte ) {return true;}var checkX : int = pixX/_stepX;var checkY : int = pixY/_stepY;var index_total:int = checkY * int(mapWidth/_stepX) + checkX;var index_array:int = index_total / 8;var index_bit:int = index_total % 8;var mask:uint = 1 << index_bit;var ori:uint = _byte[index_array] ;var result:uint = ori & mask;return Boolean(result);} }

二、frontLayer

frontLayer是地图上的遮盖物,比如雕塑等,人站在其后会看不见的。

一般来说有三种实现方法:

1.遮盖层在人物层之上,人物在遮盖层之后时,被遮盖完全不透明。

2.遮盖层在人物层之上,人物在遮盖物之后时,对应的遮盖物变为半透明。

3.人物层在遮盖层之上,人物在遮盖物之后时,人物变为半透明。

 

进入场景后生成所有代表遮罩物的对象,但是并不马上下载图片(类似没有bitmapdata的bitmap)。需要一个线程一直检查mask是否在屏幕中,检测到出现在屏幕中时再去下载并addchild,不在屏幕中时removechild。

三、miniMap

miniMap是当前地图的缩小版,在进入场景时马上下载,置于地图之下;也可以用于右上角的小地图。

需要把miniMap放大,将其高宽设为地图的实际高宽。因为地图要分块加载,miniMap显示在下面,避免出现地图未下载时黑块。

四、tileLayer

tileLayer是显示地图的。

 

1.需求

由于游戏地图太大,比如我们的大小为5452*4000。有3种解决方法。

1.flash最大支持 2880 x 2880大小的bitmap,可以通过BigAssCanvas 解决。

2.美术预先切好成小图,自己拼成大图,一次性加载全图。

3.从效率上考虑,只需要下载玩家可视范围内的地图。

所以我们将地图切块为500*300的大小。

2.资源组织

在资源文件夹下组织如下:

 

res

map

MAP_01_001 ...  各个文件夹代表各张地图:文件夹名如

0.1.2... 包含地图切分后的水平一行内的所有子图,文件夹名为行号。

0.1.2...   代表一个子图,文件名为列号。

item 文件夹,内含地图上的各个遮盖物,置于FrontLayer的mask层。

map.jpg 完整大的地图

mini.jpg 置于miniMap层的小地图

 

某地图文件下  0文件夹中0.jpg,就是左上角那块地图, 0文件夹中1.jpg就是0_0右边那一块。

 

3.实现

随着人物走动,地图也以人物为中心加载地图块。可以指定一个值PRE_LOAD_AREA设定加载的范围,比如人物所在块dx,dy,如果PRE_LOAD_AREA=1(本文假设都是这个值),则加载dx-1,dy-1 ;dx+1,dy+1  这个矩形框内的9块地图块。这个值由屏幕大小和地图块大小两者决定,只要加载区域能覆盖整个游戏显示区域;或者再考虑到网速,确保玩家走到一个新的区域时地图都已经加载完,在网速较慢时需要增大PRE_LOAD_AREA。

 

_tileTable缓存了当前地图的所有已加载或准备加载的地图块,key为坐标形如x_y的字符串,value为Bitmap。Bitmap不一定有data,是准备加载,或加载中的;有data的就是已经加载完毕的。

 

 

整个地图的移动由主容器stageContainer来移动,这里不详述移动,只考虑加载。

Class TileLayer:

 

 

function setCenterPix:

当用户走到地图上px py(非界面stage的x y)的位置时,调用setCenterPix(px,py)。会把像素坐标转为所在地图块的坐标x y。如果还在同一地图块中(记录了当前中心地图块位置),直接返回。如果移动到新的中心地图块,则继续调用setCenter( x, y ),获取显示矩形内的9个地图块。

 

function setCenter:

setCenter( x, y )需要考虑到边界时,所取的矩形就不再是x-1,y-1 ;x+1,y+1,而是靠边界的同样大小9格的矩形。比如人站在右上角 0,0时,取的矩形就是 0,0-2,2。边界时地图的移动也一样不再跟着移动了,人物还是在走动的,只是不居中了。

_tileFlag记录了上一个中心位置时所加载的9个地图块的坐标,key为x_y形式。赋给临时变量localtileFlag,然后设置_tileFlag为新的9个地图块坐标。同时调用loadTile( x, y ) 一一载入矩形内的9个地图块。在下载完数据的回调函数processData中通过ImageDecoder解码地图块。

之后再通过clearUnused()清除不在当前显示矩形内的地图块,如果设置了。

tip:localtileFlag此处似乎没用。

 

function loadTile:

loadTile( x, y )会载入x,y坐标的地图块。

它先调用getBitmap( x, y )获得地图块所在Bitmap。未生成Bitmap的地图块,将new 一个Bitmap,设置好相对于tileLayer的像素坐标,并缓存到_tileTable,返回无data的Bitmap。

如果获得的Bitmap没有data,则调用MapLoader下载地图块。

tip:此处_tileFlag[ x + "_" + y ] = true; 多此一举,直接在setCenter( x, y )中设置了就行了。



这里几个函数都是一个调用另一个,线性得调用下来。

个人趋向做成各个单独的功能性函数,放在一个总的函数中执行。1.pix坐标到块坐标的函数,2.从块X,Y 获得9个地图块矩形区域的函数。3.前后两次获取需要新加载的地图块,需要去掉的地图块。


/** * 处理Tile base的地图。 * 将加载过的Tile做一个位图缓存。 * 可以不做缓存,CPU占用差别并不明显,推荐不缓存 * 内存占用: * 1 不缓存情况下,预加载区域5,浏览器中稳定在50M以内 * 2 缓存情况,根据地图总大小,10000*10000像素约需要400M空间。 * * @author Ryan * */public class TileLayer extends Sprite{public static var instance:TileLayer;// 测试用,可以关闭加载public static var DO_LOAD:Boolean = true;/** * 位图缓存开关,默认关 * 可由用户设置。关闭会导致走动一顿一顿。测试效率时关闭。 */public static var USE_CACHE : Boolean = true;public static var SHOW_NUM:int = 6;/** * 推荐使用200*120 * 整个场景5*5,减少http请求数。 */public static var TILE_WIDTH:int = 500;//200;public static var TILE_HEIGHT:int = 300;//120;// 这个数字越小,加载速度越快public static var PRE_LOAD_AREA:int = 1;/** * 保存tile的loader表。 */private var _tileTable : Object;/** * 根据url缓存tile的bitmapData */private var _tileCache : Object;private var _tileFlag : Object;private var _mapClient:MapLoader;private var _mapId : int;private var _mapData:Object;/** * tile个数 */private var _width :int;private var _height:int;private var _px:int;private var _py:int;private var _preX : int;private var _preY : int;private var _mapDecoder:ImageDecoder;private var _decoder : ImageDecoder;private var loader:URLLoader;private var miniMap:Bitmap;private var minimapURL:String;private var loadedCounter:int = 0;private var _miniCache:Object = {};public function TileLayer(){_tileTable = {};_tileCache = {};_mapClient = new MapLoader();_mapClient.onData = onData;_decoder = new ImageDecoder();_mapDecoder = new ImageDecoder();_mapId = -1;_preX = -1;_preY = -1;loader = new URLLoader();loader.dataFormat = URLLoaderDataFormat.BINARY;miniMap = new Bitmap();addChild( miniMap );instance = this;addEventListener( MouseEvent.MOUSE_DOWN, mouseDownHandler);}public function setMap( mid : int, pixX:int, pixY :int, pixWidth : int , pixHeight : int ) : void {clearAll();_mapId = mid;_mapData = TemplateReader.getTemplateById("Map",_mapId.toString());_width = pixWidth/TILE_WIDTH;_height = pixHeight/TILE_HEIGHT;_px = pixWidth;_py = pixHeight;setBackground();setCenterPix( pixX, pixY );}private function setBackground():void {if ( _miniCache[_mapData.res] ){miniMap.bitmapData = BitmapData(_miniCache[_mapData.res]);miniMap.width = _px;miniMap.height = _py;return;}loader.addEventListener(Event.COMPLETE,loadCompleteHandler);minimapURL = UserData.resRoot + "map/" + _mapData.res + "/mini.jpg";loader.load(new URLRequest(minimapURL)); // 加载一个略缩图,放置真实地图下面,真实地图加载进来后会盖在上面}private function loadCompleteHandler(event:Event):void {loader.removeEventListener(Event.COMPLETE,loadCompleteHandler);// BaseMap.setScale( _px, _py );miniMap.width = _px;miniMap.height = _py;// miniMap.bitmapData = null;_mapDecoder.decode(loader.data, miniMap, miniCallBack);}private function miniCallBack(bitmapData:BitmapData):void{// BaseMap.callBack(bitmapData);miniMap.width = _px;miniMap.height = _py;_miniCache[_mapData.res] = bitmapData;// MMOGame.net.call("onChangedScene", null);}public function getMiniMapURL():String{return minimapURL;}public function setCenterPix( x : Number, y : Number , offx:Number = 0 , offy:Number = 0 ) : void {x += offx;y += offy;var dx:int = x/TILE_WIDTH;var dy:int = y/TILE_HEIGHT;if( _preX == dx && _preY == dy ){return;}if( !DO_LOAD ){return;}setCenter( dx, dy );}/** * 设置tileLayer的中心位置 * 加载周边区域。 * 根据地图大小考虑是否要释放远离视野范围的视图。 * unload也会带来消耗。 * @param x * @param y * */private function setCenter( x : int , y : int ) : void {// 标记使用到的tilevar localtileFlag:Object = _tileFlag;_tileFlag = {};_preX = x;_preY = y;var x1:int = x - PRE_LOAD_AREA;var y1:int = y - PRE_LOAD_AREA;var x2:int = x + PRE_LOAD_AREA;var y2:int = y + PRE_LOAD_AREA;// 这里处理在地图边缘的情况,将调整加载区域 if( x1 < 0 ) {x1 = 0;if (x2 < _width) x2 += 1;}if( y1 < 0 ) {y1 = 0;if( y2 < _height) y2 += 1;}if( x2 > _width ) {if (x1 > 0) x1 -= 1;x2 = _width;}if( y2 > _height) {if (y1 > 0) y1 -= 1;y2 = _height}for( var i :int = x1; i <= x2; i ++ ){for( var j : int = y1; j <= y2; j ++ ){if (localtileFlag) {if (localtileFlag[i + "_" + j]) {_tileFlag[i + "_" + j] = true;} else {loadTile( i, j );}} else {loadTile( i, j );}}}// 考虑是否放在回调中做 clear 没使用过的TileclearUnused();}private var _timer:TimerClock = TimerClock.getInstance();private var _eventHandler:uint = 0;private function sendFinishEvent():void{dispatchEvent(new TileLayerEvent( TileLayerEvent.ENTER_MAP_FINISH ));}/** * 这个函数中,可以增加异步队列,将优先级降低。 * @param obj * */ private function onData( obj :Object ) : void {processData( obj );// _timer.setTimeout( processData,10,obj ); }/** * 实际处理位图信息 * @param obj * */private function processData( obj :Object ):void{// 判断是否是进入地图后的初始加载if( loadedCounter ++ == SHOW_NUM ){// 因为有异步处理,所以可能需要一个延迟if( _eventHandler > 0 ){_timer.remove( _eventHandler );}// 暂时延迟3秒。_eventHandler = _timer.setTimeout( sendFinishEvent, 1000 );}var bmp : Bitmap = getBitmap( obj.x, obj.y );// 重复加载的,不再解码。if( bmp.bitmapData ){return;}var s:Number = getTimer();// 这个操作是异步的。_decoder.decode( obj.data, bmp );if(getTimer() - s)trace( "decode Data:",getTimer() - s );} /** * 根据x,y加载一个tile,如果已经加载过,则不再加载。 * @param x * @param y * */ private function loadTile( x : int, y : int ) : void { // 标记使用状态 if( !_tileFlag ) _tileFlag = {};if( _tileFlag[ x + "_" + y ] ) return; _tileFlag[ x + "_" + y ] = true; var bmp : Bitmap = getBitmap( x, y ); if( bmp.bitmapData ){ // 已经加载。在过图时会清空所有Loader return; } // 判断内存中是否有缓存 var url : String = UserData.resRoot + "map/"+_mapData.res+"/"+y+"/"+x+".jpg"; if( _tileCache[ url ] ){ bmp.bitmapData = _tileCache[ url ]; return; } // 为了编辑方便,正式输出可以用x_y的形式。 var loadInfo :Object = {};loadInfo.type = MapConnection.MSG_TYPE_LOAD; loadInfo.url = url; loadInfo.x = x; loadInfo.y = y;loadInfo.mid = _mapId; _mapClient.sendMessage( loadInfo ); } /** * 根据位置得到一个loader ,如果没有会新创建,不会返回空。 * @param x 实际坐标/TILE_WIDTH并取整过后的坐标。 * @param y * @return * */ private function getBitmap( x : int , y : int ) : Bitmap { var key : String = x + "_" + y; if( _tileTable[ key ] ){// 这里重新加一次。addChild( _tileTable[ key ] as Bitmap ); return _tileTable[ key ]; } var bmp :Bitmap = new Bitmap();bmp.smoothing = true; bmp.x = x * TILE_WIDTH; bmp.y = y * TILE_HEIGHT; bmp.bitmapData = null; _tileTable[ key ] = bmp; addChild( bmp ); return bmp; } public function clearAll() : void {_tileFlag = {}; for each( var bmp :Bitmap in _tileTable ){if( bmp.bitmapData ){bmp.bitmapData.dispose();bmp.bitmapData = null;} }loadedCounter = 0;// 切换地图时,清除地图位图缓存(使用浏览器缓存)。_mapClient.clearAll();_preX = -1;_preY = -1; } /** * 清除所有未使用的tile * */ private function clearUnused() : void { for ( var key :String in _tileTable ){ if( !_tileFlag[ key ] ){ var bmp : Bitmap = _tileTable[key];if( !USE_CACHE ){ bmp.bitmapData = null;}if( bmp.parent ){// 没有bitmapData的bmp也会绘制,所以要移除。bmp.parent.removeChild( bmp );} // 不清除bmp对象 } } }private function mouseDownHandler( event : Object ) : void {Player.target = null;}}

 

 

Class MapLoader:

管理多个TileLoader下载地图块。

本来所有游戏内图片应该统一用一个管理器的,但是在切换地图后要清除原来异步还没下载完的任务。所以地图得单一使用一个管理器,每个任务也带了地图id。

frontLayer的遮盖物,也是和地图相关的。



        public static var MAX_LOADER : int = 9; //最多TileLoader数
        public var urlList:Vector.<Object>;//所有TileLoader都在下载地图块时,将新的任务放入此等待队列。当一个TileLoader下载完时会从此队列取任务。
        public var freeList:Vector.<TileLoader>;//空闲的TileLoader队列
        public var onData :Function;//下载完的回调函数

        public var mapId:int;//当前所在地图id
        private var _lastGetTime:Number;//上一次取TileLoader的时间,当过了很久没有空闲TileLoader就新建一个。

public class MapLoader{// 同时加载的图片数量,并不是越大越好。/* * modify, make sure that all the request for map will be queued at once * as usual, not all the loader will be used, this feature is useful for * the first login */public static var MAX_LOADER : int = 9; public var urlList:Vector.<Object>;public var freeList:Vector.<TileLoader>; public var onData :Function;public var mapId:int;private var _lastGetTime:Number; public function MapLoader() {freeList = new Vector.<TileLoader>;urlList = new Vector.<Object>;initTileList(); } // 这个地方做法不是很好。public function getFreeLoader() : TileLoader {var i:int = 0;var now:Number = new Date().getTime();while (i < freeList.length) {if (freeList[i].url == null) {_lastGetTime = now;return freeList[i];}i++;}if( now - _lastGetTime > 5000 ){var tl:TileLoader = new TileLoader(this);freeList.push( tl );_lastGetTime = now;return tl;}return null;} public function initTileList() : void {var i:int = 0;while (i < MAX_LOADER) {freeList[i] = new TileLoader(this);i++;}return;} public function clearAll() : void{// 清空列表urlList = new Vector.<Object>;var i:int = 0;while (i < freeList.length) {if (freeList[i].url != null) {TileLoader(freeList[i]).loadNext();}i++;}return;} // ---------------------------------------------------------------- // // 远程调用接口 // // ---------------------------------------------------------------- /** * 收到地图数据 * @param obj * */ public function receiveMessage(obj:Object) : void { if( onData != null ){ onData( obj ); } } public function sendMessage(obj:Object) : void{// 判断是否切换了地图,如果是,则重新加载新的队列// 防止加载到其他地图的资源。if( mapId != obj.mid ){clearAll();mapId = obj.mid;}var loader:TileLoader = null;switch( obj.type ){case MapConnection.MSG_TYPE_LOAD:loader = getFreeLoader();if ( loader ) {loader.load(obj);} else {urlList.push(obj);}break;case MapConnection.MSG_TYPE_CLEAR:clearAll();break;default:break;}return;} }

Class TileLoader:

通过UrlLoader下载地图。

/** * 底图块加载,如果要渐进加载则使用URLStream。 * @author Ryan * */ public class TileLoader { private var _urlLoader:URLLoader; private var _server:*; private var _obj : Object;private var _sendFunc:Function; public var url:String; public function TileLoader(conn:*) { this._server = conn; _urlLoader = new URLLoader(); _urlLoader.dataFormat = URLLoaderDataFormat.BINARY; _urlLoader.addEventListener(Event.COMPLETE, completeHandler); _urlLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);if( conn is MapServer ){_sendFunc = _server.sendMessage;} else {_sendFunc = _server.receiveMessage;} } public function completeHandler(event:Event) : void { // 如果小于40000一次发送,否则分2次。 _obj.data = _urlLoader.data;_sendFunc(_obj); loadNext(); } public function load( obj :Object ) : void{ url = obj.url; if( url ){ _obj = obj; _urlLoader.load(new URLRequest(url)); } } public function loadNext() : void {url = null; _urlLoader.close(); if ( _server.urlList.length > 0) { _obj = _server.urlList.shift(); //trace( "Loaing url : ",_obj.url ); load( _obj ); } } public function errorHandler(event:IOErrorEvent) : void { //_server.sendStatus("IOErrorURL=" + url); // IOError 会导致URLLoader使用时间变长,加载变慢,需要尽量避免。 //trace("IOErrorURL=" + url); url = null; loadNext(); } }

Class ImageDecoder:

通过Loader解码图片。参考ActionScript显示大量外部jpg图片

你可能感兴趣的:(游戏,function,object,Flash,null,url)