Move player using keyboard in javascript - javascript

I am making a web game (not with webGL, but with html elements) and I need to move character with WASD keys.
I tried these things:
Used event listeners keydown and keyup.
Problem is that it is unresponsive and doesnt work really well when multiple keys are pressed simultaneously
Used setInterval(20ms) and event listeners.
On my stronger laptop everything works fine, but I feel like it is using insane amount of cpu power because my laptop starts sounding like a plane. On weaker laptop it wasn't working as well as first one, it was choppy and laggy
keyDict = {}
document.addEventListener('keydown', key => keyDict[key.code] = true);
document.addEventListener('keyup', key => keyDict[key.code] = false);
moveID = setInterval(move, 20)
function move()
{
if(!finished)
{
newDirs = [0,0]
//Left
if(keyDict.ArrowLeft == true)
{
newDirs[0] -= 1;
}
//Right
if(keyDict.ArrowRight == true)
{
newDirs[0] += 1;
}
//Up
if(keyDict.ArrowUp == true)
{
newDirs[1] -= 1;
}
//Down
if(keyDict.ArrowDown == true)
{
newDirs[1] += 1;
}
map.updateDir(newDirs);
}
}
Used requestAnimationFrame and event listeners.
On stronger laptop it looks like it utilizes 144 fps and its even more smoother, but sometimes it doesn't even respond to my controls. My laptop still sounds as it is working too hard
keyDict = {}
document.addEventListener('keydown', key => keyDict[key.code] = true);
document.addEventListener('keyup', key => keyDict[key.code] = false);
requestAnimationsFrame(move)
function move()
{
same code...
requestAnimationFrame(move)
}
I want to make it responsive and very smooth and I know there is way but don't know how. Example of this is mouse, your laptop doesn't get worked up from scrolling (and, for example, moving google maps with mouse is smooth and doesn't use cpu as much).

Don't use the 20ms interval.
Move the player inside the requestAnimationFrame depending on which key Event.code is pressed and holds a truthy value inside of your keyDict object:
const keyDict = {};
const Player = {
el: document.querySelector("#player"),
x: 200,
y: 100,
speed: 2,
move() {
this.el.style.transform = `translate(${this.x}px, ${this.y}px)`;
}
};
const updateKeyDict = (ev) => {
const k = ev.code;
if (/^Arrow\w+/.test(k)) { // If is arrow
ev.preventDefault();
keyDict[k] = ev.type === "keydown"; // set boolean true / false
}
};
const update = () => {
// Determine move distance to account diagonal move: 1/Math.sqrt(2) = ~0.707
let dist =
keyDict.ArrowUp && (keyDict.ArrowLeft || keyDict.ArrowRight) ||
keyDict.ArrowDown && (keyDict.ArrowLeft || keyDict.ArrowRight) ? 0.707 : 1;
dist *= Player.speed;
if (keyDict.ArrowLeft) Player.x -= dist;
if (keyDict.ArrowUp) Player.y -= dist;
if (keyDict.ArrowRight) Player.x += dist;
if (keyDict.ArrowDown) Player.y += dist;
Player.move();
}
document.addEventListener('keydown', updateKeyDict);
document.addEventListener('keyup', updateKeyDict);
(function engine() {
update();
window.requestAnimationFrame(engine);
}());
#player {
position: absolute;
left: 0;
top: 0;
width: 20px;
height: 20px;
background: #000;
border-radius: 50%;
}
Click here to focus, and use arrows
<div id="player"></div>
The above example uses Event.code for Arrows, which gives "ArrowLeft/Up/Right/Down" but you can change it accordingly to use "KeyW/A/S/D" instead.
"WASD" keys example
const keyDict = {};
const Player = {
el: document.querySelector("#player"),
x: 200,
y: 100,
speed: 2,
move() {
this.el.style.transform = `translate(${this.x}px, ${this.y}px)`;
}
};
const updateKeyDict = (ev) => {
const k = ev.code;
if (/^Key[WASD]/.test(k)) { // If is "KeyW,A,S,D" key
ev.preventDefault();
keyDict[k] = ev.type === "keydown"; // set boolean true / false
}
};
const update = () => {
// Determine move distance to account diagonal move: 1/Math.sqrt(2) = ~0.707
let dist =
keyDict.KeyW && (keyDict.KeyA || keyDict.KeyD) ||
keyDict.KeyS && (keyDict.KeyA || keyDict.KeyD) ? 0.707 : 1;
dist *= Player.speed;
if (keyDict.KeyA) Player.x -= dist;
if (keyDict.KeyW) Player.y -= dist;
if (keyDict.KeyD) Player.x += dist;
if (keyDict.KeyS) Player.y += dist;
Player.move();
}
document.addEventListener('keydown', updateKeyDict);
document.addEventListener('keyup', updateKeyDict);
(function engine() {
update();
window.requestAnimationFrame(engine);
}());
#player {
position: absolute;
left: 0;
top: 0;
width: 20px;
height: 20px;
background: #000;
border-radius: 50%;
}
Click here to focus, and use keys WASD
<div id="player"></div>

