So, I've created myself a little demo with javascript/html canvas in the context of a gameloop. You can move a small square by pressing the w,a,s,d keys. However, when held down for more than 3 or 4 seconds, the canvas becomes stuttery and the square almost stops moving.
Here's the javascript;
// --------------------------------------------------------------------
// -- MAIN GAME LOOP
// --------------------------------------------------------------------
function gameLoop(){
update();
render();
requestAnimationFrame(gameLoop);
}
function update(){
processInput();
};
function render(){
var canvas = document.getElementById('viewport');
var ctx = canvas.getContext('2d');
if(upDown){
rect.top -= rect.speed;
}else if(downDown){
rect.top += rect.speed;
}else if(leftDown){
rect.left -= rect.speed;
}else if(rightDown) {
rect.left += rect.speed;
}
ctx.clearRect(0, 0, 1024, 768);
ctx.beginPath();
ctx.rect(rect.left, rect.top, 50, 50, true);
ctx.closePath();
ctx.fill();
};
var rect = {
top: 0,
left: 0,
speed: 5
};
// --------------------------------------------------------------------
// -- OTHER FUNCTIONS
// --------------------------------------------------------------------
var rightDown = false;
var leftDown = false;
var upDown = false;
var downDown = false;
function processInput(){
$(document).keydown(function(e){
console.log(e.keyCode);
if(e.keyCode == 87){upDown = true;}
if(e.keyCode == 83){downDown = true;}
if(e.keyCode == 68){rightDown = true;}
if(e.keyCode == 65){leftDown = true;}
}).keyup(function(){
upDown = false;
downDown = false;
rightDown = false;
leftDown = false;
})
}
$(document).ready(function(){
requestAnimationFrame(gameLoop);
});
Anyone got any ideas?
Here's my codepen;
http://codepen.io/anon/pen/wKGJOr
The issue is because you're calling processInput (via update) within your gameloop. This function is attaching new keydown and keyup event handlers every time it is called. It's only necessary to call it once. Remove the call from update, and (for example) call it within the ready function instead:
$(document).ready(function(){
processInput();
requestAnimationFrame(gameLoop);
});
By registering more and more event handlers, you're causing a lot more code to run than is necessary, hence the stuttering.
Updated codepen.
Related
I'm trying to implement key input in my ping-pong game. The main point is that the up and down arrow keys don't work at all. My browser console doesn't display any errors messages.
Here is my code, this is WIP some Objects are not implemented yet
var playerBat = {
x: null,
y: null,
width: 20,
height: 80,
UP_DOWN: false,
DOWN_DOWN: false,
update: function() {
// Keyboard inputs
window.addEventListener('keydown', onKeyDown, false);
window.addEventListener('keyup', onKeyUp, false);
var key = {
UP: 38,
DOWN: 40
};
function onKeyDown(e) {
if (e.keyCode == 38)
this.UP_DOWN = true;
else if (e.keyCode == 40)
this.DOWN_DOWN = true;
}
function onKeyUp(e) {
if (e.keyCode == 38)
this.UP_DOWN = false;
else if (e.keyCode == 40)
this.DOWN_DOWN = false;
}
this.y = Math.max(Math.min(this.y, Canvas_H - this.height), 0); // Collide world bounds
},
render: function() {
ctx.fillStyle = '#000';
ctx.fillRect(this.x, this.y, this.width, this.height);
if (this.UP_DOWN)
this.playerBat.y -= 5;
else if (this.DOWN_DOWN)
this.playerBat.y += 5;
}
};
The events are firing, the problem is that you're adding them on each update. What you'll want to do it take the callbacks and the addEventListeners outside in a method such as addEvents, which should be called ONCE during initialization. Currently the huge amount of event handlers being triggered kills the page.
function addEvents() {
window.addEventListener('keydown', onKeyDown, false);
window.addEventListener('keyup', onKeyUp, false);
var key = {
UP: 38,
DOWN: 40
};
function onKeyDown(e) {
if (e.keyCode == key.UP) {
playerPaddle.UP_DOWN = true;
}
if (e.keyCode == key.DOWN) {
playerPaddle.DOWN_DOWN = true;
}
}
function onKeyUp(e) {
if (e.keyCode == key.UP) {
playerPaddle.UP_DOWN = false;
}
if (e.keyCode == 40) {
playerPaddle.DOWN_DOWN = key.DOWN;
}
}
}
After reviewing it further there are some other problems. First of all, the logic for actually changing the X and Y of the paddle should be within the update method (since that's what's usually used to change object properties), while the render method should simply draw the shapes and images using the object's updated properties.
Second, you're trying to access this.playerBat.y within the render method, however 'this' actually IS the playerBat. So in order to properly target the 'y' property you'd need to write this.y instead.
I also noticed that you've got a key map, with UP and DOWN keycodes defined, but don't actually use it, instead you use numbers. Maybe something you were planning on doing?
I reimplemented the code you have provided and added a init function to playerBat that attaches the event listeners for keydown and keyup events. I just kept the relevant bits and implemented objects as functions, but the concept should still be the applicable.
The callback function passed into addEventListener needs to bind this, otherwise the this value inside the callback (this.UP_DOWN and this.DOWN_DOWN) won't be the same as the this value in the enclosing scope; the one value you intended.
<canvas id='canvas' style="background:#839496">Your browser doesn't support The HTML5 Canvas</canvas>
<script>
var canvas = document.getElementById('canvas');
canvas.width = window.innerWidth-20;
canvas.height = window.innerHeight-20;
var ctx = canvas.getContext('2d');
var Canvas_W = Math.floor(canvas.width);
var Canvas_H = Math.floor(canvas.height);
/*
* Define a Player object.
*/
function PlayerBat(){
this.x = null;
this.y = null;
this.width = 20;
this.height = Canvas_H/3;
this.UP_DOWN = false;
this.DOWN_DOWN = false;
this.init = function() {
console.log('init');
// MUST bind `this`!
window.addEventListener('keydown', function(e){
console.log('keydown');
if (e.keyCode == 38) this.UP_DOWN = true;
else if (e.keyCode == 40) this.DOWN_DOWN = true;
}.bind(this), false);
// MUST bind `this`!
window.addEventListener('keyup', function(e){
console.log('keyUp')
if (e.keyCode == 38) this.UP_DOWN = false;
else if (e.keyCode == 40) this.DOWN_DOWN = false;
}.bind(this), false);
};
this.update = function() {
var key = {UP: 38, DOWN: 40};
this.y = Math.max(Math.min(this.y, Canvas_H - this.height), 0);
};
this.render = function() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Redraw paddle
ctx.fillStyle = '#00F';
ctx.fillRect(this.x, this.y, this.width, this.height);
this.y = (this.UP_DOWN) ? this.y - 5 : ((this.DOWN_DOWN) ? this.y + 5 : this.y );
};
}
function GameRunner(){
// Create instance of player
var playerBat = new PlayerBat();
playerBat.init();
// Execute upon instantiation of GameRunner
(function () {
playerBat.x = playerBat.width;
playerBat.y = (Canvas_H - playerBat.height) / 2;
})();
function step() {
playerBat.update();
playerBat.render();
requestAnimationFrame(step);
}
// Public method. Start animation loop
this.start = function(){
requestAnimationFrame(step);
}
}
// Create GameRunner instance
var game = new GameRunner();
// Start game
game.start();
</script>
I'm making a game, and I'd like to know how to make a character move more smoothly. The character can already move, but it moves really choppy; when you click the arrow key, it instantly appears 10 pixels ahead. I'd like it to move smoothly so it doesn't just "appear" 10 pixels ahead of itself.
Here is the Code:
document.onkeydown = checkKey;
var canvas;
var ctx;
var up;
var down;
var left;
var right;
var bobX = 200;
var bobY = 200;
var bobWidth = 30;
var bobHeight = 30;
window.onload = function() {
canvas = document.getElementById("gameCanvas");
ctx = canvas.getContext("2d");
var fps = 200; // frames per second
setInterval(function() {
updateAll();
drawAll();
}, 1000/fps)
};
var drawAll = function() {
// draw background
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// draw bob
ctx.fillStyle = "red";
ctx.fillRect(bobX, bobY, bobWidth, bobHeight);
};
var updateAll = function() {
if (up == true) {
up = false;
}
if (down == true) {
bobY += 1;
down = false;
}
if (left == true) {
bobX -= 1;
left = false;
}
if (right == true) {
bobX += 1;
right = false;
}
};
function checkKey(e) {
e = e || window.event;
if (e.keyCode == '38') {
up = true;
}
else if (e.keyCode == '40') {
down = true;
}
else if (e.keyCode == '37') {
left = true;
}
else if (e.keyCode == '39') {
right = true;
}
}
I tried doing moving it by one pixel every keypress, but it moves very slowly when I do that.
Your screen has maximum refreshrate, usually 60 fps. Some screens can get up to 120fps, but that's a rather rare case.
So what is happening here:
var fps = 200; // frames per second
setInterval(function() {
updateAll();
drawAll();
}, 1000/fps)
};
The canvas gets redrawn and the position gets updated at a rate which your screen can't catch up with. You simply can't see that your character only moves 1 pixel instead of 10 pixel.
Solution would be to use requestAnimationFrame instead. Which invokes when the screen refreshes:
function animate() {
requestAnimationFrame(animate);
updateAll();
drawAll();
};
animate();
I am building a simple 2D game as an attempt to learn canvas. The character can run around a virtual environment, and a variable called yOffset controls his offset from the top of the screen. I also have a global variable called running which sets itself to true or false based on whether or not the character is running (not shown here). My goal is to make the character bob up and down whilst he is running, and all the below code does is spawn lots of setInterval()s. Is this the right way to make my character run, or should I do it another way? If so, how?
$(document).keydown(function(e) {
if(e.which == 97) {
running = true;
run();
} else if(e.which == 100) {
running = true;
run();
} else if(e.which == 119) {
running = true;
run();
} else if(e.which == 115) {
running = true;
run();
}
});
(yes, if the character stops running, the running variable does go to false [not shown here] - I've already made sure the running variable works well)
runTimer = 0;
function run() {
if(runTimer == 0 && running) {
runTimer = 1;
yOffset = 80;
setTimeout(function() {
yOffset = 120;
}, 150);
setTimeout(function() { if (running) { runTimer = 0;run(); } }, 300);
}
}
If you need more information, the version that I am currently working on is available here.
I think you can simplify your code, and in fact you must in the quite probable case where you'd like to add some other characters.
To allow re-use of the animation, it's better to separate what is an animation (== the different steps that your character will go through), and an animation state (== in which step your character is now).
I wrote here some elements of an animation system.
So i define what is an animation step, a whole Animation (which is so far only an array of animation step), and an Animator (which holds the state, one might see it as a 'reader' of an animation).
Once you defined the animation and animators, and started the animators, you just have to call tick(time) to have the animation move on, and offset() to read the offset, which is way simpler than fighting with a bunch of setIntervals.
http://jsfiddle.net/xWwFf/
// --------------------
function AnimationStep(duration, offset) {
this.duration = duration;
this.offset = offset;
// you might add : image index, rotation, ....
}
// --------------------
function Animation(animationSteps) {
this.steps = animationSteps; // Array of AnimationStep
}
// define a read-only length property
Object.defineProperty(Animation.prototype, 'length', {
get: function () {
return this.steps.length
}
});
// --------------------
function Animator() {
this.currentAnimation = null;
this.step = -1;
this.running = false;
this.remainingTime = 0; // remaining time in current step;
}
Animator.prototype.startAnim = function (newAnim, firstStep) {
this.currentAnimation = newAnim;
this.step = firstStep || 0;
this.remainingTime = newAnim.steps[this.step].duration;
this.running = true;
}
Animator.prototype.tick = function (dt) {
// do nothing if no animation ongoing.
if (!this.running) return;
this.remainingTime -= dt;
// 'eat' as many frames as required to have a >0 remaining time
while (this.remainingTime <= 0) {
this.step++;
if (this.step == this.currentAnimation.length) this.step = 0;
this.remainingTime += this.currentAnimation.steps[this.step].duration;
}
};
Animator.prototype.offset = function () {
return this.currentAnimation.steps[this.step].offset;
}
// ______________________________
// example
var bounceAnim = [];
bounceAnim.push(new AnimationStep(200, 10));
bounceAnim.push(new AnimationStep(180, 20));
bounceAnim.push(new AnimationStep(150, 30));
bounceAnim.push(new AnimationStep(300, 40));
bounceAnim.push(new AnimationStep(320, 45));
bounceAnim.push(new AnimationStep(200, 40));
bounceAnim.push(new AnimationStep(120, 30));
bounceAnim.push(new AnimationStep(100, 20));
var anim1 = new Animation(bounceAnim);
var animator1 = new Animator();
var animator2 = new Animator();
animator1.startAnim(anim1);
animator2.startAnim(anim1, 3);
// in action :
var ctx = document.getElementById('cv').getContext('2d');
function drawScene() {
ctx.fillStyle = 'hsl(200,60%, 65%)';
ctx.fillRect(0, 0, 600, 200);
ctx.fillStyle = 'hsl(90,60%,75%)';
ctx.fillRect(0, 200, 600, 200);
ctx.fillStyle = 'hsl(10,60%,75%)';
ctx.fillRect(200, 200 + animator1.offset(), 22, 22);
ctx.fillStyle = 'hsl(40,60%,75%)';
ctx.fillRect(400, 200 + animator2.offset(), 22, 22);
animator1.tick(20);
animator2.tick(20);
}
setInterval(drawScene, 20);
I was trying to create a sample paint application using HTML 5 canvas. Then I added a button to redraw what user had drawn earlier. I am not sure what I am doing wrong or may be completely wrong. When I click redraw button multiple times it generates some magical animation by drawing lines all over. Even though if I log the starting point of drawing the image its same every time.
Demo: http://jsfiddle.net/BW57H/6/
Steps to reproduce:
Draw some circle or rectangle or something by clicking the mouse and dragging it on the rectangular box. Then click reset and redraw , click redraw couple of times after that and see the result.
I am not sure what I have done. I have not read a lot about Canvas. But I am curious to know what is going on here. Thanks.
html
<body>
<canvas id="paint" width="600px" height="300px"></canvas>
<div id="controls">
<button name="reset" id="reset">Reset</button>
<button name="redraw" id="redraw">Re-Draw</button>
</div>
</body>
css
#paint{
border: solid;
}
js
$(document).ready(function(){
var x, y, context, painter;
var xCounter = 0 , yCounter = 0;
var xarray = [];
var yarray = [];
function init(){
while(document.getElementById("paint") === undefined){
//do nothing
}
console.log("Loaded document now registering events for canvas");
var canvas = document.getElementById("paint");
context = canvas.getContext('2d');
painter = new Painter();
canvas.addEventListener('mousedown', capture, false);
canvas.addEventListener('mouseup', capture, false);
canvas.addEventListener('mousemove', capture, false);
document.getElementById("reset").addEventListener("click",function(){ clearCanvas(canvas);}, false);
document.getElementById("redraw").addEventListener("click", function(){
autoDraw();
}, false);
}
function clearCanvas(canvas){
context.save();
// Use the identity matrix while clearing the canvas
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
// Restore the transform
context.restore();
};
function capture(event){
if(event.which !== 1){
return;
}
x = event.layerX;
y = event.layerY;
switch(event.type){
case 'mousedown':
painter.startPaint(event);
break;
case 'mouseup':
painter.endPaint(event);
break;
case 'mousemove':
painter.paint(event);
break;
}
};
var Painter = function(){
var self = this;
self.paintStarted = false;
self.startPaint = function(event){
self.resetRecordingParams();
self.paintStarted = true;
context.beginPath();
context.moveTo(x,y);
self.record();
}
self.endPaint = function(event){
self.paintStarted = false;
self.record();
self.paint(event)
}
self.paint = function(event){
if(self.paintStarted){
context.lineTo(x,y);
context.stroke();
self.record();
}
}
self.record = function(){
xarray[xCounter++] = x;
yarray[yCounter++] = y;
}
self.resetRecordingParams = function(){
xarray = [];
yarray = [];
xCounter = 0;
yCounter= 0;
}
return self;
}
function autoDraw(){
context.beginPath();
console.log('starting at: '+xarray[0]+','+yarray[0]);
context.moveTo(xarray[0],yarray[0]);
for (var i = 0; i < xarray.length; i++) {
setTimeout(drawLineSlowly, 1000+(i*20), i);
};
}
function drawLineSlowly(i)
{
context.lineTo(xarray[i],yarray[i]);
context.stroke();
}
init();
});
You don't have any kind of check to see whether or not you are already drawing, so here is your code with those changes commented, as well as the real-pixel-location fixes (http://jsfiddle.net/upgradellc/htJXy/1/):
$(document).ready(function(){
var x, y, context, painter, canvas;
var xCounter = 0 , yCounter = 0;
var xarray = [];
var yarray = [];
function init(){
while(document.getElementById("paint") === undefined){
//do nothing
}
console.log("Loaded document now registering events for canvas");
canvas = document.getElementById("paint");
context = canvas.getContext('2d');
painter = new Painter();
canvas.addEventListener('mousedown', capture, false);
canvas.addEventListener('mouseup', capture, false);
canvas.addEventListener('mousemove', capture, false);
document.getElementById("reset").addEventListener("click",function(){ clearCanvas(canvas);}, false);
document.getElementById("redraw").addEventListener("click", function(){
autoDraw();
}, false);
}
function clearCanvas(canvas){
context.save();
// Use the identity matrix while clearing the canvas
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
// Restore the transform
context.restore();
};
function capture(event){
if(event.which !== 1){
return;
}
tempPos = getMousePos(canvas, event);
x = tempPos.x;
y = tempPos.y;
switch(event.type){
case 'mousedown':
painter.startPaint(event);
break;
case 'mouseup':
painter.endPaint(event);
break;
case 'mousemove':
painter.paint(event);
break;
}
};
var Painter = function(){
var self = this;
self.paintStarted = false;
//this keeps track of whether or not we are currently auto drawing
self.currentlyAutoDrawing = false;
self.startPaint = function(event){
self.resetRecordingParams();
self.paintStarted = true;
context.beginPath();
context.moveTo(x,y);
self.record();
}
self.endPaint = function(event){
self.paintStarted = false;
self.record();
self.paint(event);
}
self.paint = function(event){
if(self.paintStarted){
context.lineTo(x,y);
context.stroke();
self.record();
}
}
self.record = function(){
xarray[xCounter++] = x;
yarray[yCounter++] = y;
}
self.resetRecordingParams = function(){
xarray = [];
yarray = [];
xCounter = 0;
yCounter= 0;
}
return self;
}
function autoDraw(){
context.beginPath();
//If we are already auto-drawing, then we should just return instead of starting another drawing loop cycle
if(painter.currentlyAutoDrawing){
console.log("painter is already auto drawing");
return;
}
painter.currentlyAutoDrawing = true;
console.log('starting at: '+xarray[0]+','+yarray[0]);
context.moveTo(xarray[0],yarray[0]);
for (var i = 0; i < xarray.length; i++) {
setTimeout(drawLineSlowly, 1000+(i*20), i);
};
}
function drawLineSlowly(i)
{
//when we reach the last element in the array, update painter with the fact that autodrawing is now complete
if(xarray.length == i+1){
painter.currentlyAutoDrawing=false;
}
console.log(xarray.length+" "+i);
context.lineTo(xarray[i],yarray[i]);
context.stroke();
}
function getMousePos(canv, evt) {
var rect = canv.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
init();
});
Obviously you don't stop the previous timeout loop before you start a new...
Use setInterval instead of setTimeout, and clearInterval by next push. So I think the problem is not with the canvas, just with your redraw animation. Btw it is strange because there is some difference between the redraw and the original draw...
var drawInterval = null;
function autoDraw(){
if (drawInterval) {
//here your can reset the previous - still running - redraw
clearInterval(drawInterval);
}
context.beginPath();
console.log('starting at: '+xarray[0]+','+yarray[0]);
context.moveTo(xarray[0],yarray[0]);
var i=0;
setInterval(function (){
++i;
if (i<xarray.length)
drawLineSlowly(i);
else
clearInterval(drawInterval);
},20);
}
note:
There is still bug in the redraw, but at least it does not kill the browser...
Btw the strange "animation" is because you does not check by redraw if you are currently drawing or not, so you start draw and redraw together and they interfere... You have to stop redraw when you start drawing.
i am trying to drag and drop an image of a card on the canvas of the javascript but the mouseup event does not seem to be working even though it is inside the main(). Once the card is selected, it follows around the mouse but it does not seem to let go when i let go of the mouse. I also know that the image is repeated from not clearing the screen.
function main(){
var ctx = cvs.getContext("2d");
var img = new Image();
img.src = "allcards.png";
var imgX = 75;
var imgY = 75;
draw();
function draw(){
ctx.drawImage(img,0,0,97,129,imgX,imgY,100,100);
}
cvs.addEventListener("mouseup", function(ev){
greaterX = false;
lessX = false;
greaterY = false;
lessY = false;
}
);
cvs.addEventListener("mousedown", function(ev){
if(ev.clientX <= (imgX + 97)){
var greaterX = true;
}
if(ev.clientY <= (imgY + 129)){
var greaterY = true;
}
if(ev.clientX >= imgX){
var lessX = true;
}
if(ev.clientY >= imgY){
var lessY = true;
}
if(greaterX == true)
if(greaterY == true)
if(lessX == true)
if(lessY == true)
cvs.addEventListener("mousemove", function(ev){
var offsetX = (ev.clientX - imgX);
var offsetY = (ev.clientY - imgY);
imgX = imgX + offsetX;
imgY = imgY + offsetY;
draw();
});
});
};
greaterX, lessX, etc, are all defined with var inside of your mousedown function, meaning their scope is limited to the mousedown function only.
Therefore, it is useless to try and set them back to false inside of your mouseup function. You need to declare your variables in the main part of your function:
function main() {
var greaterX, lessX, greaterY, lessY;
var ctx = cvs.getContext("2D");
//etc...
Now, simply setting greaterX, lessX, etc back to false is not enough, because the mousemove event checker inside of mousedown is still active. When you apply an event listener, it stays there until you remove it.
So, the next step is to separate the mousemove event function into it's own function (I used "mouseMoveHandler" for the name) and remove the event listener using .removeEventListener(type, listener, useCapture) inside of mouseup.
The mousemove function:
function mouseMoveHandler(ev) {
offsetX = (ev.clientX - imgX);
offsetY = (ev.clientY - imgY);
imgX = imgX + offsetX;
imgY = imgY + offsetY;
draw();
}
The mousedown function (important part):
if (greaterX === true) { //you need the brackets for multi-line if statements
if (greaterY === true) {
if (lessX === true) {
if (lessY === true) {
cvs.addEventListener("mousemove",mouseMoveHandler,false);
}
}
}
}
And finally, the mouseup function:
cvs.addEventListener("mouseup", function(ev) {
greaterX = false;
lessX = false;
greaterY = false;
lessY = false;
cvs.removeEventListener('mousemove',mouseMoveHandler,false);
});
Here's a jsFiddle with the solution, but not using your image.