-1

I want to simulate car rotation and movement in the new direction in a game I am designing.

According to the following answer, within the HTML5 canvas element you cannot rotate individual elements.

The movement itself is happening in this function, I expect the vehicle to move but the entire canvas moves (try pressing left and right). I couldn't figure out how to rotate the car.

Game._rotate = function(dirx, direction) {
  this.ctx.translate(200,200);
}

According to this tutorial, I can only rotate the car itself by rotating the canvas. I'd like the rotation and movement to emulate the following: https://oseiskar.github.io/js-car/.

The code itself here: https://plnkr.co/edit/K5X8fMhUlRLhdeki.

methuselah
  • 12,766
  • 47
  • 165
  • 315
  • Instead of rotating the image, use a spritesheet with NSEW directions? – Mr. Polywhirl Apr 05 '20 at 12:46
  • https://stackoverflow.com/q/32468969/1435 – spender Apr 05 '20 at 12:49
  • Your car should know how to render itself. You will need to pass the canvas into the render for the car. – Mr. Polywhirl Apr 05 '20 at 12:52
  • What's your question? You seem to understand you cannot do what you are asking with a single canvas. If you're just asking for suggestions, that's too open ended for stack overflow since there are various approaches: use multiple canvas tags, use svg, use HTML – Ruan Mendes Apr 05 '20 at 13:04

1 Answers1

1

You need to render the car relative to ITS position. Also, you can do the rotation inside its RENDER.

The changes below will handle rotation for the car, but you have bigger problems. I would take time and invest in learning how vectors work. The position, speed and acceleration should all be 2D vectors that provide vector math like adding and multiplication.

Also, provide an initial angle to your car so it is initially rendered the right way. It also appears that your acceleration and deceleration are mixed up.

function Car(map, x, y) {
  this.map = map;
  this.x = x;
  this.y = y;
  this.angle = 0; // IMPORTANT
  this.width = map.tsize;
  this.height = map.tsize;
  this.image = Loader.getImage('car');
}

Car.speed = 0;
Car.acceleration = 0;
Car.friction = 5;
Car.moveAngle = 0;
Car.maxSpeed = 500;
Car.forward = 'FORWARD';
Car.backward = 'BACKWARD';
Car.left = 'LEFT';
Car.right = 'RIGHT';

// Render relative to car...
Car.prototype.render = function(ctx) {
  ctx.save();
  ctx.translate(this.x, this.y);
  ctx.rotate(this.angle);
  ctx.drawImage(this.image, -this.width / 2, -this.height / 2);
  ctx.restore();
};

Game.update = function (delta) {
  var dirx = 0;
  var diry = 0;
  if (Keyboard.isDown(Keyboard.LEFT)) {
    this._rotate(dirx, Car.left)
  }
  else if (Keyboard.isDown(Keyboard.RIGHT)) {
    this._rotate(dirx, Car.right)
  }
  else if (Keyboard.isDown(Keyboard.UP)) {
    this._rotate(dirx, Car.up)
    diry = accelerate(diry, Car.forward);
  }
  else if (Keyboard.isDown(Keyboard.DOWN)) {
    this._rotate(dirx, Car.down)
    diry = accelerate(diry, Car.backward);
  }
  else {
    decelerate();
  }

  this.car.move(delta, dirx, diry);
  this.camera.update();
};

Game._rotate = function(dirx, direction) {
  let angleInDegrees = 0;
  switch (direction) {
    case 'UP':
     angleInDegrees = 0;
     break;
    case 'RIGHT':
     angleInDegrees = 90;
     break;
    case 'DOWN':
     angleInDegrees = 180;
     break;
    case 'LEFT':
     angleInDegrees = 270;
     break;
  }
  this.car.angle = angleInDegrees * (Math.PI / 180);
}

Game.render = function () {
  // draw map background layer
  this._drawLayer(0);

  this.car.render(this.ctx);

  // draw map top layer
  this._drawLayer(1);
};

Vectors

Here is an example using vectors.

loadImage('https://i.stack.imgur.com/JY7ai.png')
  .then(function(img) {
    main(img);
  })

function main(img) {
  let game = new Game({
    canvas: document.querySelector('#viewport')
  });
  let car = new Car({
    img: img,
    imgRadiansOffset : -Math.PI / 2,
    position: new Victor(game.canvas.width, game.canvas.height).divide(new Victor(2, 2)),
    velocity: new Victor(0, -1),
    showBorder: true
  });
  game.addLayer(car);
  game.start();
}

