Edit: Ran the code on a more powerful computer and the grid rendered correctly. Possible hardware limitation? Computer where the problem occurred was a Samsung series 3 Chromebook. I'm thinking it has to do with trying to draw too many lines at the same time. Will test later.
I'm trying to draw a grid onto a canvas using the lineTo() method. The lines draw properly in the beginning, but any line that is drawn completely past 2048 pixels either down or to the right doesn't show up. Line going from inside this point to past it still show up on the other side, just lines that only are only drawn past the point don't show up.
Here's my JavaScript:
function drawGrid() {
//data.tiles is the map stored as an array of arrays
//tilesize = 60
var bw = data.tiles[0].length * tilesize;
var bh = data.tiles.length * tilesize;
ctx.beginPath();
for (i = bw; i >= 0; i -= tilesize) {
ctx.moveTo(i, 0);
ctx.lineTo(i, bh);
}
for (i = bh; i >= 0; i -= tilesize) {
ctx.moveTo(0, i);
ctx.lineTo(bw,i);
}
ctx.strokeStyle = "black";
ctx.stroke();
}
I've checked the data.tiles variable, and it's reading the right number. Really have no idea what I messed up here.
HTML Canvas has a maximum render area depending on your browser & hardware.
once you exceed these limits well your done pretty much.
try pre-rendering or use multiple canvas' positioned with CSS.
If you can see images drawn beyond 2048 then there's no reason a lineTo wouldn't be drawn also.
In the code you calculate bw and bh in different ways. You might check if this is a problem. If not, we'll need to see more code.
// bw uses data.tiles[0]
var bw = data.tiles[0].length * tilesize;
// bh uses data.tiles with no subscript
var bh = data.tiles.length * tilesize;
Related
As I've ditched using Phaser, I've gotten more work done to the point of the sprite moving, and I finished programming the collision detection, but I need something more... I decided to make coins to collect for a currency. I tried programming them to appear at random points on the map using a randomRange function. I managed to get one coin spawn in the upper left corner of the canvas, but I made one slight change, and the coin just stopped spawning in the canvas at all. I've called the coins under the function collectibles, which I've called upon in the update and draw functions. Just adding them to the update and draw functions made my player sprite be locked in place! Do I need a JS library just to make the coins spawn?
Here's the code:
function collectibles () {
this.srcX = 0;
this.srcY = 0;
this.width = 20; //how wide the sprite is in pixels
this.height = 20; //how tall the sprite is in pixels
this.drawX = this.randomRange;
this.drawY = this.randomRange;
this.centerX = this.drawX + (this.width / 2);
this.centerY = this.drawY + (this.height / 2);
}
Any errors in console? If 'randomRange' is a function it lacks braces() at the end where you call it. 'this' might differ depending on where you call the function 'collectibles'.
I am experimenting with canvas and I am having some trouble.
Please see this codepen:
http://codepen.io/JonnyBoggon/pen/YGgKqQ
I would like to generate two (or more potentially) floating images - which collide - like the circles in my codepen. So, exactly as it is now, but with images rather than circles.
function makeAmpersands(num) {
var x, y, vx, vy, r, m, ke, colliding, src;
for (var i = 0; i < num; i++) {
x = Math.random() * canvas.width;
y = Math.random() * canvas.height;
vx = Math.random() * 1 - .5;
vy = Math.random() * 1 - .5;
r = 150;
m = density * (4 / 3) * Math.PI * Math.pow(r, 3);
ke = .5 * m * (vx + vx) * (vy + vy);
colliding = false;
src = siteURL+'/assets/img/floating-ampersand-1.png';
B.push(new ampersand(x, y, vx, vy, r, m, ke, colliding, src));
}
}
I have no idea how to turn those objects into an image object, with a different src for each.
Please excuse my lack of knowledge with canvas; this is my first attempt at creating something.
Any help would be greatly appreciated.
To load and render images using canvas 2D
Create and load image
Create an new Image object, assign the src the URL of the image you wish to load. The image will then begin to load. You will not be able to know how long the image may take to load so you need to either wait until the image fires the onload event or if you are sure that the resource will always be available only use the image if its complete property is === true
As I do not know if your images resource is reliable the code below is a mix of the above method, using the onload event to flag that the image has loaded.
var image = new Image(); // a new image object same as <img> tag
image.src = "imageURL"; // the url pointing to the image
image.onload = function(){ this.ready = true; }; // flag image ready
// This will not happen until
// the current code has exited
Draw an image onto the canvas.
To render the image use the 2D context function drawImage. This function has up to 9 arguments many of which are optional. For full details see MDN drawImage.
If you try to render the image before it has loaded then you will of course not see anything. If the image has an error during loading attempting to draw it may throw an error and stop your code from running. So always be sure your image is ready and safe to draw.
From the above image load snippet the following snippet renders the image
if(image.ready){ // is it ready
ctx.drawImage(image,x,y); // draw image with top left at x,y
}
Loading many images.
It is inefficient to check the image for ready each time you render it. Once ready it is always so. This answer shows how you can load many images. If you have an ongoing animation instead of calling the drawImages function when all images have loaded, call a function that starts the animation, or set a flag to indicate that all images have loaded and are safe to render.
A complete image render function.
The 2D API allows you to draw an image that is scaled, rotated, fade in/out. Rendering a image like this is sometimes called a sprite (From the old 16bit days)
Function to draw a scaled rotated faded image / sprite with the rotation around its center. x and y are the position on the canvas where the center will be. scale is 1 for no scale <1 for smaller, and >1 for larger. rot is the rotation with 0 being no rotation. Math.PI is 180 deg. Increasing rot will rotate in a clockwise direction decreasing will rotate the other way. alpha will set how transparent the image will be with 0 being invisible and 1 as fully visible. Trying to set global alpha with a value outside 0-1 range will result in no change. The code below does a check to ensure that alpha is clamped. If you trust the alpha value you can set globalAlpha directly
function drawSprite(image,x,y,scale,rot,alpha){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rot);
ctx.globalAlpha = alpha < 0 ? 0 : alpha > 1 ? 1 : alpha; // if you may have
// alpha values outside
// the normal range
ctx.drawImage(image,-image.width / 2, -image.height / 2);
}
// usage
drawSprite(image,x,y,1,0,1); // draws image without rotation or scale
drawSprite(image,x,y,0.5,Math.PI/2,0.5); // draws image rotated 90 deg
// scaled to half its size
// and semi transparent
The function leaves the current transform and alpha as is. If you render elsewhere (not using this function) you need to reset the current state of the 2D context.
To default
ctx.setTransform(1,0,0,1,0,0);
ctx.globalAlpha = 1;
To keep the current state use
ctx.save();
// draw all the sprites
ctx.restore();
I'm working on a game for a university assignment. The idea is that you defend the centre circle from the incoming asteroids (lines) by drawing a line (click, drag & release to draw a line) which blocks them. An asteroid hitting a line should destroy both the asteroid the line.
The problem I'm currently having is that the collision isn't being detected.
I have arrays of objects of both lines & asteroids. The lines consist of simply start & end x & y, the asteroids consist of a random speed & a random angle (their incoming angle) - the context is rotated, the asteroid drawn, & then it reset for the next line.
To detect collision, I use getImageData & check in front of the asteroids however many pixels the line will progress in that iteration (basically, their speed) & if the colour is red, it will destroy the asteroid - I haven't got round to destroying the line yet, will tackle that hurdle when I come to it (suggestions are welcome though).
function asteroids_draw() {
for (var i = 0; i < asteroids.length; i++) {
// Drawing setup
context.save();
context.translate(width / 2, height / 2);
context.rotate(asteroids[i].angle);
// Detecting close asteroids
if ((asteroids[i].distance - asteroids[i].speed) < planet.size) {
asteroids.splice(i, 1);
game_life_lost();
context.restore();
return;
} else if ((asteroids[i].distance - asteroids[i].speed) < 150){
asteroids[i].colour = '#FF0000';
}
// Scanning ahead for lines
for (var j = 0; j < asteroids[i].speed; j++) {
if (context.getImageData(asteroids[i].distance - j, 0, 1, 1).data[0] == 255) {
asteroids.splice(i, 1);
context.restore();
return;
}
}
// Drawing asteroid
context.beginPath();
context.moveTo(asteroids[i].distance -= asteroids[i].speed, 0);
context.lineTo(trig, 0);
context.strokeStyle = asteroids[i].colour;
context.stroke();
context.closePath();
context.restore();
}
}
The problem is, the asteroids never collide with the lines & I can't for the life of me see why, or see another simple way of doing it. Any advice would be much appreciated, thanks in advance.
I think your problem is that when you rotate the context, previously drawn items(lines) don't get rotated, only objects drawn after the rotation are rotated. See this page for more info.
You could try performing your asteroid/line intersection test before you translate and rotate the canvas, and use cosine and sine to find the x and y coordinates of the pixels you want to get image data from.
var pixelLocation =
[Math.cos(asteroids[i].angle) * j, Math.sin(asteroids[i].angle) * j];
if (context.getImageData(pixelLocation[0], pixelLocation[1], 1, 1).data[0] == 255) {
Just make sure your angle is in radians before passing to cos and sin.
I thought about the problem some more, & realised this method of doing things definitely isn't the best way. It should be doable without a view - a la Model View Controller design pattern. The best way to solve it would be to use maths!
There's simple maths for the intersection of two lines, but this needs intersection of two lines in a range. I found an algorithm which simplifies this further, using eight coordinates - the start x & y & the end x & y of the two lines.
I've posted the results. Thanks for the help.
See here - http://schnell.dreamhosters.com/nysbc/test6.php
JSFiddle - http://jsfiddle.net/VauFH/
The piece that draws text...
function draw_arc_text(ctx, str, radius){
ctx.save();
str = str.toUpperCase();
var radians_per_letter = 8 * Math.PI/180;
ctx.rotate((105 - (radius/60)) * Math.PI/180);
for (var n = 0; n < str.length; n++) {
ctx.save();
ctx.rotate(n * radians_per_letter);
ctx.fillText(str[n], 0, -radius);
ctx.restore();
}
ctx.restore();
}
As you can probably tell the spacing between the text on top of the colored discs is a little off. I've tried having a set amount of radians/degrees per letter, but the further out in radius you go the more that spacing becomes, so outermost text starts separating at a rapid pace. I've also tried working out some kind of formula that will incorporate radius into how much spacing each letter gets, but I can't seem to get that quite right either. Anyone have any ideas?
Also any tweaks in efficiency would be appreciated as well. I like to be as optimized as I can whenever possible.
Live Demo
probably a mathematicians worst nightmare :P. The following works though. Basically I just divide the result by the radius / 100. It gives the impression of equal spacing.
Another change I made, was to use requestAnimationFrame instead of interval. Intervals aren't very performant when compared to setTimeout and especially when compare to requestAnimationFrame for canvas. You'll notice you don't get a nasty hangup anymore when you leave the tab and go back to it.
I also got rid of the jQuery dependency because all you were using was document.ready so it seemed unneeded.
function draw_arc_text(ctx, str, radius){
ctx.save();
str = str.toUpperCase();
var textWidth = Math.round(ctx.measureText(str).width);
var radians_per_letter = (((textWidth/str.length)) * Math.PI/180)/(radius*.01);
ctx.rotate(95 * Math.PI/180);
for (var n = 0; n < str.length; n++) {
ctx.save();
ctx.rotate(n * radians_per_letter);
ctx.fillText(str[n], 0, -radius);
ctx.restore();
}
ctx.restore();
}
I don't have much experience with the canvas element, but is there any way you could use CSS' letter-spacing property on the text?
It seems certain tiles will not get drawn. I have a tileset split-up into 32x32 squares and uses a 2D 100x100 array to draw the map onto the canvas.
Then it sets the "viewport" for the player. Since it's one big map, the player is always centered on the edge, unless they run near the edge.
However, this has caused problems drawing the map. Red block is "player"
Somethings I found out was that, a higher viewport (15x10) will give the ability to draw SOME previously not-drawn tiles.
Here's the code. You can download the tileset to test on localhost or below on jsFiddle http://mystikrpg.com/images/all_tiles.png
Everything below is well commented.
Even if change viewport I do see some tiles get drawn, not all.
http://pastebin.com/cBTum1aQ
Here is jsFiddle: http://jsfiddle.net/weHXU/
Working Demo
Basically I took a different approach to drawing the tiles. Using putImageData and getImageData can cause performance issues, also by pre-allocating all the image data in the beginning you are incurring a memory penalty to start out with. You already have the data in the form of the image you might as well just reference it directly instead, and it should actually be faster.
Heres the method I use
for (y = 0; y <= vHeight; y++) {
for (x = 0; x <= vWidth; x++) {
theX = x * 32;
theY = y * 32;
var tile = (board[y+vY][x+vX]-1),
tileY = Math.floor(tile/tilesX),
tileX = Math.floor(tile%tilesX);
context.drawImage(imageObj, tileX*tileWidth, tileY*tileHeight, tileWidth, tileHeight, theX, theY, tileWidth, tileHeight);
}
}
Its almost the same, but instead of looking at an array of the pre saved data, I just reference the area of the already loaded image.
Explanation on getting the referenced tile
Say I have a grid thats 4x4, and I need to get tile number 7, to get the y I would do 7/4, then to get the x I would use the remainder of 7/4 (7 mod 4).
As for the original issue.. I really have no idea what was causing the missing tiles, I just changed it to my method so I could test from there but it worked right away for me.