Search code examples
javascriptvectordirection

How to add dash function with vectors


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.


Solution

  • 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:

    1. Add t and u to get { x: 4, y: 7 }
    2. 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 }