HTML5 Canvas Multiplayer Socket.io Sprite Flickering - javascript

I'm using socket.io in combination with Express to render a canvas multiplayer game, I wanted to use sprites and pixel art since the beginning, but when my canvas updates my sprite flickers a bit.
Here's the socket.io server code which first adds the connected player to players dictionary and then checks for movement emit in order to update players' position on the canvas :
let players = {};
io.on('connection', function (socket) {
players[socket.id] = { x: 300, y: 300};
socket.on('disconnect', function(){
delete players[socket.id];
});
socket.on('movement', function(data) {
let player = players[socket.id] || {};
if (data.left) {
player.x -= 5;
}
if (data.up) {
player.y -= 5;
}
if (data.right) {
player.x += 5;
}
if (data.down) {
player.y += 5;
}
});
});
// Here the server emits\send the "state" of the canvas
// looping passing the dictionary of players
setInterval(function() {
io.emit('state', players);
}, 1000 / 60);
And here's the client, where the problematic code is (imo):
var socket = io();
var width = window.innerWidth;
var height = window.innerHeight;
var movement = {
up: false,
down: false,
left: false,
right: false
}
document.addEventListener('keydown', function(event) {
switch (event.keyCode) {
case 65: // A
movement.left = true;
console.log("A");
break;
case 87: // W
movement.up = true;
console.log("W");
break;
case 68: // D
movement.right = true;
console.log("D");
break;
case 83: // S
movement.down = true;
console.log("S");
break;
}
});
document.addEventListener('keyup', function(event) {
switch (event.keyCode) {
case 65: // A
movement.left = false;
break;
case 87: // W
movement.up = false;
break;
case 68: // D
movement.right = false;
break;
case 83: // S
movement.down = false;
break;
}
});
/* end movements */
let canvas = document.getElementById('canvas');
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d');
socket.on('state', function(players) {
// Clear the canvas so that the previous positions are erased
ctx.clearRect(0, 0, width, height);
// Everytime you update (frame) the state of the canvas draw all players on it
for (let id in players) {
let player = players[id];
// Drawing with fillRect = no flickering
//ctx.fillRect(player.x, player.y, 100, 100);
// Drawing my sprite image = flickering!
let image = new Image();
image.onload = function() {
ctx.mozImageSmoothingEnabled = true;
ctx.webkitImageSmoothingEnabled = true;
ctx.msImageSmoothingEnabled = true;
ctx.imageSmoothingEnabled = true;
ctx.drawImage(image, player.x, player.y);
};
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAqCAYAAABYzsDTAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAUxJREFUWEftlk0KwjAQhb1CL9EjiD+4cyciint37goiiFAE1y506xkEV56gB/AC4jXcuIq8YmRME5MJqSJUeBT/vnnzOmlSq/3yNes3BDQfNHMthq1cy1H7TV4eK/hbbEmvLiDXWNJxR0BO2ZcKVxzAkU1OptUfvaD340qI07ogfO5Fpo6/Bu92NwJCJ7h6OZcxoAPqPAgcwPN2muceHH47pEIqOPy6T7RwOjXe01LBC6Max7GA5EKS73FF5uQ73phTkA5OC0dRxFtEX4F/ci1juewmPOd02aMA7YRGAjALLsGmmxkETh+3NufO7l0jkfMHcJZluawzyYUDCLA1f07eqktnuEve1ggKu/FzA3bY7e35WqrL54rpRMA2T/9QKhyF1AKmguwu1FOWrhgbSiG6Y5wXUJd3qfBgUZjaDXYTdQX+H15KB7p5t47jAzRfydFOtcn9AAAAAElFTkSuQmCC';
}
});
setInterval(function() {
socket.emit('movement', movement);
}, 1000 / 60);
The thing is that whenever I clean the canvas with ctx.clearRect(0, 0, width, height);in my loop, I get some flickering, but only when using an image, if I use canvas shapes I get none.
Why is that so?
How do I avoid that flickering? I really need to use sprites and simple shapes are a no-no.
Is there a better way to "loop" in multiplayer using socket.io that I'm missing? (I read about animationframe but I didn't really understand how to implement it with socket.io).
Any little help is appreciated I've been struggling a lot with it.

