In my 2D (top-down) game, I am attempting to add a dash function.
Using an if (keyWentDown("e")) {}
condition inside a function named dash
. I have a thing set up to face the player's direction, being: plrvector.rotation = 90
, and I want my character to move about 50px
smoothly when E is pressed.
Problem is, I have no clue how to use vectors. Any tips or directions? I have attempted to use various techniques; however, I could not find any tutorials or anything that could help me.
CodePudding user response:
I am not sure you want to move the player exactly 50px. You would just temporarily increase the velocity by a factor. In the example below, if the E key is pressed, a "turbo" mode is activate increasing the speed by a factor of 3.
You would have to do something along the lines of:
function update(progress) {
const { player } = state;
const turbo = state.pressedKeys.turbo ? 3 : 1; // x3 speed (E)
if (state.pressedKeys.left) {
if (player.speed.x > 0) player.speed.x *= -1;
player.position.x = progress * player.speed.x * turbo;
}
}
Note: This can be modified to only allow turbo for a short duration. For instance, the player may have a turbo meter that they need to fill in order to use this mode.
The following example is adapted from: "Quick Tip: How to Make a Game Loop in JavaScript". I did not utilize vectors pre se, but I store the position and velocity (speed) in a vector-like object. If you want to convert this to vectors, you can check out victor.js. It can easily add/multiply/normalize vectors for you.
// Canvas context reference
const ctx = document.querySelector('#game').getContext('2d');
const hud = document.querySelector('#hud');
// Set the canvas size
Object.assign(ctx.canvas, { width: 600, height: 160 });
// Game state
const state = {
player: {
dimensions: { depth: 10, height: 10, width: 10 },
position: { x: Math.floor(ctx.canvas.width / 2), y: Math.floor(ctx.canvas.height / 2) },
speed: { x: 0.25, y: 0.25 }
},
pressedKeys: {
left: false,
right: false,
turbo: false,
up: false,
down: false
}
};
// Track keys
const keyMap = new Map(Object.entries({
ArrowDown: 'down',
ArrowLeft: 'left',
ArrowRight: 'right',
ArrowUp: 'up',
a: 'left',
d: 'right',
e: 'turbo',
s: 'down',
w: 'up',
}));
// Game Loop ~ Update
function update(progress) {
const { player } = state;
hud.innerText = Object.entries(state.pressedKeys)
.filter(([k, v]) => v)
.map(([k]) => `<${k}>`)
.sort()
.join(' ');
const turbo = state.pressedKeys.turbo ? 3 : 1; // x3 speed (E)
// Check pressed keys to update position
if (state.pressedKeys.left) {
if (player.speed.x > 0) player.speed.x *= -1;
player.position.x = progress * player.speed.x * turbo;
}
if (state.pressedKeys.right) {
if (player.speed.x < 0) player.speed.x *= -1;
player.position.x = progress * player.speed.x * turbo;
}
if (state.pressedKeys.up) {
if (player.speed.y > 0) player.speed.y *= -1;
player.position.y = progress * player.speed.y * turbo;
}
if (state.pressedKeys.down) {
if (player.speed.y < 0) player.speed.y *= -1;
player.position.y = progress * player.speed.y * turbo;
}
// Check bounds
const threshold = player.dimensions.width;
if (player.position.x > ctx.canvas.width - threshold) {
player.position.x = ctx.canvas.width - threshold;
} else if (player.position.x < threshold) {
player.position.x = threshold;
}
if (player.position.y > ctx.canvas.height - threshold) {
player.position.y = ctx.canvas.height - threshold;
} else if (player.position.y < threshold) {
player.position.y = threshold;
}
}
// Game Loop ~ Draw
function draw() {
const { player, pressedKeys } = state;
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(player.position.x, player.position.y, player.dimensions.width, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
// Game Loop ~ Main
function loop(timestamp) {
const progress = timestamp - lastRender;
update(progress);
draw();
lastRender = timestamp;
window.requestAnimationFrame(loop);
}
// Begin game
let lastRender = 0;
window.requestAnimationFrame(loop);
// Register keyboard events
window.addEventListener('keydown', onKeyDown, false);
window.addEventListener('keyup', onKeyUp, false);
// Event handlers
function onKeyDown({ key }) {
toggleKey(key, true);
}
function onKeyUp({ key }) {
toggleKey(key, false);
}
// Convenience
function toggleKey(key, value) {
const index = keyMap.get(key);
if (index) {
const currentValue = state.pressedKeys[index];
state.pressedKeys[index] = value ?? !currentValue;
}
}
*, *::before,*::after { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
body {
display: flex; flex-direction: column;
align-items: center; justify-content: flex-start; gap: 0.25em;
padding: 0.25em;
}
#hud { white-space: pre; text-transform: uppercase; }
<!-- See: https://www.sitepoint.com/quick-tip-game-loop-in-javascript/ -->
<canvas id="game"></canvas>
<div id="hud"></div>
Vector math
Here is an example of adding/multiplying vectors:
- Add
t
andu
to get{ x: 4, y: 7 }
- Multiply the result above by
t
to get{ x: 4, y: 14 }
const vectorAdd = (a, b) => ({ x: a.x b.x, y: a.y b.y });
const vectorMultiply = (a, b) => ({ x: a.x * b.x, y: a.y * b.y });
let t = { x: 1, y: 2 }, u = { x: 3, y: 5 }, v = vectorMultiply(vectorAdd(t, u), t);
console.log(v); // { x: 4, y: 14 }
If you want to chain these calls, you can try creating a wrapper:
const vectorAdd = (a, b) => ({ x: a.x b.x, y: a.y b.y });
const vectorMultiply = (a, b) => ({ x: a.x * b.x, y: a.y * b.y });
const vector = function({ x, y }) {
[this.x, this.y] = [x, y];
this.add = (other) => {
const { x, y } = vectorAdd(this, other);
[this.x, this.y] = [x, y];
return this;
};
this.multiply = (other) => {
const { x, y } = vectorMultiply(this, other);
[this.x, this.y] = [x, y];
return this;
};
this.value = () => ({ x: this.x, y: this.y });
return this;
}
let t = { x: 1, y: 2 }, u = { x: 3, y: 5 }, v = vector(t).add(u).multiply(t).value();
console.log(v); // { x: 4, y: 14 }