I am working on a Canvas Game on HTML5 Canvas with Vanilla JS.
For some reason I am noticing that when I set the player sprite to be drawn in at on the x-axis at x=0, the player appears indented to the right.
(this appears to be disrupting my collision detection)
I have the same issue with other sprites I have generated with the piskel app. Another sprite I used from another creator didn't have this issue.
Does anyone have any suggestions?
Here is a link to my game: http://zcbuhler.github.io/spaceDrift
The player should be at 0 on the x-axis for the starting point, but as you can see appears to be tabbed over.
General purpose sprite rendering
I render sprites with the following function.
// assumes ctx is scoped and is the rendering 2d context
function drawSprite(img, x, y, scale, rotate, alpha){
var w = img.width;
var h = img.height;
ctx.setTransform(scale, 0, 0 ,scale, x, y);
ctx.rotate(rotate);
ctx.globalAlpha = alpha;
ctx.drawImage(img, 0, 0, w, h, -w/2,-h/2, w, h);
}
It draws the sprite with its center at x,y. It is scaled and and rotated and its alpha is set. On a average laptop and on firefox it can do 2000+ sprites in realtime. On chrome its about 1000+
To set the center point use
// assumes ctx is scoped and is the rendering 2d context
function drawSpriteCenter(img, x, y, cx, cy, scale, rotate, alpha){
var w = img.width;
var h = img.height;
ctx.setTransform(scale, 0, 0 ,scale, x, y);
ctx.rotate(rotate);
ctx.globalAlpha = alpha;
ctx.drawImage(img, 0, 0, w, h, -cx,-cy, w, h);
}
where cx, and cy s the center point of the sprite. (the point around which it rotates)
To draw a sprite with a center cx,cy at x,y and a scale for x and y, rotated with alpha.
// assumes ctx is scoped and is the rendering 2d context
function drawSpriteFull(img, x, y, cx, cy, scaleX, scaleY, rotate, alpha){
var w = img.width;
var h = img.height;
ctx.setTransform(scaleX, 0, 0 ,scaleY, x, y);
ctx.rotate(rotate);
ctx.globalAlpha = alpha;
ctx.drawImage(img, 0, 0, w, h, -cx, -cy, w, h);
}
The functions modify the current transform and alpha. To reset the canvas state once you are done rendering the sprites you can use
function resetCtx(){
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 1;
}
You should be able to correct the player's position like #Bangkokian pointed out.
Simply wrap that in a function like so:
function positionPlayer(x, y) {
player.pos = [x -= (player.w / 2), y];
}
or even make that a method of the players object.
The collision detection could be solved similarly by altering the 'checkCollision' function to something like:
var checkCollision = function(rect1, rect2) {
// debugger
// centers
var rect1CenteredPos = [rect1.pos[0] - (rect1.w / 2), rect1.pos[1] - (rect1.h / 2)];
var rect2CenteredPos = [rect2.pos[0] - (rect2.w / 2), rect2.pos[1] - (rect2.h / 2)];
// console.log(bulletsArray.length);
if (rect1CenteredPos[0] < rect2CenteredPos[0] + rect2.w &&
rect1CenteredPos[0] + rect1.w > rect2CenteredPos[0] &&
rect1CenteredPos[1] < rect2CenteredPos[1] + rect2.h &&
rect1.h + rect1CenteredPos[1] > rect2CenteredPos[1]) {
return true;
}
return false;
}
Related
I have this awesome piece of code.
The idea, as you can imagine,is to draw a grid of rectangles. I want a big grid, let's say 100 X 100 or more.
However, when i run the awesome piece of code for the desired size (100X 100), my browser crashes.
How can i achieve that?
* please note: when i say 100X100 i mean the final number of rectangles (10k) not the size of the canvas.
thank u
function init() {
var cnv = get('cnv');
var ctx = cnv.getContext('2d');
var ancho = 12; // ancho means width
var alto = 12; // alto means height
ctx.fillStyle = randomRGB();
for (var i = 0; i < cnv.width; i+= ancho) {
for (var j = 0; j < cnv.height; j+= alto) {
//dibujar means to draw, rectangulo means rectangle
dibujarRectangulo(i+ 1, j+1, ancho, alto, ctx);
}
}
}
function dibujarRectangulo(x, y, ancho, alto, ctx) {
ctx.rect(x, y, ancho, alto);
ctx.fill();
ctx.closePath();
}
The dibujarRectanglo() function calls rect() function which adds a closed rectanglar subpath to the current path. Then calls fill() function to fill the current path. Then calls closePath() function to close the subpath, which does nothing since the subpath is already closed.
In other words, the first dibujarRectanglo() function call is painting a path that contains 1 rectangle subpath. The second call is painting a path that contains 2 rectangle subpaths. The third call is painting a path that contains 3 rectangle subpaths. And so on. If the loop calls dibujarRectanglo() function 10000 times then a total of 1+2+3+...+10000 = 50005000 (i.e. over 50 million) rectangle subpaths will be painted.
The dibujarRectangle() function should be starting a new path each time. For example...
function dibujarRectangulo(x, y, ancho, alto, ctx) {
ctx.beginPath();
ctx.rect(x, y, ancho, alto);
ctx.fill();
}
Then 10000 calls will only paint 10000 rectangle subpaths which is a lot faster that painting 50 million rectangle subpaths.
16,384 boxes on the wall
As I said in the comment its easy to draw a lot of boxes, it is not easy to have them all behave uniquely. Anyways using render to self to duplicate boxes exponential there are 128 * 128 boxes so that's 16K, one more iteration and it would be 64K boxes.
Its a cheat, I could have just drawn random pixels and called each pixel a box.
Using canvas you will get upto 4000 sprites per frame on a top end machine using FireFox with each sprite having a location, center point, rotation, x and y scale, and an alpha value. But that is the machine going flat out.
Using WebGL you can get much higher but the code complexity goes up.
I use a general rule of thumb, if a canva 2D project has more than 1000 sprites then it is in need of redesign.
var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
/** CreateImage.js begin **/
var createImage = function (w, h) {
var image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
/** CreateImage.js end **/
/** FrameUpdate.js begin **/
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
var boxSize = 10;
var boxSizeH = 5;
var timeDiv = 1.2;
var bBSize = boxSize * 128; // back buffer ssize
var buff = createImage(bBSize, bBSize);
var rec = createImage(boxSize, boxSize);
var drawRec = function (ctx, time) {
var size, x, y;
size = (Math.sin(time / 200) + 1) * boxSizeH;
ctx.fillStyle = "hsl(" + Math.floor((Math.sin(time / 500) + 1) * 180) + ",100%,50%)";
ctx.strokeStyle = "Black";
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.clearRect(0, 0, boxSize, boxSize);
x = Math.cos(time / 400);
y = Math.sin(time / 400);
ctx.setTransform(x, y, -y, x, boxSizeH, boxSizeH)
ctx.fillRect(-boxSizeH + size, -boxSizeH + size, boxSize - 2 * size, boxSize - 2 * size);
ctx.strokeRect(-boxSizeH + size, -boxSizeH + size, boxSize - 2 * size, boxSize - 2 * size);
}
function update(time) {
var fw, fh, px, py, i;
time /= 7;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, w, h);
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.clearRect(0, 0, bBSize, bBSize)
buff.ctx.drawImage(rec, 0, 0);
buff.ctx.drawImage(rec, boxSize, 0);
fw = boxSize + boxSize; // curent copy area width
fh = boxSize; // curent copy area height
px = 0; // current copy to x pos
py = boxSize; // current copy to y pos
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh); // make square
for (i = 0; i < 6; i++) {
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.drawImage(rec, 0, 0);
fh += fh; // double size across
px = fw;
py = 0;
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh); // make rec
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.drawImage(rec, 0, 0);
fw += fw; // double size down
px = 0;
py = fh;
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh);
}
// draw the boxes onto the canvas,
ctx.drawImage(buff, 0, 0, 1024, 1024);
requestAnimationFrame(update);
}
update();
.canv {
width:1024px;
height:1024px;
}
<canvas id="can" class = "canv" width=1024 height=1024></canvas>
I'm trying to draw a pair of skis (two rectangles) on a skier (a square) at varying rotations. I don't quite understand how you line up rotated elements on a canvas as you rotate the whole canvas, not just the object.
At the moment I have this:
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = skiier.color;
ctx.fillRect(skiier.x, skiier.y, skiier.width, skiier.height);
ctx.fillStyle = '#00f';
var angle = 20;
ctx.rotate(angle*Math.PI/180);
ctx.fillRect(skiier.x, skiier.y,100,10);
ctx.fillRect(skiier.x, skiier.y + 20,100,10);
ctx.rotate(-angle*Math.PI/180);
Which gives me this:
But what I'd like to do is the following:
Bearing in mind the x and y coords of the skier is constantly changing, how can I adjust and position the skis relative to him?
I have a demo here if it helps:
http://codepen.io/EightArmsHQ/pen/cfa7052ed205b664b066450910c830c5?editors=001
You should consider the context save, restore and translate as follows:
// ...
var angle = 20;
ctx.save(); // save the state of the ctx
ctx.translate(skiier.x, skiier.y); // translate your context point to be the same as skiier.
ctx.rotate(angle * Math.PI / 180);
ctx.fillRect(-25, 0, 100, 10); // You can draw from new context point.
ctx.fillRect(-25, 20, 100, 10); // Same here.
// Instead of rotating back, use restore()...
// Useful to restore all ctx options as they were before the save()
ctx.restore();
To rotate around a point: you need to translate context to the point, rotate context, translate context back.
For example...
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = skiier.color;
ctx.fillRect(skiier.x, skiier.y, skiier.width, skiier.height);
ctx.fillStyle = '#00f';
var angle = 20;
ctx.translate(skiier.x, skiier.y);
ctx.rotate(angle*Math.PI/180);
ctx.translate(-skiier.x, -skiier.y);
ctx.fillRect(skiier.x, skiier.y,100,10);
ctx.fillRect(skiier.x, skiier.y + 20,100,10);
ctx.rotate(-angle*Math.PI/180);
I have a texture which I use drawImage() over a clipping plane. But i have an issue where by I can't figure out how to wrap it around once it's moved more than the width of the texture so it loops indefinitely, it also does not clip for some reason.
My draw code looks like this:
var radius = 120;
var pos = {'x':canvas.width/2,'y':canvas.height/2};
var x = 0;
var offsetX = 0;
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
x += 1.1415;
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.clip();
var scale = (radius * 2) / img.height;
ctx.drawImage(img, pos.x+x, pos.y, img.width, img.height, pos.x - radius - offsetX * scale, pos.y - radius, img.width * scale, img.height * scale);
ctx.restore();
requestAnimationFrame(draw);
}
I have created the demo here so you can see what happens when the texture moves too far, it basically disappears and i need it to loop again without a gap between so its seamless: http://jsfiddle.net/dv2r8zpv/
What is the best way to draw the texture's position so it will wrap around, i don't quite understand how to do it.
I have created a seamless loop now. The code I change are below. Changing the y on drawImage encapsulates the whole circle
if (x > img.width) {
x = 0;
}
ctx.drawImage(img, x, 0, ...);
ctx.drawImage(img, -img.width+x,0, ...);
Updated answer with full circle
I have built a canvas project with https://github.com/petalvlad/angular-canvas-ext
<canvas width="640" height="480" ng-show="activeateCanvas" ap-canvas src="src" image="image" zoomable="true" frame="frame" scale="scale" offset="offset"></canvas>
I am successfully able to zoom and pan the image using following code
scope.zoomIn = function() {
scope.scale *= 1.2;
}
scope.zoomOut = function() {
scope.scale /= 1.2;
}
Additionally I want to rotate the image. any help i can get with which library i can use and how can i do it inside angularjs.
You can rotate an image using context.rotate function in JavaScript.
Here is an example of how to do this:
var canvas = null;
var ctx = null;
var angleInDegrees = 0;
var image;
var timerid;
function imageLoaded() {
image = document.createElement("img");
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
image.onload = function() {
ctx.drawImage(image, canvas.width / 2 - image.width / 2, canvas.height / 2 - image.height / 2);
};
image.src = "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcT7vR66BWT_HdVJpwxGJoGBJl5HYfiSKDrsYrzw7kqf2yP6sNyJtHdaAQ";
}
function drawRotated(degrees) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(degrees * Math.PI / 180);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
ctx.restore();
}
<button onclick="imageLoaded();">Load Image</button>
<div>
<canvas id="canvas" width=360 height=360></canvas><br>
<button onclick="javascript:clearInterval(timerid);
timerid = setInterval(function() {
angleInDegrees += 2;
drawRotated(angleInDegrees);
}, 100);">
Rotate Left
</button>
<button onclick="javascript:clearInterval(timerid);
timerid = setInterval(function() {
angleInDegrees -= 2;
drawRotated(angleInDegrees);
}, 100);">
Rotate Right
</button>
</div>
With curtesy to this page!
Once you can get your hands on the canvas context:
// save the context's co-ordinate system before
// we screw with it
context.save();
// move the origin to 50, 35 (for example)
context.translate(50, 35);
// now move across and down half the
// width and height of the image (which is 128 x 128)
context.translate(64, 64);
// rotate around this point
context.rotate(0.5);
// then draw the image back and up
context.drawImage(logoImage, -64, -64);
// and restore the co-ordinate system to its default
// top left origin with no rotation
context.restore();
To do it in a single state change. The ctx transformation matrix has 6 parts. ctx.setTransform(a,b,c,d,e,f); (a,b) represent the x,y direction and scale the top of the image will be drawn along. (c,d) represent the x,y direction and scale the side of the image will be drawn along. (e,f) represent the x,y location the image will be draw.
The default matrix (identity matrix) is ctx.setTransform(1,0,0,1,0,0) draw the top in the direction (1,0) draw the side in the direction (0,1) and draw everything at x = 0, y = 0.
Reducing state changes improves the rendering speed. When its just a few images that are draw then it does not matter that much, but if you want to draw 1000+ images at 60 frames a second for a game you need to minimise state changes. You should also avoid using save and restore if you can.
The function draws an image rotated and scaled around its center point that will be at x,y. Scale less than 1 makes the images smaller, greater than one makes it bigger. ang is in radians with 0 having no rotation, Math.PI is 180deg and Math.PI*0.5 Math.PI*1.5 are 90 and 270deg respectively.
function drawImage(ctx, img, x, y, scale, ang){
var vx = Math.cos(ang) * scale; // create the vector along the image top
var vy = Math.sin(ang) * scale; //
// this provides us with a,b,c,d parts of the transform
// a = vx, b = vy, c = -vy, and d = vx.
// The vector (c,d) is perpendicular (90deg) to (a,b)
// now work out e and f
var imH = -(img.Height / 2); // get half the image height and width
var imW = -(img.Width / 2);
x += imW * vx + imH * -vy; // add the rotated offset by mutliplying
y += imW * vy + imH * vx; // width by the top vector (vx,vy) and height by
// the side vector (-vy,vx)
// set the transform
ctx.setTransform(vx, vy, -vy, vx, x, y);
// draw the image.
ctx.drawImage(img, 0, 0);
// if needed to restore the ctx state to default but should only
// do this if you don't repeatably call this function.
ctx.setTransform(1, 0, 0, 1, 0, 0); // restores the ctx state back to default
}
I have a canvas with an id of cnv.
<canvas id='cnv'></canvas>
The dark rectangle is the whole canvas. I want to create another canvas that contains only the white region in the old canvas. How will I transfer a part of a canvas to a new canvas?
var cnv = document.getElementById('cnv');
I don't know what to do next in my code above.
Assuming you have the region specificed in x, y, width and height, you can do:
function regionToCanvas(canvas, x, y, w, h) {
var c = document.createElement("canvas"), // create new canvas
ctx = c.getContext("2d"); // context for new canvas
c.width = w; // set size = w/h
c.height = h;
ctx.drawImage(canvas, x, y, w, h, 0, 0, w, h); // draw in region at (0,0)
return c; // return canvas
}
Then call, example:
var newCanvas = regionToCanvas(cnv, x, y, width, height);
document.body.appendChild(newCanvas); // add to DOM