You actually have a larger problem in your code than this small flickering.
You are sending image data over the network every 60th of a second.
You won't be able to reach 60FPS animation on every end with such logic.
The first thing to do, is to create an assets manager, where you will load the required spritesheets for the game.
In this case, it seems to be some skins. So load once an HTMLImage with a sprite-sheet that will contain all your skins sprites before the game even starts and make this HTMLImage available for your rendering loop, e.g by setting it as a global of your game's function.
const assets = {
skin: new Image();
};
assets.skin.onload = e => tellTheGameWeAreReady();
assets.skin.src = 'path/to/skin_spritesheet.png';
Then what you will send over the socket is only the coordinates inside this sprite-sheet that the other players will have to draw.
Since your HTMLImage containing this sprite-sheet will already have loaded, all you have to do in the socket.on('state' handler is to draw the corresponding part of the sprite sheet at given coordinates.
socket.on('state', function(players) {
ctx.clearRect(0, 0, width, height);
for( let id in players ) {
let player = players[id];
/*//socket should emit each 'player' as
{
x: x_position of player in world,
y: y_position of player in world,
skin: {
x: x_position of to be displayed sprite in the sprite-sheet
y: y_position of to be displayed sprite in the sprite-sheet
w: width of to be displayed sprite in the sprite-sheet
h: height of to be displayed sprite in the sprite-sheet
}
}
*/
ctx.drawImage( assets.skins,
player.skin.x,
player.skin.y,
player.skin.w,
player.skin.h,
player.x,
player.y,
player.skin.w,
player.skin.h
);
}
});

Related

JavaScript audio.pause() and audio.play() not working in audio visualizer

I have an audio visualizer to which I'm attempting to add controls. Unfortunately, the problem cannot be easily replicated in a snippet because a special server must be setup to allow frequency access to the audio. However, I'll describe the problem the best I can.
All JavaScript for the project is below. I haven't tried skip functions yet but play/pause doesn't work.
This block handles all the play/pause.
function updatePlayState(){
//console.log(paused)
console.log(audio.paused)
//audio.play();
audio.pause();
console.log(audio.paused)
/*if(!paused){
paused = true;
audio.pause();
console.log(audio.src)
}else{
audio.play();
}*/
}
When I click the button, the console logs false and then true. However, it continues the same behavior after additional clicks. The audio also doesn't pause. The audio object I'm using is global so scope must not be the issue. I'm just wanting to get the audio to pause and then I'll move on to additional functionality.
//initialize global variables...
var audio, canvas, ctx, audioCtx, source, analyser, playlist_index = 0, full_screen = false, paused = false;
var playlist = [
//'http://localhost/audio-visualizer/audio/audio.mp3',
'http://localhost/audio-visualizer/audio/HaxPigMeow.mp3',
'http://localhost/audio-visualizer/audio/4ware.mp3',
'http://localhost/audio-visualizer/audio/Narwhals_song.mp3'
];
//when the page loads...
window.addEventListener('load', function() {
//initialize the canvas...
initializeCanvas();
//initialize audio...
initializeAudio();
//initialize audio analyzer (get frequency information)...
initializeAudioAnalyser();
//initialize audio controls...
initializeAudioControls();
});
//when the window is resized...
window.addEventListener('resize', function() {
resizeCanvas();
});
function initializeCanvas() {
//get the canvas...
canvas = document.getElementById('canvas');
//create a canvas context to draw graphics...
ctx = canvas.getContext('2d');
//resize the canvas to fit the window...
resizeCanvas();
}
function resizeCanvas() {
//set height of canvas...
canvas.width = window.innerWidth;
//set width of canvas...
canvas.height = window.innerHeight;
//set width of context...
ctx.width = window.innerWidth;
//set height of context...
ctx.height = window.innerHeight;
//reset drawing properties...
setCanvasDrawingProperties();
}
function initializeAudio() {
//load the audio...
audio = new Audio(playlist[playlist_index]);
//bypass CORS (Cross Origin Resource Sharing) restrictions...
audio.crossOrigin = 'anonymous';
//when the audio finishes playing; replay...
//audio.loop = true;
//play automatically...
//audio.autoplay = true;
//wait until audio fully loads before playing...
audio.oncanplaythrough = function() {
setTimeout(function() {
window.addEventListener('click',function(e){
audio.play();
//request full screen access...
if(e.target.tagName != 'INPUT'){
var root_element = document.documentElement;
rfs = root_element.requestFullscreen
|| root_element.webkitRequestFullScreen
|| root_element.mozRequestFullScreen
|| root_element.msRequestFullscreen
;
rfs.call(root_element);
}
//show audio controls....
document.getElementById('controlContainer').style.display = 'block';
setTimeout(function(){
document.getElementById('controlContainer').style.opacity = '1';
},500);
//hide the loading message...
document.getElementById('overlayLoadingMessage').style.opacity = '0';
window.setTimeout(function() {
document.getElementById('overlayLoadingMessage').style.display = 'none';
}, 500);
});
}, 1000);
};
audio.addEventListener('ended',function(){
skipForward();
playlist_index++;
if(playlist_index == playlist.length){
playlist_index = 0;
}
audio.src = playlist[playlist_index];
audio.crossOrigin = 'anonymous';
audio.play();
})
}
function initializeAudioControls(){
document.getElementById('skipBack').addEventListener('click',skipTrackBackward);
document.getElementById('skipForward').addEventListener('click',skipTrackForward);
document.getElementById('pause').addEventListener('click',updatePlayState);
function skipTrackForward(){
console.log('skip forward')
}
function skipTrackBackward(){
console.log('skip backward')
}
function updatePlayState(){
//console.log(paused)
console.log(audio.paused)
//audio.play();
audio.pause();
console.log(audio.paused)
/*if(!paused){
paused = true;
audio.pause();
console.log(audio.src)
}else{
audio.play();
}*/
}
}
function initializeAudioAnalyser() {
//create an audio context for browsers (including older webkit)...
if(window.webkitAudioContext){
//an older browser which needs to use the webkit audio constructor...
audioCtx = new window.webkitAudioContext;
}else{
//a newer browser which has full support for the audio context...
audioCtx = new window.AudioContext;
}
//create a new analyser...
analyser = audioCtx.createAnalyser();
//create new media source for the audio context...
source = audioCtx.createMediaElementSource(audio);
//connect the analyser to the source...
source.connect(analyser);
//connect audio output device information to the analyser to gather audio frequencies...
analyser.connect(audioCtx.destination);
//set drawing properties...
setCanvasDrawingProperties();
//let's do this thing (time to animate)...
animate();
}
function setCanvasDrawingProperties() {
//set background color of future drawing...
ctx.fillStyle = '#fff';
//blur radius (50px)...
ctx.shadowBlur = 50;
//shadow color...
ctx.shadowColor = "#ddd";
}
function animate() {
//clear canvas...
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
//create new frequency array map...
frequencyBinaryCountArray = new Uint8Array(analyser.frequencyBinCount);
//input frequency data into the array map...
analyser.getByteFrequencyData(frequencyBinaryCountArray);
//calculate radius based on frequency information (uses channel 50 right now)..
var r = frequencyBinaryCountArray[50];
//set x of circle...
var x = (window.innerWidth / 2);
//set y of circle...
var y = (window.innerHeight / 2);
//set start angle (the circumference of the circle)...
var startAngle = 2 * Math.PI;
//set end angle (the end circumference of the circle)...
var endAngle = 0 * Math.PI;
//draw a circle; radius is based on frequency...
//begin the drawing...
ctx.beginPath();
//draw the circle...
ctx.arc(x, y, r, startAngle, endAngle);
//fill the circle with a color...
ctx.fill();
//close the path...
ctx.closePath();
//do it again (appx 60 times per second)...
requestAnimationFrame(animate);
}
Tested in Chrome 67.x (latest version as of this post) on macOS High Sierra.
I was stuck on it a while but, immediately after posting the question, I discovered the global click listener that starts playing the audio was also firing when I clicked the pause button. It was overriding the pause function. To fix it, I moved the audio play inside the e.target tagname exception. However, to prevent future confusion, I added a button to start the visualization rather than a global click event.
Thanks for looking.

Display canvas as gif for video preview

I'm working on a website and I am dealing with videos.
My need is to display a gif a a preview / teaser for the videos on another page, that redirects to the video.
What I found & added so far
HTML
<div id=thumbs></div>
CSS
#video {width:320px}
JS
var i =0;
var video = document.createElement("video");
var thumbs = document.getElementById("thumbs");
video.addEventListener('loadeddata', function() {
thumbs.innerHTML = "";
video.currentTime = i;
}, false);
video.addEventListener('seeked', function() {
var j = video.duration;
var u = j/4;
// now video has seeked and current frames will show
// at the time as we expect
generateThumbnail(i);
// when frame is captured, increase
i+=u;
// if we are not passed end, seek to next interval
if (i <= video.duration) {
// this will trigger another seeked event
video.currentTime = i;
}
}, false);
video.preload = "auto";
video.src = "https://www.html5rocks.com/en/tutorials/video/basics/devstories.webm";
function generateThumbnail() {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 160;
c.height = 90;
ctx.drawImage(video, 0, 0, 160, 90);
thumbs.appendChild(c);
thumbs.replaceChild(c, thumbs.childNodes[0]);
}
What I do is I get a video from its Url and I get 5 frames at equal timing. It gives me canvas and I'd like to display them as a gif or a succession of images.
Since you only want to display it, instead of trying to generate a gif, the easiest way is probably to do the animation yourself.
You already have the code to fetch the video frames, but you are currently showing it in the DOM, and forget about it once displayed.
What you can do from this, is to store these frames, directly as canvas, and draw all these canvases on a final, visible canvas, in a timed loop.
var thumbsList = []; // we will save our frames as canvas in here
var delay = 500; // the speed of the animation (ms)
function generateThumbnail() {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 160;
c.height = 90;
ctx.drawImage(video, 0, 0, 160, 90);
thumbsList.push(c); // store this frame in our list
if (thumbsList.length === 1) {
displayThumbs(); // start animating as soon as we got a frame
}
}
// initialises the display canvas, and starts the animation loop
function displayThumbs() {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
c.width = 160;
c.height = 90;
thumbs.appendChild(c);
startAnim(ctx); // pass our visible canvas' context
}
function startAnim(ctx) {
var currentFrame = 0;
// here is the actual loop
function anim() {
ctx.drawImage(thumbsList[currentFrame], 0, 0); // draw the currentFrame
// increase our counter, and set it to 0 if too large
currentFrame = (currentFrame + 1) % thumbsList.length;
setTimeout(anim, delay); // do it again in x ms
}
anim(); // let's go !
}
var i = 0;
var video = document.createElement("video");
var thumbs = document.getElementById("thumbs");
/* OP's code */
video.addEventListener('loadeddata', function() {
thumbs.innerHTML = "";
video.currentTime = i;
}, false);
video.addEventListener('seeked', function() {
var j = video.duration;
var u = j / 4;
// now video has seeked and current frames will show
// at the time as we expect
generateThumbnail(i);
// when frame is captured, increase
i += u;
// if we are not passed end, seek to next interval
if (i <= video.duration) {
// this will trigger another seeked event
video.currentTime = i;
} else {
// displayFrame(); // wait for all images to be parsed before animating
}
}, false);
video.preload = "auto";
video.src = "https://www.html5rocks.com/en/tutorials/video/basics/devstories.webm";
<div id=thumbs></div>

How to move shapes from its new instance by requestAnimationFrame?

Here is the fiddle.
Small rectangle will be created to simulate a bullet when the spacebar(keycode 32) is pressed. I encountered some problems: How to move them yo the top (decrease the y coordinate)?
Can anyone help me? Thx!
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var ps = false;
init();
function init(){
context.rect((cw-5)/2, ch-5, 5, 5);
context.fill();
update();
}
function update(){
if(ps){
playerShoot();
}
requestAnimationFrame(update);
}
function playerShoot(){
var b = new bullet(2);
}
function bullet(speed){
this.speed = speed;
speed++;
context.ellipse((cw-1)/2, ch-10-speed, 1, 3, 0, 0, Math.PI*2);
context.fill();
}
document.addEventListener("keydown", function(e){
switch(e.keyCode){
case 32:
ps = true;
break;
};
});
document.addEventListener("keyup", function(e){
switch(e.keyCode){
case 32:
ps = false;
break;
};
});
I've explained a lot of the code in the comments in the code itself.
A couple of other points:
Some browsers (including mine, i.e. Firefox v44.0.2) don't draw ellipses. So I've made your bullet another rectangle.
I used fillRect instead of rect just because I know that better.
I redrew the bullet by drawing over the old one with the opaque background color. However, you could also clear the rectangle around the previous bullet if you wanted.
You incremented speed in your example. That's probably not what you want from a conceptual point of view, even if you had gotten the visual results that you want. I suspect you want your bullets to move at a constant speed. Therefore, the speed variable should be constant, i.e. not change. Rather, you should use the speed constant to regularly change the position of the bullet. I changed bulletY, which is the vertical position of the bullet.
For simplicity, I've only allowed one bullet on the screen at a time.
I've limited the code to running 500 cycles. That's mostly to just not annoy Stack Overflow users who try the code...they don't want an infinite loop happening.
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var ps = false;
// some new variables
var bulletShowing = false; // is a bullet currently showing?
var bulletY; // the vertical position of the bullet
var speed = 8; // the bullet speed
var time = 500; // the time remaining
init();
function init() {
// draw background
context.fillStyle = "yellow";
context.fillRect(0, 0, cw, ch);
// draw gun
context.fillStyle = "black";
context.fillRect((cw - 5) / 2, ch - 5, 5, 5);
// update the scene
update();
}
function update() {
if (ps) {
playerShoot();
}
// if a bullet is supposed to be showing then, well, show it
if (bulletShowing) {
// redraw the bullet (erase the old, draw the new)
drawBullet();
// if the bullet has gone off-screen, allow a new shot
if (bulletY < -5) {
bulletShowing = false;
}
}
// give a visual indicator of time remaining
document.querySelector("div").innerHTML = "Time: " + time;
// decrement the time
time -= 1;
// if there is still time remaining, do it all again
if (time >= 0) {
requestAnimationFrame(update);
}
}
function playerShoot() {
// indicate a bullet will now be showing
bulletShowing = true;
// start the bullet out near the gun
bulletY = ch - 10;
}
function drawBullet() {
// erase the old bullet by drawing over it with the background color
// this rectangle is slightly larger than the bullet itself
// to ensure the entire old bullet is drawn over
context.fillStyle = "yellow";
context.fillRect((cw - 1) / 2 - 2, bulletY - 1, 5, 7);
// move the bullet position
bulletY -= speed;
// draw the new bullet
context.fillStyle = "black";
context.fillRect((cw - 1) / 2 - 1, bulletY, 3, 5);
}
document.addEventListener("keydown", function(e) {
switch (e.keyCode) {
case 32:
// only allow one bullet on the screen at a time
// (for the sake of coding simplicity)
if (!bulletShowing) {
ps = true;
}
break;
};
});
document.addEventListener("keyup", function(e) {
switch (e.keyCode) {
case 32:
ps = false;
break;
};
});
#myCanvas {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, 5%);
background-color: #cccccc;
z-index: -1;
}
<p>Click on the canvas, then use the space bar to fire bullets one at a time.</p>
<div></div>
<canvas id="myCanvas" width=300 height=150></canvas>

