Simulate car rotation in JavaScript [duplicate] - javascript

This question already has answers here:
How do I rotate a single object on an html 5 canvas?
(8 answers)
Closed 2 years ago.
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.

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>

Related

Javascript Canvas Viewport Camera for Top down RPG game

edit: what is my question exactly?
I want to create a "viewport" camera effect that will follow the player without moving the background
I'm adding a websocket support and will render more characters on the map - i need movement to happen based on the player and not the map - so that i can update the rest of the players with movement position correctly
original post:
I've gone over most other posts about this subject.
It seems that, everyone, has their own unique problem for their own unique implementation of the "Canvas topdown game".
That makes it really hard finding a solution for your problem without refactoring your whole code.
So hopefully you guys will be able to help me this time.
Simple game (so far) - utilizing Vue3 btw but that's besides the point - most of the code is just Vanilla stuff.
Simple topdown map.
X and Y Axis
What I'm doing in my implementation is taking a PNG I've created with the Tiled application
Zoomed in by about 400% - drawing it with ctx.drawImage
then I'm also setting boundaries
and then drawing the player
what I'm doing now is adding a "force" value to the x and y position on key pressed (WASD)
Which means the character moves on the map and collision detection works
what I want is the map to draw x amount of pixels based on where the character is positioned WITHOUT moving the map since I'm already implementing some SocketIO code to make this multiplayer
I'm really really lost here, not sure what I should be doing to make the map draw a viewport....
I hope some of this makes sense
Some code
function create() {
const canvas: HTMLCanvasElement = document.getElementById("game") as HTMLCanvasElement;
if (!canvas) return console.error("canvas is undefined");
const ctx: CanvasRenderingContext2D = canvas.getContext("2d") as CanvasRenderingContext2D;
ctx.imageSmoothingEnabled = false;
initializeGameWindow(canvas, ctx);
setEvents(canvas, ctx);
const playerSpriteWidth = 192;
const playerSpriteHeight = 68;
const spriteFrames = 4;
background = new Sprite();
player = new Sprite();
update(canvas, ctx);
}
function update(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
window.requestAnimationFrame(() => update(canvas, ctx));
drawGame(canvas, ctx);
}
function drawGame(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
background.draw(ctx);
boundaries.forEach((boundary) => {
boundary.draw(ctx);
});
player.value.draw(ctx);
let moving: boolean = true;
player.value.moving = false;
if (keys.w.pressed && lastKey.value === "w") {
player.value.moving = true;
player.value.image = player.value.sprites.up;
for (let i = 0; i < boundaries.length; i++) {
if (
rectengularCollision(player.value, {
...boundaries[i],
position: { x: boundaries[i].position.x, y: boundaries[i].position.y + Sprite.force },
})
) {
moving = false;
break;
}
}
if (!moving) return;
player.value.position.y -= Sprite.force;
} else if (keys.a.pressed && lastKey.value === "a") {
player.value.moving = true;
player.value.image = player.value.sprites.left;
for (let i = 0; i < boundaries.length; i++) {
if (
rectengularCollision(player.value, {
...boundaries[i],
position: { x: boundaries[i].position.x + Sprite.force, y: boundaries[i].position.y },
})
) {
moving = false;
break;
}
}
if (!moving) return;
player.value.position.x -= Sprite.force;
} else if (keys.s.pressed && lastKey.value === "s") {
player.value.moving = true;
player.value.image = player.value.sprites.down;
for (let i = 0; i < boundaries.length; i++) {
if (
rectengularCollision(player.value, {
...boundaries[i],
position: { x: boundaries[i].position.x, y: boundaries[i].position.y - Sprite.force },
})
) {
moving = false;
break;
}
}
if (!moving) return;
player.value.position.y += Sprite.force;
} else if (keys.d.pressed && lastKey.value === "d") {
player.value.moving = true;
player.value.image = player.value.sprites.right;
for (let i = 0; i < boundaries.length; i++) {
if (
rectengularCollision(player.value, {
...boundaries[i],
position: { x: boundaries[i].position.x - Sprite.force, y: boundaries[i].position.y },
})
) {
moving = false;
break;
}
}
if (!moving) return;
player.value.position.x += Sprite.force;
}
}
export class Sprite {
static force: number = 3; // speed, velocity, acceleration, etc.
frames: number;
spriteIteration: number = 0;
elapsed: number = 0;
defaultSrc: string;
image: HTMLImageElement;
sprites: { up: HTMLImageElement; down: HTMLImageElement; left: HTMLImageElement;
right: HTMLImageElement };
position: { x: number; y: number };
width: number = 0;
height: number = 0;
moving: boolean = false;
constructor(
position: { x: number; y: number },
src : string,
frames: number = 1,
sprites: { up: string; down: string; left: string; right: string } = { up:
"", down: "", left: "", right: "" },
) {
this.defaultSrc = src;
this.moving = false;
const { up, down, left, right } = this.initSprites(sprites);
this.sprites = { up, down, left, right };
this.image = down;
this.frames = frames;
this.position = position;
this.image.onload = () => {
this.width = this.image.width / this.frames;
this.height = this.image.height;
};
}
draw(ctx: CanvasRenderingContext2D) {
ctx.drawImage(
// src
this.image,
// crop from x axis
this.spriteIteration * this.width,
// crop from y axis
0,
// crop width
this.image.width / this.frames,
// crop height
this.image.height,
// x position on canvas
this.position.x,
// y position on canvas
this.position.y,
// width on canvas
this.image.width / this.frames,
// height on canvas
this.image.height,
);
}
initSprites(sprites: { up: string; down: string; left: string; right: string }) {
const up = new Image();
up.src = sprites.up;
const down = new Image();
down.src = sprites.down !== "" ? sprites.down : this.defaultSrc;
const left = new Image();
left.src = sprites.left;
const right = new Image();
right.src = sprites.right;
return { up, down, left, right };
}
}
Since you haven't included a runnable snippet for me to work with, here's a very generic example that shows how you can use CanvasRenderingContext2D's transformation methods to change the viewport of a scene.
In draw, you'll see:
Reset the transform to use the center of the <canvas> as (0, 0) using setTransform
To draw from the perspective of a player:
translate the context by -x and -y multiplied by the zoom factor
scale the canvas with the zoom factor
// Initialize
const cvs = document.createElement("canvas");
cvs.width = 200;
cvs.height = 200;
const ctx = cvs.getContext("2d");
// State
const players = [
[-20, -30, "green"],
[50, 70, "blue"]
];
let viewport = {
x: 0,
y: 0,
zoom: 1
};
// Drawing methods
const clear = () => {
ctx.fillStyle = "white";
ctx.fillRect(-cvs.width / 2, -cvs.height / 2, cvs.width, cvs.height);
};
const drawMap = () => {
const w = cvs.width - 25;
const h = cvs.height - 25;
ctx.fillStyle = "#efefef";
ctx.fillRect(
-w / 2,
-h / 2,
w,
h
);
}
const drawPlayers = () => {
for (const [x, y, color] of players) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, 10, Math.PI * 2, 0);
ctx.closePath();
ctx.fill();
}
}
const drawCenter = () => {
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.fillRect(-1, -15, 2, 30);
ctx.fillRect(-15, -1, 30, 2);
}
const draw = () => {
// Zoom out and set (0, 0) to center of canvas
ctx.setTransform(1, 0, 0, 1, cvs.width / 2, cvs.height / 2);
clear();
const { zoom, x, y } = viewport;
ctx.translate(-x * zoom, -y * zoom);
ctx.scale(zoom, zoom);
drawMap();
drawPlayers();
// For debugging: mark the center of the canvas
ctx.setTransform(1, 0, 0, 1, cvs.width / 2, cvs.height / 2);
drawCenter();
}
draw();
document.body.appendChild(cvs);
// UI
const updateViewport = () => {
const centerValue = document.querySelector("input[name=center]:checked").value;
const [x, y] = players[centerValue] || [0, 0];
const zoom = document.querySelector("input[type=range]").valueAsNumber;
viewport = { x: x, y: y, zoom };
draw();
};
document.querySelectorAll("input").forEach(el => {
el.addEventListener("input", updateViewport);
});
body { display: flex; }
canvas { border: 1px solid red; position: relative; }
fieldset { margin-bottom: 1rem; }
<div>
<fieldset>
<legend>View center:</legend>
<label>
<input type="radio" value="-1" name="center" checked> Map
</label>
<label>
<input type="radio" value="0" name="center"> Green player
</label>
<label>
<input type="radio" value="1" name="center"> Blue player
</label>
</fieldset>
<fieldset>
<legend>Zoom</legend>
<input type="range" min="0.1" max ="3" step="0.1" value="1">
</fieldset>
</div>

How do i move the "interaction" with the "blob" in this animated blob?

I downloaded this javascript blob graphic, I've added it to my page, and moved it to the right using CSS, But when I move the mouse to interact with the blob, it still thinks the blob is in the middle of the screen. How do I move the interaction to the right with the blob?
let canvas, ctx;
let render, init;
let blob;
class Blob {
constructor() {
this.points = [];
}
init() {
for (let i = 0; i < this.numPoints; i++) {
let point = new Point(this.divisional * (i + 1), this);
// point.acceleration = -1 + Math.random() * 2;
this.push(point);
}
}
render() {
let canvas = this.canvas;
let ctx = this.ctx;
let position = this.position;
let pointsArray = this.points;
let radius = this.radius;
let points = this.numPoints;
let divisional = this.divisional;
let center = this.center;
ctx.clearRect(0, 0, canvas.width, canvas.height);
pointsArray[0].solveWith(pointsArray[points - 1], pointsArray[1]);
let p0 = pointsArray[points - 1].position;
let p1 = pointsArray[0].position;
let _p2 = p1;
ctx.beginPath();
ctx.moveTo(center.x, center.y);
ctx.moveTo((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
for (let i = 1; i < points; i++) {
pointsArray[i].solveWith(pointsArray[i - 1], pointsArray[i + 1] || pointsArray[0]);
let p2 = pointsArray[i].position;
var xc = (p1.x + p2.x) / 2;
var yc = (p1.y + p2.y) / 2;
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc);
// ctx.lineTo(p2.x, p2.y);
ctx.fillStyle = '#000000';
// ctx.fillRect(p1.x-2.5, p1.y-2.5, 5, 5);
p1 = p2;
}
var xc = (p1.x + _p2.x) / 2;
var yc = (p1.y + _p2.y) / 2;
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc);
// ctx.lineTo(_p2.x, _p2.y);
// ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
ctx.strokeStyle = '#000000';
// ctx.stroke();
/*
ctx.fillStyle = '#000000';
if(this.mousePos) {
let angle = Math.atan2(this.mousePos.y, this.mousePos.x) + Math.PI;
ctx.fillRect(center.x + Math.cos(angle) * this.radius, center.y + Math.sin(angle) * this.radius, 5, 5);
}
*/
requestAnimationFrame(this.render.bind(this));
}
push(item) {
if (item instanceof Point) {
this.points.push(item);
}
}
set color(value) {
this._color = value;
}
get color() {
return this._color || '#000000';
}
set canvas(value) {
if (value instanceof HTMLElement && value.tagName.toLowerCase() === 'canvas') {
this._canvas = canvas;
this.ctx = this._canvas.getContext('2d');
}
}
get canvas() {
return this._canvas;
}
set numPoints(value) {
if (value > 2) {
this._points = value;
}
}
get numPoints() {
return this._points || 32;
}
set radius(value) {
if (value > 0) {
this._radius = value;
}
}
get radius() {
return this._radius || 150;
}
set position(value) {
if (typeof value == 'object' && value.x && value.y) {
this._position = value;
}
}
get position() {
return this._position || { x: 0.5, y: 0.5 };
}
get divisional() {
return Math.PI * 2 / this.numPoints;
}
get center() {
return { x: this.canvas.width * this.position.x, y: this.canvas.height * this.position.y };
}
set running(value) {
this._running = value === true;
}
get running() {
return this.running !== false;
}}
class Point {
constructor(azimuth, parent) {
this.parent = parent;
this.azimuth = Math.PI - azimuth;
this._components = {
x: Math.cos(this.azimuth),
y: Math.sin(this.azimuth) };
this.acceleration = -0.3 + Math.random() * 0.6;
}
solveWith(leftPoint, rightPoint) {
this.acceleration = (-0.3 * this.radialEffect + (leftPoint.radialEffect - this.radialEffect) + (rightPoint.radialEffect - this.radialEffect)) * this.elasticity - this.speed * this.friction;
}
set acceleration(value) {
if (typeof value == 'number') {
this._acceleration = value;
this.speed += this._acceleration * 2;
}
}
get acceleration() {
return this._acceleration || 0;
}
set speed(value) {
if (typeof value == 'number') {
this._speed = value;
this.radialEffect += this._speed * 5;
}
}
get speed() {
return this._speed || 0;
}
set radialEffect(value) {
if (typeof value == 'number') {
this._radialEffect = value;
}
}
get radialEffect() {
return this._radialEffect || 0;
}
get position() {
return {
x: this.parent.center.x + this.components.x * (this.parent.radius + this.radialEffect),
y: this.parent.center.y + this.components.y * (this.parent.radius + this.radialEffect) };
}
get components() {
return this._components;
}
set elasticity(value) {
if (typeof value === 'number') {
this._elasticity = value;
}
}
get elasticity() {
return this._elasticity || 0.001;
}
set friction(value) {
if (typeof value === 'number') {
this._friction = value;
}
}
get friction() {
return this._friction || 0.0085;
}}
blob = new Blob();
init = function () {
canvas = document.getElementById('canvas');
canvas.setAttribute('touch-action', 'none');
let resize = function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
window.addEventListener('resize', resize);
resize();
let oldMousePoint = { x: 0, y: 0 };
let hover = false;
let mouseMove = function (e) {
let pos = blob.center;
let diff = { x: e.clientX - pos.x, y: e.clientY - pos.y };
let dist = Math.sqrt(diff.x * diff.x + diff.y * diff.y);
let angle = null;
blob.mousePos = { x: pos.x - e.clientX, y: pos.y - e.clientY };
if (dist < blob.radius && hover === false) {
let vector = { x: e.clientX - pos.x, y: e.clientY - pos.y };
angle = Math.atan2(vector.y, vector.x);
hover = true;
// blob.color = '#77FF00';
} else if (dist > blob.radius && hover === true) {
let vector = { x: e.clientX - pos.x, y: e.clientY - pos.y };
angle = Math.atan2(vector.y, vector.x);
hover = false;
blob.color = null;
}
if (typeof angle == 'number') {
let nearestPoint = null;
let distanceFromPoint = 100;
blob.points.forEach(point => {
if (Math.abs(angle - point.azimuth) < distanceFromPoint) {
// console.log(point.azimuth, angle, distanceFromPoint);
nearestPoint = point;
distanceFromPoint = Math.abs(angle - point.azimuth);
}
});
if (nearestPoint) {
let strength = { x: oldMousePoint.x - e.clientX, y: oldMousePoint.y - e.clientY };
strength = Math.sqrt(strength.x * strength.x + strength.y * strength.y) * 10;
if (strength > 100) strength = 100;
nearestPoint.acceleration = strength / 100 * (hover ? -1 : 1);
}
}
oldMousePoint.x = e.clientX;
oldMousePoint.y = e.clientY;
};
// window.addEventListener('mousemove', mouseMove);
window.addEventListener('pointermove', mouseMove);
blob.canvas = canvas;
blob.init();
blob.render();
};
window.addEventListener('DOMContentLoaded', () => {
init();
});
canvas {
position: absolute;
touch-action: none;
}
#canvarse {
padding-left: 800px;
}
<div id="canvarse">
<canvas id="canvas"></canvas>
<!-- partial:index.partial.html -->
<script src="https://code.jquery.com/pep/0.4.3/pep.js"></script>
<!-- partial -->
<script src="js/script.js"></script>
</div>
If you run the code snippet, you can see that the blob still thinks its in the middle of the canvas when it's actually where its supposed to be on the right.
I'm struggling to see how I can move the interaction across with the blob?
Anyone help?
seems like it was pretty easy you just have to play with the position function in your JavaScript, First remove your CSS completely because it is unnecessary and find this particular line on your JavaScript and change the X axis position if you want to move the blob horizontally and Y axis if you want vertical movement
get position() {
return this._position || { x: 0.8, y: 0.5 };
}
by default it was x:0.5 which means center so I changed it to 0.8 slight movement towards right side. hope this helps