Related

How do i Make a sprite change with keyboard input i'm using vanilla JS

so ive been testing out HTML canvas. im trying to get a sprite to change on keyboard input.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id='Game' width='200' height='200' style='border: 2px solid #000000;'></canvas>
<script>
window.onload = function(){
var Game = document.getElementById('Game');
var context = Game.getContext('2d')
var room = new Image();
var lx = 0;
var ly = 0;
var li = 0;
var lo = 0;
var lwidth = 100;
var lheight = 100;
room.onload = function(){
context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
}
room.src = 'https://i.ibb.co/D7fL7yN/Room.png';
var sprite = new Image();
var cx = 0;
var cy = 125;
var sy = 0;
var sx = 0;
var swidth = 35;
var sheight = 34;
sprite.onload = function(){
context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
}
sprite.src = 'https://i.ibb.co/7VhjqPr/John-Sheet.png';
}
</script>
</body>
</html>
ive been searching on how to change the SX with Keyboard input so my character changes sprites. can you help me? a code example would be best!
Tracking keyboard state.
You can create an object that hold the state of the keyboard, specifically the keys you are interested in reacting to. Use the "keydown" and "keyup" KeyboardEvent to update the keyboard state as the events fire. Use the KeyboardEvent property code to workout which key is changing. DO NOT use keyCode as that has depreciated and is Non Standard
You also want to prevent the default behaviour of keys. Eg prevent arrow keys scrolling the page. This is done by calling the event preventDefault function
const keys = {
ArrowRight: false,
ArrowLeft: false,
ArrowUp: false,
ArrowDown: false,
}
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
function keyEvent(event) {
if (keys[event.code] !== undefined) {
keys[event.code] = event.type === "keydown";
event.preventDefault();
}
}
Then in the game you need only check the keyboard state
if (keys.ArrowRight) { moveRight() }
if (keys.ArrowLeft) { moveLeft() }
// and so on
In the demo below the keys are binded to game actions, meaning that what and how many keys are used are independent of the action. The are also set via configuration, so that key binding can be changed without changing game code. You can also bind other inputs as in example
Animation
To animate you should use the timer function requestAnimationFrame as it is specifically designed to give the best animation results. It will call your rendering function, you can consider the rendering function like a loop, that is call every time you step forward in animation time.
Putting it together
The demo below use the above (modified) methods to get keyboard input and render the scene via animation frame requests.
It also uses some techniques (simple versions of) that help make your game a better product.
Encapsulates the player as an object
Maintains game state by holding the current rendering function in currentRenderState
Has configuration config so all important values are in one place, and could be loaded (from JSON file) to easily change the game without changing code.
Has configurable keyboard binding, Note more than one key can be bound to a game action. In example movement is via WASD or arrow keys.
All text is configurable (making it language independent)
Passes the 2D context to all rendering code.
Separates the game from the rendering. This makes it easier to port the game to low end or high end devices or even move it to a server where ctx is replaced with coms and the game can be broadcast . The game does not change only how it is rendered
var currentRenderState = getFocus; // current game state
const config = {
player: {
start: {x: 100, y:100},
speed: 2,
imageURL: "https://i.stack.imgur.com/C7qq2.png?s=64&g=1",
},
keys: { // link key code to game action
up: ["ArrowUp", "KeyW"],
down: ["ArrowDown", "KeyS"],
left: ["ArrowLeft", "KeyA"],
right: ["ArrowRight", "KeyD"],
},
touchableTime: 140, // in ms. Set to 0 or remove to deactivate
text: {
focus: "Click canvas to get focus",
loading: "Just a moment still loading media!",
instruct: "Use arrow keys or WASD to move",
}
};
requestAnimationFrame(mainLoop); // request first frame
const ctx = gameCanvas.getContext("2d");
const w = gameCanvas.width, h = gameCanvas.height;
const player = {
image: (()=> {
const img = new Image;
img.src = config.player.imageURL;
img.addEventListener("load", () => player.size = img.width, {once: true});
return img;
})(),
x: config.player.start.x,
y: config.player.start.y,
size: 0,
speed: config.player.speed,
direction: 0,
update() {
var oldX = this.x, oldY = this.y;
if (actions.left) { this.x -= this.speed }
if (actions.right) { this.x += this.speed }
if (actions.up) { this.y -= this.speed }
if (actions.down) { this.y += this.speed }
if (this.x < 0) { this.x = 0 }
else if (this.x > w - this.size) { this.x = w - this.size }
if (this.y < 0) { this.y = 0 }
else if (this.y > h - this.size) { this.y = h - this.size }
const mx = this.x - oldX, my = this.y - oldY;
if (mx !== 0 || my !== 0) { this.direction = Math.atan2(my, mx) }
},
draw(ctx) {
if (ctx) {
ctx.setTransform(1, 0, 0, 1, this.x + this.size / 2, this.y + this.size / 2);
ctx.rotate(this.direction + Math.PI / 2); // rotate 90 deg as image points up
ctx.drawImage(this.image,-this.size / 2, -this.size / 2, this.size, this.size);
}
}
}
function drawText(ctx, text, size, color) {
if (ctx) {
ctx.fillStyle = color;
ctx.font = size + "px Arial";
ctx.textAlign = "center";
ctx.fillText(text, w / 2, h * (1/4));
}
}
function getFocus(ctx) {
drawText(ctx, config.text.focus, 24, "black");
}
function drawScene(ctx) {
if (!player.size === 0) {
drawText(ctx, config.text.loading, 16, "blue")
actions.hasInput = false; // ensure instruction are up when ready
} else {
if (!actions.hasInput) { drawText(ctx, config.text.instruct, 16, "blue") }
player.update();
player.draw(ctx);
}
}
function mainLoop() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, w, h);
currentRenderState(ctx);
requestAnimationFrame(mainLoop); // request next frame
}
// keys holds action name for each named key. eg for up action ArrowUp: "up", KeyW: "up",
const keys = Object.entries(config.keys)
.reduce((keys, [action,codes]) =>
codes.reduce((keys, code) => (keys[code] = action, keys), keys), {});
// actions are set true when key down. NOTE first up key for action cancels action
const actions = Object.keys(config.keys)
.reduce((actions,action) => (actions[action] = false, actions),{});
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
function keyEvent(event) {
if (keys[event.code] !== undefined) {
actions[keys[event.code]] = event.type === "keydown";
event.preventDefault();
actions.hasInput = true;
}
}
if (config.touchableTime) {
const actionTimers = {};
touchable.addEventListener("click", (e) => {
if (e.target.dataset.action) {
actions[e.target.dataset.action] = true;
clearTimeout(actionTimers[e.target.dataset.action]);
actionTimers[e.target.dataset.action] = setTimeout(() => actions[e.target.dataset.action] = false, config.touchableTime);
actions.hasInput=true;
if (currentRenderState !== drawScene) {
window.focus();
currentRenderState = drawScene;
}
}
});
} else {
touchable.classList.add("hide");
}
gameCanvas.addEventListener("click", () => currentRenderState = drawScene, {once: true});
canvas {border: 1px solid black}
#game {
width:402px;
height:182px;
font-size: 24px;
user-select: none;
}
.left {
position: absolute;
top: 160px;
left: 10px;
cursor: pointer;
}
.right {
position: absolute;
top: 160px;
left: 355px;
cursor: pointer;
}
#touchable span:hover {color: red}
.hide { display: none }
<div id="game">
<canvas id="gameCanvas" width="400" height="180"></canvas>
<div id="touchable">
<div class="left">
<span data-action="up">▲</span>
<span data-action="down">▼</span>
</div>
<div class="right">
<span data-action="left">◄</span>
<span data-action="right">►</span>
</div>
</div>
</div>
Click to snippet frame area for focusing keyboard events
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id='Game' width='200' height='200' style='border: 2px solid #000000;'></canvas>
<script>
window.onload = function(){
// Keyboard collect
const keys = [];
document.onkeydown = e => {
var code = e.which;
if(keys.indexOf(code) < 0){
keys.push(code);
}
};
document.onkeyup = e => keys.splice(keys.indexOf(e.which),1);
// constants
const Game = document.getElementById('Game');
const context = Game.getContext('2d')
const room = new Image();
const lx = 0;
const ly = 0;
const li = 0;
const lo = 0;
const lwidth = 100;
const lheight = 100;
room.onload = function(){
context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
}
room.src = 'https://i.ibb.co/D7fL7yN/Room.png';
const sprite = new Image();
const swidth = 35;
const sheight = 34;
const sy = 0;
sprite.onload = function(){
context.drawImage(sprite,0,sy,swidth,sheight,0,cy,50,50);
}
sprite.src = 'https://i.ibb.co/7VhjqPr/John-Sheet.png';
// variables
let cx = 0;
let cy = 125;
let sx = 0;
// new variables
const frames_per_step = 20;
let moving = false; // moving flag
let step = 0; // frame counter (for steps)
// main loop function
function tick() {
// keyboard process
if (keys.length) {
keys.forEach(item => {
switch(item){
case 68:case 39://D and right arrow
cx += 1; // move right
// change sprite
if (step++ < frames_per_step / 2) {
sx = 35; // leg up
} else {
sx = 70; // leg down
if(step > frames_per_step) step = 0;
}
moving = true;
break;
case 65:case 37://A and left arrow
cx -= 1; // move left
// change sprite
if (step++ < frames_per_step / 2) {
sx = 105;
} else {
sx = 140;
if(step > frames_per_step) step = 0;
}
moving = true;
break;
// no sprite mechanics here, just move
case 87:case 38://W adn arrow up
cy -= 1;
break;
case 83:case 40://S adn arrow down
cy += 1;
break;
}
});
// render
context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
} else if(moving) { // return sprite to stay position
sx = 0;
context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
moving = false;
} // else do nothing
requestAnimationFrame(tick);
}
tick();
}
</script>
</body>
</html>

