使用Bezier曲线的拼图游戏

Ani*_*ban 2 html5 bezier canvas

我试图制作一些像这样的拼图 -

在此输入图像描述 在此输入图像描述

到目前为止我用lineTo尝试了什么 -

outside: function (ctx, s, cx, cy) {
        ctx.lineTo(cx, cy)
        ctx.lineTo(cx+s*.3, cy)
        ctx.lineTo(cx+s*.5, cy+s*-.2)
        ctx.lineTo(cx+s*.7, cy)
        ctx.lineTo(cx+s, cy)
    },
    inside: function (ctx, s, cx, cy) {
        ctx.lineTo(cx, cy)
        ctx.lineTo(cx+s*.3, cy)
        ctx.lineTo(cx+s*.5, cy+s*+.2)
        ctx.lineTo(cx+s*.7, cy)
        ctx.lineTo(cx+s, cy)
    },
Run Code Online (Sandbox Code Playgroud)

小提琴链接

mar*_*rkE 8

高效的拼图设计很简单,它的工作原理如下:

链接代码已经显示了如何通过重复使用单面设计来有效地组装您的一个拼图.

你插图右侧的作品是传统的(或"日式")作品.这意味着它的两侧长度均匀并且完全互锁.日本风格的作品是最容易设计的,因为它是一个设计代码,可以在整个拼图中重复使用.

具有讽刺意味的是,虽然日式拼图是最容易编码的,但是对于用户而言,它们更难解决,因为许多部件在没有正确解决拼图的情况下将物理地装配在一起.

如何设计日式拼图游戏

  1. 通过组合多个立方贝塞尔曲线来设计拼图块的一侧(不是更多!).

  2. 根据需要,使用变换将一个拼图设计应用于顶部,右侧,底部或左侧.(或自动操作原始Bezier控制点以将一个拼图设计应用于4个边的代码函数).镜像原始的侧面设计,为您的作品提供各种"精明"和"炫耀"的一面.

  3. 通过镜像每个相邻方的设计来组装拼图:

    • 给左上角(0,0)一个随机的右侧(无论是inny还是outy).
    • 让我们假设片段(0,0)被分配了一个外面的右侧.然后右边的下一块(1,0)必须得到一个内侧的左侧.
    • 现在给piece(1,0)一个随机的右侧(无论是inny还是outy),而piece(2,0)必须得到镜像类型的side.等等...
    • 所以一般来说,通过为所有部分分配随机右侧并在下一部分的左侧镜像指定侧来填充拼图.
    • 垂直做同样的事.通过为所有部分指定随机底部边并在下一部分的顶部镜像指定侧面来填充拼图.

设计你已经说明的2个例子

我假设链接代码不是您的代码,因为它已经显示了如何在插图右侧设计该片段(!).

// Given the center point of the piece (cx,cy) and the side length (s)
// The single side "outy" design is below
// Use this single design (with transforms/mirroring) to make all pieces
ctx.lineTo(cx + s * .34, cy);
ctx.bezierCurveTo(cx + s * .5, cy, cx + s * .4, cy + s * -.15, cx + s * .4, cy + s * -.15);
ctx.bezierCurveTo(cx + s * .3, cy + s * -.3, cx + s * .5, cy + s * -.3, cx + s * .5, cy + s * -.3);
ctx.bezierCurveTo(cx + s * .7, cy + s * -.3, cx + s * .6, cy + s * -.15, cx + s * .6, cy + s * -.15);
ctx.bezierCurveTo(cx + s * .5, cy, cx + s * .65, cy, cx + s * .65, cy);
ctx.lineTo(cx + s, cy);
Run Code Online (Sandbox Code Playgroud)

然后,您可以重复使用这一组贝塞尔曲线以及变换来创建整个拼图.转换==移动,旋转和镜像单一设计,构成任何拼图的任何一面.

插图左侧的部分可能来自Freeform Style拼图游戏.它更复杂,因为它使用3种不同的侧面设计.我假设你还没有展示其他侧面设计,因为你展示的3面设计不允许所有部件互锁以完成拼图.

创建自由形式拼图时,您有多种选择.

非互锁自由形式

在这种风格中,您基本上可以拍摄图像并绘制将其剪切成非均匀片段的线条,这些片段可以排列成图像.可以把它想象成随机切片的披萨.即使碎片没有互锁,你也可以将这些碎片装在一起改造披萨.嗯,披萨!:-)

联锁自由形式

