简体   繁体   中英

How can I achieve a wraparound effect in an arcade-like game?

I am working on a clone of the game Spacewar! . In the game, ships can travel to the edge of the map and wrap around to the other side, at times split in half, one half on each side of the screen (when the ships go into the corners they are split in four). You can play the game here .

Right now I am using the modulus operator to wrap a div around the screen, but it doesn't split the block in half like I would it to. Is there any way this is possible in JavaScript or jQuery?

That depend on how you are rendering the ships. I am assuming you are using sprites. Let:

  • xs,ys be the target screen resolution
  • txs,tys be the sprite resolution
  • x0,y0 is sprite position (top left corner)
  • x -axis is going right, y -axis is going down and (0,0) is top left corner for booth sprite and screen

There are two basic approaches I know of:

  1. Wrapping coordinates directly in sprite render.

    This is the easiest way but you need access to the sprite render. Find the rendering loops they should look like this:

     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; } 

    So just add the x,y wraps to it:

     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; } } 

    Of coarse these conditions inside rendering loop are slowing things down a bit. So you can optimize by copying the code into 4 instances each on its own set of coordinates so the if s are not inside loops anymore.

  2. dividing wrapped sprites

    This approach is a bit faster (no additional if s in the rendering loop) and also does not require any tampering with rendering code. The idea is render non wrapped sprites as usual and if wrapping is detected render 2/4 sprite copies at wrapped positions

    概观

    So in the code it would be like this:

     // 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); 

    BTW. this approach can be done in geometry GLSL shader if you are considering to use it.

Wrapping sprites

Interesting link you have provided. They have implemented a full CPU emulation and running the game written in assembly.

Improving the modulo

Anyway if you are using the canvas to render sprites (images) the easiest way is a simple modulo with a modification to handle negative values.

The normal modulo breaks down when using negative values.

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

You need the modulo to always return a positive value in the correct direction. To handle the negative number do the modulo twice, the first will get within the range you want but +/- the range. Then make the negative positive by adding the range. Then just do the modulo again.

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

Simple wrapping

Using the above modulo algorithm it is then a simple matter to check boundaries and render the sprite as needed.

// 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
    }
}

Wrapping rotated sprites

As the game has sprites that are rotated the above function will not work as the rotation will change the size of the sprite. To handle the rotated sprite you need to check the max size it can be. This is the length of the diagonal line across the sprite which can be found with sqrt(width * width + height * height)

Assuming that you want the sprite rotated about its center you can find the sprite max extent (top,bottom,left and right) by subtracting and adding half the diagonal to the x,y center position. The just as the first function, do the modulo and draw the sprite as needed.

There will be some situations where the sprite is drawn on the opposite side even though it is not visible. If you are drawing lots of sprites (100+) you may want to get the exact extent rather than the max extent but then you will have to transform at least one corner of the sprite to get the horizontal and vertical extent. Then just use those values rather than diag as in the function.

// 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 
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM