I'm trying to get mouse input in my game; any help would be appreciated.
I call the event listeners in my init() function, and I have both my mouseMoved() and mouseClicked() functions. But I just haven't been able to get any response.
(I was asked to make a jsFiddle for this project, so here it is. It's not rendering the images, for some reason. But once there's input, there should be text on the top left the shows the mouse coordinates. Also, when you click on the canvas, you should get an alert.)
var canvasBg = document.getElementById('canvasBg');
var ctxBg = canvasBg.getContext('2d');
var canvasEntities = document.getElementById('entities');
var entitiesCtx = canvasEntities.getContext('2d');
var isPlaying = false;
var player;
var enemy;
var mouseX, mouseY;
var playerImg = new Image();
playerImg.src = 'http://placekitten.com/g/50/50';
var enemyImg = new Image();
enemyImg.src = 'http://placehold.it/50x50';
window.onload = init;
var requestAnimFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
// main functions
function init() {
console.debug('init()');
player = new Entity(250, // xpos
225, // ypos
0, // xd
0, // yd
3, // speed
50, // width
50, // height
playerImg, // imgSrc
true); // player?
enemy = new Entity(500,225,0,0,1,25,25,enemyImg,false);
canvasBg.addEventListener('mousemove', mouseMoved, false);
canvasBg.addEventListener('click', mouseClicked, false);
startLoop();
}
function loop() {
// console.debug('game loop');
if(isPlaying){
update();
draw();
requestAnimFrame(loop);
}
}
function startLoop() {
isPlaying = true;
loop();
}
function stopLoop() {
isPlaying = false;
}
function clearAllCtx() {
ctxBg.clearRect(0, 0, canvasBg.width, canvasBg.height);
Entity.clearCtx();
}
function draw(){
clearAllCtx();
player.draw();
enemy.draw();
}
function update(){
player.update();
}
// end of main functions
// input handling
function mouseMoved(e) {
mouseX = e.layerX - canvasBg.offsetLeft;
mouseY = e.layerY - canvasBg.offsetTop;
document.getElementById('mouseCoors').innerHTML = 'X: ' + mouseX + ' Y: ' + mouseY;
}
function mouseClicked(e) {
alert('You clicked the mouse!');
}
// end of input handling
// Entity functions
function Entity(xpos, ypos, xd, yd, speed, width, height, imagesrc, player) {
this.xpos = xpos;
this.ypos = ypos;
this.xd = xd;
this.yd = yd;
this.speed = speed;
this.width = width;
this.height = height;
this.imagesrc = imagesrc;
this.player = player;
}
Entity.clearCtx = function(){
entitiesCtx.clearRect(0,0,canvasBg.width,canvasBg.height);
};
Entity.prototype.draw = function () {
entitiesCtx.drawImage(this.imagesrc, this.xpos, this.ypos);
};
Entity.prototype.update = function () {
this.xpos += this.xd;
this.ypos -= this.yd;
};
// end of Entity functions
So theres a few things going on, first the fiddle loading was set incorrectly, once I changed it to no wrap in body everything works.
The actual issue you're having is due to the background canvas being under the entities canvas, so it cant get any of the mouse events.
One solution would be to use pointer-events and set it to none like so pointer-events: none on the entities canvas.
Live Demo
#entities {
margin: -500px auto;
pointer-events: none;
}
Another option if you need wider browser support is to have the entities canvas capture the mouse events instead.
Live Demo of Option 2
Related
This is my first posted question, so be gentle lol. I am trying to make a little game that will display different animated sprites on a canvas element that is laid over a user's webcam feed. Right now I am just working with one sprite to get the functionality right. I only want the sprite to appear and follow the mouse when left-click is held down. Right now the sprite appears on mousedown and animates, but disappears if I move the mouse instead of following. I only want it to disappear on mouseup. I have looked all over for the answer, but haven't been able to find one. Here is the js I have:
'use strict';
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const errorMsgElement = document.querySelector('span#errorMsg');
canvas.width = window.innerWidth;
canvas.height =window.innerHeight;
const constraints = {
// audio: true,
video: {
// width: 1280, height: 720
width: window.innerWidth,
height: window.innerHeight
}
};
// Access webcam
async function init() {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleSuccess(stream);
} catch (e) {
errorMsgElement.innerHTML = `navigator.getUserMedia error:${e.toString()}`;
}
}
// Success
function handleSuccess(stream) {
window.stream = stream;
video.srcObject = stream;
}
// Load init
init();
//Code for drawing sprite on Canvas
var image = new Image();
image.src = "Starsprite.png";
var ctx = canvas.getContext("2d");
let magic = false;
var spriteWidth = 187;
var spriteHeight = 60;
var rows = 1;
var cols = 3;
var width = spriteWidth/cols;
var height = spriteHeight/rows;
var curFrame = 0;
var frameCount = 3;
let x =0;
let y =0;
var srcX=0;
var srcY=0;
function startPosition(e){
magic =true;
x =e.clientX;
y =e.clientY;
}
function movePosition(e){
ctx.clearRect(x,y,width,height);
x =e.clientX;
y =e.clientY;
}
function endPosition(){
magic =false;
ctx.clearRect(x,y,width,height);
}
canvas.addEventListener("mousedown", startPosition);
canvas.addEventListener("mousemove",movePosition);
canvas.addEventListener("mouseup", endPosition);
canvas.addEventListener("mousedown",draw);
function updateFrame(){
curFrame = ++curFrame % frameCount;
srcX = curFrame * width;
}
function draw(){
if (!magic) return;
ctx.clearRect(x,y,width,height);
updateFrame();
ctx.drawImage(image,srcX,srcY,width,height, x,y,width,height);
console.log(x + ", " + y);
}
setInterval(draw,200);
The movePosition function does update the x & y coordinates, but the sprite doesn't show while the mouse is moving. I tried creating an event listener to run the draw function on mousemove, but it messes up the animation because it fires every time the browser updates the mouse position. Any ideas on tackling this problem?
Ok, so I have worked some more on it and animated the movement instead of trying to call the function over again. I based this on instructions for animating keystroke inputs. It works fairly well, but if I move the mouse very quickly to a new position it doesn't follow it all the way.
//Code for drawing on Canvas
var image = new Image();
image.src = "Starsprite.png";
var ctx = canvas.getContext("2d");
let magic = false;
var spriteWidth = 187;
var spriteHeight = 60;
var rows = 1;
var cols = 3;
var width = spriteWidth/cols;
var height = spriteHeight/rows;
var curFrame = 0;
var frameCount = 3;
let x =0;
let y =0;
let mousex =0;
let mousey =0;
var stillx =0;
var stilly =0;
var srcX=0;
var srcY=0;
var left =false;
var right =false;
var up = false;
var down = false;
var speed = 60;
var lift = 30;
var buffer = 100;
function startPosition(e){
magic =true;
x =e.clientX;
y =e.clientY;
}
function moveDirection(e){
stillx = x + buffer;
stilly = y + buffer;
mousex = e.clientX;
mousey = e.clientY;
if(mousex>stillx){
left = false;
right = true;
}
if(mousex<stillx){
left = true;
right = false;
}
if(mousey>stilly){
down = true;
up = false;
}
if(mousey<stilly){
down = false;
up = true;
}
}
function endPosition(){
magic =false;
ctx.clearRect(x,y,width,height);
}
canvas.addEventListener("mousedown", startPosition);
canvas.addEventListener("mousemove",moveDirection);
canvas.addEventListener("mouseup", endPosition);
canvas.addEventListener("mousedown",draw);
function moveSprite(){
if(right && x<canvas.width-width){
x+=speed;
// x+=speed;
right =false;
}
if(left && x>0){
x-=speed;
left=false;
}
if(down && y<canvas.height-height){
y+=lift;
down=false;
}
if(up && y>0){
y-=lift;
up=false;
}
}
function updateFrame(){
curFrame = ++curFrame % frameCount;
srcX = curFrame * width;
moveSprite();
}
function draw(){
if (!magic) return;
ctx.clearRect(x,y,width,height);
updateFrame();
ctx.drawImage(image,srcX,srcY,width,height, x,y,width,height);
}
setInterval(draw,200);
If anyone has a better solution please let me know!
I have a problem where i have 2 object for example and i did some moving stuff using the keyboard events. Now the problem is that i don't know when to clear the canvas so i can keep multiple instances of them and also to move them individually.
const canvas = document.getElementById('canvas-game');
const context = canvas.getContext('2d');
// Set Canvas To Whole Screen
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
// Player
class Player {
constructor(xPosition = 0, yPosition = 0, height = 25, width = 25) {
this.xPosition = xPosition;
this.yPosition = yPosition;
this.height = height;
this.width = width;
this.moveEvents = this.moveEvents.bind(this);
}
draw() {
// this.clear();
let { xPosition, yPosition, height, width} = this;
context.beginPath();
context.rect(xPosition, yPosition, height, width);
context.closePath();
context.fill();
// Bind Events
this.initEvents();
}
initEvents() {
canvas.addEventListener('keydown', this.moveEvents);
}
clear() {
context.clearRect(0, 0, canvas.height, canvas.width);
}
moveEvents(event) {
let keyPressed = event.keyCode;
if (keyPressed === 38 || keyPressed === 87) {
this.yPosition -= 5;
} else if (keyPressed === 40 || keyPressed === 83) {
this.yPosition += 5;
} else if (keyPressed === 37 || keyPressed === 65) {
this.xPosition -= 5;
} else if (keyPressed === 39 || keyPressed === 68) {
this.xPosition += 5;
}
this.draw();
}
}
// Enemy
class Enemy extends Player {
constructor() {
super();
}
}
function update(...components) {
components.forEach((item) => {
item.draw();
});
}
function init() {
let player = new Player(100, 100);
let player2 = new Player(200, 200);
update(player, player2);
}
init();
It works as it is but it leaves the trail while updating. Many thanks.
Demo Here: jsFiddle
move the clear function out of the Player class (and into the same context as update) since it shouldn't be responsible for clearing the global canvas
call clear in the update function before the draw calls of the players e.g.
function update(...components) {
clear();
components.forEach((item) => {
item.draw();
});
}
replace the draw call from the moveEvents function with the global update function as you want to redraw the whole scene again after each move (as you need to clear the whole canvas)
So im trying to learn about canvas and canvas games and im currently (more or less) following the W3Schools tutorial on canvas games.
At some point in the tutorial i had the idea to make 2 players, which should both be controled on the same keyboard (NOT online multiplayer).
So i followed the logic given by the tutorial and found the key codes for both WASD and the arrows.
I understand 95% of my code, which means i did not just copy everything without understanding it. (I will comeback to this soon)
The problem with my code is that when i add another player to the system i can move them freely when i only control one player at a time, when i try to move both players at the same time, they cannot be moved freely and i can only press a total of 4 buttons at a time.
Tryout the snippet and play around with the cubes with WASD and the arrows to see what im talking about.
As i said there is a part i do not understand 100% which might be the place for this error? I marked it out anyway on the code snippet.
So all in all my question is: Why can i not move both players freely at the same time?
The whole code is as following and i marked the part i do not understand:
For best experience, use the full screen function
{
function startGame() {
myGameArea.start();
myStick = new component(100, 100, 200, 200, "red");
myStick2 = new component(100, 100, 600, 200, "green");
}
var myGameArea = {
canvas : document.createElement("canvas"),
start : function() {
var bodyID = document.getElementById("body");
this.canvas.width = bodyID.offsetWidth;
this.canvas.height = (bodyID.offsetHeight);
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[2]);
this.interval = setInterval(updateGameArea, (1000 / 60));
//The part i do not understand
window.addEventListener('keydown', function (e) {
myGameArea.keys = (myGameArea.keys || []);
myGameArea.keys[e.keyCode] = true;
});
window.addEventListener('keyup', function (e) {
myGameArea.keys[e.keyCode] = false;
});
//End
},
clear : function(){
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
};
function component(width, height, x, y, color, mLeft, mRight, mUpLeft, mUpRigth){
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.speedX = 0;
this.speedY = 0;
this.update = function(){
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
};
this.newPos = function(){
this.x += this.speedX;
this.y += this.speedY;
};
this.player1 = function(){
this.speedX = 0;
this.speedY = 0;
if (myGameArea.keys && myGameArea.keys[65]) {this.speedX = -2; } // Left
if (myGameArea.keys && myGameArea.keys[68]) {this.speedX = 2; } // Right
if (myGameArea.keys && myGameArea.keys[87]) {this.speedY = -2; } // Up
if (myGameArea.keys && myGameArea.keys[83]) {this.speedY = 2; } // Down
};
this.player2 = function(){
this.speedX = 0;
this.speedY = 0;
if (myGameArea.keys && myGameArea.keys[37]) {this.speedX = -2; } // Left
if (myGameArea.keys && myGameArea.keys[39]) {this.speedX = 2; } // Right
if (myGameArea.keys && myGameArea.keys[38]) {this.speedY = -2; } // Up
if (myGameArea.keys && myGameArea.keys[40]) {this.speedY = 2; } // Down
};
}
function updateGameArea(){
myGameArea.clear();
myStick.player1();
myStick.newPos();
myStick2.player2();
myStick2.newPos();
myStick.update();
myStick2.update();
}
}
.nm{
margin: 0;
padding: 0;
}
canvas{
display: block;
background-color: lightgray;
}
<html>
<head>
<meta charset="UTF-8">
<title>Canvas stick game!</title>
<link rel="stylesheet" href="css/standard.css">
</head>
<body id="body" onload="startGame()" class="nm" style="height: 100vh">
</body>
</html>
<script src="js/canvas.js"></script>
I know this is redundant (anyone who reads the comments below your question will figure this out), but just so it's here as an answer, your problem is a keyboard bug.
Because of the way (some) non-digital keyboards work, certain keyboard combinations do not work properly. (ie. Certain keys will cancel each other out.)
(This is just another reason to supply customizable game controls, customizable program shortcuts, etc. Another reason is the fact that people with DVORAK keyboards are likely to find your QWERTY optimized game controls to be cumbersome.)
Other:
PS: I cannot reproduce your problem, and if you tried it on another computer, it is likely that you could not reproduce it either.
PPS: For more information check out the following article: Keyboards are evil.
Not sure what the bug was, looked ok as it was but as there were several issues. Saddly W3Schools is not what I would consider a uptodate, but I can offer no alternative.
I made changes to improve the whole thing but you will still lose keys, I am not sure why as the standard says nothing about loss of keys (It says all keys MUST be reported)
When hitting 4 or more keys at the same time (within 1/60th second) none of them are reported. This is highly unlikely when two people are playing, but when one person is testing both direction pads it happens often. I am unaware of a solution.
The key event keyCode has depreciated and you should be using event.key which is a string but event.key is only partially supported.
{
// key maps
const KEY_UP = 38;
const KEY_DOWN = 40;
const KEY_LEFT = 37;
const KEY_RIGHT = 39;
const KEY_W = 87;
const KEY_S = 83;
const KEY_A = 65;
const KEY_D = 68;
const DIR_KEY_MAP2 = [KEY_W, KEY_S, KEY_A, KEY_D];
const DIR_KEY_MAP1 = [KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT];
const BLOCK_DEFAULT_FOR = [KEY_W, KEY_S, KEY_A, KEY_D, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT];
const keyEvents = function(event){ // keyboard event listener sets the key array to true if keydown false if not
if(!event.repeat){ // ignore repeating key events
myGameArea.keys[event.keyCode] = event.type === "keydown";
}
// block default action for keys in array BLOCK_DEFAULT_FOR
if(BLOCK_DEFAULT_FOR.indexOf(event.keyCode) > -1){
event.preventDefault();
}
}
function startGame() {
myGameArea.start();
myStick = new Component(100, 100, 200, 200, "red", DIR_KEY_MAP2);
myStick2 = new Component(100, 100, 600, 200, "green", DIR_KEY_MAP1);
}
var myGameArea = {
canvas : document.createElement("canvas"),
keys : [], // create the key array
start : function() {
var bodyID = document.getElementById("body");
this.canvas.width = bodyID.offsetWidth;
this.canvas.height = (bodyID.offsetHeight);
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[2]);
requestAnimationFrame(updateGameArea); // request the first animation frame don't use setInterval
window.addEventListener('resize', function () { // for stackoverflow
myGameArea.canvas.width = bodyID.offsetWidth;
myGameArea.canvas.height = bodyID.offsetHeight;
});
window.addEventListener('keydown', keyEvents); // this is called once for every key down
window.addEventListener('keyup', keyEvents); // this is called once for every key up.
},
clear : function () {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
};
function Component(width, height, x, y, color, keyMap){ // key map is the keys used to control
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.speedX = 0;
this.speedY = 0;
// get reference to context
var ctx = myGameArea.context;
// clear the keys
var i = 3;
while(i >= 0){ myGameArea.keys[keyMap[i--]] = false; }
this.update = function(){
this.userInput();
this.x += this.speedX;
this.y += this.speedY;
}
this.display = function(){
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
};
this.userInput = function(){ // keyMap is accessed via closure
this.speedY = this.speedX = 0;
if (myGameArea.keys[keyMap[2]]) {this.speedX = -2; } // Left
if (myGameArea.keys[keyMap[3]]) {this.speedX = 2; } // Right
if (myGameArea.keys[keyMap[0]]) {this.speedY = -2; } // Up
if (myGameArea.keys[keyMap[1]]) {this.speedY = 2; } // Down
};
}
function updateGameArea(){
myGameArea.clear();
myStick.update();
myStick2.update();
myStick.display();
myStick2.display();
requestAnimationFrame(updateGameArea); // request the next animation frame in 1/60th second
}
}
.nm{
margin: 0;
padding: 0;
}
canvas{
display: block;
background-color: lightgray;
}
<html>
<head>
<meta charset="UTF-8">
<title>Canvas stick game!</title>
<link rel="stylesheet" href="css/standard.css">
</head>
<body id="body" onload="startGame()" class="nm" style="height: 100vh">
</body>
</html>
<script src="js/canvas.js"></script>
The title might be misleading but that is the best I could come up with for a summary of my question.
Anyways, I need to figure out how to make a list, or a container, in this case a plain rectangle that contains a list of items, which can be dragged up and down in order to reveal other items in the container. In a way it would resemble a constrained div with a slider bar, but without the slider.
Now, I have an idea on using KonvaJS, former KineticJS to put all the items in the container in a group, and make the group draggable in certain directions, etc.
However the catch is that the sliding of the elements top or down should not only be on drag, but on flick also. So if you kind of flick your finger/mouse upwards the list would keep sliding by, until the end, where the speed would vary based on the flick intensity. If determining the flick intensity or speed is too complicated, then just any type of flick would need to slide the whole list to the bottom, or top.
So this should kind of resemble the standard vertical slide widgets you have on your android or ios. Now do you have any ideas on how I can proceed with this, or how would you go about this. Any ideas are welcome.
Working demo: http://jsbin.com/gefuvu/edit?js,output
Usual drag and drop is already supported by draggable property. For limit drag&drop to vertical scrolling I am using this simple dragBound:
const group = new Konva.Group({
draggable: true,
dragBoundFunc: (pos) => {
const minY = -group.getClientRect().height + stage.height();
const maxY = 0;
const y = Math.max(Math.min(pos.y, maxY), minY);
return {y, x: 0}
}
});
"Flick" implementation:
// setup flick
let lastY = null;
let dY = 0;
group.on('dragstart', () => {
lastY = group.y();
dy = 0;
});
group.on('dragmove', () => {
dy = lastY - group.y();
lastY = group.y();
});
group.on('dragend', () => {
// if last move is far way it means user move pointer very fast
// for this case we need to automatically "scroll" group
if (dy > 5) {
group.to({
y: -group.getClientRect().height + stage.height()
});
}
if (dy < -5) {
group.to({
y: 0
});
}
});
I guess that when you talk about "flick" you actually mean "scroll".
Edit : Missed the point of the question, also missed the [konvajs] tag. But here is a way to do it without any library, hoping it may help someone coming this way.
The simplest idea is to make two objects, a container and a content, each one with a canvas.
On mouse's wheel event, update the content position, then redraw its canvas to the container's one or if you need to handle drag, listen to the mousemove event, set a dragging flag to true, that you remove on mouseup. On mousemove update the position after you calculated the moving speed by checking the last event's timestamp and the new one's. Then on mouseup, start an animation that will decrease the speed of your movement :
// our container object
var container = {
width: window.innerWidth - 2,
height: window.innerHeight - 2,
top: 0,
left: 0,
canvas: document.getElementById('container'),
isOver: function(x, y) {
return (x >= this.left && x <= this.left + this.width &&
y >= this.top && y <= this.top + this.height);
},
};
// our content object
var content = {
width: container.width * 2,
height: container.height * 2,
top: 0,
left: 0,
background: 'rgba(0,255,0,.5)',
canvas: document.createElement('canvas'),
// set an init function to draw the texts
init: function() {
var ctx = this.ctx;
ctx.font = '20px sans-serif';
ctx.textBaseline = 'top';
ctx.fillText('Hello World', 0, 0);
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText('Middle world', this.width / 2, this.height / 2);
ctx.textBaseline = 'bottom';
ctx.textAlign = 'left';
var textLength = ctx.measureText('Bye World').width;
ctx.fillText('Bye World', this.canvas.width - textLength, this.canvas.height);
ctx.fillStyle = this.background;
ctx.fillRect(0, 0, this.width, this.height);
},
};
// init the objects
var init = function(obj) {
var c = obj.canvas;
obj.ctx = c.getContext('2d');
c.width = obj.width;
c.height = obj.height;
if (obj.init) {
obj.init();
}
}
// our drawing function
var draw = function() {
container.ctx.clearRect(0, 0, container.width, container.height);
container.ctx.drawImage(content.canvas, content.left, content.top);
};
// update the content position
container.update = function(x, y) {
// if the content is smaller, we don't need to scroll
if (content.width > container.width) {
var maxX = Math.max(container.width, content.width);
var minX = Math.min(container.width, content.width);
content.left -= x;
// if we are at one end
if (content.left < minX - maxX) {
content.left = minX - maxX;
} // or another
else if (content.left > 0) {
content.left = 0;
}
}
if (content.height > container.height) {
var maxY = Math.max(container.height, content.height);
var minY = Math.min(container.height, content.height);
content.top -= y;
if (content.top < minY - maxY) {
content.top = minY - maxY;
} else if (content.top > 0) {
content.top = 0;
}
}
};
var drag = {
friction: .1,
sensibility: 18,
minSpeed: .01,
};
var mouseMove_Handler = function(e) {
// we're not dragging anything, stop here
if (!drag.dragged) {
return;
}
var rect = this.getBoundingClientRect();
var posX = e.clientX - rect.left;
var posY = e.clientY - rect.top;
// how long did it take since last event
var deltaTime = (e.timeStamp - drag.lastDragTime) / drag.sensibility;
// our moving speed
var deltaX = (drag.lastDragX - posX) / deltaTime;
var deltaY = (drag.lastDragY - posY) / deltaTime;
// update the drag object
drag.lastDragX = posX;
drag.lastDragY = posY;
drag.lastDeltaX = deltaX;
drag.lastDeltaY = deltaY;
drag.lastDragTime = e.timeStamp;
// update the container obj
drag.dragged.update(deltaX, deltaY);
// redraw
draw();
};
var mouseDown_Handler = function(e) {
// if we are sliding, stop it
if (drag.sliding) {
cancelAnimationFrame(drag.sliding);
drag.sliding = null;
}
var rect = this.getBoundingClientRect();
var posX = e.clientX - rect.left;
var posY = e.clientY - rect.top;
// first check that the event occurred on top of our container object
// we could loop through multiple ones
if (container.isOver(posX, posY)) {
// init our drag object
drag.dragged = container;
drag.lastDragX = posX;
drag.lastDragY = posY;
drag.lastDragTime = e.timeStamp;
}
};
var mouseUp_Handler = function(e) {
// store a ref of which object we were moving
var container = drag.dragged;
// we're not dragging anymore
drag.dragged = false;
var slide = function() {
// decrease the speed
drag.lastDeltaX /= 1 + drag.friction;
drag.lastDeltaY /= 1 + drag.friction;
// check that we are still out of our minimum speed
if (drag.lastDeltaX > drag.minSpeed || drag.lastDeltaY > drag.minSpeed ||
drag.lastDeltaX < -drag.minSpeed || drag.lastDeltaY < -drag.minSpeed) {
// store a reference of the animation
drag.sliding = requestAnimationFrame(slide);
} else {
drag.sliding = null;
drag.lastDeltaX = drag.lastDeltaY = 0;
}
container.update(drag.lastDeltaX, drag.lastDeltaY);
draw();
};
slide();
};
// add the wheel listener, for a polyfill check the MDN page :
// https://developer.mozilla.org/en-US/docs/Web/Events/wheel#Listening_to_this_event_across_browser
var mouseWheel_Handler = function(e) {
// get the position of our canvas element
var rect = this.getBoundingClientRect();
var posX = e.clientX - rect.left;
var posY = e.clientY - rect.top;
// first check that the event occurred on top of our container object
if (container.isOver(posX, posY)) {
// tell the browser we handle it
e.preventDefault();
e.stopPropagation();
// send the event's deltas
container.update(e.deltaX, e.deltaY);
// redraw
draw();
}
};
container.canvas.addEventListener('mousedown', mouseDown_Handler);
container.canvas.addEventListener('mousemove', mouseMove_Handler);
container.canvas.addEventListener('mouseup', mouseUp_Handler);
container.canvas.addEventListener('mouseleave', mouseUp_Handler);
container.canvas.addEventListener('wheel', mouseWheel_Handler);
// init the objects
init(container);
init(content);
// make a first draw
draw();
// Snippet only preventions \\
// avoid the outer window to scroll
window.onscroll = function(e) {
e.preventDefault();
e.stopPropagation()
};
// if you go in full page view
window.onresize = function() {
container.width = window.innerWidth;
container.height = window.innerHeight;
content.width = container.width * 2;
content.height = container.height * 2;
init(container);
init(content);
draw();
};
body,html,canvas {
margin: 0;
display: block
}
canvas {
border: 1px solid;
}
<canvas id="container"></canvas>
I'm trying to make a game using HTML5's canvas and javascript. I believe that I have been doing everything "okay" up until the point where I need to reset the game on death. I'm not really sure what is causing the unexpected behavior but i have thoughts.
Pretty much, the game operates at 'x' speed and then when it resets everything is moving much faster. I have not changed any of the variables for any speed of anything and when debugging and inspecting my code the speeds are the same on the newly created objects as the first time the game is started but they are moving much faster. If you die again it resets again and operates even faster. Die, rinse, repeat and it keeps speeding up until the page would probably hit "not responding" state.
I don't know for sure what I'm doing wrong that is causing the speed difference or at least the illusion of the speed difference. I have an idea that it has something to do with my game loop running multiple times into forever every time it's reset but I don't understand how/why it would do that. any feedback is appreciated.
Also, you can visit my testing page to see all of the code if you need more than I posted below: game test playground
var windowx,
windowy,
canvas,
player,
quadTree,
ctx;
var drawObjects;
var keyState;
var mainloop;
var animFrame;
var ONE_FRAME_TIME;
var reset = function () {
//get canvas and set height and width
canvas = document.getElementById('canvas');
canvas.setAttribute('width', windowx / 2);
canvas.setAttribute('height', windowy / 2);
ctx = canvas.getContext("2d");
drawObjects = [];
keyState = {};
quadTree = new Quadtree(quadTreeBounds);
//make the friendly square
player = new Rectangle(20, 20, 40, 40, 0, 0, XPhysicsBehaviorNormal, YPhysicsBehaviorNormal, XBoundaryBehaviorInCanvas, YBoundaryBehaviorInCanvas, playerObjectType, '#580000', null);
drawObjects.push(player);
drawObjects.push(new Rectangle(40, 100, canvas.width + (distanceOutsideCanvasBeforeDie / 2), canvas.clientHeight - 100, defaultEnemyRectangleVelocity, 0, null, YPhysicsBehaviorNormal, null, YBoundaryBehaviorInCanvas, enemyObjectType, null, OutOfCanvasDieBehavior));
backgroundMusicAudio.play();
//define main loop
mainloop = function () {
buildQuadTree();
updateGame();
drawGame();
};
//define the windowanimationframeobject
animFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
null;
if (animFrame !== null) {
var recursiveAnim = function () {
mainloop();
animFrame(recursiveAnim, canvas);
};
// start the mainloop
animFrame(recursiveAnim, canvas);
} else {
// fallback to setInterval if the browser doesn't support requestAnimationFrame
ONE_FRAME_TIME = 1000.0 / 60.0;
setInterval(mainloop, ONE_FRAME_TIME);
}
}
$(function () {
//get window width and height;
windowx = window.innerWidth;
windowy = window.innerHeight;
reset();
$(document).on('change', '#sound-enabled-toggle', function() {
var isChecked = $(this).is(':checked');
$('#sound-enabled-toggle').blur();
if (isChecked) {
backgroundMusicAudio.play();
playerJumpAudio = playerJumpMusicAudioSetup();
playerBlinkAudio = playerBlinkMusicAudioSetup();
} else {
backgroundMusicAudio.pause();
playerJumpAudio = new Audio('');
playerBlinkAudio = new Audio('');
}
});
});
//left the function here in case I need to do anything else but for now it's just clearing.
function buildQuadTree() {
quadTree.clear();
}
function updateGame() {
//determine if there are any keys pushed at the current point
keyPressActions();
//loop for calculating and updating all objects positions/values.
for (var i = 0; i < drawObjects.length; i++) {
var object = drawObjects[i];
quadTree.insert(new SimpleRectangle(object.x, object.y, object.width || (object.radius * 2), object.height || (object.radius * 2), object.name));
object.update();
//roundFloatingPoints Numbers to 2 decimal places
roundObjectVelocitiesAndPoints(object);
}
PlayerDeathTrigger(player);
}
function drawGame() {
//clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = "20px Verdana";
ctx.fillText("100,000", (canvas.width * .8), (canvas.clientHeight * .1));
ctx.font = "15px Verdana";
ctx.fillText("Temp Score", (canvas.width * .8), (canvas.clientHeight * .05));
//draw all objects in drawObjects
for (var i = 0; i < drawObjects.length; i++) {
var object = drawObjects[i];
object.draw();
}
}
Overall I would recommend re-factoring this code so your reset function doesn't re-create so many objects. Basically the reset function is just re-creating a bulk of the game logic.
The specific issue you identified appears to be coming from the fact that you call setInterval on each reset without clearing the previous interval. See the code below with the minimal changes to prevent this issue.
var windowx,
windowy,
canvas,
player,
quadTree,
ctx,
gameLoop;
var drawObjects;
var keyState;
var mainloop;
var animFrame;
var ONE_FRAME_TIME;
var reset = function() {
// Remove our old game loop
clearInterval(gameLoop);
//get canvas and set height and width
canvas = document.getElementById('canvas');
canvas.setAttribute('width', windowx / 2);
canvas.setAttribute('height', windowy / 2);
ctx = canvas.getContext("2d");
drawObjects = [];
keyState = {};
quadTree = new Quadtree(quadTreeBounds);
//make the friendly square
player = new Rectangle(20, 20, 40, 40, 0, 0, XPhysicsBehaviorNormal, YPhysicsBehaviorNormal, XBoundaryBehaviorInCanvas, YBoundaryBehaviorInCanvas, playerObjectType, '#580000', null);
drawObjects.push(player);
drawObjects.push(new Rectangle(40, 100, canvas.width + (distanceOutsideCanvasBeforeDie / 2), canvas.clientHeight - 100, defaultEnemyRectangleVelocity, 0, null, YPhysicsBehaviorNormal, null, YBoundaryBehaviorInCanvas, enemyObjectType, null, OutOfCanvasDieBehavior));
backgroundMusicAudio.play();
//define main loop
mainloop = function() {
buildQuadTree();
updateGame();
drawGame();
};
//define the windowanimationframeobject
animFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
null;
if (animFrame !== null) {
var recursiveAnim = function() {
mainloop();
animFrame(recursiveAnim, canvas);
};
// start the mainloop
animFrame(recursiveAnim, canvas);
} else {
// fallback to setInterval if the browser doesn't support requestAnimationFrame
ONE_FRAME_TIME = 1000.0 / 60.0;
gameLoop = setInterval(mainloop, ONE_FRAME_TIME);
}
}
$(function() {
//get window width and height;
windowx = window.innerWidth;
windowy = window.innerHeight;
reset();
$(document).on('change', '#sound-enabled-toggle', function() {
var isChecked = $(this).is(':checked');
$('#sound-enabled-toggle').blur();
if (isChecked) {
backgroundMusicAudio.play();
playerJumpAudio = playerJumpMusicAudioSetup();
playerBlinkAudio = playerBlinkMusicAudioSetup();
} else {
backgroundMusicAudio.pause();
playerJumpAudio = new Audio('');
playerBlinkAudio = new Audio('');
}
});
});
//left the function here in case I need to do anything else but for now it's just clearing.
function buildQuadTree() {
quadTree.clear();
}
function updateGame() {
//determine if there are any keys pushed at the current point
keyPressActions();
//loop for calculating and updating all objects positions/values.
for (var i = 0; i < drawObjects.length; i++) {
var object = drawObjects[i];
quadTree.insert(new SimpleRectangle(object.x, object.y, object.width || (object.radius * 2), object.height || (object.radius * 2), object.name));
object.update();
//roundFloatingPoints Numbers to 2 decimal places
roundObjectVelocitiesAndPoints(object);
}
PlayerDeathTrigger(player);
}
function drawGame() {
//clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = "20px Verdana";
ctx.fillText("100,000", (canvas.width * .8), (canvas.clientHeight * .1));
ctx.font = "15px Verdana";
ctx.fillText("Temp Score", (canvas.width * .8), (canvas.clientHeight * .05));
//draw all objects in drawObjects
for (var i = 0; i < drawObjects.length; i++) {
var object = drawObjects[i];
object.draw();
}
}