Mouse position is only read on the first frame

i have been having trouble with reading a mouse position on a canvas. The code is working (semi) correctly as it reads the position when clicking he canvas in IE but only on one frame, in chrome it is just displaying the value as 0.
Here is the full code:
<script>
var blip = new Audio("blip.mp3");
blip.load();
var levelUp = new Audio("levelUp.mp3");
levelUp.load();
var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
context.font = '18pt Calibri';
context.fillStyle = 'white';
//load and draw background image
var bgReady = false;
var background = new Image();
background.src = 'images/background.jpg';
background.onload = function(){
bgReady = true;
}
var startMessage = 'Click the canvas to start';
//load plane image
var planeReady = false;
var planeImage = new Image();
planeImage.src = 'images/plane.png';
planeImage.onload = function() {
planeReady = true;
}
//load missile image
var missileReady = false;
var missileImage = new Image();
missileImage.src = 'images/missile-flipped.gif';
missileImage.onload = function() {
missileReady = true;
}
//initialise lives and score
var score = 0;
var lives = 3;
var missilesLaunched = 0;
var missileSpeed = 5;
var level = 1;
var missileX = 960;
var missileY = Math.random() * 500;
if (missileY > 480) {
missileY = 480;
}
function getMousePos(canvas, event) {
return {
x: input.x - rect.left,
y: input.y - rect.top
};
}
function update_images(event) {
var pos = getMousePos(canvas.getBoundingClientRect(), mouseInput);
planeImage.y = pos.y;
missileX = missileX - missileSpeed;
if (missileX < - 70) {
missilesLaunched++;
missileX = 960;
missileY = Math.random() * 500;
if (missileY > 480) {
missileY = 480;
}
blip.play();
score = missilesLaunched;
if (score % 5 == 0) {
missileSpeed = missileSpeed + 2;
level++;
levelUp.play();
}
}
}
function reload_images() {
if (bgReady = true) {
context.drawImage(background, 0, 0);
}
if (planeReady = true) {
context.drawImage(planeImage, 10, planeImage.y);
}
if (missileReady = true) {
context.drawImage(missileImage, missileX, missileY);
}
context.fillText('Lives: ' + lives, 200, 30);
context.fillText('Score: ' + score, 650, 30);
context.fillText('Level: ' + missileSpeed, 420, 30);
context.fillText('Position: ' + missileImage.y, 420, 70);
}
function main(event) {
var mouseInput = { x: 0, y: 0 };
document.addEventListener("mousemove", function (event) {
mouseInput.x = event.clientX;
mouseInput.y = event.clientY;
});
update_images(event);
reload_images();
if (lives > 0) {
window.requestAnimationFrame(main);
}
else {
}
}
function start() {
context.drawImage(background, 0, 0);
context.fillText('Click the canvas to start', 350, 250);
function startMain(event) {
game.removeEventListener("click", startMain);
main(event);
}
canvas.addEventListener("mousedown", startMain);
}
start();
</script>
Joe, you should actually be capturing the mouse position every time you click...
...but you're actually also starting a new game (without stopping the old one), every time you click, too.
First problem: starting game engine several times to draw on the same instance of the canvas
Solution:
In your start function, you need to remove the mousedown event listener, after you've triggered it.
function start () {
// ... other setup
function startMain (event) {
canvas.removeEventListener("click", startMain);
main(event);
}
canvas.addEventListener("click", startMain);
}
Now it will only listen for the first click, before starting, and will only start once.
Second Problem: mouse doesn't update as expected
Solution: two issues here...
...first, you are passing event into main on first call...
...after that, you're passing main into requestAnimationFrame.
requestAnimationFrame won't call it with an event, it will call it with the number of microseconds (or ms or some other unit as a fractional precision of ms) since the page was loaded.
So the first time you got main({ type: "mousedown", ... });.
The next time you get main(4378.002358007);
So lets refactor the startMain we had above, so that main never ever collects an event, just a time.
function startMain ( ) {
canvas.removeEventListener("click", startMain);
requestAnimationFrame(main);
}
The next problem is that even if you were getting just events, you're only ever capturing a click event (which as we mentioned earlier, fires a new copy of the game logic).
Your solution is to separate the code which catches mouse events from the code which reads mouse position.
var mouseInput = { x: 0, y: 0 };
document.addEventListener("mousemove", function (event) {
mouseInput.x = event.clientX;
mouseInput.y = event.clientY;
});
function getMousePos (rect, input) {
return {
x : input.x - rect.left,
y : input.y - rect.top
};
}
// currently in updateImages (should not be there, but... a different story)
var pos = getMousePos(canvas.getBoundingClientRect(), mouseInput);
You've got other problems, too...
You're calling getMousePos and passing in game at the moment. I don't see where game is defined in your JS, so either you're making game somewhere else (begging for bugs), or it's undefined, and your app blows up right there.
You should really be building this with your console / dev-tools open, in a hands-on fashion, and cleaning bugs in each section, as you go.