在这种风格中,您可以设计2个以上的侧面,并以与传统风格拼图相同的方式创建拼图.通常,您创建一个将用于所有左右侧的设计,并创建第二个用于所有上下侧的设计.复杂性是两种类型的边必须在它们相遇的地方相互配合.这意味着side-type-1必须共享一个镜像模式,它与side-type-2相交.

因此,要设计插图左侧的部分,您必须决定是希望它是Interlocking-Freeform还是Non-interlocking-Freeform.

非互锁自由形式更容易.只需拆开3种类型的侧面,并与镜像合作伙伴一起使用即可切割图像.

对于Interlocking-Freeform,您需要进行更多的设计工作.您必须创建额外的侧面设计,这些侧面设计将与您已创建的3个设计互锁.

这是拼图游戏的快速浏览...祝你的项目好运!

[ 额外细节 ]

对于插图右侧的部分,常见的"外部"看起来像是"肩部和头部"的轮廓.

Bezier设置的肩膀和头部分解如下:

  • 一个Bezier为"左肩"
  • 一个Bezier为"左脖子"
  • 一个Bezier为"左头"
  • Bezier为"右脑袋"
  • Bezier为"右颈"
  • Bezier为"右肩"

肩部和头部Bezier套装可能如下所示:

在此输入图像描述

以下是创建具有"肩膀和头部"形状的外侧的控制点的一个具体示例:

var ShouldersAndHeadCubicBezierControlPoints=[
    {cx1:0,  cy1:0,  cx2:35,cy2:15, ex:37, ey:5},   // left shoulder
    {cx1:37, cy1:5,  cx2:40,cy2:0,  ex:38, ey:-5},  // left neck
    {cx1:38, cy1:-5, cx2:20,cy2:-20,ex:50, ey:-20}, // left head
    {cx1:50, cy1:-20,cx2:80,cy2:-20,ex:62, ey:-5},  // right head
    {cx1:62, cy1:-5, cx2:60,cy2:0,  ex:63, ey:5},   // right neck
    {cx1:63, cy1:5,  cx2:65,cy2:15, ex:100,ey:0},   // right shoulder
];
Run Code Online (Sandbox Code Playgroud)

一旦拥有了"外部"曲线集,就可以使用画布的上下文转换将"外部"翻转到其镜像的"内部".或者,您可以手动反转曲线控制点的"外部"数组.

插图:顶部标签和顶部插槽(顶部插槽是顶部标签镜像)

在此输入图像描述

显示顶部标签和顶部插槽的示例:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
  var BB=canvas.getBoundingClientRect();
  offsetX=BB.left;
  offsetY=BB.top;        
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }

ctx.lineWidth=3;
var colors=['red','green','blue','gold','purple','cyan'];

var bSet=makeBeziers();

draw(bSet,50,100);

var bSetMirrored=mirror(bSet,1,-1,0,0);

draw(bSetMirrored,50,200);

function draw(bSet,transX,transY){
  ctx.translate(transX,transY);
  ctx.scale(2,2);
  for(var i=0;i<bSet.length;i++){
    var b=bSet[i];
    ctx.beginPath();
    ctx.bezierCurveTo(b.cx1,b.cy1,b.cx2,b.cy2,b.ex,b.ey);
    ctx.strokeStyle=colors[i];
    ctx.stroke();
  }
  ctx.setTransform(1,0,0,1,0,0);
}


function makeBeziers(){
  return([
    {cx1:0,  cy1:0,  cx2:35,cy2:15, ex:37, ey:5},   // left shoulder
    {cx1:37, cy1:5,  cx2:40,cy2:0,  ex:38, ey:-5},  // left neck
    {cx1:38, cy1:-5, cx2:20,cy2:-20,ex:50, ey:-20}, // left head
    {cx1:50, cy1:-20,cx2:80,cy2:-20,ex:62, ey:-5},  // right head
    {cx1:62, cy1:-5, cx2:60,cy2:0,  ex:63, ey:5},   // right neck
    {cx1:63, cy1:5,  cx2:65,cy2:15, ex:100,ey:0},   // right shoulder
  ]);
    }

    function mirror(b,signX,signY,x,y){
    var a=[];
         for(var i=0;i<b.length;i++){
    var bb=b[i];
    a.push({
      cx1: bb.cx1*signX+x,
      cy1: bb.cy1*signY+y,
      cx2: bb.cx2*signX+x,
      cy2: bb.cy2*signY+y,
      ex:  bb.ex*signX+x,
      ey:  bb.ey*signY+y
    });
  }
  return(a);
}
Run Code Online (Sandbox Code Playgroud)
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas" width=300 height=300></canvas>
Run Code Online (Sandbox Code Playgroud)