Camera movement in a 2d game

I really wanna know how to make 2d camera movement in html and javascript, so i wrote this code which is just a character and a block so i can test if the camera movement is working. This is my code:
var c = document.getElementById("screen");
var ctx = c.getContext("2d");
var rightPressed;
var leftPressed;
var x = (c.width - 75) / 2;
var speed = 10;
function block() {
ctx.fillStyle = "#fff";
ctx.fillRect(390, 225, 80, 25);
}
function character() {
ctx.fillStyle = "#00f"
ctx.fillRect(x, 250, 50, 50);
}
function keyUpHandler(event) {
if (event.keyCode == 37) {
leftPressed = false;
} else if (event.keyCode == 39) {
rightPressed = false;
}
}
function keyDownHandler(event) {
if (event.keyCode == 37) {
leftPressed = true;
} else if (event.keyCode == 39) {
rightPressed = true;
}
}
document.addEventListener("keyup", keyUpHandler, false);
document.addEventListener("keydown", keyDownHandler, false);
function draw() {
ctx.clearRect(0, 0, c.width, c.height);
block();
character();
if (rightPressed && (x + 50) < c.width) {
x += speed;
} else if (leftPressed && x > 0) {
x -= speed;
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
#screen {
display: block;
margin: auto;
background-color: #0f0;
border: 2px solid black;
}
#headline {
text-align: center;
}
<h1 id="headline">2d Camera movement</h1>
<canvas id="screen" width="500" height="300"></canvas>
Is there anyone who knows how to do this then please tell me, also it would be nice if you could put it into this code :)
I created two classes, a Game and a Ball. The balls will move when a player presses the left or right key.
The balls know their own position and speed, the game does not care.
Just tell the ball to update according to the game's current state.
I adapted a good portion of code from here:
"Quick Tip: How to Make a Game Loop in JavaScript"
I added ES6 classes, vector support and dynamic game element support.
Demo
const canvasTxt = window.canvasTxt.default;
const KeyCodeMap = {
37: 'left',
38: 'up',
39: 'right',
40: 'down',
65: 'left',
68: 'right',
83: 'down',
87: 'up'
};
Object.assign(canvasTxt, {
align: 'left',
vAlign: 'top',
font: 'monospace',
fontSize: 24
});
const main = () => {
Game.DEBUG = true; // Enable global DEBUG mode.
const ctx = document.getElementById('screen').getContext('2d');
const game = new Game(ctx);
game.gameElements.push(...[
new Ball({
speed: new Victor(10, 0),
position: new Victor(game.getWidth() / 2, game.getHeight() / 2),
color: 'red',
size: 20
}),
new Ball({
speed: new Victor(20, 0),
position: new Victor(game.getWidth() / 2, game.getHeight() / 4),
color: 'green',
size: 30
}),
new Ball({
speed: new Victor(20, 50),
position: new Victor(100, 140),
color: 'cyan',
size: 25
}),
]);
game.redraw();
};
class Ball {
constructor(options) {
let opts = Object.assign({}, Ball.defaultOptions, options);
this.position = opts.position;
this.speed = opts.speed;
this.size = opts.size;
this.color = opts.color;
}
update(container, state) {
if (state.pressedKeys.left && !state.pressedKeys.right) {
this.position.subtract(this.speed);
}
if (state.pressedKeys.right && !state.pressedKeys.left) {
this.position.add(this.speed);
}
this.checkBounds(container); // Make sure object is in-bounds...
}
checkBounds(container) {
if (this.position.x > container.width) {
this.position.x = 0;
}
if (this.position.x < 0) {
this.position.x = container.width;
}
if (this.position.y > container.height) {
this.position.y = 0;
}
if (this.position.y < 0) {
this.position.y = container.height;
}
}
draw(ctx) {
ctx.save();
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.size / 2, 0, 2 * Math.PI, false);
ctx.fillStyle = this.color;
ctx.fill();
//ctx.lineWidth = 1;
//ctx.strokeStyle = '#000000';
ctx.stroke();
ctx.restore();
}
}
Ball.defaultOptions = {
position: new Victor(0, 0),
speed: new Victor(0, 0),
size: 1,
color: '#FFFFFF'
};
class Game {
constructor(ctx) {
this.ctx = ctx;
this.gameElements = [];
this.resize();
this.state = {
pressedKeys: {} // Capture key pressed
};
this.__lastRender = 0;
this.__animationId = null;
window.onresize = (e) => this.resize(e);
document.addEventListener('click', (e) => this.clickHandler(e), false);
document.addEventListener('keyup', (e) => this.keyUpHandler(e), false);
document.addEventListener('keydown', (e) => this.keyDownHandler(e), false);
}
clickHandler(e) {
this.isRunning() ? this.pause() : this.run(); // Pause, if running
}
keyUpHandler(event) {
let key = KeyCodeMap[event.keyCode];
delete this.state.pressedKeys[key];
}
keyDownHandler(event) {
let key = KeyCodeMap[event.keyCode];
if (key) this.state.pressedKeys[key] = true;
}
getWidth() {
return this.ctx.canvas.width;
}
getHeight() {
return this.ctx.canvas.height;
}
resize(event) {
this.ctx.canvas.width = window.innerWidth * 2
this.ctx.canvas.height = window.innerHeight * 2
}
update(progress) {
this.gameElements.forEach(gameElement => gameElement.update(this.ctx.canvas, this.state));
}
redraw() {
this.ctx.clearRect(0, 0, this.getWidth(), this.getHeight());
this.gameElements.forEach(gameElement => gameElement.draw(this.ctx));
if (Game.DEBUG) {
this.__renderDebugText(this.ctx);
}
}
/* #private */ __renderDebugText(ctx) {
ctx.save();
let text = 'DEBUG OUTPUT:\n\n' + JSON.stringify(Object.assign({
running: this.isRunning()
}, this.state), null, 2);
let offset = { x: 16, y: 16 };
let bounds = { width: 280, height: 320 };
ctx.fillStyle = 'rgba(16, 16, 16, 0.8)';
ctx.fillRect(offset.x, offset.y, bounds.width, bounds.height);
ctx.lineWidth = 1;
ctx.strokeStyle = '#333';
ctx.strokeRect(offset.x, offset.y, bounds.width, bounds.height);
ctx.fillStyle = '#FFF';
canvasTxt.drawText(ctx, text, offset.x + 4, offset.y + 4, bounds.width, bounds.height);
ctx.restore();
}
loop(timestamp) {
this.update(timestamp - this.__lastRender);
this.redraw();
this.__lastRender = timestamp;
this.__animationId = window.requestAnimationFrame((ts) => this.loop(ts));
}
isRunning() {
return this.__animationId != null;
}
run() {
this.__animationId = window.requestAnimationFrame((ts) => this.loop(ts));
}
pause() {
cancelAnimationFrame(this.__animationId);
this.__animationId = null;
this.redraw();
}
}
Game.DEBUG = false; // Default is off
main();
html,
body {
margin: 0;
padding: 0;
}
canvas {
background: #000;
height: 100%;
width: 100%;
display: block
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/victor/1.1.0/victor.min.js"></script>
<script src="https://unpkg.com/canvas-txt#2.0.6/build/index.js"></script>
<canvas id="screen" />

How to change the image to another in canvas for some time and then change back again

I am new to game development and I am developing a simple game where I can get a point when I hit the monster by car. So now I want to change the color of the car (if red then blue and vice versa ) when I hit the monster for a while (5 seconds) and then switch back to the original color. I tried with
var myObj = {
imgs: ["http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_2048/v1492045665/road_dwsmux.png", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_32/v1491958999/car_p1k2hw.png","http://res.cloudinary.com/dfhppjli0/image/upload/v1491958478/monster_rsm0po.png","http://res.cloudinary.com/dfhppjli0/image/upload/v1492579967/car_03_ilt08o.png"],
currentImg: 0,
draw: function(){
ctx.drawImage(this.imgs[this.currentImg], this.x, this.y)
},
changeImg: function(index){
this.currentImg = index
}
}
and then draw with
images.__createImage("background","img[0]");
But it did not work.
My working :pen
Any help is appreciated
You were already loading images you need for changing appearance forth and back so that was ok, you don't need to keep url array inside your hero (car) object and loading it again when it is needed.
I have added changeImage property to your heroDefault (car) so I can mark the object that it needs to check if the image need to be changed back, and I also added changeImageTime to heroDefault where I store time after the change has to be done, so when car hits a monster I check changeImage to true and I set changeImageTime to Date.now() + 5000 which means I store current time plus 5 seconds (5000ms):
if (monster.isTouching(this)) {
monster.reset();
monstersCaught += 1;
this.changeImage = true;
this.changeImageTime = Date.now() + 5000; //5 sec from now.
this.image = (this.image === images.hero)? images.hero_other : images.hero;
}
Then inside your heroDefault update function I check if there is need to change image back and if the time has passed, if so I change image back and mark the object so it won't compare time no more by setting changeImage to false.
if(this.changeImage){
if(Date.now() > this.changeImageTime){
this.changeImage = false;
this.image = (this.image === images.hero)? images.hero_other : images.hero;
}
}
// Create the canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 2048;
canvas.height = 1024;
document.body.appendChild(canvas);
var monstersCaught = 0;
var lastFrameTime;
var frameTime = 0; // in seconds used to control hero speed
// The main game loop
function main(time) {
if (lastFrameTime !== undefined) {
frameTime = (time - lastFrameTime) / 1000; // in seconds
}
lastFrameTime = time
updateObjects();
render();
requestAnimationFrame(main);
};
// this is called when all the images have loaded
function start() {
monstersCaught = 0;
resetObjs();
requestAnimationFrame(main);
}
function displayStatus(message) {
ctx.setTransform(1, 0, 0, 1, 0, 0); // set default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.font = "24px Helvetica";
ctx.textAlign = "center";
ctx.textBaseline = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
// reset objects
function resetObjs() {
monsters.array.forEach(monster => monster.reset());
heros.array.forEach(hero => hero.reset());
}
// Update game objects
function updateObjects(modifier) {
monsters.array.forEach(monster => monster.update());
heros.array[0].update('random');
heros.array[1].update('random');
heros.array[2].update('random');
heros.array[3].update('random');
heros.array[4].update('random');
heros.array[5].update('random');
heros.array[6].update('random');
heros.array[7].update('random');
}
function drawObjects(modifier) {
monsters.array.forEach(monster => monster.draw());
heros.array.forEach(hero => hero.draw());
}
// Draw everything
function render() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // set default transform
ctx.drawImage(images.background, 0, 0);
drawObjects();
// Score
ctx.setTransform(1, 0, 0, 1, 0, 0); // set default transform
ctx.fillStyle = "rgb(250, 250, 250)";
ctx.font = "24px Helvetica";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillText("points : " + monstersCaught, 32, 32);
}
// hold all the images in one object.
const images = { // double underscore __ is to prevent adding images that replace these functions
__status: {
count: 0,
ready: false,
error: false,
},
__onready: null,
__createImage(name, src) {
var image = new Image();
image.src = src;
images.__status.count += 1;
image.onerror = function() {
images.__status.error = true;
displayStatus("Error loading image : '" + name + "'");
}
image.onload = function() {
images.__status.count -= 1;
if (images.__status.count === 0) {
images.__status.ready = true;
images.__onready();
}
if (!images.__status.error) {
displayStatus("Images remaing : " + images.__status.count);
}
}
images[name] = image;
return image;
}
}
// Handle all key input
const keys = { // key input object
ArrowLeft: false, // only add key names you want to listen to
ArrowRight: false,
ArrowDown: false,
ArrowUp: false,
keyEvent(event) {
if (keys[event.code] !== undefined) { // are we interested in this key
keys[event.code] = event.type === "keydown";
event.preventDefault();
}
}
}
// default setting for objects
const objectDefault = {
x: 0,
y: 0,
dir: 0, // the image rotation
isTouching(obj) { // returns true if object is touching box x,y,w,h
return !(this.x > obj.x + obj.w || this.y > obj.y + obj.h || this.x + this.w < obj.x || this.y + this.h < obj.y);
},
draw() {
ctx.setTransform(1, 0, 0, 1, this.x + this.w / 2, this.y + this.h / 2);
ctx.rotate(this.dir);
ctx.drawImage(this.image, -this.image.width / 2, -this.image.height / 2);
},
reset() {},
update() {},
}
// default setting for monster object
const monsterDefault = {
w: 32, // width
h: 32, // height
reset() {
this.x = this.w + (Math.random() * (canvas.width - this.w * 2));
this.y = this.h + (Math.random() * (canvas.height - this.h * 2));
},
}
// default settings for hero
const heroDefault = {
w: 32, // width
h: 32, // height
speed: 256,
spawnPos: 1.5,
distanceTraveledInOneDirection: 0,
autoDirection: 'right',
changeImage: false, // If true, will check if changeImageTime passed and image needs to be changed
changeImageTime: 0, // Time after image will be changed
reset() {
this.x = canvas.width / this.spawnPos;
this.y = canvas.height / this.spawnPos;
this.autoDirection = 'right';
this.distanceTraveledInOneDirection = 0;
},
auto() {
this.y -= this.speed * frameTime;
},
update(dir) {
this.distanceTraveledInOneDirection += this.speed * frameTime;
if (dir === 'random') {
dir = this.autoDirection; //set new direction
if (this.distanceTraveledInOneDirection > 300) { //make this random or use a timestamp instead of distance if you want
this.autoDirection = ['up', 'down', 'right', 'left','right','down','left','up'][Math.floor(Math.random() * 4)]; //we have traveled in one direction long enough, time to roll dice and change direction.
dir = this.autoDirection; //set new direction
this.distanceTraveledInOneDirection = 0;
}
}
if (!dir) {
if (keys.ArrowUp) dir = 'up'
if (keys.ArrowDown) dir = 'down'
if (keys.ArrowLeft) dir = 'left'
if (keys.ArrowRight) dir = 'right'
}
if (dir === 'up') { // Player holding up
this.y -= this.speed * frameTime;
this.dir = Math.PI * 0; // set direction
}
if (dir === 'down') { // Player holding down
this.y += this.speed * frameTime;
this.dir = Math.PI * 1; // set direction
}
if (dir === 'left') { // Player holding left
this.x -= this.speed * frameTime;
this.dir = Math.PI * 1.5; // set direction
}
if (dir === 'right') { // Player holding right
this.x += this.speed * frameTime;
this.dir = Math.PI * 0.5; // set direction
}
if (Math.sign(this.speed) === -1) { // filp directio of second car
this.dir += Math.PI; // set direction
}
monsters.array.forEach(monster => {
if (monster.isTouching(this)) {
monster.reset();
monstersCaught += 1;
this.changeImage = true;
this.changeImageTime = Date.now() + 5000; //5 sec from now.
this.image = (this.image === images.hero)? images.hero_other : images.hero;
}
});
if (this.x >= canvas.width || this.y >= canvas.height || this.y < 0 || this.x < 0) {
this.reset();
}
if(this.changeImage){
if(Date.now() > this.changeImageTime){
this.changeImage = false;
this.image = (this.image === images.hero)? images.hero_other : images.hero;
}
}
}
}
// objects to hold monsters and heros
const monsters = { // dont call a monster "array"
array: [], // copy of monsters as array
};
const heros = { // dont call a monster "array"
array: [], // copy of heros as array
};
// add monster
function createMonster(name, settings = {}) {
monsters[name] = Object.assign({}, objectDefault, monsterDefault, settings, {
name
});
//monsters[name] = {...objectDefault, ...monsterDefault, ...settings, name};
monsters[name].reset();
monsters.array.push(monsters[name]);
return monsters[name];
}
// add hero to heros object
function createHero(name, settings) {
heros[name] = Object.assign({}, objectDefault, heroDefault, settings, {
name
});
//heros[name] = {...objectDefault, ...heroDefault, ...settings, name};
heros[name].reset();
heros.array.push(heros[name]);
return heros[name];
}
// set function to call when all images have loaded
images.__onready = start;
// load all the images
images.__createImage("background", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_2048/v1492045665/road_dwsmux.png");
images.__createImage("hero", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_32/v1491958999/car_p1k2hw.png");
images.__createImage("monster", "http://res.cloudinary.com/dfhppjli0/image/upload/v1491958478/monster_rsm0po.png");
images.__createImage("hero_other", "http://res.cloudinary.com/dfhppjli0/image/upload/v1492579967/car_03_ilt08o.png");
// create all objects
createHero("hero", {
image: images.hero,
spawnPos: 1.5
});
createHero("hero3", {
image: images.hero,
spawnPos: 2
});
createHero("hero9", {
image: images.hero_other,
spawnPos: 2.6
});
createHero("hero12", {
image: images.hero,
spawnPos: 1.75
});
createHero("hero15", {
image: images.hero,
spawnPos: 1.8
});
createHero("hero18", {
image: images.hero,
spawnPos: 2.4
});
createHero("hero21", {
image: images.hero_other,
spawnPos: 2.8
});
createHero("hero24", {
image: images.hero,
spawnPos: 1.9
});
createMonster("monster", {
image: images.monster,
});
createMonster("monster3", {
image: images.monster
});
createMonster("monster9", {
image: images.monster
});
createMonster("monster12", {
image: images.monster
});
createMonster("monster15", {
image: images.monster
});
createMonster("monster18", {
image: images.monster
});
// add key listeners
document.addEventListener("keydown", keys.keyEvent);
document.addEventListener("keyup", keys.keyEvent);
canvas.addEventListener('click', function(event) {
createMonster("monster24", {
image: images.monster
});
}, false);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Simple Canvas Game</title>
</head>
<body>
<script src="game.js"></script>
</body>
</html>

Canvas Quadratic Curve Changing Middle Point

I want change the middle point of canvas quadratic curve which is far away from the line while dragging middle point. On dragging i want to stick with the link as now its increasing the distance of the middle point.http://jsfiddle.net/ashishbhatt/yqgak7eq/1/
http://s25.postimg.org/hegxhlgrj/123123.jpg
Please check the above reference image for the same.
Code :
(function() {
var canvas, ctx, code, point, style, drag = null, dPoint;
// define initial points
function Init(quadratic) {
point = {
p1: { x:0, y:100 },
p2: { x:200, y:100 }
};
if (quadratic) {
point.cp1 = { x: 100, y: 50 };
}
else {
point.cp1 = { x: 100, y: 100 };
point.cp2 = { x: 100, y: 100 };
}
// default styles
style = {
curve: { width: 2, color: "#333" },
cpline: { width: 1, color: "#C00" },
point: { radius: 6, width: 1, color: "#900", fill: "rgba(200,200,200,0.5)", arc1: 0, arc2: 2 * Math.PI }
}
// line style defaults
ctx.lineCap = "round";
ctx.lineJoin = "round";
// event handlers
canvas.onmousedown = DragStart;
canvas.onmousemove = Dragging;
canvas.onmouseup = canvas.onmouseout = DragEnd;
DrawCanvas();
}
// draw canvas
function DrawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// control lines
ctx.lineWidth = style.cpline.width;
ctx.strokeStyle = style.cpline.color;
ctx.beginPath();
//ctx.moveTo(point.p1.x, point.p1.y);
//ctx.lineTo(point.cp1.x, point.cp1.y);
if (point.cp2) {
//ctx.moveTo(point.p2.x, point.p2.y);
//ctx.lineTo(point.cp2.x, point.cp2.y);
}
else {
//ctx.lineTo(point.p2.x, point.p2.y);
}
ctx.stroke();
// curve
ctx.lineWidth = style.curve.width;
ctx.strokeStyle = style.curve.color;
ctx.beginPath();
ctx.moveTo(point.p1.x, point.p1.y);
if (point.cp2) {
//ctx.bezierCurveTo(point.cp1.x, point.cp1.y, point.cp2.x, point.cp2.y, point.p2.x, point.p2.y);
}
else {
ctx.quadraticCurveTo(point.cp1.x, point.cp1.y, point.p2.x, point.p2.y);
}
ctx.stroke();
// control points
for (var p in point) {
ctx.lineWidth = style.point.width;
ctx.strokeStyle = style.point.color;
ctx.fillStyle = style.point.fill;
ctx.beginPath();
ctx.arc(point[p].x, point[p].y, style.point.radius, style.point.arc1, style.point.arc2, true);
ctx.fill();
ctx.stroke();
}
ShowCode();
}
// show canvas code
function ShowCode() {
var myCounter = 0;
if(point.cp1.y < 100){
myCounter = point.cp1.y-100;
}else if(point.cp1.y > 100){
myCounter = point.cp1.y-100;
}
if (code) {
code.firstChild.nodeValue = myCounter
}
}
// start dragging
function DragStart(e) {
e = MousePos(e);
var dx, dy;
for (var p in point) {
//console.log(p)
//dx = point[p].x - e.x;
if(p == 'cp1'){
dy = point[p].y - e.y;
if ((dy * dy) < style.point.radius * style.point.radius) {
drag = p;
dPoint = e;
canvas.style.cursor = "move";
return;
}
}
}
}
// dragging
function Dragging(e) {
if (drag) {
e = MousePos(e);
//point[drag].x += e.x - dPoint.x;
point[drag].y += e.y - dPoint.y;
dPoint = e;
DrawCanvas();
}
}
// end dragging
function DragEnd(e) {
drag = null;
canvas.style.cursor = "default";
DrawCanvas();
}
// event parser
function MousePos(event) {
event = (event ? event : window.event);
return {
//x: event.pageX - canvas.offsetLeft,
y: event.pageY - canvas.offsetTop
}
}
// start
canvas = document.getElementById("canvasSettings");
code = document.getElementById("code");
if (canvas.getContext) {
ctx = canvas.getContext("2d");
Init(canvas.className == "quadratic");
}
})();

Categories

Resources