一、问题概述
如何将下列的一个图形(下图左)用鼠标沿着白色格线切成多块(比如沿着黑色路径切割成下图右的两块)呢?
二、解决思路
把组成图形的每个小方块全部存入数组A中,关键要考虑的是如何根据切割路径将数组A分解为数组A和B,B用来存储切割下来的那块图形中的小方块。要解决此问题需要以下几步:
1.为数组A中的小方块建立链接表,每个小方块有top、bottom、left、right四个属性,分别指向其上下左右的小方块,当小方块不存在时指向null;
2.根据鼠标移动位置,获得图形的切割路径,存储在一数组中;
3.根据切割路径,更新数组A中各小方块的链接关系,切割路径两侧的小方块将断开链接(left、right等属性指向null);
4.从数组A中任意选择一个方块m,通过广度优先搜索,搜索所有与m有链接关系的方块,将其存入数组B,剩下的即为数组A;
以下为具体实现细节:
(注:本程序的实现借助了createjs框架来管理和绘制图形,并使用了我自己编写的createjsExtend工具库中的相关工具,在使用到这些工具时,我会做出必要的注解)
三、问题实现
一些变量说明:
stage:用createjs构建的舞台
canvas = document.getElementById(“canvas”); stage = new createjs.Stage(canvas);
root: 一个容器,包含用flashcc创建的所有元素,此实例中所创建的所有图形也将加载到root中,首先需要将root加入stage中
stage.addChild(root);
用到的全局变量
var rows=4,columns=9;//初始图形包含4行9列的小方块 var rectHeight=rectWidth=50;//每个小方块的宽高 var shapeConts=[];//存储舞台上切割出来的所有图形,初始只有一个 var pathShape;//切割路径 var cutState=false;//判断是否处于切割状态 var mousedown=false;//判断鼠标是否按下
1.创建初始图形并为图形中的小方块建立链接关系
用createjs创建一个影片剪辑shapescont作为图形的容器,此容器包含此图形中的各个小方块,shapescont拥有的属性如下:
shapescont.shapes,一个数组,用来存储此图形中的所有小方块;
shapescont.linkpots,一个数组,存储图形中小方块之间链接点坐标,即所有小方块的顶点坐标;
shapesCont.cutPath,一个数组,存储此图形被切割过的所有链接点;
shapesCont.cutfinished,布尔值,在对图形切割过程中,此值为false,切割完成后为true;
创建初始图形代码如下:
1 function createInitShapes(){ 2 var initShapes=[];//一个二维数组,存储图形中的小方块 3 var shapesCont=new createjs.MovieClip(); 4 shapesCont.shapes=[];//存储图形形状 5 shapesCont.linkPots=[];//存储图形中各链接点坐标,相对于shapesCont 6 shapesCont.cutPath=[];//存储图形中的切割路径,相对于shapesCont 7 for(var row=0;row<rows;row++){ 8 var rowArr=[]; 9 for(var column=0;column<columns;column++){ 10 var shape=new createjs.Shape(); 11 shape.graphics.setStrokeStyle(2).beginStroke("#ffffff").beginFill("red").drawRect(0,0,rectWidth,rectHeight);//画一个小方块 12 shape.x=rectWidth*column; 13 shape.y=rectHeight*row; 14 shapesCont.shapes.push(shape); 15 shapesCont.addChild(shape); 16 rowArr.push(shape); 17 } 18 initShapes.push(rowArr); 19 } 20 shapesCont.x=250; 21 shapesCont.y=100; 22 shapesCont.linkPots=getLinkPot(shapesCont);//得到shapesCont中的链接结点 23 shapesCont.addDragAction(new createjs.Rectangle(0,0,1024,768),stage,false,false);//这里为shapesCont图形添加拖动效果 24 shapeConts.push(shapesCont); 25 root.addChild(shapesCont); 26 27 //为每个方块设置上下左右的图形链接; 28 (function setLinkShapes(){ 29 for(var row=0;row<rows;row++){ 30 for(var column=0;column<columns;column++){ 31 initShapes[row][column].left=column-1<0?null:initShapes[row][column-1]; 32 initShapes[row][column].top=row-1<0?null:initShapes[row-1][column]; 33 initShapes[row][column].right=column+1>8?null:initShapes[row][column+1]; 34 initShapes[row][column].buttom=row+1>3?null:initShapes[row+1][column]; 35 } 36 } 37 })(); 38 }
获得图形中方块间各个链接结点的方法,求解思路:遍历图形中各方块的四个顶点,根据顶点位置判断两个方块的顶点是否重叠,防止重复加入
1 //获得shapeCont中所有小方块之间的链接点 2 function getLinkPot(shapeCont){ 3 var arr=new Array(); 4 for(var i=0;i<shapeCont.shapes.length;i++){ 5 6 var pot1={x:shapeCont.shapes[i].x,y:shapeCont.shapes[i].y}; 7 var pot2={x:shapeCont.shapes[i].x+rectWidth,y:shapeCont.shapes[i].y}; 8 var pot3={x:shapeCont.shapes[i].x,y:shapeCont.shapes[i].y+rectHeight}; 9 var pot4={x:shapeCont.shapes[i].x+rectWidth,y:shapeCont.shapes[i].y+rectHeight}; 10 if(myarrayIndexOf(arr,pot1)==-1){ 11 arr.push(pot1); 12 } 13 if(myarrayIndexOf(arr,pot2)==-1){ 14 arr.push(pot2); 15 } 16 17 if(myarrayIndexOf(arr,pot3)==-1){ 18 arr.push(pot3); 19 } 20 21 if(myarrayIndexOf(arr,pot4)==-1){ 22 arr.push(pot4); 23 } 24 } 25 return arr; 26 27 //根据坐标位置判断两个点是否一样 28 function myarrayIndexOf(arr,pot){ 29 if(arr.length==0){ 30 return -1; 31 } 32 for(var i=0;i<arr.length;i++){ 33 if(arr[i].x==pot.x&&arr[i].y==pot.y){ 34 return i; 35 } 36 } 37 return -1; 38 } 39 }
2.切割图形,获得shapescont的切割路径,存入shapescont的cutPath数组中
鼠标按下并移动时开始切割图形,切割过程中会根据鼠标离shapescont各链接结点的距离,来不断的将链接结点加入shapescont的cutPath数组中,从而最终形成shapescont的切割路径,如下图,蓝色点为切割路径
1 //添加鼠标移动事件 2 stage.addEventListener("stagemousemove",function(e){ 3 if(mousedown&&cutState){ 4 for(var i=0;i<shapeConts.length;i++){ 5 cuting(shapeConts[i]); 6 } 7 } 8 }); 9 10 //开始切割 11 function cuting(shapeCont){ 12 shapeCont.cutfinish=false; 13 //鼠标在图形上的位置 14 var mouse={x:stage.mouseX-shapeCont.x,y:stage.mouseY-shapeCont.y}; 15 drawCutPath(shapeCont); 16 for(var i=0;i<shapeCont.linkPots.length;i++){ 17 //根据鼠标离链接结点的距离,来判断将哪一个链接结点加入切割路径中 18 if(createjsExtend.getDistance(mouse,shapeCont.linkPots[i])<10){ 19 if(shapeCont.cutPath.length==0){ 20 arrayUtils.addSingleEleToArray(shapeCont.cutPath,shapeCont.linkPots[i]); 21 }else if(Math.abs(createjsExtend.getDistance(shapeCont.linkPots[i],shapeCont.cutPath[shapeCont.cutPath.length-1])-50)<15){ 22 arrayUtils.addSingleEleToArray(shapeCont.cutPath,shapeCont.linkPots[i]); 23 } 24 } 25 } 26 }
说明:arrayUtils.addSingleEleToArray(arr,ele);自己写的一个数组工具,作用为将ele加入arr中,保证ele在arr中的唯一性;
3.根据切割路径,更新shapescont中各小方块的链接关系
这里首先需要根据每一段切割路经,获得其路经两侧的小方块(下图绿色部分),使两侧的小方块不再链接
首先需要知道每一小段切割路经是水平还是垂直,当为水平时,需要得到路经上下两侧的方块,否则要得到左右两侧的方块。
如下图,A、B为切割路径上相连的两点,可以根据A、B点x坐标是否相等来判断AB是否垂直,当垂直时,取得y坐标较小的点的坐标(B点)即为路径右侧即方块m点的坐标(小方块注册点在左上角,坐标系中y轴正方向朝下),m.left即为路径左侧的方块。同样的思路可以取得路径上下两侧的方块。
切断路径两侧方块链接的代码如下:
1 //根据shapeCont中的切割路径,更新形状之间的链接状态,被切开的两块形状将不再链接 2 function updateLinkState(shapeCont){ 3 for(var i=0;i<shapeCont.cutPath.length-1;i++){ 4 //获得每一小段切割路径两端的坐标 5 var startPos=shapeCont.cutPath[i]; 6 var endPos=shapeCont.cutPath[i+1]; 7 if(startPos.x==endPos.x&&Math.abs(Math.abs(startPos.y-endPos.y)-50)<15){ 8 var pos=startPos.y<endPos.y?startPos:endPos; 9 for(var j=0;j<shapeCont.shapes.length;j++){ 10 if(shapeCont.shapes[j].x==pos.x&&shapeCont.shapes[j].y==pos.y){ 11 var rightShape=shapeCont.shapes[j]; 12 } 13 } 14 if(rightShape!=null&&rightShape.left!=null){ 15 rightShape.left.right=null; 16 rightShape.left=null; 17 } 18 } 19 20 if(startPos.y==endPos.y&&Math.abs(Math.abs(startPos.x-endPos.x)-50)<20){ 21 var pos=startPos.x<endPos.x?startPos:endPos; 22 for(var j=0;j<shapeCont.shapes.length;j++){ 23 if(shapeCont.shapes[j].x==pos.x&&shapeCont.shapes[j].y==pos.y){ 24 var buttomShape=shapeCont.shapes[j] 25 } 26 } 27 if(buttomShape!=null&&buttomShape.top!=null){ 28 buttomShape.top.buttom=null; 29 buttomShape.top=null; 30 } 31 } 32 } 33 }
4.使用广度优先算法,将图形分成两块
经过上面的处理,路径两侧的图形将不再拥有链接关系,接下来要如何将路径两侧的方块分在两个组呢,这里可用广度优先算法的思路求解,方法如下:
1.从图形中任意选择一个方块比如m,定义一数组linkShapes,用来存储能与m连通的所有方块,定义队列checkShapes存储待检查的所有方块,将m加入checkShapes中
2.从队列checkShapes的队头取一方块k(第一次取到m),遍历k四周的方块,将能查找到的方块加入待检查数组checkShapes,将k加入linkShapes
3.重复第2步,直到checkShapes为空
这样就将与m连通的所有方块分到了一个组,实现代码如下:
1 //查找所有与shape能联通的方块 2 function getAllLinksShape(shape){ 3 var linkShapes=[]; 4 var checkShapes=[]; 5 checkShapes.push(shape) 6 while(checkShapes.length>0){ 7 var shape=checkShapes.shift(); 8 if(shape.right!=null&&linkShapes.indexOf(shape.right)==-1&&checkShapes.indexOf(shape.right)==-1){ 9 checkShapes.push(shape.right); 10 } 11 12 if(shape.buttom!=null&&linkShapes.indexOf(shape.buttom)==-1&&checkShapes.indexOf(shape.buttom)==-1){ 13 checkShapes.push(shape.buttom); 14 } 15 16 if(shape.left!=null&&linkShapes.indexOf(shape.left)==-1&&checkShapes.indexOf(shape.left)==-1){ 17 checkShapes.push(shape.left); 18 } 19 20 if(shape.top!=null&&linkShapes.indexOf(shape.top)==-1&&checkShapes.indexOf(shape.top)==-1){ 21 checkShapes.push(shape.top); 22 } 23 linkShapes.push(shape); 24 }; 25 return linkShapes; 26 }
四、运用场景设想
运用本实例的制作思路,可以实现很多非常有趣的小游戏,比如可以用一张照片填充本实例的小方块,制作一款自己可以随意切割的拼图小游戏。
(原创博文,转载请注明出处)