I am creating a simple game with functions to move a player forward,turn left,turn right, or turn around. I want the key presses to fire the specific corresponding function only once. I found this helpful code that fires the event only once, but I cannot seem to figure out how to specify certain key presses within it.
var shouldHandleKeyDown = true;
document.onkeydown = function(){
if (!shouldHandleKeyDown) return;
shouldHandleKeyDown = false;
// HANDLE KEY DOWN HERE
}
document.onkeyup = function(){
shouldHandleKeyDown = true;
}
})();
What I am trying to make happen is:
User presses up?
Move forward function occurs ONCE (even if the up key is held)
User presses left? Turn left function occurs once etc....
Thank you all for any help with this.
EDIT FOR CLARIFICATION:
I am trying to build a first-person dungeon crawler in the style of old titles like Wizardry for the NES. Basically I want each press of the forward button to move the player forward one frame. If they press left then the player turns left one frame, etc. If you are familiar with the games you will get what I mean.
I would ignore onkeydown events and just use onkeyup. That way the event is only fired once the user lifts their finger, ensuring just one movement per key press.
To determine which key was pressed inside your event handler, pass the event to your function. Here is a link to the values of the keys, so you could do something like:
document.onkeyup = function(event) {
var keycode = event.keyCode;
if (keycode === 38) {
moveUp();
}
}
There is no single good way to tackle this problem, it mostly depends on what you'll do with these key inputs.
For instance, if it is to control an always updating animation, then simple semaphores in a dictionary with {left: bool, right: bool, up: bool, down: bool} sets to true in keydown and to false in keyup, and that gets checked at every frame is enough.
const directions = {
up: false,
right: false,
down: false,
left: false
};
onkeydown = e => {
const key = e.key.replace('Arrow', '').toLowerCase();
if(key in directions) {
e.preventDefault();
directions[key] = true;
}
};
onkeyup = e => {
const key = e.key.replace('Arrow', '').toLowerCase();
if(key in directions) {
e.preventDefault();
directions[key] = false;
}
};
const w = canvas.width = innerWidth;
const h = canvas.height = innerHeight;
const ctx = canvas.getContext('2d');
const character = {
x: w/2,
y: h/2,
update: function() {
const speed = 2;
// this is where we will check our keyboard's status
// we do it every frame
let dir_x = 0,
dir_y = 0;
dir_x += directions.left ? -1 : 0;
dir_x += directions.right ? 1 : 0;
dir_y += directions.up ? -1 : 0;
dir_y += directions.down ? 1 : 0;
this.x += dir_x * speed;
this.y += dir_y * speed;
},
draw: function() {
ctx.fillRect(this.x - 20, this.y-20, 40, 40);
}
};
function anim() {
character.update();
ctx.clearRect(0,0,w, h);
character.draw();
requestAnimationFrame(anim);
}
anim();
:root,body{margin:0px}
<canvas id="canvas"></canvas>
However if you are not keeping a continuous animation loop and really need this event to fire at first action, then in the keydown handler you may need to add a logic
...
if(key in directions) {
e.preventDefault();
const changed = directions[key] !== true;
directions[key] = true;
if(changed) {
triggerChange(key);
}
}
Where triggerChange(key) would be a function ready to trigger your solo action.
const directions = {
up: false,
right: false,
down: false,
left: false
};
function triggerChange(dir) {
character.update();
}
onkeydown = e => {
const key = e.key.replace('Arrow', '').toLowerCase();
if (key in directions) {
e.preventDefault();
const changed = directions[key] !== true;
directions[key] = true;
if (changed) {
triggerChange(key);
}
}
};
onkeyup = e => {
const key = e.key.replace('Arrow', '').toLowerCase();
if (key in directions) {
e.preventDefault();
directions[key] = false;
}
};
const w = canvas.width = innerWidth;
const h = canvas.height = innerHeight;
const ctx = canvas.getContext('2d');
const character = {
x: w / 2,
y: h / 2,
update: function() {
const speed = 5;
// this is where we will check our keyboard's status
// we do it every frame
let dir_x = 0,
dir_y = 0;
dir_x += directions.left ? -1 : 0;
dir_x += directions.right ? 1 : 0;
dir_y += directions.up ? -1 : 0;
dir_y += directions.down ? 1 : 0;
this.x += dir_x * speed;
this.y += dir_y * speed;
},
draw: function() {
ctx.fillRect(this.x - 20, this.y - 20, 40, 40);
}
};
function anim() {
// it is now keydown that is responsible for character.update()
ctx.clearRect(0, 0, w, h);
character.draw();
requestAnimationFrame(anim);
}
anim();
:root,body{margin:0}
<canvas id="canvas"></canvas>
Change function() to function(e) and pressed key is in e.key
(after run snippet click on white rectangle below run button and press keys)
var shouldHandleKeyDown = true;
document.onkeydown = function(e) {
if (!shouldHandleKeyDown) return;
shouldHandleKeyDown = false;
console.log(e.key);
// HANDLE KEY DOWN HERE
}
document.onkeyup = function(){
shouldHandleKeyDown = true;
}
Related
I have some code that I've used to create controls for a long time, and it works well, in that it properly captures and releases the mouse (at least in chrome).
function createSlider(slider, width, height)
{
slider.width = width;
slider.height = height;
slider.style.display = 'inline';
//
slider.onchange = new Event('onchange');
//
slider.addEventListener('mousedown', function(e)
{
var e = window.event || e;
if (e.button == 0)
{
e.preventDefault();
if (slider.setCapture)
slider.setCapture();
slider.mouseDown0 = true;
slider.sx = e.clientX;
slider.sv = slider.value;
}
});
slider.addEventListener('losecapture', function()
{
slider.mouseDown0 = false;
});
document.addEventListener('mouseup', function(e)
{
var e = window.event || e;
if (e.button == 0 && slider.mouseDown0)
slider.mouseDown0 = false;
}, true);
(slider.setCapture ? slider : document).addEventListener('mousemove', function(e)
{
if (slider.mouseDown0)
{
var dx = slider.sx - e.clientX;
var adaptive = 10 * Math.pow(Math.abs(dx), slider.acc);
slider.value = Math.min(Math.max(slider.min, slider.sv - dx*slider.scale*adaptive), slider.max);
//TODO: if (log) do log scaling
slider.paint();
slider.dispatchEvent(slider.onchange);
}
}, true);
slider.onmousewheel = function(e)
{
e.preventDefault();
var s = e.target;
s.value = Math.min(Math.max(s.min, s.value + (e.wheelDeltaY > 0 ? 1 : -1) * s.scale), s.max);
s.dispatchEvent(s.onchange);
s.paint();
};
slider.paint = function()
{
//...
};
//
slider.paint();
}
But when I tried using this code inside of a Figma plugin window, it loses mouse capture as soon as the mouse leaves the window. Is there something that I need to adjust for this to work?
Apparently you cannot, but you can use pointer lock to solve the issue for some situations.
So I have a maze here and the problem arises when user changes rowNum/colNum in the maze.
When user changes either of them, it calls
ctx.clearRect(0, 0, canvas.width, canvas.height).
This clears up the whole canvas but when user again starts moving the player, the player from previously cleared up canvas appears up in the this canvas. see here.
clearRect() clears up the canvas but I don't know why the player is still left there to interact with.
codesandbox link
Here's a shortened description of what my code does to draw the player-
main.js
let customGrid = document.querySelector("#rows,#columns");
customGrid.forEach(elem => elem.addEventListener("change", e => {
// detect changed element
if (e.target.id === "row")
customGrid[0].value = parseInt(e.target.value);
else
customGrid[1].value = parseInt(e.target.value);
ctx.clearRect(0, 0, canvas.width, canvas.width);
// setting myMaze to new instance of Maze
myMaze = new Maze(ctx, 600, customGrid[0].value, customGrid[1].value);
myMaze.setup();
myMaze.drawMap();
})
);
Next, myMaze.drawMap() contains-
//set player
this.player = new Player(this.ctx, this.goal, this.cellWidth, this.cellHeight);
this.player.setPlayer(this);
From here setPlayer calls-
setPlayer(myMaze) {
....
this.drawPlayer();
this.listenMoves(myMaze);
}
listenMoves(myMaze) {
window.addEventListener("keydown", function handler(e) {
myMaze.player.move(e.keyCode, myMaze);
let reachedCol = myMaze.player.colNum === myMaze.goal.colNum ? true : false;
let reachedRow = myMaze.player.rowNum === myMaze.goal.rowNum ? true : false;
if (reachedRow && reachedCol) {
alert("reached!");
window.removeEventListener("keydown", handler);
}
});
}
move(input, myMaze) {
let current = myMaze.grid[this.rowNum][this.colNum];
let walls = current.walls;
switch(input) {
case 37:
if(!walls.leftWall) {
this.colNum -= 1;
} break;
case 38:
if(!walls.topWall) {
this.rowNum -= 1;
} break;
case 39:
if(!walls.rightWall) {
this.colNum += 1;
} break;
case 40:
if(!walls.bottomWall) {
this.rowNum += 1;
}
}
this.ctx.clearRect(current.xCord, current.yCord, current.width, current.height);
current.drawCell();
this.drawPlayer();
}
I would move the event listener up to main.js. Since Maze has the reference to Player, it should be possible. I am assuming you have a global variable of Maze (myMaze).
let myMaze;
let reched = false;
window.addEventListener("keydown", function handler(e) {
if (!myMaze || reched) {
return;
}
myMaze.player.move(e.keyCode, myMaze);
myMaze.player.handleMove();
let reachedCol = myMaze.player.colNum === myMaze.goal.colNum ? true : false;
let reachedRow = myMaze.player.rowNum === myMaze.goal.rowNum ? true : false;
if (reachedRow && reachedCol) {
alert("reached!");
reched = true;
}
});
If you want to keep the event handler as Player's method, you could do something like below. And call unListenMoves() when the grid size changes.
class Player {
constructor(ctx, goal, cellWidth, cellHeight, myMaze) {
// keep the Maze instance as a Player's prop for the later use
this.myMaze = myMaze;
// we need to bind this here, not in listenMoves
this.handleMove = this.handleMove.bind(this);
}
listenMoves() {
window.addEventListener("keydown", this.handleMove);
}
unListenMoves() {
window.removeEventListener("keydown", this.handleMove);
}
handleMove(e) {
const myMaze = this.myMaze;
myMaze.player.move(e.keyCode, myMaze);
let reachedCol = myMaze.player.colNum === myMaze.goal.colNum ? true : false;
let reachedRow = myMaze.player.rowNum === myMaze.goal.rowNum ? true : false;
if (reachedRow && reachedCol) {
alert("reached!");
window.removeEventListener("keydown", this.handleMove);
}
}
}
var gameLink = $("#theGame");
$(window).keydown(function (e) {
if (e.which === 32) {
window.location.href = "thegame.html;
}
});
I am very new to jQuery and I can't seem to figure out where to add the "tap" event for mobile to this code.
This is pretty much all you need: http://jsfiddle.net/gianlucaguarini/56Szw/
*code from fiddle
var getPointerEvent = function(event) {
return event.originalEvent.targetTouches ? event.originalEvent.targetTouches[0] : event;
};
var $touchArea = $('#touchArea'),
touchStarted = false, // detect if a touch event is sarted
currX = 0,
currY = 0,
cachedX = 0,
cachedY = 0;
//setting the events listeners
$touchArea.on('touchstart mousedown',function (e){
e.preventDefault();
var pointer = getPointerEvent(e);
// caching the current x
cachedX = currX = pointer.pageX;
// caching the current y
cachedY = currY = pointer.pageY;
// a touch event is detected
touchStarted = true;
$touchArea.text('Touchstarted');
// detecting if after 200ms the finger is still in the same position
setTimeout(function (){
if ((cachedX === currX) && !touchStarted && (cachedY === currY)) {
// Here you get the Tap event
$touchArea.text('Tap');
}
},200);
});
$touchArea.on('touchend mouseup touchcancel',function (e){
e.preventDefault();
// here we can consider finished the touch event
touchStarted = false;
$touchArea.text('Touchended');
});
$touchArea.on('touchmove mousemove',function (e){
e.preventDefault();
var pointer = getPointerEvent(e);
currX = pointer.pageX;
currY = pointer.pageY;
if(touchStarted) {
// here you are swiping
$touchArea.text('Swiping');
}
});
Im using the following function to drag a div with a handle.
Problem is that in some browsers when i drag at the same time text is selected and highlighted on the page..
Any ideas how to fix it?
function enableDragging (ele) {
var dragging = dragging || false,
x, y, Ox, Oy,
current;
enableDragging.z = enableDragging.z || 1;
var grabber = document.getElementById("myHandle");
grabber.onmousedown = function (ev) {
ev = ev || window.event;
var target = ev.target || ev.srcElement;
current = target.parentElement;
dragging = true;
x = ev.clientX;
y = ev.clientY;
Ox = current.offsetLeft;
Oy = current.offsetTop;
current.style.zIndex = ++enableDragging.z;
console.log(dragging);
document.onmousemove = function(ev) {
ev = ev || window.event;
//pauseEvent(ev);
if (dragging == true) {
var Sx = ev.clientX - x + Ox,
Sy = ev.clientY - y + Oy;
current.style.top = Sy + "px";
current.style.left = Sx + "px";
document.body.focus();
// prevent text selection in IE
document.onselectstart = function () { return false; };
// prevent IE from trying to drag an image
ev.ondragstart = function() { return false; };
return false;
}
}
document.onmouseup = function(ev) {
//alert("stop");
dragging && (dragging = false);
}
};
}
function pauseEvent(e){
if(e.stopPropagation) e.stopPropagation();
if(e.preventDefault) e.preventDefault();
e.cancelBubble=true;
e.returnValue=false;
return false;
}
Drag is started by:
var ele = document.getElementById("divWrapper");
enableDragging(ele);
I'm sorry, I forget the second question in your previous post. However, you need to prevent default from onmouseup too:
document.onmouseup = function(ev) {
ev = ev || window.event;
dragging && (dragging = false);
if (ev.preventDefault) {
ev.preventDefault();
} else {
ev.cancelBubble=true;
ev.returnValue=false;
}
return false;
}
EDIT
Or simply use your pauseEvent() function. Notice, that in your code there's this: ev.ondragstart = function () {...};. It's no use to assign the eventhandler to the Event object, rather assign that to the document or document.body. And also use that pauseEvent() in every eventhandler function too.
It looks like it's better to move document.onselectstart and document.ondragstart to the onmousedown handler, since those events have already fired when execution moves to onmousemove().
I have a textbox has a numeric value.
now what I want is to keep increasing that numeric value while im pressing and holding any of arrow keys.
I know how to do this if I was pressing only one time. it will be increased by 1 only. but what If I want to keep increasing the value while i'm holding the arrow keys. how to do that?
thanks
There's a small jQuery plugin for doing this:
https://github.com/nakupanda/number-updown
Usages:
$('#simplest').updown();
$('#step').updown({
step: 10,
shiftStep: 100
});
$('#minMax').updown({
min: -10,
max: 10
});
$('#minMaxCircle').updown({
min: -10,
max: 10,
circle: true
});
View live demo here:
http://jsfiddle.net/XCtaH/embedded/result/
Keyboard and mousewheel events supporte
This is not fully tried and tested by me, but here is a thought - You might want to track KeyDown events because that's the event which is queued by the OS when the key is first pressed. You might also want to implement some sort of delay when incrementing this way so as not to overwhelm the client-side script and have numbers change at a speed to high for user to track.
ok after some tests I made here is how its done:
var setTimeoutId;
var keyIs = "up";
function myIncrementFunction()
{
var num = parseFloat(myText.value)+1;
myText.value = num;
}
myText.onkeydown = function(e)
{
keyIs = "down";
if(keyIs == "down")
{
var e = e || event ;
if (e.keyCode == 38)
{
for(var s=0; s<1; s++)
setTimeoutId = setTimeout('myIncrementFunction()',100);
}
}
}
myText.onkeyup = function(e)
{
keyIs = "up";
}
If you don't care about supporting Opera, this is easy:
textbox.onkeydown = function(e)
{
if (e.keyCode == 38)
{
incrementTextBox();
}
}
However, Opera doesn't fire keydown for key repeats... you'll have to mimic that by calling incrementTextBox() at an interval, and stopping when the key is lifted. I tested this in WebKit (Chrome 6.0), FF3, Opera 10.6, IE7, IE8, IE9, even IE Quirks:
var textbox = null;
window.onload = function()
{
var timeoutId = null;
var intervalId = null;
var incrementRepeatStarted = false;
function startIncrementKeyRepeat()
{
timeoutId = window.setTimeout(function()
{
intervalId = window.setInterval(incrementTextBox, 50);
}, 300);
}
function abortIncrementKeyRepeat()
{
window.clearTimeout(timeoutId);
window.clearInterval(intervalId);
timeoutId = null;
intervalId = null;
}
function endIncrementKeyRepeat()
{
abortIncrementKeyRepeat();
incrementRepeatStarted = false;
}
textbox = document.getElementById("incrementer");
textbox.onkeydown = function(e)
{
e = e || window.event;
if (e.keyCode == 38)
{
if (!incrementRepeatStarted)
{
startIncrementKeyRepeat();
incrementRepeatStarted = true;
}
else if (timeoutId || intervalId)
{
abortIncrementKeyRepeat();
}
incrementTextBox();
}
else if (incrementRepeatStarted)
{
endIncrementKeyRepeat();
}
}
textbox.onkeyup = endIncrementKeyRepeat;
}
function incrementTextBox()
{
var val = parseInt(textbox.value) || 0;
val++;
textbox.value = val;
}
I wanted to do this, i just used input field with type="number"