class Game {
  constructor(options) {
    Object.assign(this, Game.defaultOptions, options);
    if (this.canvas != null) {
      Object.assign(this, {
        width: this.canvas.width,
        height: this.canvas.height
      });
      this.addListeners();
    }
  }
  addLayer(layer) {
    this.layers.push(layer);
    layer.parent = this;
  }
  start() {
    this.id = setInterval(() => {
      this.render();
    }, 1000 / this.rate); // frames per second
  }
  render() {
    let ctx = this.canvas.getContext('2d');
    ctx.clearRect(0, 0, this.width, this.height);
    this.layers.forEach(layer => layer.render(ctx));
  }
  addListeners() {
    if (this.canvas != null) {
      window.addEventListener('keydown', (e) => {
        this.handleKeyDown(e.keyCode);
      });
      window.addEventListener('keyup', (e) => {
        this.handleKeyUp(e.keyCode);
      });
    }
  }
  handleKeyDown(keyCode) {
    this.layers.forEach(layer => {
      layer.update(keyCode !== this.lastKeyCode ? keyCode : null);
    });
    this.lastKeyCode = keyCode;
  }
  handleKeyUp(keyCode) {
    this.layers.forEach(layer => {
      layer.update(this.lastKeyCode); // calls reset...
    });
  }
}

Game.defaultOptions = {
  id: null,
  rate: 30,
  layers: [],
  canvas: null,
  width: 0,
  height: 0
};

class Car {
  constructor(options) {
    Object.assign(this, Car.defaultOptions, options);
    if (this.img != null) {
      Object.assign(this, {
        width: this.img.width,
        height: this.img.height
      });
    }
  }
  render(ctx) {
    ctx.save();
    ctx.translate(this.position.x, this.position.y);
    ctx.rotate(this.velocity.angle() - this.imgRadiansOffset);
    ctx.drawImage(this.img, -this.width / 2, -this.height / 2, this.width, this.height);
    if (this.showBorder) {
      ctx.strokeStyle = '#C00';
      ctx.setLineDash([4, 8]);
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.rect(-this.width / 2, -this.height / 2, this.width, this.height);
      ctx.stroke();
    }
    ctx.restore();
  }
  update(keyCode) {
    if (keyCode != null) this.changeDirection(keyCode);
    this.position.add(this.velocity.add(this.acceleration));
    this.detectCollision();
  }
  detectCollision() {
    let xMin = this.width / 2, xMax = this.parent.width - xMin;
    let yMin = this.height / 2, yMax = this.parent.height - yMin;
    if (this.position.x < xMin) this.position.x = xMin;
    if (this.position.x > xMax) this.position.x = xMax;
    if (this.position.y < yMin) this.position.y = yMin;
    if (this.position.y > yMax) this.position.y = yMax;
  }
  changeDirection(keyCode) {
    switch (keyCode) {
      case 37:
        this.reset(new Victor(-1, 0)); // LEFT
        break;
      case 38:
        this.reset(new Victor(0, -1)); // UP
        break;
      case 39:
        this.reset(new Victor(1, 0)); // RIGHT
        break;
      case 40:
        this.reset(new Victor(0, 1)); // DOWN
        break;
    }
  }
  reset(dirVect) {
    this.velocity = new Victor(this.speedFactor, this.speedFactor).multiply(dirVect);
    this.acceleration = new Victor(this.accelFactor, this.accelFactor).multiply(dirVect);
  }
}

Car.defaultOptions = {
  position: new Victor(0, 0),
  width: 0,
  height: 0,
  img: null,
  imgRadiansOffset: 0,
  velocity: new Victor(0, 0),
  acceleration: new Victor(0, 0),
  showBorder: false,
  speedFactor: 3, // Velocity scalar
  accelFactor: 1  // Acceleration scalar
};

function loadImage(url) {
  return new Promise(function(resolve, reject) {
    var img = new Image;
    img.onload = function() {
      resolve(this)
    };
    img.onerror = img.onabort = function() {
      reject("Error loading image")
    };
    img.src = url;
  })
}
#viewport {
  border: thin solid grey;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/victor/1.1.0/victor.min.js"></script>
<canvas id="viewport" width="400" height="160"></canvas>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132