I am having some difficulty copying a canvas from my buffer canvas to the canvas on my page. Thus far I have built a Render object, a Level object, and I have my main game loop (currently just a launch function).
I am able to write to the buffer canvas in the Render object just fine (if I add a document.body.append() statement the canvas successfully appends to the document with the necessary content) but I cannot copy from the buffer canvas to my main canvas. See below for a snippet of my code:
function Renderer(bufferWidth, bufferHeight) {
var c=document.createElement('canvas');
var ctx=c.getContext('2d');
c.width=bufferWidth;
c.height=bufferHeight;
this.getBufferContext = function() { return ctx; };
this.getBufferElement = function() { return c; };
this.drawToCanvas = function(canvasCtx) {
canvasCtx.drawImage(c,0,0);
};
}
var c = document.getElementById('mycanvas');
var ctx = c.getContext('2d');
var render = new Renderer(c.width, c.height);
var level1 = new Level('images/imagequality.png');
level1.Draw(render.getBufferContext());
render.drawToCanvas(ctx);
Note that Renderer is in a separate file and is loaded using the script tags in my HTML page.
As mentioned earlier, the drawToCanvas() function doesn't appear to successfully copy data from one canvas to another. Appending my source canvas confirms that it contains the expected data.
edit: I have listed my level code below.
function Level(mapname) {
var map=new Image();
map.src=mapname;
this.Draw = function(renderer) {
map.onload = function() { renderer.drawImage(map,0,0); };
};
}
I have good news, and I have bad news.
The good news is the code you show here works 100%
here is the demo: http://jsbin.com/upatij/edit#javascript,html,live
bad news: that means that something inside your Level code is broken as my stub Level code works perfectly in your framework... :-(
stub Level:
function Level() {
this.Draw = function(xxctx) {
for (var i = 0; i < 30; i++) {
xxctx.moveTo(10 + (i * 40 % 300), 10 + (parseInt(i / 6, 10) * 40));
xxctx.lineTo(40 + (i * 40 % 300), 40 + (parseInt(i / 6, 10) * 40));
xxctx.moveTo(40 + (i * 40 % 300), 10 + (parseInt(i / 6, 10) * 40));
xxctx.lineTo(10 + (i * 40 % 300), 40 + (parseInt(i / 6, 10) * 40));
}
xxctx.stroke();
};
}
good luck! -ck
AFTER YOUR SEEING YOUR LEVEL CODE:
The problem is one of synchronicity, your use of classes here are hiding the problem from you, via deceptive naming, as your Level.Draw, is not a draw function at all... let me unwrap it for you:
var c = document.getElementById('mycanvas');
var ctx = c.getContext('2d');
// var render = new Renderer(c.width, c.height);
var Renderer_c = document.createElement('canvas');
var Renderer_ctx = Renderer_c.getContext('2d');
document.body.appendChild(Renderer_c); //added to show
Renderer_c.width = c.width;
Renderer_c.height = c.height;
// var level1 = new Level('images/imagequality.png');
var map = new Image();
document.body.appendChild(map); //add to show
map.src = 'http://th06.deviantart.net/fs71/150/i/2011/255/9/5/omnom_upside_down_by_earnurm-d49pjnl.png';
console.log('at ' + 1);
// level1.Draw(render.getBufferContext());
map.onload = function() {
console.log('at ' + 3);
//this happens async:
alert('drawing now!');
Renderer_ctx.drawImage(map,0,0);
};
console.log('at ' + 2);
// render.drawToCanvas(ctx);
ctx.drawImage(Renderer_c, 0, 0);
If you run that code you will see that at the moment at which onload is called everything else has already executed, you'll notice how the console will read:
at 1
at 2
at 3
and as such, at the moment when alert('drawing now!'); is executed...
// render.drawToCanvas(ctx);
ctx.drawImage(Renderer_c, 0, 0);
will have already run... Basically your Draw() function is actually an asynchronous "Load". Unfortunately, you current conceptualization does not work. Your Draw() function needs to be an async one like this:
function Level(mapname) {
var map=new Image();
document.body.appendChild(map); //add to show
map.src=mapname;
this.asyncDraw = function(renderer, onComplete) {
map.onload = function() {
renderer.drawImage(map,0,0);
onComplete();
};
};
}
and the function should then be called like this in your example:
level1.asyncDraw(render.getBufferContext(), function() {
render.drawToCanvas(ctx);
});
I should probably go on to say that this type of asynchronisity makes HTML5 game programming a little tricky as you really have to throw up the "Loading..." spinner and refrain from going into your rendering loop until all "resources" have loaded. In all practicality you need the concept of "ready" eg. Load(fOnReady) AND Draw(ctx) instead of just asyncDraw(ctx, fOnReady)...
the updated jsbin is here: http://jsbin.com/upatij/2/edit
hope this helps -ck
Related
I am making a battleship game with polar coordinates. After the user chooses two points, a battleship should be drawn in the middle. My Battleship constructor looks like this:
function Battleship(size, location, source){
this.size = size;
//initializing the image
this.image = new Image();
this.image.src = source;
this.getMiddlePoint = function(){
//get midpoint of ship
...
}
this.distanceBetween = function(t1, t2){
//dist between two points
}
this.display = function(){
var point = [this.radius];
point.push(this.getMiddlePoint());
point = polarToReal(point[0], point[1] * Math.PI / 12);
//now point has canvas coordinates of midpoint
var width = this.distanceBetween(this.info[0][0], this.info[this.info.length-1][0]);
var ratio = this.image.width / width;
ctx.drawImage(this.image, point[0] - width/2, point[1] - this.image.height / ratio / 2, width, this.image.height / ratio);
//draws the image
}
}
The display method of each ship gets called at a certain point (after the user has chosen the location). For some reason, the images do not show the first time I do this, but when I run this code at the very end:
for(var i = 0; i<playerMap.ships.length; i++){
playerMap.ships[i].display();
}
All ships are displayed correctly (not aligned well, but they are displayed). I think there is a problem with loading the images. I am not sure how to fix this. I tried using image.onload but I never got that to work. I also tried something like this:
var loadImage = function (url, ctx) {
var img = new Image();
img.src = url
img.onload = function () {
ctx.drawImage(img, 0, 0);
}
}
but the same problem kept happening. Please help me fix this problem. Here is the game in its current condition. If you place ships, nothing happens, but after you place 5 (or 10) ships, they suddenly all load.
EDIT:
I solved the problem by globally defining the images. This is still very bad practice, since I wanted this to be in the battleship object. This is my (temporary) solution:
var sub = [];
for(var i = 1; i<5; i++){
sub[i] = new Image();
sub[i].src = "/img/ships/battleship_"+i+".png";
}
The code:
// create stuff
var ghObj = {
x : 0,
y : 0
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var ghost = new Image();
ghost.src = "ghost.png"
//define variables
var ghostMove = function () {
ghObj.x+=(Math.floor(Math.random() * 9 - 4))
console.log(ghObj.x)
ghObj.y+=(Math.floor(Math.random() * 9 - 4))
}
var ghostCheck = function () {
if (ghObj.x<0) {
ghObj.x=0
}
if (ghObj.x>390) {
ghObj.x=390
}
if (ghObj.y<0) {
ghObj.y=0
}
if (ghObj.y>390) {
ghObj.y=390
}
}
var drawIm = function (sprite, position) {
ctx.save();
ctx.translate(position.x, position.y);
ctx.drawImage(sprite, 0, 0, sprite.width, sprite.height, 0, 0, sprite.width, sprite.height);
ctx.restore();
};
// begin "game" when ghost is loaded
ghost.onload = function() {
mainLoop()
}
// main loop
function() {
ghostMove()
ghostCheck()
drawIm(ghost, ghObj)
setInterval(mainLoop, 1000)
}
sorry if its a bit long.
what's supposed to happen is the ghost moves randomly round the screen at a steady rate.
instead, it moves randomly but increasingly quickly and leaves copies of itself everywhere.
Isn't the restore function supposed to clear the screen each time?
have i got the game loop wrong?
thanks in advance.
function mainLoop(){
setInterval(mainLoop,100);
}
I think the error is obvious. I recommend to use setTimeout or requestAnimationFrame instead... And this should remove the duplicates i think its an optical ilusion
...
So, I'm relatively new to Javascrip. Though what I want to do is give a moving image on my canvas an id, so that I can ultimately use onclick to use the image as a clickable image, so I can redirect the user to another page, which is what would happen when the image is clicked. Here is my code so far. I need help. If you need any more clarification I will try to explain further.
var ctx;
var imgBg;
var imgDrops;
var x = 0;
var y = 0;
var noOfDrops = 50;
var fallingDrops = [];
function drawBackground(){
ctx.drawImage(imgBg, 0, 0); //Background
}
function draw() {
drawBackground();
for (var i=0; i< noOfDrops; i++)
{
ctx.drawImage (fallingDrops[i].image, fallingDrops[i].x, fallingDrops[i].y); //The rain drop
fallingDrops[i].y += fallingDrops[i].speed; //Set the falling speed
if (fallingDrops[i].y > 1000) { //Repeat the raindrop when it falls out of view
fallingDrops[i].y = -25 //Account for the image size
fallingDrops[i].x = Math.random() * 10000; //Make it appear randomly along the width
}
}
}
function setup() {
var canvas = document.getElementById('canvasRegn');
if (canvas.getContext) {
ctx = canvas.getContext('2d');
imgBg = new Image();
imgBg.src = "http://images.susu.org/unionfilms/films/backgrounds/hd/space-jam.jpg";
setInterval(draw, 36);
for (var i = 0; i < noOfDrops; i++) {
// Charles Barkley
var fallingDr = new Object();
fallingDr["image"] = new Image();
fallingDr.image.src = 'http://xenboards.ignimgs.com/external_data/attachments/8/8795-f09b907a01726a25ca2fbd2f588e3f0e.jpg';
fallingDr["x"] = Math.random() * 10000;
fallingDr["y"] = Math.random() * 5;
fallingDr["speed"] = 3 + Math.random() * 5;
fallingDrops.push(fallingDr);
// Bugs bunny
var fallingDr2 = new Object();
fallingDr2["image"] = new Image();
fallingDr2.image.src = 'http://i.imgur.com/zN2CSAf.png'
fallingDr2["x"] = Math.random() * 10000;
fallingDr2["y"] = Math.random() * 5;
fallingDr2["speed"] = 3 + Math.random() * 5;
fallingDrops.push(fallingDr2);
// Michael Jordan
var fallingDr3 = new Object();
fallingDr3["image"] = new Image();
fallingDr3.image.src = 'http://i.imgur.com/XxvJiGg.png'
fallingDr3["x"] = Math.random() * 10000;
fallingDr3["y"] = Math.random() * 5;
fallingDr3["speed"] = 3 + Math.random() * 5;
fallingDrops.push(fallingDr3);
// Daffy duck
var fallingDr4 = new Object();
fallingDr4["image"] = new Image();
fallingDr4.image.src = 'http://i.imgur.com/QZogw2L.png'
fallingDr4["x"] = Math.random() * 10000;
fallingDr4["y"] = Math.random() * 5;
fallingDr4["speed"] = 3 + Math.random() * 5;
fallingDrops.push(fallingDr4);
fallingDr4.image.id = "Daffy";
}
}
}
setup();
window.onload = function(){
document.getElementById("Daffy").onclick=function(){
alert("Hello World");
}
}
Try:
fallingDr4.image.onclick=function(){
alert(this.id);
}
should alert "Duffy".
Your problem is that you're trying to catch the click event on an element that is not in the document and hence, not clickable by the user.
When you call var img = new Image() a new <img> element is created, with all its properties that you can already modify in your javascript. But this element is only available to your scripts, and is not displayed into the page until you call document.anyElement.appendChild(img). So it's better to consider this as an imageObject more than to an element (even if it actually also is).
What is in your document, and accessible to your user, is the <canvas> element. So if you want to know if the user has clicked, you will have to attach the eventListener to this canvasElement.
But the canvasElement doesn't know what it does represent. When you call context.drawImage(), you're just applying the pixels from the imageSource to the ones of the canvas, and all reference to the original imageObject are lost.
To workaround this, you'll then have to store the position of your drawn image into the canvas, and then check if the click event you caught was inside these positions.
Click events' clientX and clientY properties of the Event passed as arguments of you handler will give you the position of the cursor when the event occurred. But the positions are relative to the top-left corner of the window. So you'll also need to make these relative to the top-left corner of your canvas, which can be done by calling the getBoundingClientRect() method of the canvasElement.
Here is a simplified example :
// this function will be called at image's load
var init = function() {
// a reference to our "in-screen" canvasElement
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// "kitty" will be the js object that will help us know if we clicked on our image
// "this" refers to the imageObject
var kitty = {
width: this.width,
height: this.height,
// random positions
left: Math.random() * (canvas.width - this.width),
top: Math.random() * (canvas.height - this.height),
};
// draw our image at the random positions we created
ctx.drawImage(this, kitty.left, kitty.top, kitty.width, kitty.height);
// here we're listening to mousemove event,
// but click event shares the same clientX & clientY properties
var moveHandler = function(evt) {
// in the "evt" object passed, we can get the x and y positions relative to the window
// so we make these relatives to the canvas ones
var canvasRect = canvas.getBoundingClientRect();
var x = evt.clientX - canvasRect.left;
var y = evt.clientY - canvasRect.top;
// now we've got our relative positions, we can check if we were inside the image
if (x >= kitty.left && x <= (kitty.left + kitty.width) && y >= kitty.top && y <= (kitty.top + kitty.height) ) {
// we are over the image, do something
canvas.style.cursor = 'pointer';
document.body.style.backgroundColor = 'lightblue';
} else {
canvas.style.cursor = 'default';
document.body.style.backgroundColor = 'transparent';
}
};
// attach this event handler to the canvasElement
canvas.addEventListener('mousemove', moveHandler);
};
// this will create an imageObject, which will stay "off-screen" (never appendded to the document)
var img = new Image();
// wait that the image has loaded before trying to make any magic
img.onload = init;
img.src = "http://lorempixel.com/200/70/cats";
body {
width: 100vw;
text-align: center;
}
canvas {
margin: 0 auto;
position: relative;
}
<canvas id="canvas" width="500" height="500"></canvas>
(you may need to scroll to actually see the image, or go fullscreen)
The Problem
I am finding it rather difficult to get my head around this, I am attempting to move an image using the mouse along the X axis only. I am finding it hard to even move the image at all and the many tutorials I have looked at arnt really helping me. Here is what I am trying to say:
As you can see by my beautiful image above I only want to image to move left and right at the bottom of the page.
The Code and the Question
Here is my first attempt, when I try this all the images loaded on the canvas no longer appear making it very hard for me to understand why it isnt working.
<script type="text/javascript">
//Referencing the canvas
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var width = canvas.getAttribute('width');
var height = canvas.getAttribute('height');
//Images
var bggameImage = new Image();
var playerImage = new Image();
var enemyImage = new Image();
var projectileImage = new Image();
var livesImage = new Image();
//Canvas dimensions
var width = 480;
var height = 320;
//Loading in the backgroundImage
bggameImage.src = "Images/bggameImage.png";
bggameImage.onload = function(){
context.drawImage(bggameImage, 0, 0);
}
//Loading in the playerImage
playerImage.src = "Images/playerImage.png";
playerImage.onload = function(){
context.drawImage(playerImage, 165, 240);
}
//Loading in the projectileImage
projectileImage.src = "Images/projectileImage.png";
projectileImage.onload = function(){
context.drawImage(projectileImage, 65, 240);
}
var playerImage = {
x:176,
y:74,
}
function init() {
playerImage.src = "Images/playerImage.png";
//Moving player
myCanvas.addEventListener("mousemove", function (e) {
var bounding_box = myCanvas.getBoundingClientRect();
playerImage = (e.clientX - bounding_box.left) * (myCanvas.width / bounding_box.width) - playerImage.width / 2;
playerImage = (e.clientY - bounding_box.top) * (myCanvas.height / bounding_box.height) - playerImage.height / 2;
}
)
</script>
The whole "function init()" part is what I have just tried but I thought I would include this anyway, I understand that I am loading in the playerImage twice.
You're using the same variable name twice (playerImage), so your image is being overwritten. You're using it for the image and also to store the position. Change the playerImage that's storing x and y to be playerPosition or something like that. Update that variable on your mouse event and then render the image according to that variable's values.
Ultimately, you're going to have to look at a game loop using setTimeout or requestAnimationFrame. So, this will become crucial at that stage. And yes, you shouldn't be loading the player image twice either. Do all of that at the start and only start your game when all your assets have successfully loaded.
For instance...
var playerImage;
var alienImage;
var bulletImage;
var assetCount = 0;
function loadAssets() {
playerImage = new Image();
playerImage.onload = checkAssetsLoaded;
playerImage.src = "assets/images/Brush01.png";
alienImage = new Image();
alienImage.onload = checkAssetsLoaded;
alienImage.src = "assets/images/Brush02.png";
bulletImage = new Image();
bulletImage.onload = checkAssetsLoaded;
bulletImage.src = "assets/images/Brush03.png";
}
function checkAssetsLoaded(event) {
assetCount++;
console.log("An asset has loaded!: " + assetCount);
if (assetCount == 3) {
startGame();
}
}
function startGame() {
// Start your game initialization logic here.
console.log("Game starting!");
}
So, I have an <img> tag that has an onclick attribute. The onclick calls a function called analyze(this), with this being the image.
The analyze function does some things to the image that aren't entirely relevant, except for the fact that it draws it onto the <canvas> element (using the drawImage function).
But now, I want to also pick the color I just clicked on in the image. I am currently using the method answered here (the answer with 70+ votes, not the chosen one): How do I get the coordinates of a mouse click on a canvas element?
But, I think I might be doing this wrong. I have the image drawn and my functions called (and those all work), but the color picking part isn't being called. I think that this is because I didn't actually capture the event. This is generally how my code looks:
<img onclick="javascript:analyze(this);" />
function analyze(img_elem) {
// This is getting the canvas from the page and the image in it
var canvaselement = document.getElementById('canvas').getContext('2d'),
img = new Image();
img.onload = function () {
canvaselement.drawImage(img, 0, 0, 250, 250);
...
canvaselement.onClick = function () {
var coords = canvaselement.relMouseCoords(event);
pick(img, canvaselement, coords); // pass in coordinates
}
}
img.src = img_elem.src;
}
function relMouseCoords(event) {
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var currentElement = this;
do {
totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
}
while (currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX;
canvasY = event.pageY - totalOffsetY;
return {
x: canvasX,
y: canvasY
}
}
function pick(img, canvaselement, coords) {
var pickedColor = "";
canvaselement.drawImage(img, 0, 0, 250, 250);
xx = coords.x;
yy = coords.y;
var imgData = canvas.getImageData(xx, yy, 1, 1).data;
pickedColor = rgbToHex(imgData);
//alert(pickedColor);
return pickedColor;
}
So, the code never gets to the pick function. I have a feeling that it's because I didn't actually capture the onclick event. I'm also not even sure if this is the right way to get the coordinates on the canvas, I'm just sort of hoping that I even get to that part of the debugging process at this point.
Thanks for your help!
The problem is probably that you're assigning canvaselement to the results of getContext('2d') and not to the element itself, which you will need for the click event binding. Create two variables, one for the DOM element itself and one for the context, something like:
var canvaselement = document.getElementById('canvas'),
canvaselementctx = canvaselement.getContext('2d');
...
canvaselement.onClick = function() {
var coords = canvaselementctx.relMouseCoords(event);
...
}
You have a couple of errors in the code but the reason the code you got from the linked post is that you forgot to include the prototype definition it uses:
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
Now you can call relMouseCoords on the canvas element:
/// event name in lower case
canvaselement.onclick = function () {
var coords = canvaselement.relMouseCoords(event);
//...
However, you will still get problems as you don't use a canvas context for the drawing calls.
function analyze(img_elem) {
// This is getting the canvas from the page and the image in it
var canvaselement = document.getElementById('canvas').getContext('2d'),
/// get context like this
ctx = canvaselement.getContext('2d'),
img = new Image();
img.onload = function () {
/// use context to draw
ctx.drawImage(img, 0, 0, 250, 250);
//...