I am trying to toggle a particle emitter to on or off.
I have some Reactjs code that creates rain/snow particles with the canvas element.
Every notion i have came up with to properly kill the animation still results in memory leaks (in this case i think it is just projecting a new canvas over the old and still running the original animation in memory).
Here is the Codepen.
Here is the page code:
class CanvasComponent extends React.Component {
componentDidMount() {
this.BtnOnOff();
}
constructor() {
super();
this.toggleState = this.toggleState.bind(this);
this.state = {
isActive : false
}
}
toggleState() {
this.setState({isActive:!this.state.isActive});
this.BtnOnOff();
//console.log(this.state.isActive);
}
snowMaker() {
// Ref canvas & get context
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Weather Types/////////////
var liteSnow = 13; //code 13 for snow light fluries.
var snow = 16; //code 13 for snow fluries.
var hevySnow = 41; //code 41 for Heavy snow.
var hevySnow2 = 43; //code 43 for Heavy snow.
var DRain = 9; //code 9 for drizzel
var HRain = 3; //code 3/4 for Thunderstorms/severe thunderstorms
var Code = 43; // Code will take in the current weather code.
// Resize canvas
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
// Variables
if (Code === 13) { /// Make it snow (light fluries)
var drops = [];
var dropColour = "rgba(255,255,255,1)";
var dropLengths = [3, 3, 3, 3, 3, 3, 3];
var dropSkews = [-2, -1, 0, 1, 2];
var maxDrops = 100;
var velocity = 8;
var flutter = 5;
}
else if (Code === 16){ /// Make it snow (fluries)
var drops = [];
var dropColour = "rgba(255,255,255,1)";
var dropLengths = [3, 3, 3, 3, 3, 3, 3];
var dropSkews = [-2, -1, 0, 1, 2];
var maxDrops = 500;
var velocity = 7;
var flutter = 5;
}
else if (Code === 41||Code === 43){ /// Make it Heavy snow
var drops = [];
var dropColour = "rgba(255,255,255,1)";
var dropLengths = [3, 2, 3, 2, 3, 2, 3];
var dropSkews = [-3, -1, 0, 1, 3];
var maxDrops = 800;
var velocity = .5;
var flutter = 8;
}
else if (Code === 9){ /// Make it rain
var drops = [];
var dropColour = "rgba(255,255,255,0.41)";
var dropLengths = [4, 5, 3, 6, 2, 3, 3];
var dropSkews = [0, 0.2, 0, 0, 0.1];
var maxDrops = 100;
var velocity =1;
var flutter = 1;
}
else if (Code === 3||Code === 4){ /// Make it ThunderStorms
var drops = [];
var dropColour = "rgba(255,255,255,0.5)";
var dropLengths = [10, 8, 8, 8, 7, 15, 9];
var dropSkews = [-0.2, -0.3, -0.2, -0.2, 0.1];
var maxDrops = 1000;
var velocity = .8;
var flutter = .1;
}
// Raindrop class
class Droplet {
constructor(x, y, length, skew) {
this.x = x;
this.y = y;
this.length = length;
this.skew = skew;
}
// Move method
move() {
// Increment x & y
this.y += this.length / velocity;
this.x += this.skew / flutter;
// Set limits
if (this.y > height) {
this.y = 0;
}
if (this.x > width || this.x < 0) {
this.y = 0;
this.x = Math.floor(Math.random() * width);
}
}
// Draw method
draw(ctx) {
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x + this.skew, this.y + this.length);
ctx.strokeStyle = dropColour;
ctx.stroke();
}
}
// Create drops and push to array
for (let i = 0; i < maxDrops; i++) {
let instance = new Droplet(
Math.floor(Math.random() * width),
Math.floor(Math.random() * height),
randVal(dropLengths),
randVal(dropSkews)
);
drops.push(instance);
}
// Animation loop
function loop() {
// Clear Canvas
ctx.clearRect(0, 0, width, height);
// Draw / Move drops
for (let drop of drops) {
drop.move();
drop.draw(ctx);
}
// Animation Frame
requestAnimationFrame(loop)
}
// Begin animation
loop();
// Resize canvas - responsive
window.addEventListener('resize', resize);
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
// Function for random array values
function randVal(array) {
return array[Math.floor(Math.random() * array.length)];
}
}////////End of update canvas
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
snowKiller() {
// Ref canvas & get context
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
var Code = 13;
// Resize canvas
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
// Variables
if (Code === 13) { /// Make it snow (light fluries)
var drops = [];
var dropColour = "";
var dropLengths = [0];
var dropSkews = [0];
var maxDrops = 0;
var velocity = 0;
var flutter = 0;
}
// Raindrop class
class Droplet {
constructor(x, y, length, skew) {
this.x = x;
this.y = y;
this.length = length;
this.skew = skew;
}
// Move method
move() {
// Increment x & y
this.y += this.length / velocity;
this.x += this.skew / flutter;
// Set limits
if (this.y > height) {
this.y = 0;
}
if (this.x > width || this.x < 0) {
this.y = 0;
this.x = Math.floor(Math.random() * width);
}
}
// Draw method
draw(ctx) {
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x + this.skew, this.y + this.length);
ctx.strokeStyle = dropColour;
ctx.stroke();
}
}
// Create drops and push to array
for (let i = 0; i < maxDrops; i++) {
let instance = new Droplet(
Math.floor(Math.random() * width),
Math.floor(Math.random() * height),
randVal(dropLengths),
randVal(dropSkews)
);
drops.push(instance);
}
// Animation loop
function loop() {
// Clear Canvas
ctx.clearRect(0, 0, width, height);
// Draw / Move drops
for (let drop of drops) {
drop.move();
drop.draw(ctx);
}
// Animation Frame
requestAnimationFrame(loop)
}
// Begin animation
loop();
// Resize canvas - responsive
window.addEventListener('resize', resize);
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
// Function for random array values
function randVal(array) {
return array[Math.floor(Math.random() * array.length)];
}
}////////End of update canvas
BtnOnOff(){
const OnOff =$('#Button').attr('class');
if(OnOff=== "active"){
// alert('this is on!')
this.snowMaker();
}else {
this.snowKiller();
// alert('this is off!');
}
console.log(OnOff);
}
render() {
return (
<div>
<button id="Button" className={this.state.isActive ? 'inactive' : 'active'} onClick ={this.toggleState}>{this.state.isActive ? 'STOP' : 'START'}</button>
<canvas id="canvas"/>
</div>
);
}
}
ReactDOM.render(<CanvasComponent/>, document.getElementById('app'));
Idealy the solution would be something like clearing the requestAnimationFrame but i can't seem to figure it out.
Help would be very much apreciated.
Thanks
(Code tl;dr) you can always use the request-ID returned by requestAnimationFrame(), like this:
var reqID;
function loop() {
// rain
reqID = requestAnimationFrame(loop);
}
// start loop same way
reqID = requestAnimationFrame(loop);
Then when you want to stop it:
cancelAnimationFrame(reqID);
Calling it with an empty "reqID" is safe.
Related
I don't know if this makes me lazy or what I just to use this kind method but it doesn't work well in react.
Here is the link of the code
HOVER
So basically my error here is the part of ctx = canvas.getContext('2d') I try to copy all the codes and do this something in my react
useEffect(() => {
const rippleSettings = {
maxSize: 100,
animationSpeed: 5,
strokeColor: [148, 217, 255],
};
const canvasSettings = {
blur: 8,
ratio: 1,
};
function Coords(x, y) {
this.x = x || null;
this.y = y || null;
}
const Ripple = function Ripple(x, y, circleSize, ctx) {
this.position = new Coords(x, y);
this.circleSize = circleSize;
this.maxSize = rippleSettings.maxSize;
this.opacity = 1;
this.ctx = ctx;
this.strokeColor = `rgba(${Math.floor(rippleSettings.strokeColor[0])},
${Math.floor(rippleSettings.strokeColor[1])},
${Math.floor(rippleSettings.strokeColor[2])},
${this.opacity})`;
this.animationSpeed = rippleSettings.animationSpeed;
this.opacityStep = (this.animationSpeed / (this.maxSize - circleSize)) / 2;
};
Ripple.prototype = {
update: function update() {
this.circleSize = this.circleSize + this.animationSpeed;
this.opacity = this.opacity - this.opacityStep;
this.strokeColor = `rgba(${Math.floor(rippleSettings.strokeColor[0])},
${Math.floor(rippleSettings.strokeColor[1])},
${Math.floor(rippleSettings.strokeColor[2])},
${this.opacity})`;
},
draw: function draw() {
this.ctx.beginPath();
this.ctx.strokeStyle = this.strokeColor;
this.ctx.arc(this.position.x, this.position.y, this.circleSize, 0,
2 * Math.PI);
this.ctx.stroke();
},
setStatus: function setStatus(status) {
this.status = status;
},
};
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
console.log(ctx)
const ripples = [];
const rippleStartStatus = 'start';
const isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
canvas.style.filter = `blur(${canvasSettings.blur}px)`;
canvas.width = width * canvasSettings.ratio;
canvas.height = height * canvasSettings.ratio;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
let animationFrame;
// Function which is executed on mouse hover on canvas
const canvasMouseOver = (e) => {
const x = e.clientX * canvasSettings.ratio;
const y = e.clientY * canvasSettings.ratio;
ripples.unshift(new Ripple(x, y, 2, ctx));
};
const animation = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const length = ripples.length;
for (let i = length - 1; i >= 0; i -= 1) {
const r = ripples[i];
r.update();
r.draw();
if (r.opacity <= 0) {
ripples[i] = null;
delete ripples[i];
ripples.pop();
}
}
animationFrame = window.requestAnimationFrame(animation);
};
animation();
canvas.addEventListener('mousemove', canvasMouseOver);
})
I removed the part of GUI based on the link because I don't need the settings. So what I want here is the getContext('2d') I don't understand why it is wrong. Can anyone figure out it for me? Should I be asking this question? I feel so unlucky right now hahha badly need help.
I have a canvas where I use "fillText" with a string, saying for example "stackoverflow". Then I read the imagedata of the canvas in order to pick out each pixel of that text.
I want to pick the following from the pixel: x position, y position and its color. Then I would like to loop over that array with those pixels so I can draw back the text pixel by pixel so I have full control of each pixel, and can for example animate them.
However, I dont get it as smooth as I want. Look at my attach image, and you see the difference between the top text and then the text I've plotted out using fillRect for each pixel. Any help on how to make the new text look like the "fillText" text does?
Thanks
UPDATE: Added my code
var _particles = [];
var _canvas, _ctx, _width, _height;
(function(){
init();
})();
function init(){
setupParticles(getTextCanvasData());
}
function getTextCanvasData(){
// var w = 300, h = 150, ratio = 2;
_canvas = document.getElementById("textCanvas");
// _canvas.width = w * ratio;
// _canvas.height = h * ratio;
// _canvas.style.width = w + "px";
// _canvas.style.height = h + "px";
_ctx = _canvas.getContext("2d");
_ctx.fillStyle = "rgb(0, 154, 253)";
// _ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
var str = "stackoverflow";
_ctx.font = "32px EB Garamond";
_ctx.fillText(str,0,23);
_width = _canvas.width;
_height = _canvas.height;
var data32 = new Uint32Array(_ctx.getImageData(0, 0, _width, _height).data.buffer);
var positions = [];
for(i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
positions.push({
x: (i % _width),
y: ((i / _width)|0),
});
}
}
return positions;
}
function setupParticles(positions){
var i = positions.length;
var particles = [];
while(i--){
var p = new Particle();
p.init(positions[i]);
_particles.push(p);
drawParticle(p);
}
}
function drawParticle(particle){
var x = particle.x;
var y = particle.y;
_ctx.beginPath();
_ctx.fillRect(x, y, 1, 1);
_ctx.fillStyle = 'green';
}
function Particle(){
this.init = function(pos){
this.x = pos.x;
this.y = pos.y + 30;
this.x0 = this.x;
this.y0 = this.y;
this.xDelta = 0;
this.yDelta = 0;
}
}
Here is an update to your code that reuses the alpha component of each pixel. There will still be some detail lost because we do not keep the antialiasing of the pixels (which in effect alters the actual color printed), but for this example the alpha is enough.
var _particles = [];
var _canvas, _ctx, _width, _height;
(function(){
init();
})();
function init(){
setupParticles(getTextCanvasData());
}
function getTextCanvasData(){
// var w = 300, h = 150, ratio = 2;
_canvas = document.getElementById("textCanvas");
// _canvas.width = w * ratio;
// _canvas.height = h * ratio;
// _canvas.style.width = w + "px";
// _canvas.style.height = h + "px";
_ctx = _canvas.getContext("2d");
_ctx.imageSmoothingEnabled= false;
_ctx.fillStyle = "rgb(0, 154, 253)";
// _ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
var str = "stackoverflow";
_ctx.font = "32px EB Garamond";
_ctx.fillText(str,0,23);
_width = _canvas.width;
_height = _canvas.height;
var pixels = _ctx.getImageData(0, 0, _width, _height).data;
var data32 = new Uint32Array(pixels.buffer);
var positions = [];
for(i = 0; i < data32.length; i++) {
if (data32[i] & 0xffff0000) {
positions.push({
x: (i % _width),
y: ((i / _width)|0),
a: pixels[i*4 + 3] / 255
});
}
}
return positions;
}
function setupParticles(positions){
var i = positions.length;
var particles = [];
while(i--){
var p = new Particle();
p.init(positions[i]);
_particles.push(p);
drawParticle(p);
}
}
function drawParticle(particle){
var x = particle.x;
var y = particle.y;
_ctx.beginPath();
_ctx.fillStyle = `rgba(0,128,0,${particle.alpha})`;
_ctx.fillRect(x, y, 1, 1);
}
function Particle(){
this.init = function(pos){
this.x = pos.x;
this.y = pos.y + 30;
this.x0 = this.x;
this.y0 = this.y;
this.xDelta = 0;
this.yDelta = 0;
this.alpha = pos.a;
}
}
<canvas id="textCanvas"></canvas>
I am making a map that renders position of game objects (Project Zomboid zombies):
As user zooms out, single dots are no longer useful. Instead, I'd like to render distribution of zombies on an area using red color gradient. I tried to loop over all zombies for every rendered pixel and color it reciprocally to the sum of squared distances to the zombies. The result:
That's way too blurry. Also the results are more influenced by the zombies that are AWAY from the points - I need to influence them more by the zombies that are CLOSE. So what this is is just math. Here's the code I used:
var h = canvas.height;
var w = canvas.width;
// To loop over more than 1 pixel (performance)
var tileSize = 10;
var halfRadius = Math.floor(tileSize/2);
var time = performance.now();
// "Squared" because we didnt unsquare it
function distanceSquared(A, B) {
return (A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
}
// Loop for every x,y pixel (or region of pixels)
for(var y=0; y<h; y+=tileSize) {
for(var x=0; x<w; x+=tileSize) {
// Time security - stop rendering after 1 second
if(performance.now()-time>1000) {
x=w;y=h;break;
}
// Convert relative canvas offset to absolute point on the map
var point = canvasPixeltoImagePixel(x, y);
// For every zombie add sqrt(distance from this point to zombie)
var distancesRoot = 0;
// Loop over the zombies
var zombieCoords;
for(var i=0; i<zombies_length; i++) {
// Get single zombie coordinates as {x:0, y:0}
if((coords=zombies[i].pixel)==null)
coords = zombies[i].pixel = tileToPixel(zombies[i].coordinates[0], zombies[i].coordinates[1], drawer);
// square root is a) slow and b) probably not what I want anyway
var dist = distanceSquared(coords, point);
distancesRoot+=dist;
}
// The higher the sum of distances is, the more intensive should the color be
var style = 'rgba(255,0,0,'+300000000/distancesRoot+')';
// Kill the console immediatelly
//console.log(style);
// Maybe we should sample and cache the transparency styles since there's limited ammount of colors?
ctx.fillStyle = style;
ctx.fillRect(x-halfRadius,y-halfRadius,tileSize,tileSize);
}
}
I'm pretty fine with theoretical explanation how to do it, though if you make simple canvas example with some points, what would be awesome.
This is an example of a heat map. It's basically gradient orbs over points and then ramping the opacity through a heat ramp. The more orbs cluster together the more solid the color which can be shown as an amplified region with the proper ramp.
update
I cleaned up the variables a bit and put the zeeks in an animation loop. There's an fps counter to see how it's performing. The gradient circles can be expensive. We could probably do bigger worlds if we downscale the heat map. It won't be as smooth looking but will compute a lot faster.
update 2
The heat map now has an adjustable scale and as predicted we get an increase in fps.
if (typeof app === "undefined") {
var app = {};
}
app.zeeks = 200;
app.w = 600;
app.h = 400;
app.circleSize = 50;
app.scale = 0.25;
init();
function init() {
app.can = document.getElementById('can');
app.ctx = can.getContext('2d');
app.can.height = app.h;
app.can.width = app.w;
app.radius = Math.floor(app.circleSize / 2);
app.z = genZ(app.zeeks, app.w, app.h);
app.flip = false;
// Make temporary layer once.
app.layer = document.createElement('canvas');
app.layerCtx = app.layer.getContext('2d');
app.layer.width = Math.floor(app.w * app.scale);
app.layer.height = Math.floor(app.h * app.scale);
// Make the gradient canvas once.
var sCircle = Math.floor(app.circleSize * app.scale);
app.radius = Math.floor(sCircle / 2);
app.gCan = genGradientCircle(sCircle);
app.ramp = genRamp();
// fps counter
app.frames = 0;
app.fps = "- fps";
app.fpsInterval = setInterval(calcFps, 1000);
// start animation
ani();
flicker();
}
function calcFps() {
app.fps = app.frames + " fps";
app.frames = 0;
}
// animation loop
function ani() {
app.frames++;
var ctx = app.ctx;
var w = app.w;
var h = app.h;
moveZ();
//ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#006600";
ctx.fillRect(0, 0, w, h);
if (app.flip) {
drawZ2();
drawZ();
} else {
drawZ2();
}
ctx.fillStyle = "#FFFF00";
ctx.fillText(app.fps, 10, 10);
requestAnimationFrame(ani);
}
function flicker() {
app.flip = !app.flip;
if (app.flip) {
setTimeout(flicker, 500);
} else {
setTimeout(flicker, 5000);
}
}
function genGradientCircle(size) {
// gradient image
var gCan = document.createElement('canvas');
gCan.width = gCan.height = size;
var gCtx = gCan.getContext('2d');
var radius = Math.floor(size / 2);
var grad = gCtx.createRadialGradient(radius, radius, radius, radius, radius, 0);
grad.addColorStop(1, "rgba(255,255,255,.65)");
grad.addColorStop(0, "rgba(255,255,255,0)");
gCtx.fillStyle = grad;
gCtx.fillRect(0, 0, gCan.width, gCan.height);
return gCan;
}
function genRamp() {
// Create heat gradient
var heat = document.createElement('canvas');
var hCtx = heat.getContext('2d');
heat.width = 256;
heat.height = 5;
var linGrad = hCtx.createLinearGradient(0, 0, heat.width, heat.height);
linGrad.addColorStop(1, "rgba(255,0,0,.75)");
linGrad.addColorStop(0.5, "rgba(255,255,0,.03)");
linGrad.addColorStop(0, "rgba(255,255,0,0)");
hCtx.fillStyle = linGrad;
hCtx.fillRect(0, 0, heat.width, heat.height);
// create ramp from gradient
var ramp = [];
var imageData = hCtx.getImageData(0, 0, heat.width, 1);
var d = imageData.data;
for (var x = 0; x < heat.width; x++) {
var i = x * 4;
ramp[x] = [d[i], d[i + 1], d[i + 2], d[i + 3]];
}
return ramp;
}
function genZ(n, w, h) {
var a = [];
for (var i = 0; i < n; i++) {
a[i] = [
Math.floor(Math.random() * w),
Math.floor(Math.random() * h),
Math.floor(Math.random() * 3) - 1,
Math.floor(Math.random() * 3) - 1
];
}
return a;
}
function moveZ() {
var w = app.w
var h = app.h;
var z = app.z;
for (var i = 0; i < z.length; i++) {
var s = z[i];
s[0] += s[2];
s[1] += s[3];
if (s[0] > w || s[0] < 0) s[2] *= -1;
if (s[1] > w || s[1] < 0) s[3] *= -1;
}
}
function drawZ() {
var ctx = app.ctx;
var z = app.z;
ctx.fillStyle = "#FFFF00";
for (var i = 0; i < z.length; i++) {
ctx.fillRect(z[i][0] - 2, z[i][1] - 2, 4, 4);
}
}
function drawZ2() {
var ctx = app.ctx;
var layer = app.layer;
var layerCtx = app.layerCtx;
var gCan = app.gCan;
var z = app.z;
var radius = app.radius;
// render gradients at coords onto layer
for (var i = 0; i < z.length; i++) {
var x = Math.floor((z[i][0] * app.scale) - radius);
var y = Math.floor((z[i][1] * app.scale) - radius);
layerCtx.drawImage(gCan, x, y);
}
// adjust layer for heat ramp
var ramp = app.ramp;
// apply ramp to layer
var imageData = layerCtx.getImageData(0, 0, layer.width, layer.height);
d = imageData.data;
for (var i = 0; i < d.length; i += 4) {
if (d[i + 3] != 0) {
var c = ramp[d[i + 3]];
d[i] = c[0];
d[i + 1] = c[1];
d[i + 2] = c[2];
d[i + 3] = c[3];
}
}
layerCtx.putImageData(imageData, 0, 0);
// draw layer on world
ctx.drawImage(layer, 0, 0, layer.width, layer.height, 0, 0, app.w, app.h);
}
<canvas id="can" width="600" height="400"></canvas>
Is there a way to keep count of the number of shapes drawn on a canvas
I'm using a brush of sorts to draw a string of circles on a canvas and would like to find a way to count how many are present
var mainCanvas = document.getElementById('draw1');
mainContext = mainCanvas.getContext('2d');
var CircleBrush = {
iPrevX: 0,
iPrevY: 0,
// initialization function
init: function () {
mainContext.globalCompositeOperation = 'source-over';
mainContext.lineWidth = 1;
mainContext.strokeStyle = '#4679BD';
mainContext.lineWidth = 1;
mainContext.lineJoin = 'round';
},
startCurve: function (x, y) {
this.iPrevX = x;
this.iPrevY = y;
mainContext.fillStyle = '#4679BD';
},
draw: function (x, y) {
var iXAbs = Math.abs(x - this.iPrevX);
var iYAbs = Math.abs(y - this.iPrevY);
var rad = 6;
if (iXAbs > 10 || iYAbs > 10) {
mainContext.beginPath();
mainContext.arc(this.iPrevX, this.iPrevY, rad, Math.PI * 2, false);
mainContext.fill();
mainContext.stroke();
this.iPrevX = x;
this.iPrevY = y;
}
}
};
var circleCounter = [0];
mainContext.font = '21pt Arial';
mainContext.fillStyle = '#262732';
mainContext.textBaseline = 'top';
mainContext.fillText(circleCounter, 20, 20);
CircleBrush.init();
$('#draw1').mousedown(function (e) { // mouse down handler
cMoeDo = true;
var canvasOffset = $('#draw1').offset();
var canvasX = Math.floor(e.pageX - canvasOffset.left);
var canvasY = Math.floor(e.pageY - canvasOffset.top);
CircleBrush.startCurve(canvasX, canvasY);
circleCounter ++1;
});
$('#draw1').mouseup(function (e) { // mouse up handler
cMoeDo = false;
});
$('#draw1').mousemove(function (e) { // mouse move handler
if (cMoeDo) {
var canvasOffset = $('#draw1').offset();
var canvasX = Math.floor(e.pageX - canvasOffset.left);
var canvasY = Math.floor(e.pageY - canvasOffset.top);
CircleBrush.draw(canvasX, canvasY);
circleCounter ++1;
}
})
Demo fiddle http://jsfiddle.net/A2vyY/
Thanks in advance
You need to clear the space for the counter and redraw the count. In order to do so I put the counter and text drawing in the draw function like so
draw: function (x, y) {
var iXAbs = Math.abs(x - this.iPrevX);
var iYAbs = Math.abs(y - this.iPrevY);
var rad = 6;
if (iXAbs > 10 || iYAbs > 10) {
mainContext.beginPath();
mainContext.arc(this.iPrevX, this.iPrevY, rad, Math.PI*2, false);
mainContext.fill();
mainContext.stroke();
this.iPrevX = x;
this.iPrevY = y;
circleCounter ++;
mainContext.clearRect(0,0,50,25);
mainContext.fillText(circleCounter, 5, 5);
}
}
Updated jsFiddle (I moved the counter some so that there is more room for the dots)
You can put the counter in a separate div and just update the text
<div id="content">
<div id="counter">0</div>
<canvas id="draw1" height="500" width="500"></canvas>
</div>
Return true when a circle is drawn, false if not
draw: function (x, y) {
/* ... */
if (iXAbs > 10 || iYAbs > 10) {
/* ... */
return true;
}
return false;
}
increment and show as necessary
if (CircleBrush.draw(canvasX, canvasY)) {
++circleCounter;
$('#counter').text(circleCounter);
}
See modified JSFiddle
I'm trying to make a simple (or so I thought) memory game. Unfortunately it does not update state of cards when user clicks on them. I'm running out of ideas, probably because it's my first javascript game. I suppose there is a problem with game loop. Could anyone at least point me in the right direction and help me understand what needs to be changed/rewritten?
//HTML5 Memory Game implementation
//main variables
var cards = [1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8];
var exposed = [makeArray("false",16)];
var first_card = 0;
var second_card = 0;
var moves = 0;
var WIDTH = 800;
var HEIGHT = 100;
var state = 0;
var mouseX = 0;
var mouseY = 0;
//creating canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = WIDTH;
canvas.height = HEIGHT;
document.getElementById("game").appendChild(canvas);
//filling empty array with number,character,object
function makeArray(value, length) {
var newArray = [];
var i = 0;
while (i<length) {
newArray[i] = value;
i++;
}
return newArray;
}
//shuffling algorithm
function shuffle(array) {
var copy = [];
var n = array.length;
var i;
while (n) {
i = Math.floor(Math.random() * n--);
copy.push(array.splice(i, 1)[0]);
}
return copy;
}
//where user clicks
function getClickPosition(event) {
var X = event.pageX - canvas.offsetLeft;
var Y = event.pageY - canvas.offsetTop;
return mouse = [X, Y];
}
//read click position
function readPos(event) {
mousePos = getClickPosition(event);
mouseX = mousePos[0];
mouseY = mousePos[1];
}
//initializing
function init() {
state = 0;
moves = 0;
exposed = [makeArray("false",16)];
cards = shuffle(cards);
}
//drawing cards
function draw() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i in cards) {
if (exposed[i] === true) {
ctx.fillStyle = "rgb(250, 250, 250)";
ctx.font = "50px Courier New";
ctx.fillText(cards[i], (i*50+12), 65);
} else {
ctx.strokeStyle = "rgb(250, 0, 0)";
ctx.fillStyle = "rgb(0, 0, 250)";
ctx.fillRect(i*50, 0, 50, 100);
ctx.strokeRect(i*50, 0, 50, 100);
}
}
};
//update cards
function update() {
if (exposed[parseInt(mouseX / 50)] === false) {
if (state == 0) {
state = 1;
first_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
} else if (state == 1) {
state = 2;
second_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
} else {
if (cards[first_card] != cards[second_card]) {
exposed[first_card] = false;
exposed[second_card] = false;
}
state = 1;
first_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
}
}
}
addEventListener('click', readPos, false);
setInterval(function() {
update();
draw();
}, 16);
I would check your addEventListener method: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
I also recommend you look into using jQuery.
After copy and pasting your code I found a couple of things:
You didn't add an event listener to anything, you should add it to something so I added it to document.
You initialize the exposed array with values "false" and later check if they are false. These are not the same, the string "false" isn't the Boolean false.
You initializes the exposed array as a multi dimensional array [[false,false,false ...]] this should be a single dimension array because later you check exposed[1] (1 depending on the mouse x position.
No need to call draw and update every 16 milliseconds, you can call it after someone clicked.
Wrapped the whole thing up in a function so there are no global variables created.
Here is the code after changing these obvious errors. There might be room for optimization but for now I've gotten the problems out.
<!DOCTYPE html>
<html>
<head>
<title>test</title>
</head>
<body>
<div id="game"></div>
<script type="text/javascript">
(function(){
//HTML5 Memory Game implementation
//main variables
var cards = [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8];
var exposed = makeArray(false, 16);
var first_card = 0;
var second_card = 0;
var moves = 0;
var WIDTH = 800;
var HEIGHT = 100;
var state = 0;
var mouseX = 0;
var mouseY = 0;
//creating canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = WIDTH;
canvas.height = HEIGHT;
document.getElementById("game").appendChild(canvas);
//filling empty array with number,character,object
function makeArray(value, length) {
var newArray = [];
var i = 0;
while (i < length) {
newArray.push(value);
i++;
}
return newArray;
}
//shuffling algorithm
function shuffle(array) {
var copy = [];
var n = array.length;
var i;
while (n) {
i = Math.floor(Math.random() * n--);
copy.push(array.splice(i, 1)[0]);
}
return copy;
}
//where user clicks
function getClickPosition(event) {
var X = event.pageX - canvas.offsetLeft;
var Y = event.pageY - canvas.offsetTop;
return mouse = [X, Y];
}
//read click position
function readPos(event) {
mousePos = getClickPosition(event);
mouseX = mousePos[0];
mouseY = mousePos[1];
update();
draw();
}
//initializing
function init() {
state = 0;
moves = 0;
exposed = makeArray(false, 16);
cards = shuffle(cards);
}
//drawing cards
function draw() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i in cards) {
if (exposed[i] === true) {
ctx.fillStyle = "rgb(150, 150, 150)";
ctx.font = "50px Courier New";
ctx.fillText(cards[i], (i * 50 + 12), 65);
} else {
ctx.strokeStyle = "rgb(250, 0, 0)";
ctx.fillStyle = "rgb(0, 0, 250)";
ctx.fillRect(i * 50, 0, 50, 100);
ctx.strokeRect(i * 50, 0, 50, 100);
}
}
};
//update cards
function update() {
if (exposed[parseInt(mouseX / 50)] === false) {
if (state == 0) {
state = 1;
first_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
} else if (state == 1) {
state = 2;
second_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
} else {
if (cards[first_card] != cards[second_card]) {
exposed[first_card] = false;
exposed[second_card] = false;
}
state = 1;
first_card = parseInt(mouseX / 50);
exposed[parseInt(mouseX / 50)] = true;
}
}
}
document.body.addEventListener('click', readPos, false);
init();
draw();
})();
</script>
</body>
</html>
Your overall logic was good.
The point that was 'bad' was the way you handle the event :
the event handler should store some valuable information that
the update will later process and clear.
Here you mix your update with event handling, which cannot work
especially since the event will not fire on every update.
So i did a little fiddle to show you, the main change is
the click event handler, which update the var last_clicked_card :
http://jsfiddle.net/wpymH/
//read click position
function readPos(event) {
last_clicked_card = -1;
mousePos = getClickPosition(event);
mouseX = mousePos[0];
mouseY = mousePos[1];
// on canvas ?
if ((mouseY>100)||(mouseX<0)||(mouseX>WIDTH)) return;
// now yes : which card clicked ?
last_clicked_card = Math.floor(mouseX/50);
}
and then update is the processing of this information :
//update cards
function update() {
// return if no new card clicked
if (last_clicked_card == -1) return;
// read and clear last card clicked
var newCard = last_clicked_card;
last_clicked_card=-1;
// flip, store it as first card and return
// if there was no card flipped
if (state==0) { exposed[newCard] = true;
first_card = newCard;
state = 1 ;
return; }
// just unflip card if card was flipped
if ((state = 1) && exposed[newCard]) {
exposed[newCard]=false ;
state=0;
return;
}
// we have a second card now
second_card = newCard;
exposed[second_card] = true;
draw();
// ... i don't know what you want to do ...
if (cards[first_card] == cards[second_card]) {
alert('win'); }
else {
alert('loose'); }
exposed[first_card]=false;
exposed[second_card]=false;
state=0;
}