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:
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.
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.
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.