如何在类似街机的游戏中实现环绕效果?

时间:2016-09-21 01:23:52

标签: javascript jquery animation graphics 2d-games

我正在制作游戏 Spacewar!的克隆版。在游戏中,船只可以行进到地图的边缘并环绕到另一侧,有时会分成两半,一半在屏幕的两侧(当船进入角落时,它们被分成四个)。你可以玩游戏here

现在我正在使用模数运算符在屏幕周围包裹div,但它并没有像我想的那样将块分割成两半。有没有办法在JavaScript或jQuery中实现这一点?

2 个答案:

答案 0 :(得分:1)

这取决于你如何渲染船只。我假设你正在使用精灵。让:

  • xs,ys是目标屏幕分辨率
  • txs,tys是精灵分辨率
  • x0,y0是精灵位置(左上角)
  • x - 轴向右,y - 轴向下,(0,0)在展位精灵和屏幕的左上角

我知道有两种基本方法:

  1. 直接在精灵渲染中包裹坐标。

    这是最简单的方法,但您需要访问精灵渲染。找到它们看起来像这样的渲染循环:

    for (y=y0,ty=0;ty<tys;ty++,y++)
     for (x=x0,tx=0;tx<txs;tx++,x++)
      {
      color=sprite[ty][tx];
      if (color!=color_transparent) screen[y][x]=color;
      }
    

    所以只需添加x,y换行符:

    while (x0>=xs) x0-=xs; // just normalize start position
    while (x0<  0) x0+=xs;
    while (y0>=ys) y0-=ys;
    while (y0<  0) y0+=ys;
    for (y=y0,ty=0;ty<tys;ty++,y++)
     {
     if (y>=ys) y=0; // wrap y
     for (x=x0,tx=0;tx<txs;tx++,x++)
      {
      if (x>=xs) x=0; // wrap x
      color=sprite[ty][tx];
      if (color!=color_transparent) screen[y][x]=color;
      }
     }
    

    粗略的渲染循环中的这些条件正在减慢一些事情。因此,您可以通过将代码复制到4个实例中进行优化,每个实例位于其自己的坐标集上,因此if不再位于循环内部。

  2. 分割包裹的精灵

    这种方法有点快(渲染循环中没有额外的if),也不需要任何篡改渲染代码。这个想法是像往常一样呈现非包装精灵,如果检测到包裹,则在包裹位置渲染2/4精灵副本

    overview

    所以在代码中它会是这样的:

    // just normalize start position
    while (x0>=xs) x0-=xs;
    while (x0<  0) x0+=xs;
    while (y0>=ys) y0-=ys;
    while (y0<  0) y0+=ys;
    render_sprite(x0,y0,sprite);
    // compute copies coordinates
    x1=x0; if (x1+txs>xs) x1-=xs;
    y1=y0; if (y1+tys>ys) y1-=ys;
    // render copies
    if  (x0!=x1)            render_sprite(x1,y0,sprite);
    if            (y0!=y1)  render_sprite(x0,y1,sprite);
    if ((x0!=x1)&&(y0!=y1)) render_sprite(x1,y1,sprite);
    

    顺便说一句。如果您考虑使用它,可以在几何 GLSL 着色器中完成此方法。

答案 1 :(得分:1)

包装精灵

您提供的有趣链接。他们已经实现了完整的CPU仿真并运行了用汇编编写的游戏。

改进模数

无论如何,如果你使用画布来渲染精灵(图像),最简单的方法是使用一个简单的模数来修改处理负值。

使用负值时,正常的模数会崩溃。

x = 100 % 200; // 100
x = 300 % 200; // 100 
x = -100 % 200; // -100  the wrong sign should be 100
x = -50 % 200;  // -50 wrong sign wrong direction should be 150

您需要模数始终以正确的方向返回正值。要处理负数,请执行两次模数,第一次将在您想要的范围内,但+/-范围。然后通过添加范围来做出负面正面。然后再做模数。

var range = 200;
var x = 150;
x = ((x % range) + range) % range; // result 150
x = -50;
x = ((x % range) + range) % range; // result 150 correct.

简单包装

使用上面的模数算法,检查边界并根据需要渲染精灵是一件简单的事情。

// assuming ctx is canvas context 2D
// canvas is the canvas.
// img is a HTMLImageElement

var canW = canvas.width;                               // the canvas size
var canH = canvas.height;                              
// draw a sprite that is wrapped around the edges of the canvas
function drawWrappedSprite(img,x,y){
    x1 = ((x % canW) + canW) % canW;                   // wrap around correcting for negative values
    y1 = ((y % canH) + canH) % canH;
    x2 = x1 + img.width;                               // get right and bottom
    y2 = y1 + img.height;
    ctx.drawImage(img, x1, y1);                        // draw first copy
    if(x2 > canW){                                     // check if touching right side
        ctx.drawImage(img, x1 - canW, y1);             // draw left copy
        if(y2 > canH){                                 // check if touching bottom
            ctx.drawImage(img, x1 - canW, y1 - canH);  // draw top copy
        }
    }
    if(y2 > canH){
        ctx.drawImage(img, x1 , y1 - canH);            // draw top copy
    }
}

包装旋转的精灵

由于游戏具有旋转的精灵,因此旋转将改变精灵的大小,因此上述功能将无效。要处理旋转的精灵,你需要检查它的最大尺寸。这是精灵对角线的长度,可以使用sqrt(width * width + height * height)

找到

假设您希望精灵围绕其中心旋转,您可以通过减去对角线的x,y中心位置减半并添加一半来找到精灵最大范围(顶部,底部,左侧和右侧)。作为第一个函数,按模数进行模拟并根据需要绘制精灵。

在某些情况下,精灵在相反的一侧绘制,即使它不可见。如果要绘制大量精灵(100+),可能需要获得精确范围而不是最大范围,但是您必须至少转换精灵的一个角来获得水平和垂直范围。然后只使用这些值,而不是函数中的diag

// assuming ctx is canvas context 2D
// canvas is the canvas.
// img is a HTMLImageElement

var canW = canvas.width;                               // the canvas size
var canH = canvas.height;  
// draws sprite rotated about its center by r
function drawWrappedRotatedSprite(img,x,y,r){  // x,y center pos, r rotation in radians
    var diag = Math.sqrt(img.width * img.width + img.height * img.height);  // get diagonal size
    diag /= 2;                                      // half the diagonal
    x1 = (((x - diag) % canW) + canW) % canW;       // get left extent position  and 
    y1 = (((y - diag) % canH) + canH) % canH;       // wrap around correcting for negative values
    var w = - img.width / 2;                        // get image width height
    var h = - img.height / 2                        // offset in rotated space
    x2 = x1 + diag * 2;                             // get right and bottom max extent
    y2 = y1 + diag * 2;
    ctx.setTransform(1,0,0,1,x1 + diag, y1 + diag); // set origin
    ctx.rotate(r);                                  // set rotation
    ctx.drawImage(img, w, h);                      // draw image rotated around its center    
    if(x2 > canW){                                  // check if touching right side
        ctx.setTransform(1,0,0,1,x1 + diag - canW, y1 + diag); // set origin
        ctx.rotate(r);                              // set rotation
        ctx.drawImage(img,w,h);                     // draw image rotated around its center    
        if(y2 > canH){                              // check if touching bottom
            ctx.setTransform(1,0,0,1,x1 + diag - canW, y1 + diag - canH); // set origin
            ctx.rotate(r);                          // set rotation
            ctx.drawImage(img,w,h);                 // draw image rotated around its center    
        }
    }
    if(y2 > canH){
        ctx.setTransform(1,0,0,1,x1 + diag, y1 + diag - canH); // set origin
        ctx.rotate(r);                              // set rotation
        ctx.drawImage(img,w,h);                     // draw top image rotated around its center    
    }

    ctx.setTransform(1,0,0,1,0,0); // reset the transform (should only do this after all sprites
                                   // using this function have been drawn 
}