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();
Related
I'm making a game on HTML5 canvas(It's infinite), and I want to make bushes in my game randomly from Perlin noise. I want chunks to be a 16 by 16 grid of tiles, and every tile is 85 pixels away from each other. I just draw the grid with this simple code:
ctx.lineWidth=4,ctx.strokeStyle="#000",ctx.globalAlpha=.06,ctx.beginPath();
for(var p=-5;p<canvas.width;p+=85){ctx.moveTo(p + (offset[0] % 85),0),ctx.lineTo(p + (offset[0] % 85),canvas.height)};
for(var m=-5;m<canvas.height;m+=85){ctx.moveTo(0,m + (offset[1] % 85)),ctx.lineTo(canvas.width,m + (offset[1] % 85))};
ctx.stroke();
ctx.globalAlpha=1;
here's the current code I have for bushes, but I don't know how to make it actually work:
for(x=offset[0];x<offset[0]+16;x++) {
for(y=offset[1];y<offset[1]+16;y++) {
if (noise.perlin2(x / 10, y/10) < -0.6) {
ctx.drawImage(treeimage, x, y)
}
}
}
I have access to the current chunk of the player, and the offset of objects based on the player's current position (just adding and subtracting to/from it when the player presses an arrow key).
EDIT:
Sorry, I didn't explain this correctly. The images show, I can't figure out how to make the bushes have the correct position on the screen when the player moves around, and/or make the 8 chunks around the player also load, but only show when stuff is on the screen.
Just an example:
Before I move just a bit:
After I move just a bit:
Before you call
ctx.drawImage(treeimage, x, y)
You must make sure that image is retrieved from server.
One way to do it is insert a little IMG tag on page and later retrieve image from it
<img id="treeimage" src="images/mapobjects/tree2.png" style="display:none;">
window.onload = function() { //use onload if you create drawing at page load
var treeimage = document.getElementById("treeimage");
ctx.drawImage(treeimage, x, y);
}
Another way is to create IMG tag by code and never display it but ensure it has onload function. After its called since now you can use your image anytime.
window.treeimage = document.createElement('img'); //make it global or take care about scope
treeimage.src = "images/mapobjects/tree2.png";
treeimage.onload = function(){
//only from now you can use that image
ctx.drawImage(treeimage, x, y);
};
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;
So I have a basic canvas setting, where sprites are added a little above the canvas and fall down the page, before being removed if their Y position is greater than the height of the canvas. It's not an impressive creation.
It all works fine, but what I'd really like is for each unique sprite to also fade out as it moves down the page. From what I've seen, there's no simple way to go about this.
Modifying the global alpha of the canvas context isn't good enough, because this affects the whole canvas at once (as far as I've seen). I want to affect each sprite individually - so it'll start with an opacity of 255 and gradually decrease to 0 as it also moves down the page.
Altering the image data seems like a pretty hefty task, especially considering the position of the images are always changing (well, vertically, at least) and there can be up to 60 of these on the page at one time.
I know I could (if I really wanted to) create and remove HTML image tags and modify each images opacity via CSS, but this also doesn't seem very practical, again considering I can have up to 60 on the page at any one time.
Is there any way I can achieve this, even if it's one of the aforementioned techniques made a little more efficient?
a) If you are only drawing those objects, you can just set the globalAlpha prior to any draw, like :
function drawSprite(x,y) {
ctx.globalAlpha = 1 - (y/canvasHeight) ;
ctx.drawImage(mySprite, x, y);
}
this way all draws are made with the right alpha.
(you have to define var canvasHeight=canvas.height earlier)
b) if you perform some other operations and you're not sure next operation will set the globalAlpha, just restore it to one after the draw (all other operations are supposed to use an alpha of 1 here ):
function drawSprite(x,y) {
ctx.globalAlpha = 1 - (y/canvasHeight) ;
ctx.drawImage(mySprite, x, y);
ctx.globalAlpha = 1 ;
}
c) another flavor might be to save/restore the globalAlpha by yourself :
function drawSprite(x,y) {
var lastGlobalAlpha = ctx.globalAlpha ;
ctx.globalAlpha = 1 - (y/canvasHeight) ;
ctx.drawImage(mySprite, x, y);
ctx.globalAlpha = lastGlobalAlpha ;
}
this way you're sure drawSprite won't affect current globalAlpha, whatever its value.
d) Lastly you'll have to throw an eye at ctx.save() and ctx.restore() which allow you to perform local changes that won't affect other draws. Since, here, you only change globalAlpha, you'd better use a), b) or best : c), but i'll just write the code for the record :
function drawSprite(x,y) {
ctx.save();
ctx.globalAlpha = 1 - (y/canvasHeight) ;
ctx.drawImage(mySprite, x, y);
ctx.restore();
}
My Problem: I've got an ImageObject, that is being used to create a PatternObject. My problem is, that i changed the width and height properties of the Image before creating the pattern, but that doesn't actually remap the image (and that is also no problem). The thing is, that I now have got a pattern, that is of a different size (the original image size) than the image itself. If I want to draw a line with the fillStyle of that pattern, it doesn't fit (because I need a pattern of the new size).
My question: Is there an easy way, to achieve that the pattern's width and height can be adjusted?
My tries and why I dont like them:
1) Render the original image to a canvas with the new size and create the pattern from that. Didn't use this, because the pattern cannot be loaded directly as a result of the canvas being created and rendered to too slowly. But I want that pattern directly
2) Calculate the variance between the new image size and the original one, change the lineWidth of the context, so the patterns height fits exactly and scale the line down, so it has a nice size. Didn't use that because I render in realtime and this is way too slow to be used later in webapps.
Using canvas (your step 1) is the most flexible way.
It's not slower using a canvas to draw on another canvas than using an image directly. They both use the same element basis (you're blitting a bitmap just as you do with an image).
(Update: Drawing using pattern as style do go through an extra step of a local transformation matrix for the pattern in more recent browsers.)
Create a new canvas in the size of the pattern and simple draw the image into it:
patternCtx.drawImage(img, 0, 0, patternWidth, patternHeight);
Then use the canvas of patternCtx as basis for the pattern (internally the pattern caches this image the first time it's drawn, from there, if possible, it just doubles out what it has until the whole canvas is filled).
The other option is to pre-scale the images to all the sizes you need them to be, load them all in, and then choose the image which size is the one you need.
The third is to draw the image yourself as a pattern. This however is not so efficient compared to the built-in method, though using the above mentioned method (internal) you can get a usable result.
Example of manual patterning:
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
fillPattern(this, 64, 64);
change.onchange = change.oninput = function() {
fillPattern(img, this.value, this.value);
}
};
img.src = "//i.stack.imgur.com/tkBVh.png";
// Fills canvas with image as pattern at size w,h
function fillPattern(img, w, h) {
//draw once
ctx.drawImage(img, 0, 0, w, h);
while (w < canvas.width) {
ctx.drawImage(canvas, w, 0);
w <<= 1; // shift left 1 = *2 but slightly faster
}
while (h < canvas.height) {
ctx.drawImage(canvas, 0, h);
h <<= 1;
}
}
<input id=change type=range min=8 max=120 value=64><br>
<canvas id=canvas width=500 height=400></canvas>
(or with a video as pattern).
The question title may be vague. Basically, imagine a racing game built in canvas. The track takes up 10,000 x 10,000 pixels of screen space. However the browser window is 500 x 500 pixels. The car should stay centered in the browser and the 'viewable' area of the 10,000 x 10,000 canvas will change. Otherwise the car would just drive off the edge at disappear.
Does this technique have a name?
What are the basic principles to make this happen?
If the car should stay at the same position (relative to the canvas' position), then you should not move the car. Instead, move the background picture/track/map to the other side.
Causing your eyes to think the car moves right can be done by either moving the car to the right, or by moving the map to the left. The second option seems to be what you want, since the car won't move whereas the viewable area (i.e. the map) will.
This is a quick demo from scratch: http://jsfiddle.net/vXsqM/.
It comes down to altering the map's position the other way round:
$("body").on("keydown", function(e) {
if(e.which === 37) pos.x += speed; // left key, so move map to the right
if(e.which === 38) pos.y += speed;
if(e.which === 39) pos.x -= speed;
if(e.which === 40) pos.y -= speed;
// make sure you can't move the map too far.
// clamp does: if x < -250 return -250
// if x > 0 return 0
// else it's allowed, so just return x
pos.x = clamp(pos.x, -250, 0);
pos.y = clamp(pos.y, -250, 0);
draw();
});
You can then draw the map with the position saved:
ctx.drawImage(img, pos.x, pos.y);
If you're looking for a way to actually move the car when the map cannot be moved any further (because you're driving the car close to a side of the map), then you'd have to extend the clamping and also keep track of when the car should be moved and how far: http://jsfiddle.net/vXsqM/1/.
// for x coordinate:
function clamp2(x, y, a, b) { // x = car x, y = map x, a = min map x, b = max map x
return y > b ? -y : y < a ? a - y : x;
}
The position clamping then becomes a little more complex:
// calculate how much car should be moved
posCar.x = clamp2(posCar.x, posMap.x, -250, 0);
posCar.y = clamp2(posCar.y, posMap.y, -250, 0);
// also don't allow the car to be moved off the map
posCar.x = clamp(posCar.x, -100, 100);
posCar.y = clamp(posCar.y, -100, 100);
// calculate where the map should be drawn
posMapReal.x = clamp(posMap.x, -250, 0);
posMapReal.y = clamp(posMap.y, -250, 0);
// keep track of where the map virtually is, to calculate car position
posMap.x = clamp(posMap.x, -250 - 100, 0 + 100);
posMap.y = clamp(posMap.y, -250 - 100, 0 + 100);
// the 100 is because the car (circle in demo) has a radius of 25 and can
// be moved max 100 pixels to the left and right (it then hits the side)
Two things:
Canvas transformation methods
First, the canvas transformation methods (along with context.save() and context.restore() are your friends and will greatly simplify the math needed to view a portion of a large 'world`. If you use this approach, you can get the desired behavior just by specifying the portion of the world that is visible and the world-coordinates of everything you want to draw.
This is not the only way of doing things* but the transformation methods are meant for exactly this kind of problem. You can use them or you can reinvent them by manually keeping track of where your background should be drawn, etc., etc.
Here's an example of how to use them, adapted from a project of mine:
function(outer, inner, ctx, drawFunction) {
//Save state so we can return to a clean transform matrix.
ctx.save();
//Clip so that we cannot draw outside of rectangle defined by `outer`
ctx.beginPath();
ctx.moveTo(outer.left, outer.top);
ctx.lineTo(outer.right, outer.top);
ctx.lineTo(outer.right, outer.bottom);
ctx.lineTo(outer.left, outer.bottom);
ctx.closePath();
//draw a border before clipping so we can see our viewport
ctx.stroke();
ctx.clip();
//transform the canvas so that the rectangle defined by `inner` fills the
//rectangle defined by `outer`.
var ratioWidth = (outer.right - outer.left) / (inner.right - inner.left);
var ratioHeight = (outer.bottom - outer.top) / (inner.bottom - inner.top);
ctx.translate(outer.left, outer.top);
ctx.scale(ratioWidth, ratioHeight);
ctx.translate(-inner.left, -inner.top);
//here I assume that your drawing code is a function that takes the context
//and draws your world into it. For performance reasons, you should
//probably pass `inner` as an argument too; if your draw function knows what
//portion of the world it is drawing, it can ignore things outside of that
//region.
drawFunction(ctx);
//go back to the previous canvas state.
ctx.restore();
};
If you are clever, you can use this to create multiple viewports, picture-in-pictures, etc. of different sizes and zoom in and out on stuff.
Performance
Second, as I commented in the code, you should make sure your drawing code knows what portion of your larger 'world' will be visible so that you don't do a lot of work trying to draw things that will not be visible.
The canvas transformation methods are meant for solving exactly this kind of problem. Use 'em!
*You will likely have problems if your world is so large that its coordinates cannot fit in an appropriate integer. You'll hit that problem roughly when your world exceeds billion (10^9) or a long trillion (10^18) pixels in any dimension, depending on whether the integers are 32- or 64-bit. If your world isn't measured in pixels but in 'world units', you'll run into problems when your world's total size and smallest feature scale lead to floating point inaccuracies. In that case, you will need to do extra work to keep track of things... but you'll probably still want to use the canvas transformation methods!
My very first game was a racing game where I moved the background instead of the car and although I want to think now that I had my reasons to make it so... I just didn't know better.
There are a few techniques that you need to know to achieve this well.
Tiled background. You need to make your track out of smaller pieces that tiled. To To draw 10,000 x 10,000 pixels is 100MPix image usually such image will have 32bit depth (4 bytes) this will end up being 400MB in memory. Compressions like PNG, JPEG won't help you since these are made to store and transfer images. They cant be rendered to a canvas without decompressing.
Move the car along your track. There is nothing worst then moving the BG under the car. If you need to add more features to your game like AI cars... now they will have to move along the map and to implement car collisions you need to make some not hard but strange spacial transformations.
Add camera entity. The camera needs to have position and viewport size (this is the size of your canvas). The camera will make or break your game. This is the entity that will give you the sense of speed in the game... You can have a camera shake for collisions, if you have drifts if your game the camera can slide pass the desired position and center back to the car, etc. Of course the most important thing will be tracking the car. Some simple suggestions I can give you are to not put the car in dead center of the camera. put the car a little behind so you can see a bit more what's in front of your. The faster the car moves the more you should offset the camera. Also you can't just compute the position of the camera instead compute desired position and slowly per frame move the current camera position to the desired position.
Now when you have camera and a large tiled map, when you draw the tiles you have to subtrack the camera position. You can also compute which tiles are not visible and skip them. This technique will allow you do extend your game with even larger maps or you can stream your map where you don't have all the tiles loaded and load in advance on background (AJAX) what will be visible soon.