vanCopper bio photo

vanCopper

Try Your Best.

Email Github

原创博文,转载请声明

终于折腾好了先来张效果图:

image

Demo

很早之前就跟同事研究过帝国时代里的地形拼接,偶尔中看到参考一&&参考二这两篇文章(ps:话说跟博主还有过一面之缘,第一眼看过去就可以肯定这人是技术宅,可惜的是没能成为同事一起共事)于是研究了一下,读了博主的代码,这里分享并记录下来。
为什么要做这种自动拼接? 很多游戏的地图都是基于Tile拼接而成,拼接地图的主要目的是资源的重用性,降低美术成本。但拼接的地图对于游戏的地图的表现力是有部分损失的。 当我们游戏里需要拼接一条或者多条路再或者变态的九路十八弯的路,再或者是那弯弯的小溪….对于素材和游戏逻辑复杂度来讲绝对是蛋痛的。所以牛人们就想到了这种牛逼的地形的自动拼接。

我们先来观察下资源图片
image

仔细观察我们可以发现每个格子(16*16)的图片是有一定规律的,大致分为下面几种:

A: 内向转角 0,1,6,7 12,17,42,47 B:向外转角 4,5,10,11 C:上下连接 24,29,30,35 D:左右连接 14,15,44,45 E:填充物 26,27,32,33
再观察我们的拼接而成的地图 你会发现大格子的尺寸为 32*32 那么也就是说一个大格子是由四个小格子组合而成的。所以在生成填充的时候如何确定四个小格子应该使用的目标图片便是关键了。
再继续观察我们利用小图片拼接成的大图片,我们又发现了规律。为了描述清楚,我们先定义一些名词(ps:我阅读上面的Blog时就读了好久没读明白那些名词)

块:把一个32x32的格子称为块
目标块:我们将要填充的32x32的格子
同类块:该32x32的格子已经被填充过

规律: A: 对于每个块里面的16x16的小块 该小块斜方向的同类块对目标块的影响当且仅当斜方向相邻的两个垂直方向上都有同类块的时候才会出现,也就是目标小块的填充为填充物。
如图:

image
目标块为13 因为0,1,12均已被填充 那么块13的左上角的小块为填充物为 26(填充物)

B:对于每个块里面的16x16的小块 该小块两个相邻的垂直方向上都有同类块,且他们之间的斜方向没有同类块的时候,目标小块的填充为 向外转角。
如图:

image
目标块为13 因为 1, 12已被填充 0未被填充 所以块13的左上角的小块填充物为 4(向外转角)

C:对于每个块里面的16x16的小块 该小块的某个垂直方向上有同类块,且相邻的一个垂直方向上没有同类块,无论相邻的斜方向上有没有同类块,目标小块的填充为 向上下或者向左右连接物。
如图:

image
目标块为13 因为只有一个垂直方向上1被填充 所有块13的左上角的小块填充物为 24(上下连接)

D:对于每个块里面的16x16的小块 该小块的某个垂直方向上没有同类快,且相邻的方向也没有同类块,无论相邻的斜方向上有没有同类块,目标小块的填充为 内向转角
如图:

image
目标块为13 1,12均无填充物,目标小块的填充物为 0(内转角)

ok到这里,自动拼接的原理基本上叙述完了。具体该怎么做呢?这个也许每个人的实现方法会有所不同,我把代码贴上来供有兴趣的朋友参考和自己备份。

