Algorithm to draw sprites on infinite map - javascript

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

Related

Generating images in HTML5 canvas

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

Javascript sprites moving between two points

I'm wanting to get some sprites moving between two points in my (very basic) javascript game. Each sprite is randomly placed in the level, so I want them to move back and forth between their base position. I have the code below
function Taniwha(pos) {
this.basePos = this.pos;
this.size = new Vector(0.6, 1);
this.move = basePos + moveDist(5,0));
}
Taniwha.prototype.type = "taniwha"
var moveSpeed = 4;
Taniwha.prototype.act = function(step) {
this.move = ???
and this is where I get stuck. I'm not sure how to tell it to go left, back to base pos, right then back to base pos again (I plan to loop this). Does anyone have any advice? (also using Eloquen Javascript's example game as an outline/guide for this, if how I'm doing things seems odd!)
For horizontal movement, change x coordinate of the position.
var pos = { x: 1, y: 2 };
pos.x++ ; // This will move right
pos.x-- ; // This will move left
Likewise for vertical movement. You also need to update the coordinates after change for the object which you are drawing.
In truth ,there are lots of library to develop a game.
Use those, control a sprite is very easy.
Like:
Pixijs
CreateJS
Both of them are opensource project, you can watch and learn the source.
And have lots of examples and document.

Javascript inidividual sprite fade out on canvas?

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

JavaScript - putImageData doesn't draw every tile

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.

SVG animation along path with Raphael

I have a rather interesting issue with SVG animation.
I am animating along a circular path using Raphael
obj = canvas.circle(x, y, size);
path = canvas.circlePath(x, y, radius);
path = canvas.path(path); //generate path from path value string
obj.animateAlong(path, rate, false);
The circlePath method is one I have created myself to generate the circle path in SVG path notation:
Raphael.fn.circlePath = function(x , y, r) {
var s = "M" + x + "," + (y-r) + "A"+r+","+r+",0,1,1,"+(x-0.1)+","+(y-r)+" z";
return s;
}
So far, so good - everything works. I have my object (obj) animating along the circular path.
BUT:
The animation only works if I create the object at the same X, Y coords as the path itself.
If I start the animation from any other coordinates (say, half-way along the path) the object animates in a circle of the correct radius, however it starts the animation from the object X,Y coordinates, rather than along the path as it is displayed visually.
Ideally I would like to be able to stop/start the animation - the same problem occurs on restart. When I stop then restart the animation, it animates in a circle starting from the stopped X,Y.
UPDATE
I created a page that demonstrates the issue:
http://infinity.heroku.com/star_systems/48eff2552eeec9fe56cb9420a2e0fc9a1d3d73fb/demo
Click "start" to start the animation.
When you stop and re-start the animation, it continues from the current circle coords in a circle of the correct dimensions.
The problem is that Raphael has no way of knowing that the circle is already part-way along the path. The "start" function means just that -- start an animation. imo it would be broken if it did anything else.
That said, your use case is a valid one, and might warrant another function -- a 'pause' of some sort. Of course, getting that into trunk would take longer probably than you want to wait.
From the Raphael source code, here's what happens when you call 'stop'.
Element[proto].stop = function () {
animationElements[this.id] && animationElements[length]--;
delete animationElements[this.id];
return this;
};
This decrements the total number of animations, and removes that animation from the list. Here's what the 'pause' function might look like:
Element[proto].pause = function () {
animationElements[this.id] && animationElements[length]--;
this._paused_anim = animationElements[this.id];
delete animationElements[this.id];
return this;
};
this saves the animation to be resumed later. then
Element[proto].unpause = function () {
this._paused_anim && (animationElements[this.id]=this._paused_anim);
++animationElements[length] == 1 && animation();
return this;
};
would unpause. Given scoping conditions, these two functions might need to be injected right into the Raphael source code (it's core hacking, I know but sometimes there's no alternative). I would put it right below the "stop" function shown above.
Try that, and tell me how it goes.
====EDIT====
Ok, so it looks like you'll have to modify the "start" attribute of animationElements[this.id]... something like:
this._pause_time = (+new Date) - animationElements[this.id].start;
in the pause, and then
animationElements[this.id].start = (+new Date) - this._pause_time;
on resume.
http://github.com/DmitryBaranovskiy/raphael/blob/master/raphael.js#L3064

Categories