Rotating and Caching in canvas bug

I am making a game and in it I would like to have the ability to use cached canvas's instead of rotating the image every frame. I think I made a function for adding images that makes sense to me, but I am getting an error every frame that tells me that the canvas object is no longer available.
INVALID_STATE_ERR: DOM Exception 11: An attempt was made to use an
object that is not, or is no longer, usable.
You might also need to know that I am using object.set(); to go ahead and add that image to a renderArray. That may be affecting whether the canvas object is still avaliable?
Here is the function that returns a cached canvas, (I took it from a post on this website :D)
rotateAndCache = function(image, angle){
var offscreenCanvas = document.createElement('canvas');
var offscreenCtx = offscreenCanvas.getContext('2d');
var size = Math.max(image.width, image.height);
offscreenCanvas.width = size;
offscreenCanvas.height = size;
offscreenCtx.translate(size/2, size/2);
offscreenCtx.rotate(angle + Math.PI/2);
offscreenCtx.drawImage(image, -(image.width/2), -(image.height/2));
return offscreenCanvas;
}
And here is some more marked up code:
var game = {
render:function(){
//gets called every frame
context.clearRect(0, 0, canvas.width, canvas.height);
for(i = 0; i < game.renderArray.length; i++){
switch(game.renderArray[i].type){
case "image":
context.save();
context.translate(game.renderArray[i].x, game.renderArray[i].y);
context.rotate(Math.PI*game.renderArray[i].rotation/180);
context.translate(-game.renderArray[i].x, -game.renderArray[i].y);
context.drawImage(game.renderArray[i].image, game.renderArray[i].x, game.renderArray[i].y);
context.restore();
break;
}
if(game.renderArray[i].remove == true){
game.renderArray.splice(i,1);
if(i > 1){
i--;
}else{
break;
}
}
}
},
size:function(width, height){
canvas.height = height;
canvas.width = width;
return height + "," + width;
},
renderArray:new Array(),
//initialize the renderArray
image:function(src, angle){
if(angle != undefined){
//if the argument 'angle' was given
this.tmp = new Image();
this.tmp.src = src;
//sets 'this.image' (peach.image) to the canvas. It then should get rendered in the next frame, but apparently it doesn't work...
this.image = rotateAndCache(this.tmp, angle);
}else{
this.image = new Image();
this.image.src = src;
}
this.x = 0;
this.y = 0;
this.rotation = 0;
this.destroy = function(){
this.remove = true;
return "destroyed";
};
this.remove = false;
this.type = "image";
this.set = function(){
game.renderArray.push(this);
}
}
};
var canvas, context, peach;
$(document).ready(function(){
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
//make the variable peach a new game.image with src of meme.jpg and an angle of 20.
peach = new game.image('meme.jpg', 20);
peach.set();
game.size(700,500);
animLoop();
});
If you want, here is this project hosted on my site:
http://keirp.com/zap
There are no errors on your page, at least not anymore or not that I can see.
It's quite possible that the problem is an image that is not done loading. For instance that error will happen if you try to make a canvas pattern out of a not-yet-finished-loading image. Use something like pxloader or your own image loading function to make sure all the images are complete before you start drawing.
Anyway, it's nigh impossible to figure out what was or is happening since your code isn't actually giving any errors (anymore).

Categories

Resources