拼图设计思路与源码一[画图篇]
想要制作非正规矩形的凹凸拼图,第一步就得先画好每一个小拼图的轮廓.然后再通过位图填充,从而制作出每一个小拼图碎片.小碎片的轮廓示意图如下图所示:

图1
如图1所示,为拼图中左上角的一个碎片,所以该碎片只要画右边与下边的轮廓,其它边都为直线.
每一边的轮廓都由一个圆弧(BCD)以及圆弧两个端点(B点,D点)与矩形的两个角(A点与E点)相连的两条直线(AB与DE)组成.所以一整个碎片轮廓就是由这一系列的直线与弧线组成的.
为了不使每一个碎片的外形都一样,所以加了一小些随机的因素.例如圆弧的凹与凸,以及圆心的位置.
当圆弧随机为凸时,圆心的位置在图中的直线OP上的随意一点.相反,为凹时,则为O'P上的随意一点.其中P为AE的中点.当然,我们也可以在AE中取一小段线段作为P的随机点也行.
在本实例中,为了不让四条边的圆弧都为凹时出现相交的情况,所以圆弧的半径为碎片矩形长宽中最短一边的1/6.且圆弧的圆心角都为270度.当然在保证四条边的弧线都为凹时不相交的情况下,半径以及圆心角也都可以加上一些随机的因素的.
至此,我们只要用一个数组来存储四条边上的这些圆弧以及端点角点的信息,然后通过一个连线的函数来进行画图,就可以画出一个封闭的拼图碎片轮廓了.
其它碎片,我们要计算的也只是右边与下边,左边与上边都是沿着上一个碎片的信息画的.如第二列的左边,其实就与第一列的右边是一样的.第二行的上边其实就是第一行的下边.
另外需要注意的是,AS3中对于封闭区域轮廓的要求必须是头尾顺笔连接而成..如图中的右边.画图的顺序必须是:A连到B,然后两从B顺时针画圆弧BCD,再D连到E.不能先连直线AB与DE,然后再画一个圆弧BCD,而且还不分顺画或逆画,这样子就算外形看是一个封闭的曲线,但当填充时就会出现意想不到的效果.
以下是计算某一边的信息的函数,通过参数pos来决定要画右边或下边
public function getArcDot(pos:String):Object{
var rnd:Number = Math.random()<0.5 ? 0:1;//0为凸,1为凹
var dist:Number = Math.sqrt(2 * m_pieceArcRadiu * m_pieceArcRadiu);//圆弧两个商战的距离
var obj:Object = new Object();
switch(pos){
case "right"://画右边
var _arcY:Number = m_pieceH / 2;
var _arcX:Number = this.m_pieceArcRadiu * Math.random() * Math.pow(-1,rnd) + m_pieceW;
var _angle:Number = 225 - rnd * 180;//开始角度
//画圆弧的起笔位置,顺时针画,凸时,起点在下,凹时起点在上.
var nStartX:Number = _arcX + Math.cos(_angle* Math.PI / 180) * m_pieceArcRadiu;
var nStartY:Number = _arcY + Math.sin(_angle* Math.PI / 180) * m_pieceArcRadiu;
obj.o = new Point(_arcX,_arcY);
obj.angle = _angle;
if(rnd == 0){
obj.p1 = new Point(nStartX,nStartY);
obj.p2 = new Point(nStartX,nStartY + dist);
obj.clockwise = true;
}
else
{
obj.p2 = new Point(nStartX,nStartY);
obj.p1 = new Point(nStartX,nStartY - dist);
obj.clockwise = false;
}
break;
case "down":
var _arcX:Number = m_pieceW / 2;
var _arcY:Number = m_pieceArcRadiu * Math.random() * Math.pow(-1,rnd) + m_pieceH;
var _angle:Number = 315 - rnd * 180; //开始角度
//画圆弧的起笔位置,顺时针画,凸时,起点在下,凹时起点在上.
var nStartX:Number = _arcX + Math.cos(_angle* Math.PI / 180) * m_pieceArcRadiu;
var nStartY:Number = _arcY + Math.sin(_angle* Math.PI / 180) * m_pieceArcRadiu;
obj.o = new Point(_arcX,_arcY);
obj.angle = _angle;
if(rnd == 0){
obj.p1 = new Point(nStartX,nStartY);
obj.p2 = new Point(nStartX - dist,nStartY);
obj.clockwise = true;
}
else
{
obj.p2 = new Point(nStartX,nStartY);
obj.p1 = new Point(nStartX + dist,nStartY);
obj.clockwise = false;
}
break;
}
return obj;
}
其中的object的结构为:
o 圆心坐标
angle 圆弧的开始角度
p1 圆弧的第一个端点
p2 圆弧的第二个端点
clockwise 画圆弧时是顺时针画还是逆时针画
接着我们来创建一个碎片类Piece:
package net.conanlwl
{
import flash.display.Sprite;
public class Piece extends Sprite
{
private var m_row:uint;
private var m_col:uint;
public var Up:Object;
public var Right:Object;
public var Buttom:Object;
public var Left:Object;
private var m_virtualX:Number;
private var m_virtualY:Number;
public function Piece(_row:uint,_col:uint,_virtualX:Number,_virtualY:Number)
{
this.m_row = _row;
this.m_col = _col;
m_virtualX = _virtualX;
m_virtualY = _virtualY;
}
public function get virtualX():Number{
return m_virtualX;
}
public function get virtualY():Number{
return m_virtualY;
}
public function get Row():uint{
return m_row;
}
public function get Col():uint{
return m_col;
}
}
}
其中Up,Right,Buttom,Left为四边的圆弧信息,Row与Col为碎片在整张拼图中的所在行与列.virtualX与virtualY则为碎片在整张拼图正确位置时的坐标.
最后,我们再把各边的点弧信息集合起来
public function getAllDotArray(_p:Piece):Array{
var allDotArray:Array = new Array();
var obj:Object = new Object();
if(_p.Row == 0){//第0行,不用画上面
_p.Up = null;
}else
{
var tempObj:Object = (m_pieceLayer.getChildByName("piece_" + ((_p.Row-1)*m_col + _p.Col)) as Piece).Buttom;//上一行同一位置的方块
var upObj:Object = new Object();
var t:Point = new Point(tempObj.o.x,tempObj.o.y - m_pieceH);
upObj.o = t;
upObj.angle = tempObj.angle;
t = new Point(tempObj.p2.x,tempObj.p2.y - m_pieceH);
upObj.p1 = t;
t = new Point(tempObj.p1.x,tempObj.p1.y - m_pieceH);
upObj.p2 = t;
upObj.clockwise = !tempObj.clockwise;
_p.Up = upObj;
}
if(_p.Col == 0){//第0列,不用画左面
_p.Left = null;
}else{
var tempObj:Object = (m_pieceLayer.getChildByName("piece_" + (_p.Row*m_col+_p.Col-1)) as Piece).Right;
var leftObj:Object = new Object();
var t:Point = new Point(tempObj.o.x - m_pieceW,tempObj.o.y);
leftObj.o = t;
leftObj.angle = tempObj.angle;
t = new Point(tempObj.p2.x - m_pieceW,tempObj.p2.y);
leftObj.p1 = t;
t = new Point(tempObj.p1.x - m_pieceW,tempObj.p1.y);
leftObj.p2 = t;
leftObj.clockwise = !tempObj.clockwise;
_p.Left = leftObj;
}
if(_p.Row == m_row - 1){//最下一行,不用画下面
_p.Buttom = null;
}else{
_p.Buttom = getArcDot("down");
}
if(_p.Col == m_col - 1){//最右一列,不用画右面
_p.Right = null;
}else
{
_p.Right = getArcDot("right");
}
allDotArray.push(_p.Up);
allDotArray.push(new Point(m_pieceW,0));
allDotArray.push(_p.Right);
allDotArray.push(new Point(m_pieceW,m_pieceH));
allDotArray.push(_p.Buttom);
allDotArray.push(new Point(0,m_pieceH));
allDotArray.push(_p.Left);
allDotArray.push(new Point(0,0));
return allDotArray;
}
有了各边的点弧信息,只要使用连线函数就可以画出一个封闭的曲线了.
public function drawPiece(p:Piece,dotArray:Array):void{
var pen:Pen = new Pen(p.graphics);
pen.lineStyle(1,0,0.4);
pen.moveTo(0,0);
for(var i:uint=0;i<dotArray.length;i++){
if(dotArray[i] is Point){
pen.lineTo(dotArray[i].x,dotArray[i].y);
}
else
{
if(dotArray[i]!=null){
pen.lineTo(dotArray[i].p1.x,dotArray[i].p1.y);
pen.drawArc1(dotArray[i].o.x,dotArray[i].o.y,this.m_pieceArcRadiu,270,dotArray[i].angle,false,dotArray[i].clockwise);
}
}
}
}
其中的Pen类是<<ActionScript3 CookBook>>中的一个代理类.可以方便的画一些特殊的线型,如圆弧,星形等.drawArc1是我在drawArc的基础上新添的一个函数.目的是为了可以确定该圆弧是顺时针画出来的,还是逆时针画出来的.
最后,就可以使用循环连画整幅拼图的碎片以及利用位图填充了.
public function pieceCut():void{
for(var i:uint = 0; i < m_row;i++){
for(var j:uint=0;j< m_col;j++){
var p:Piece = new Piece(i,j,j * m_pieceW,i * m_pieceH);
p.name = "piece_" + (i * m_col + j);
p.graphics.beginBitmapFill(images,new Matrix(1,0,0,1,-j*m_pieceW,-i*m_pieceH));
drawPiece(p,getAllDotArray(p));
p.graphics.endFill();
m_pieceLayer.addChild(p);
p.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
p.addEventListener(MouseEvent.MOUSE_WHEEL,onMouseWheel);
}
}
setKeyboardFocus();
addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
cutCard();
}
根据矩阵的定义:
Matrix(a:Number = 1, b:Number = 0, c:Number = 0, d:Number = 1, tx:Number = 0, ty:Number = 0)
我们只要在位图填充beginBitmapFill时,对原图进行位移,便能填充到正确的图案到每一个碎片之中去.
最最最后,通过一个洗牌函数,就可以把碎片随机的平铺在游戏舞台中了.
var k:uint = 0;
var i:uint = 0;
var j:uint = 0;
var w:Number = 800 / m_col;
var h:Number = 800 / m_row;
m_pieceArray = new Array();
for(var l:uint = 0;l<m_pieceLayer.numChildren;l++){
if((m_pieceLayer.getChildAt(l) as Piece).numChildren == 0)
m_pieceArray.push(m_pieceLayer.getChildAt(l));
}
m_pieceArray.sort(Utils.arrayRandomSort);
while(k<m_pieceArray.length)
{
var p:Piece = m_pieceArray[k] as Piece;
p.x = j * w;
p.y = i * h;
var rnd:int = 2 - Math.floor(4 * Math.random());//随机生成 -1 0 1 2 四个整数
p.rotation = rnd * 90; //随机旋转
switch(rnd){
case -1:
p.y += m_pieceH;
break;
case 0:
break;
case 1:
p.x += m_pieceW;
break;
case 2:
p.x += m_pieceW;
p.y += m_pieceH;
break;
}
j++;
if(j == m_col){
j=0;
i++;
if( i == m_row)i==0;
}
k++;
}
}
评论Feed: http://www.conanlwl.net/Feed/Comment/181.aspx
引用链接: http://www.conanlwl.net/TrackBack/Save/181.aspx
加载评论中...
加载引用中...
加载相关文章中...