package
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.filters.DropShadowFilter;
    import flash.geom.Rectangle;
    import flash.text.TextField;
    import flash.text.TextFormat;

    [SWF(width="550",height="450",backgroundColor="0xCCCCCC")]

    public class autoMontage extends Sprite
    {
        [Embed(source="./files/01.png")]
        private var IMG01:Class;

        [Embed(source="./files/02.png")]
        private var IMG02:Class

        public function autoMontage()
        {
            initView();
        }

        private function initView():void
        {
            initImgView();
            initCanvas();
            initMap();
        }

        private var _map:Array;
        /**
         * 初始化地图信息
         *
         */
        private function initMap():void
        {
            _map = [];
            var i:int;
            var j:int;
            for(i=0;i<CANVAS_ROWS;i++)
            {
                _map[i] = [];
                for(j=0;j<CANVAS_COLS;j++)
                {
                    _map[i][j] = 0;
                }
            }
        }

        private static var GRID_SIZE:int = 32;
        private static var CANVAS_ROWS:int = 12;
        private static var CANVAS_COLS:int = 12;
        private var _canvas:Bitmap;
        private var _canvasBitmapData:BitmapData;
        private var _canvasContainer:Sprite = new Sprite();
        /**
         * 初始化目标画布
         *
         */
        private function initCanvas():void
        {
            var canvasW:int = CANVAS_ROWS*GRID_SIZE;
            var canvasH:int = CANVAS_COLS*GRID_SIZE;
            _canvasContainer.graphics.beginFill(0xFFFFFF);
            _canvasContainer.graphics.drawRect(0,0,canvasW,canvasH);
            _canvasContainer.graphics.endFill();
            _canvasContainer.x = IMG_W + GRID_SIZE;
            _canvasContainer.y = (this.stage.stageHeight - _canvasContainer.height)/2;
            addChild(_canvasContainer);
            _canvasBitmapData = new BitmapData(canvasW,canvasH,true,0);
            _canvas = new Bitmap(_canvasBitmapData);
            _canvasContainer.addChild(_canvas);

            var canvasGrid:Sprite = getGrid(CANVAS_ROWS,CANVAS_COLS,GRID_SIZE);
            _canvasContainer.addChild(canvasGrid);

            var numGrid:Sprite = getGridNum(CANVAS_ROWS,CANVAS_COLS,GRID_SIZE);
            _canvasContainer.addChild(numGrid);

            _canvasContainer.addEventListener(MouseEvent.CLICK,onMouseHandler);
            _canvasContainer.addEventListener(MouseEvent.MOUSE_MOVE,onMouseHandler);
        }

        private var _mouseGrid:Sprite;
        private function onMouseHandler(e:MouseEvent):void
        {
            switch(e.type)
            {
                case MouseEvent.CLICK:
                    if(_canvasContainer.hitTestPoint(this.stage.mouseX,this.stage.mouseY))
                    {
                        var row:int = _canvasContainer.mouseX/GRID_SIZE;
                        var col:int = _canvasContainer.mouseY/GRID_SIZE;
                        render(row,col);
                    }
                    break;
                case MouseEvent.MOUSE_MOVE:
                    if(!_mouseGrid)
                    {
                        _mouseGrid = getGrid(1,1,GRID_SIZE,2,0x00000);
                        _canvasContainer.addChild(_mouseGrid);
                    }
                    _mouseGrid.x = Math.floor(_canvasContainer.mouseX/GRID_SIZE)*GRID_SIZE;
                    _mouseGrid.y = Math.floor(_canvasContainer.mouseY/GRID_SIZE)*GRID_SIZE;
                    break;
            }
        }

        private var _imgArr:Array = [new IMG01(),new IMG02()];
        private static var IMG_W:int = 96;
        private static var IMG_H:int = 128;
        private static var TILE_SIZE:int = 16;
        private var _targetBitmapData:BitmapData;//当前使用的位图资源
        private var _targetBitmapDataList:Array;
        private var _bitmapIndexArr:Array = [];

        private function initImgView():void
        {
            _targetBitmapDataList = [];
            for (var i:int = 0; i < _imgArr.length; i++)
            {
                var container:Sprite = new Sprite();
                var bitmap:Bitmap = _imgArr[i];
                container.addChild(bitmap);
                _targetBitmapDataList.push(bitmap.bitmapData);
                var grid:Sprite = getGrid(IMG_W/TILE_SIZE,IMG_H/TILE_SIZE,TILE_SIZE);
                var gridNum:Sprite = getGridNum(IMG_W/TILE_SIZE,IMG_H/TILE_SIZE,TILE_SIZE,_bitmapIndexArr);
                container.addChild(grid);
                container.addChild(gridNum);
                container.y = i*IMG_H + 25*i + 25;
                container.name = ""+i;
                container.addEventListener(MouseEvent.CLICK,onChangeBitmapData);
                container.buttonMode = true;
                container.mouseChildren = false;
                addChild(container);
            }
            _targetBitmapData = _targetBitmapDataList[0];
        }

        private var _currentSelectDisplay:DisplayObject;
        private function onChangeBitmapData(e:MouseEvent):void
        {
            if(_currentSelectDisplay)
            {
                _currentSelectDisplay.filters = null;
            }
            var displayObject:DisplayObject = e.target as DisplayObject;
            displayObject.filters = [new DropShadowFilter()];
            _currentSelectDisplay = displayObject;
            var index:int = int(displayObject.name);
            _targetBitmapData = _targetBitmapDataList[index];
            _canvasBitmapData.fillRect(_canvasBitmapData.rect,0);
            initMap();
        }

        /**
         * 生成格子
         * @param rows
         * @param cols
         * @param size
         * @return
         *
         */
        private function getGrid(rows:int,cols:int,size:int,thickNess:int=1,color:uint = 0x990033):Sprite
        {
            var sp:Sprite = new Sprite();
            var i:int;
            var j:int;
            sp.graphics.lineStyle(thickNess,color,.5);

            for (i=0;i<cols+1;i++)
            {
                sp.graphics.moveTo(0, i*size);
                sp.graphics.lineTo(size*rows,i*size);
            }

            for(j=0; j<rows + 1;j++)
            {
                sp.graphics.moveTo(j*size,0);
                sp.graphics.lineTo(j*size,size*cols);
            }
            return sp;
        }

        private function getGridNum(rows:int,cols:int,size:int,indexArr:Array=null):Sprite
        {
            var sp:Sprite = new Sprite();
            var text:TextField;
            var tf:TextFormat = new TextFormat;
            tf.align = "center";
            tf.size = 11;
            var i:int = 0;
            var j:int = 0;
            var num:int = 0;
            for (i=0;i<cols; i++) {
                for (j=0;j<rows;j++) {
                    text = new TextField;
                    text.mouseEnabled = false;
                    text.defaultTextFormat = tf;
                    text.width = 20;
                    text.text = "" + num;
                    // text.text = "" + j + ":" + i;
                    if(indexArr)
                    {
                        indexArr.push([j,i]);
                    }
                    num++;
                    text.x = j*size;
                    text.y = i*size;
                    sp.addChild(text);
                }
            }
            return sp;
        }

        private function render(row:int,col:int):void
        {
            if(_map[col][row]){return;}

            _map[col][row] = 1;
            var bitmapDataIndex:Array = getBitmapDataIndex(row,col);
            refreshCanvas(row,col,bitmapDataIndex);

            var nearArr:Array = getNear(row,col);
            for(var i:int = -1;i<=1;i++)
            {
                for(var j:int = -1; j<=1; j++)
                {
                    var px:int = row + j;
                    var py:int = col +i;
                    if(nearArr[1+ i][1+j])
                    {
                        var near:Array = getBitmapDataIndex(px,py);
                        refreshCanvas(px,py,near);
                    }
                }
            }
        }

        /**
         * 刷新画布
         * @param row
         * @param col
         * @param indexArr
         *
         */
        private function refreshCanvas(row:int,col:int,indexArr:Array):void
        {
            var bmpList:Array = [];
            for(var i:int = 0;i<indexArr.length;i++)
            {
                var posArr:Array = _bitmapIndexArr[indexArr[i]];
                var px:int = posArr[0]*TILE_SIZE;
                var py:int = posArr[1]*TILE_SIZE;

                var bmpVector:Vector.<uint> = _targetBitmapData.getVector(new Rectangle(px,py,TILE_SIZE,TILE_SIZE));
                bmpList.push(bmpVector);
            }

            var index:int = 0;
            for(var j:int = 0; j<2;j++)
            {
                for(var n:int = 0; n<2; n++)
                {
                    var tx:int = row*GRID_SIZE + n*TILE_SIZE;
                    var ty:int = col*GRID_SIZE + j*TILE_SIZE;
                    var targetBmpV:Vector.<uint> = bmpList[index];
                    _canvasBitmapData.setVector(new Rectangle(tx,ty,TILE_SIZE,TILE_SIZE),targetBmpV);
                    index ++;
                }
            }
        }

        private function getBitmapDataIndex(row:int,col:int):Array
        {

            var returnIndex:Array = [12,17,42,47];
            var nearArr:Array = getNear(row,col);
            // A- 斜方向的同类块对目标块的影响当且仅当斜方向相邻的两个垂直方向上都有同类块的时候才会出现。也就是将目标块的对应小图块变成填充物。
            // B- 当两个相邻的垂直方向上都有同类块,且他们之间的斜方向上没有同类块的时候,目标快对应的小图块变成向外转角
            // C- 当某个垂直方向上有同类块,且相邻的一个垂直方向上没有同类块,无论相邻的斜方向上有没有同类块,目标块对应的小图块变成连接(向上下或者向左右连接)
            // D- 当某个垂直方向上没有同类块,且相邻的垂直方向上也没有同类块,无论相邻的斜方向上有没有同类块,目标块对应的小图块变成向内转角
            //目标区块的 左上角小区块
            if(nearArr[0][0] && nearArr[0][1] && nearArr[1][0])
            {
                returnIndex[0] = 20;//填充物
            }else if(nearArr[0][1] && nearArr[1][0])
            {
                returnIndex[0] = 4;//外转角
            }else if(nearArr[0][1])
            {
                returnIndex[0] = 24;//向上
            }else if(nearArr[1][0])
            {
                returnIndex[0] = 14;//向左
            }

            //目标区块的 右上角小区块
            if(nearArr[0][2] && nearArr[0][1] && nearArr[1][2])
            {
                returnIndex[1] = 27;
            }else if(nearArr[0][1] && nearArr[1][2])
            {
                returnIndex[1] = 5;
            }else if(nearArr[0][1])
            {
                returnIndex[1] = 29;
            }else if(nearArr[1][2])
            {
                returnIndex[1] = 15;
            }

            //目标区块的 左下角小区块
            if(nearArr[2][0] && nearArr[1][0] && nearArr[2][1])
            {
                returnIndex[2] = 32;
            }else if(nearArr[1][0] && nearArr[2][1])
            {
                returnIndex[2] = 10;
            }else if(nearArr[1][0])
            {
                returnIndex[2] = 44;
            }else if(nearArr[2][1])
            {
                returnIndex[2] = 30;
            }

            //目标区块的 右下角小区块
            if (nearArr[2][1] && nearArr[2][2] && nearArr[1][2])
            {
                returnIndex[3] = 33;
            } else if (nearArr[2][1] && nearArr[1][2])
            {
                returnIndex[3] = 11;
            } else if (nearArr[2][1])
            {
                returnIndex[3] = 35;
            }else if (nearArr[1][2])
            {
                returnIndex[3] = 45;
            }
            return returnIndex;
        }

        private function getNear(row:int,col:int):Array
        {

            var returnArr:Array = [];
            returnArr.push([0,0,0]);
            returnArr.push([0,0,0]);
            returnArr.push([0,0,0]);

            var tempIndex:int = 1;
            for(var i:int = -1; i<=1;i++)
            {
                for(var j:int = -1; j<= 1; j++)
                {
                    var tRow:int = col + i;
                    var tCol:int = row + j;

                    if(tRow<0 || tCol < 0){continue;}
                    if(tRow>CANVAS_COLS || tCol > CANVAS_ROWS){continue;}
                    if(_map[tRow] && _map[tRow][tCol])
                    {
                        //有填充物
                        returnArr[tempIndex+i][tempIndex+j] = 1;
                    }else
                    {
                        //无填充物
                        returnArr[tempIndex+i][tempIndex+j] = 0;
                    }
                }
            }
            return returnArr;
        }
    }
}

扫码分享该文