Js collision detector - hit the wall vs. hit the floor (difference)

I'm making a small game with one player and blocks that builds up the environment. The problem I'm having is knowing the difference between when the player hits the ground (the top of a block), and hitting a wall (the side of the block).
So far the player can walk on the ground just fine, but when he meets a wall, he immediately jumps to the top of that block.
This is my collision detector:
function collisionDetector(){
if(myPlayer.y + myPlayer.h > c.height){ //Bottom of the canvas
myPlayer.vy = 0;
myPlayer.ay = 0;
myPlayer.y = c.height - myPlayer.h;
myPlayer.onGround = true;
console.log(myPlayer.y + myPlayer.h, c.height);
}
if(myPlayer.x + myPlayer.w >= c.width){ //right side of canvas
myPlayer.x = c.width - myPlayer.w;
myPlayer.vx = 0;
}
if(myPlayer.x <= 0){ //Left side of canvas
myPlayer.x = 0;
myPlayer.vx = 0;
}
function hitTest(a,b){ //hitTest between two objects
if(a.y + a.h > b.y && a.y < b.y + b.h && a.x + a.w > b.x && a.x < b.x + b.w){
return true;
}
}
for(var i = 0; i < blocks.length; i++){ //Loop through blocks
if(hitTest(myPlayer, blocks[i])){ //If it touches a block
myPlayer.y = blocks[i].y - myPlayer.h;
myPlayer.onGround = true; //onGround = ready to jump
}
}
}
I realized that I'm setting the players y pos to be on top of what ever block it hits, but I cannot figure out a solution to this problem. Can anyone help me or at least lead me in the right direction? Thanks!
(Let me know if you need more of the code)
PS: the player is just a head. No body hiding behind the blocks.
So basically, what you need to do is to check collision between many points in the player.
In the snippet you can show many points represented in the player.
Bottom almost-left and almost-right (in blue), check against below blocks. They are not fully left or right, in order to prevent a race condition which will allow the player to climb walls. In that case, if the player is pushing against a wall and jumping, the collider will detect both side collision and bottom collision as true, then the player will quickly move to the top until there are no more blocks.
Left and right points (in black), check against edges of the blocks. This is just a point instead of two like the bottom edge, because we don't need more for this particular case. One more for each side could be easily added to get a better detection.
Top point (in red) checks against the top blocks. This is in the middle in order to allow the player a more easy way to tranverse the map. If this is not needed, you would need to add one more point like in the bottom edge (but never reaching the far edge, because that will generate a race condition).
So in summary, to have a good collision detection based on points (instead of raycasts), you need to detect the player like if it where a rounded shape, in order to prevent strange behaviours.
You can player around with the map layout by altering the layout variable. 0's are empty space, 1's are brown blocks and 2's are green blocks.
The collisionDetector fuction has comments to understand what's going on.
Also I have added a jump feature since I understand you would need that as well.
const c = document.getElementById('canvas');
c.width = window.innerWidth;
c.height = window.innerHeight;
const ctx = c.getContext('2d');
// map layout
const layout =
`000000001
001000001
000000101
100110111
222222222`;
// convert layout to blocks
const blocks = [...layout].reduce((a, c, i) => {
if (i === 0 || c === "\n") a.push([]);
if (c === "\n") return a;
const y = a.length - 1;
const row = a[y];
const x = row.length;
row.push({x: x * 32, y: y * 32, t:c, w:32, h:32});
return a;
}, []).reduce((a, c) => a.concat(c), []);
// player starting position
const myPlayer = {x: 32*1.5, y: 0, h: 32, w: 16, onGround: true};
const gravity = -1;
let pkl = 0, pkr = 0;
let pvely = 0;
function render() {
// player logic
const pvelx = pkr + pkl;
const speed = 2;
myPlayer.x += pvelx * speed;
myPlayer.y -= pvely;
if (pvely > -2) pvely += gravity;
const debugColliders = collisionDetector();
ctx.clearRect(0, 0, c.width, c.height);
// player render
ctx.fillStyle = '#FFD9B3';
ctx.fillRect(myPlayer.x, myPlayer.y, myPlayer.w, myPlayer.h);
renderLayout();
debugColliders();
window.requestAnimationFrame(render);
}
function renderLayout() {
const colors = {'1': '#A3825F', '2': '#7FAC72'}
blocks.forEach(b => {
if (+b.t > 0) {
ctx.fillStyle = colors[b.t];
ctx.fillRect(b.x, b.y, b.w, b.h);
}
});
}
window.addEventListener('keydown', e => {
if (e.key == 'ArrowRight') {
pkr = 1;
e.preventDefault();
} else if (e.key == 'ArrowLeft') {
pkl = -1;
e.preventDefault();
} else if (e.key == 'ArrowUp') {
if (myPlayer.onGround)
pvely = 8;
myPlayer.onGround = false;
e.preventDefault();
}
});
window.addEventListener('keyup', e => {
if (e.key == 'ArrowRight') {
pkr = 0;
} else if (e.key == 'ArrowLeft') {
pkl = 0;
}
});
function collisionDetector(){
const p = myPlayer;
const playerTop = p.y;
const playerLeft = p.x;
const playerRight = playerLeft + p.w;
const playerBottom = playerTop + p.h;
const playerHalfLeft = playerLeft + p.w * .25;
const playerHalfRight = playerLeft + p.w * .75;
const playerHMiddle = playerLeft + p.w * .5;
const playerVMiddle = playerTop + p.h * .5;
if(playerBottom > c.height){ //Bottom of the canvas
p.vy = 0;
p.ay = 0;
p.y = c.height - p.h;
p.onGround = true;
}
if(playerRight >= c.width){ //right side of canvas
p.x = c.width - p.w;
p.vx = 0;
}
if(playerLeft <= 0){ //Left side of canvas
p.x = 0;
p.vx = 0;
}
blocks.forEach(b => { //Loop through blocks
if (b.t === "0") return; // If not collidable, do nothing
const blockTop = b.y;
const blockLeft = b.x;
const blockRight = blockLeft + b.w;
const blockBottom = b.y + b.h;
// Player bottom against block top
if ((playerBottom > blockTop && playerBottom < blockBottom) && // If player bottom is going through block top but is above block bottom.
((playerHalfLeft > blockLeft && playerHalfLeft < blockRight) || // If player left is inside block horizontal bounds
(playerHalfRight > blockLeft && playerHalfRight < blockRight))) { // Or if player right is inside block horizontal bounds
p.y = blockTop - p.h;
p.onGround = true;
}
// Player top against block bottom
if ((playerTop < blockBottom && playerTop > blockTop) && // If player top is going through block bottom but is below block top.
((playerHMiddle > blockLeft && playerHMiddle < blockRight))) { // If player hmiddle is inside block horizontal bounds
p.y = blockBottom;
p.onGround = false;
}
// Player right against block left, or player left against block right
if (playerVMiddle > blockTop && playerVMiddle < blockBottom) { // If player vertical-middle is inside block vertical bounds
if ((playerRight > blockLeft && playerRight < blockRight)) { // If player vmiddle-right goes through block-left
p.x = blockLeft - p.w;
} else if ((playerLeft < blockRight && playerRight > blockLeft)) { // If player vmiddle-left goes through block-right
p.x = blockRight;
}
}
});
return function debug() {
ctx.fillStyle = 'black';
ctx.fillRect(playerLeft, playerVMiddle, 1, 1);
ctx.fillRect(playerRight, playerVMiddle, 1, 1);
ctx.fillStyle = 'red';
ctx.fillRect(playerHMiddle, playerTop, 1, 1);
ctx.fillStyle = 'blue';
ctx.fillRect(playerHalfLeft, playerBottom, 1, 1);
ctx.fillRect(playerHalfRight, playerBottom, 1, 1);
}
}
window.requestAnimationFrame(render);
html, body{ width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }
canvas { background: #7AC9F9; display: block; }
<canvas id="canvas"></canvas>
Introduce a block[i].type attribute. For instance if block[i].type=='floor' then make player stay on floor. If for another instance block[i].type=='wall' then make it stop moving through the wall. When block[i].type=='brick' or square or block or whatever, them it's a mixture of two.
Another part to be edited is when you check the collisions. What if you have only one-direction collision? What I am saying is maybe use or instead of and in this part if(a.y + a.h > b.y && a.y < b.y + b.h && a.x + a.w > b.x && a.x < b.x + b.w){
Also you could check each collision separately, like
function hitTest(a,b){ //hitTest between two objects
var collisions = {up: false, down: false, left: false, right: false};
collisions.up = (a.y + a.h > b.y ) || collisions.up
collisions.down = (a.y < b.y + b.h ) ||collisions.down
collisions.right = ( a.x + a.w > b.x) || collisions.right
collisions.left = (a.x < b.x + b.w) || collisions.left
return collisions
}
var escapeFrom = {
down: function(player, block){
player.y = block.y + block.h;
player.onGround = true; //onGround = ready to jump
},
up: function(player, block){
// you logic to escape from hitting the ceiling
},
// and for the next 2
left: function(player, block) {},
right: function(player, block){}
}
// Now here you check whether your player hits blocks
for(var i = 0; i < blocks.length; i++){ //Loop through blocks
cls = hitTest(myPlayer, blocks[i]) //If it touches a block
Object.keys(cls).map(function(direction, ind){
if (cls[direction]){
// call escape from function to escape collision
escapeFrom[direction](myPlayer, blocks[i]);
}
})
}
This is highly unoptimized, the whole your code is unoptimized, but at least it can help to move further.

Block in canvas dissapeared for one frame

I'm trying to build Snake from scratch using Javascript. But when I use the arrow keys to get it from 1 part of the canvas to the other part of the canvas it dissapears for 1 frame, how to resolve this? You can try it on: https://annedegraaff.nl/snake/
<canvas id="snake" width="400" height="400">
</html>
<script>
var canvas;
var canvasContext;
var ball1X = 12.5;
var ball1Y = 12.5;
window.onload = function() {
canvas = document.getElementById('snake');
canvasContext = canvas.getContext('2d');
var framesPerSecond = 60;
setInterval(function() {
draw();
move();
}, 1000/framesPerSecond);
}
function move() {
window.onkeydown = function(e) {
var key = e.keyCode ? e.keyCode : e.which;
if (ball1X < 12.5) {
ball1X += 395;
}else if (ball1X > 385) {
ball1X -= 395;
}
if (key == 39) {
ball1X += 10;
}else if (key == 37) {
ball1X -= 10;
}else if (key == 40) {
ball1Y += 10;
}else if (key == 38) {
ball1Y -= 10;
}
}
}
function draw() {
canvasContext.fillStyle = 'green';
canvasContext.fillRect(0,0,canvas.width,canvas.height);
canvasContext.fillStyle = 'black';
canvasContext.fillRect(ball1X,ball1Y,10,10);
}
</script>
Logic error in keyEvent handler
Though not directly evident where the problem is in the given code I am assuming it is the test for edges in the keydown handler. There are also other ting being done incorrectly that will present additional problems and difficulties as you develop the game.
Your bug
In your keyboard function you test if the ball is close to the edge and if so you move it to the other size. Looks like you move it too far and thus can not be seen.
The following is a quick fix. Move the test to after the ball has been moved and make sure the the move to the other side does not put it too far so that it is moved again on the next event.
window.onkeydown = function(e) {
var key = e.keyCode ? e.keyCode : e.which;
if (key == 39) {
ball1X += 10;
}else if (key == 37) {
ball1X -= 10;
}else if (key == 40) {
ball1Y += 10;
}else if (key == 38) {
ball1Y -= 10;
}
if (ball1X < 12.5) {
ball1X += 400;
}else if (ball1X > 400-12.5) {
ball1X -= 400;
}
}
Other problems.
Use requestAnimationFrame for animations not setInterval
Key event listeners should only record the keyboard state as they have nothing to do with the game and dont know what to do with the keys pressed. The game code should use the keyboard state and its own current state to work out what to do with each key
Use addEventListener add events as directly setting event can be overwritten
Encapsulate your game inside a function so that all the variables and functions are isolated from the global names space and you can easily insert the game into any page.
Use objects to group properties and function together. Eg you had ballX, ballY and most like will add other properties each will have a ball prefix. By creating an object named ball and adding properties like x,y you can get access to the balls x, y with via a reference ball.x, ball.y or var b = ball; b.x += 1;`. Once an object has been defined you can make many copies easily.
Change the key handler to hold the key state of only the keys you are interested in. You only want to know if the key is down so listen to key up and down setting a flag to true when a key is down.
And other stuff
Rewrite
A quick rewrite showing a better way to implement what you had. It is a recommendation only. It is a little longer than you had it and is not the only way, but if you write like this it will be easier as the game develops.
See comments for the reasons and what does what.
// create onload event handler as a function and encapsulate all variables and functions to key global name space clean
function start(){
// create canvas and context
const canvas = document.getElementById("snakeCanvas");
const ctx = canvas.getContext("2d"); // formaly canvasContext;
// get the size as we use that a lot
const width = canvas.width;
const height = canvas.height;
requestAnimationFrame(mainLoop); // will call mainloop after this function (start) has run
// create an object to hold all related properties and
// functions for the ball
const ball = { // position ball in center
x : width / 2 | 0, // the or zero ( | 0) rounds down to nearest integer
y : height / 2 | 0,
size : 10,
speed : 10,
draw() { // function to draw the ball
ctx.fillStyle = 'black';
ctx.fillRect(this.x - this.size / 2,this.y - this.size / 2, this.size, this.size);
},
update() { // moves the ball
if (keys.up === true) { this.y -= this.speed }
if (keys.down === true) { this.y += this.speed }
if (keys.left === true) { this.x -= this.speed }
if (keys.right === true) { this.x += this.speed }
// get half size
const hSize = this.size / 2;
// check for edges and move to other side of canvas
if(this.x + hSize < 0) { this.x += width }
if(this.x - hSize > width) { this.x -= width }
if(this.y + hSize < 0) { this.y += height }
if(this.y - hSize > height) { this.y -= height }
},
}
// the background function clears and displays the background
function background(){
ctx.fillStyle = 'green';
ctx.fillRect(0,0,width,height);
}
// Object to hold the current keyboard state
const keys = {
up : false,
down : false,
left : false,
right : false,
map : new Map([ // use a Map to find keys
[39,"right"], // key code and string name of keys.name
[37,"left"],
[40,"down"],
[38,"up"],
])
}
// the key event listener
function keyEvents(event){
// get key code as a string
const keyCode = event.keyCode ? event.keyCode : event.which;
// get if avalible the key name from the map
const key = keys.map.get(keyCode);
// if a key is mapped set its state
if(key){
keys[key] = event.type === "keydown";
event.preventDefault(); // pervent default action
}
}
// listent to the keyboard events and set the keyboard state
["keydown","keyup"].forEach(eventName => addEventListener(eventName, keyEvents));
// for the stackoverflow snippet we need to get focus to
// hear any of the key events
focus();
function mainLoop(time){ // time is automatic and in ms (1/1000th second)
background(); // call the background function that clears and displays the background
// update and draw the ball object
ball.update();
ball.draw();
requestAnimationFrame(mainLoop); // request next frame in 1/60th second
}
}
// when loaded start the game
addEventListener("load", start);
<canvas id="snakeCanvas" width="400" height="400">

get jquery touch position every x milliseconds

is there a way to get the touch position in a touchmove event every x milliseconds and then execute a function, when the x-coordinate at the moment and the one at the start are differing e.g. 50px?
Thanks
Try the below ;
$('document').ready(function() {
var touch,
action,
diffX,
diffY,
endX,
endY,
startX,
startY,
timer,
timerXseconds = 500, // Change to the Time(milliseconds) to check for touch position
xDifferenceX = 50, // Change to difference (px) for x-coordinates from starting point to run your function
xDifferenceY = 50; // Change to difference (px) for y-coordinates from starting point
function getCoord(e, c) {
return /touch/.test(e.type) ? (e.originalEvent || e).changedTouches[0]['page' + c] : e['page' + c];
}
function testTouch(e) {
if (e.type == 'touchstart') {
touch = true;
} else if (touch) {
touch = false;
return false;
}
return true;
}
function onStart(ev) {
if (testTouch(ev) && !action) {
action = true;
startX = getCoord(ev, 'X');
startY = getCoord(ev, 'Y');
diffX = 0;
diffY = 0;
timer = window.setInterval(checkPosition(ev), timerXseconds); // get coordinaties ever X time
if (ev.type == 'mousedown') {
$(document).on('mousemove', onMove).on('mouseup', onEnd);
}
}
}
function onMove(ev) {
if (action) {
checkPosition(ev)
}
}
function checkPosition(ev) {
endX = getCoord(ev, 'X');
endY = getCoord(ev, 'Y');
diffX = endX - startX;
diffY = endY - startY;
// Check if coordinates on Move are Different than Starting point by X pixels
if (Math.abs(diffX) > xDifferenceX || Math.abs(diffY) > xDifferenceY) {
// console.log('Start is :' + startX + ' End is : ' + endX + 'Difference is : ' + diffX);
$(this).trigger('touchend');
// here Add your function to run...
}
}
function onEnd(ev) {
window.clearInterval(timer);
if (action) {
action = false;
if (ev.type == 'mouseup') {
$(document).off('mousemove', onMove).off('mouseup', onEnd);
}
}
}
$('#monitor')
.bind('touchstart mousedown', onStart)
.bind('touchmove', onMove)
.bind('touchend touchcancel', onEnd);
});
body {
margin: 0;
padding: 0;
}
#monitor {
height: 500px;
width: 500px;
position: relative;
display: block;
left: 50px;
top: 50px;
background: green;
}
.box {
position: absolute;
top: 0;
left: 0;
right: 0;
text-align: center;
font-weight: bold;
bottom: 0;
background: white;
width: 50px;
height: 50px;
margin: auto;
font-size: 16px;
line-height: 23px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='monitor'>
<div class='box'>start here</div>
</div>
Read this post for a more detailed answer
This can be done in a few functions.
The first function is called when there is a movement of the touch event, this event stores the x and y of the touch in a separate variable.
Then we have a function that runs every X miliseconds, this function gets the x and y from the move event and dispatches then to your code.
Functions 3, 4 and 5 are used to handle the start, stop and cancel dragevents, and start/stop the second function:
var timerid;
var x;
var y;
var tick = 0;
function handleStart(evt) {
console.log("handleStart");
evt.preventDefault();
timerid = window.setInterval(timer, 50); // Replace 50 here with X
}
function handleEnd(evt) {
console.log("handleEnd");
evt.preventDefault();
window.clearInterval(timerid);
}
function handleCancel(evt) {
console.log("handleCancel");
evt.preventDefault();
window.clearInterval(timerid);
}
function handleMove(evt) {
console.log("handleMove");
evt.preventDefault();
// Select last point:
var point = evt.changedTouches[evt.changedTouches.length - 1];
x = point.pageX;
y = point.pageY;
}
function timer() {
console.log("timer");
tick++;
document.getElementById("output").innerHTML = "tick: " + tick + " x: " + x + " y:" + y;
}
var el = document.getElementById("canvas");
el.addEventListener("touchstart", handleStart, false);
el.addEventListener("touchend", handleEnd, false);
el.addEventListener("touchcancel", handleCancel, false);
el.addEventListener("touchmove", handleMove, false);
<canvas id="canvas" width="300" height="300" style="border:solid black 1px;"></canvas>
<p id=output></p>
As long as the user is pressing the screen, the code will print out the x and the y coordinate to the screen. You can also integrate the reading of the x and y into your existing game loop instead of having a separate function if that is needed for your project.
Take a look at hammer.js, it has exactly what you need. It supports "touchmove" called pan, that is being called every few milliseconds when you pan. Also there is a threshold property which determine a length in pixels you have to pan before recognizing it as a pan.

Getting a gun to fire

I'm trying to make a top down shooter game to learn about coding in HTML/JS/CSS.
I've got a canvas, my player moves and rotates on the canvas, but I'm having some trouble getting his gun working. I've got this with the aid of tutorials and searching through other stackoverflow posts, but this one I can't seem to fix alone.
I have defined a variable 'gunfire' which is set to 1 if the left mouse button is pressed and is otherwise 0, in my function draw() I have an if statement that should draw a rectangle in front of my sprite (to represent bullets) when the left mouse button is pressed.
The problem I have is that the bullets appear at all times, independent of whether the mouse button is pressed. If anyone can point out what it is that I'm doing wrong then I would be very grateful, here's the code (the canvass is created in separate HTML/CSS files):
var turn = 0;
var frameRate = 24;
var main_x = 0,
main_y = 0,
move_x = 0,
move_y = 0;
var gunfire = 0;
var speed = 4;
function keyPress(e) {
if (e.keyCode == 68) { //d
move_x = speed;
}
if (e.keyCode == 65) { //a
move_x = -speed;
}
if (e.keyCode == 83) { //s
move_y = speed;
}
if (e.keyCode == 87) { //w
move_y = -speed;
}
if (e.keyCode == 1) { //left mouse
gunfire = 1;
}
}
function keyRelease(e) {
if (e.keyCode == 68) { //d
move_x = 0;
}
if (e.keyCode == 65) { //a
move_x = 0;
}
if (e.keyCode == 83) { //s
move_y = 0;
}
if (e.keyCode == 87) { //w
move_y = 0;
}
if (e.keyCode == 1) { //left mouse
gunfire = 0;
}
}
function move() {
main_x += move_x;
main_y += move_y;
if (main_x < -220) {
main_x = -220
}
if (main_y < -220) {
main_y = -220
}
if (main_x > 220) {
main_x = 220
}
if (main_y > 220) {
main_y = 220
}
}
function mouseMove(e) {
var mouseX, mouseY;
if (e.offsetX) {
mouseX = e.offsetX;
mouseY = e.offsetY;
} else if (e.layerX) {
mouseX = e.layerX;
mouseY = e.layerY;
}
mouseX = mouseX - (top_canvas.width / 2) - main_x;
mouseY = mouseY - (top_canvas.height / 2) - main_y;
window.radians = Math.atan2(mouseY, mouseX);
//document.getElementById("info").innerHTML = radians
}
var background = new Image();
background.src = "assets/background3.jpg";
var player1 = new Image();
player1.src = "assets/player1.png";
function draw() { //draws all content on the canvas
ctx_1.save();
ctx_1.clearRect(0, 0, top_canvas.width, top_canvas.height);
ctx_1.drawImage(background, 0, 0);
ctx_1.translate(top_canvas.width / 2 + main_x, top_canvas.height / 2 + main_y);
ctx_1.rotate(turn);
ctx_1.drawImage(player1, -25, -25, 50, 70);
if (gunfire = 1) {
ctx_1.fillStyle = "#000";
ctx_1.fillRect(0, -10, 200, 20);
}
ctx_1.restore();
}
function gameLoop() {
// all functions to update go here
draw();
turn = window.radians
move();
}
function init() {
window.top_canvas = document.getElementById("top_canvas");
window.ctx_1 = top_canvas.getContext("2d");
setInterval(gameLoop, 1000 / frameRate);
//event fires every time the mouse moves
top_canvas.addEventListener("mousemove", mouseMove, false);
window.addEventListener("keydown", keyPress, false);
window.addEventListener("keyup", keyRelease, false);
}
window.addEventListener("load", init, false);
First of all, as Kippie points out, you're setting gunfire to 1 each time which results in a true statement. It must be changed to == or === (latter preferable) or simply drop the comparison as the non-zero value would be true:
if (gunfire) {
...
}
Second, you are checking for mouse clicks inside a key handler and assuming keyCode would indicate a mouse button click. keyCode 1 is not related to mouse clicks at all and no mouse clicks will end up here.
Third, you are not having any event handler for mouse clicks...
To solve these issues (in addition to the first) add an event handler for mouse clicks:
top_canvas.addEventListener("mousedown", mouseDown, false);
window.addEventListener("mouseup", mouseUp, false);
Then inside the handlers (only showing for mouse down):
function mouseDown(e) {
if (e.button === 0) { // check left mouse-button
gunfire = 1;
}
}
The reason for using window for mouse up is that in case your mouse pointer is outside the canvas element the up event won't register and the gun will continue to fire. Using the window object will allow you to register up event even in this situation.
if (gunfire = 1)
You're using the assignment operator, rather than doing a comparison.
Use === instead.

Categories

Resources