I am making a 2d multiplayer sword game using Node JS and Socket io.
I have the backend working but in the front end I cant get the sword to be attached on the player.
I want it to be like
Sword is attached to player
But the problem is when i move mouse the sword changes and goes outside of the player.
Sword is not attached to player. So If you are wondering how my game works its basically the Smiley(player) always is pointing at the mouse (yes it may be upside down) and you can move player with arrow keys and use the sword to kill other players.
Here is my code that draws the player and sword.
function drawImageLookat(img, x, y, lookx, looky){
ctx.setTransform(1, 0, 0, 1, x, y); // set scale and origin
ctx.rotate(Math.atan2(looky - y, lookx - x)); // set angle
ctx.drawImage(img,-img.width / 2, -img.height / 2); // draw image
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default not needed if you use setTransform for other rendering operations
}
const drawPlayer = (player) => {
const img = new Image()
const img2 = new Image()
img.src = "./assets/images/player.png"
img2.src = "./assets/images/sword.png"
img.onload = () => {
drawImageLookat(img, player.x, player.y, player.lookx, player.looky)
drawImageLookat(img2, player.x+50, player.y, player.lookx, player.looky)
}
};
socket.on('state', (gameState) => {
ctx.clearRect(0,0, 1200, 700)
for (let player in gameState.players) {
drawPlayer(gameState.players[player])
}
})
Basically this "state" is emitted 60 times a second with the gamestate. In the gamestate we have players object which contains stuff like LookX(Mouse X position),LookY(Mouse Y position), and X and Y of player. This code below is in server side.
const gameState = {
players: {}
}
io.on('connection', (socket) => {
socket.on('newPlayer', () => {
gameState.players[socket.id] = {
x: 250,
y: 250,
lookx: 0,
looky: 0,
width: 25,
height: 25
}
})
//More stuff below here which is skipped
setInterval(() => {
io.sockets.emit('state', gameState);
}, 1000 / 60);
Also in client side, here is how I am getting mouse position
//mouse stuf
var pos = 0;
document.addEventListener("mousemove", (e) => {
pos = getMousePos(e)
})
function getMousePos(evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
//send 2 server
setInterval(() => {
socket.emit('mouseAngle', pos);
socket.emit('playerMovement', playerMovement);
}, 1000 / 60);
So basically All I want is that when I spin the mouse, the Player should always be pointing at the mouse and the Sword is attached to the side of the Player and spins with it....
Here is my full code if you need it...
//SERVER.JS
var app = require('express')();
var express = require('express');
var path = require('path');
var http = require('http').createServer(app);
var io = require('socket.io')(http)
var htmlPath = path.join(__dirname, 'client');
app.use(express.static(htmlPath));
const gameState = {
players: {}
}
io.on('connection', (socket) => {
socket.on('newPlayer', () => {
gameState.players[socket.id] = {
x: 250,
y: 250,
lookx: 0,
looky: 0,
width: 25,
height: 25
}
})
socket.on('playerMovement', (playerMovement) => {
const player = gameState.players[socket.id]
const canvasWidth = 1200
const canvasHeight = 700
if (playerMovement.left && player.x > 0) {
player.x -= 4
}
if (playerMovement.right && player.x < canvasWidth - player.width) {
player.x += 4
}
if (playerMovement.up && player.y > 0) {
player.y -= 4
}
if (playerMovement.down && player.y < canvasHeight - player.height) {
player.y += 4
}
})
socket.on("mouseAngle", (pos) => {
const player = gameState.players[socket.id]
player.lookx = pos.x;
player.looky = pos.y;
})
socket.on("disconnect", () => {
delete gameState.players[socket.id]
})
})
setInterval(() => {
io.sockets.emit('state', gameState);
}, 1000 / 60);
http.listen(3000, () => {
console.log('listening on *:3000');
});
//Client/Index.html
var app = require('express')();
var express = require('express');
var path = require('path');
var http = require('http').createServer(app);
var io = require('socket.io')(http)
var htmlPath = path.join(__dirname, 'client');
app.use(express.static(htmlPath));
const gameState = {
players: {}
}
io.on('connection', (socket) => {
socket.on('newPlayer', () => {
gameState.players[socket.id] = {
x: 250,
y: 250,
lookx: 0,
looky: 0,
width: 25,
height: 25
}
})
socket.on('playerMovement', (playerMovement) => {
const player = gameState.players[socket.id]
const canvasWidth = 1200
const canvasHeight = 700
if (playerMovement.left && player.x > 0) {
player.x -= 4
}
if (playerMovement.right && player.x < canvasWidth - player.width) {
player.x += 4
}
if (playerMovement.up && player.y > 0) {
player.y -= 4
}
if (playerMovement.down && player.y < canvasHeight - player.height) {
player.y += 4
}
})
socket.on("mouseAngle", (pos) => {
const player = gameState.players[socket.id]
player.lookx = pos.x;
player.looky = pos.y;
})
socket.on("disconnect", () => {
delete gameState.players[socket.id]
})
})
setInterval(() => {
io.sockets.emit('state', gameState);
}, 1000 / 60);
http.listen(3000, () => {
console.log('listening on *:3000');
});
//Client/Assets/Js/script.js
var socket = io();
var canvas = document.getElementById("game");
const ctx = canvas.getContext("2d")
document.getElementById("game").width =1200;
document.getElementById("game").height = 700;
socket.emit('newPlayer');
//mouse stuff
function drawImageLookat(img, x, y, lookx, looky){
ctx.setTransform(1, 0, 0, 1, x, y); // set scale and origin
ctx.rotate(Math.atan2(looky - y, lookx - x)); // set angle
ctx.drawImage(img,-img.width / 2, -img.height / 2); // draw image
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default not needed if you use setTransform for other rendering operations
}
const drawPlayer = (player) => {
const img = new Image()
const img2 = new Image()
img.src = "./assets/images/player.png"
img2.src = "./assets/images/sword.png"
img.onload = () => {
drawImageLookat(img, player.x, player.y, player.lookx, player.looky)
drawImageLookat(img2, player.x+50, player.y, player.lookx, player.looky)
}
};
socket.on('state', (gameState) => {
ctx.clearRect(0,0, 1200, 700)
for (let player in gameState.players) {
drawPlayer(gameState.players[player])
}
})
//Client/assets/js/Controller.js
//v1
var canvas = document.getElementById("game")
const playerMovement = {
up: false,
down: false,
left: false,
right: false
};
const keyDownHandler = (e) => {
if (e.keyCode == 39 || e.keyCode == 68) {
playerMovement.right = true;
} else if (e.keyCode == 37 || e.keyCode == 65) {
playerMovement.left = true;
} else if (e.keyCode == 38 || e.keyCode == 87) {
playerMovement.up = true;
} else if (e.keyCode == 40 || e.keyCode == 83) {
playerMovement.down = true;
}
};
const keyUpHandler = (e) => {
if (e.keyCode == 39 || e.keyCode == 68) {
playerMovement.right = false;
} else if (e.keyCode == 37 || e.keyCode == 65) {
playerMovement.left = false;
} else if (e.keyCode == 38 || e.keyCode == 87) {
playerMovement.up = false;
} else if (e.keyCode == 40 || e.keyCode == 83) {
playerMovement.down = false;
}
};
document.addEventListener('keydown', keyDownHandler, false);
document.addEventListener('keyup', keyUpHandler, false);
//mouse stuf
var pos = 0;
document.addEventListener("mousemove", (e) => {
pos = getMousePos(e)
})
function getMousePos(evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
//send 2 server
setInterval(() => {
socket.emit('mouseAngle', pos);
socket.emit('playerMovement', playerMovement);
}, 1000 / 60);
If you could help me thanks!
There are many ways this can be done. The standard method is to use a second transform and multiply it with the current context transform.
2D Matrix multiplication
The 2d API has two functions to enter a matrix to change the current transform.
ctx.setTransform Replaces the current transform with a new one
ctx.transform Multiplies the current transformation with the new one setting he current transform to the resulting matrix.
This lets you attach image elements in a hierarchical transformation structure. (Tree like structure)
ctx.setTransform is used to set the root transform and you use ctx.transform to attach a child that inherits the previous transform as well has getting the secondary transform.
Say you have an image that you transform as you do with
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.rotate(Math.atan2(looky - y, lookx - x));
ctx.drawImage(img,-img.width / 2, -img.height / 2);
The origin is at the center of the image
To attach another image say for example the second image is centered on the bottom right corner of the existing image, and will rotate (scale, translate, skew, mirror, etc) with the existing image.
Just create a second transform that is relative to the current transform (previous image) and use ctx.transform to do the matrix multiplication, then render the second image
ctx.transform(1, 0, 0, 1, img.width / 2, img.height / 2); // bottom right of prev image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
Save and restore the transform.
If you need to get the parent transform you will need to save and restore the 2D API state
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.rotate(Math.atan2(looky - y, lookx - x));
ctx.drawImage(img,-img.width / 2, -img.height / 2);
ctx.save();
// draw second image
ctx.transform(1, 0, 0, 1, img.width / 2, img.height / 2);
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
ctx.restore(); // get the parent transform
// draw 3rd image at top right
ctx.transform(1, 0, 0, 1, img.width / 2, -img.height / 2);
ctx.drawImage(img3, -img3.width / 2, -img3.height / 2);
The is ok for simple rendering but can be slow or inconvenient for more complex rendering.
DOMMatrix
Rather than use the built in transforms you can use DOMMatrix as it provides functions to do all the standard matrix math.
For example the previous 3 images can be done as
const ang = Math.atan2(looky - y, lookx - x);
const ax = Math.cos(ang);
const ay = Math.sin(ang);
const parentMatrix = [ax, ay, -ay, ax, x, y];
const child1Matrix = new DOMMatrix([1, 0, 0, 1, img.width / 2, img.height / 2]);
const child2Matrix = new DOMMatrix([1, 0, 0, 1, img.width / 2, -img.height / 2]);
// draw first image with root transform
ctx.setTransform(...parentMatrix);
ctx.drawImage(img,-img.width / 2, -img.height / 2);
// draw second image at bottom right
const mat1 = new DOMMatrix(...parentMatrix);
matrix.multiplySelf(child1Matrix);
ctx.setTransform(mat1.a, mat1.b, mat1.c, mat1.d, mat1.e, mat1.f);
ctx.drawImage(img1,-img1.width / 2, -img1.height / 2);
// draw third image at top right
const mat2 = new DOMMatrix(...parentMatrix);
matrix.multiplySelf(child2Matrix);
ctx.setTransform(mat2.a, mat2.b, mat2.c, mat2.d, mat2.e, mat2.f);
ctx.drawImage(img2,-img2.width / 2, -img2.height / 2);
Related
When the program runs the mouse is clicked creating a projectile from the center of the screen moving with every frame in the direction it was fired (mouse position on click).
When N+1 projectiles have fired all projectiles on-screen move to the new clicked location instead of continuing their path.
I am can not figure out why the current projectiles change direction when the New projectile's velocity should have no effect on prior projectiles.
index.html
<canvas></canvas>
<script src="./guns.js"></script>
<script src="./indexh.js"></script>
<script src="./runh.js"></script>
runh.html
const projectilesArray = [];
let frameCount = 0;
function animate() {
animationID = requestAnimationFrame(animate);
c.fillStyle = "rgba(0, 0, 0, 1)";
c.fillRect(0, 0, canvas.width, canvas.height);
projectilesArray.forEach((Projectile, pIndex) => {
Projectile.update();
console.log(Projectile)
if (
Projectile.x + Projectile.radius < 0 ||
Projectile.x - Projectile.radius > canvas.width ||
Projectile.y + Projectile.radius < 0 ||
Projectile.y - Projectile.radius > canvas.height
) {
setTimeout(() => {
projectilesArray.splice(pIndex, 1);
}, 0);
}
});
frameCount++;
if (frameCount > 150) {
}
}
var fire = 1;
let fireRate = 1;
const mouse = {
x: 0,
y: 0,
click: true,
};
canvas.addEventListener('mousedown', (event) => {
if (fire % fireRate == 0) {
if (mouse.click == true) {
mouse.x = event.x;
mouse.y = event.y;
const angle = Math.atan2(mouse.y - (canvas.height / 2), mouse.x - (canvas.width / 2));
const fireY = Math.sin(angle);
const fireX = Math.cos(angle);
//score -= 0;
//scoreL.innerHTML = score;
var weapon = new Projectile(cannon);
weapon.velocity.x = fireX * 9;
weapon.velocity.y = fireY * 9;
projectilesArray.push(weapon);
//var gun = object.constructor()
}
}
});
animate();
indexh.js
const canvas = document.querySelector("canvas");
const c = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
class Projectile {
constructor(config) {
this.color = config.color || "rgb(60, 179, 113)";
this.radius = config.radius || 1;
this.speed = config.speed || 5;
this.rounds = config.rounds || 2;
this.x = config.x || canvas.width / 2;
this.y = config.y || canvas.height /2;
this.velocity = config.velocity;
}
draw() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
}
update() {
this.draw();
this.x = this.x + this.velocity.x * this.speed;
this.y = this.y + this.velocity.y * this.speed;
}
}
gums.js
let pistol = {
color : "rgb(255, 0, 0)",
radius : 10,
speed : 1,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
let cannon = {
color : "rgb(0, 0, 255)",
radius : 30,
speed : .5,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
Thanks
The issue is that you have this cannon object used as a configuration object:
let cannon = {
color : "rgb(0, 0, 255)",
radius : 30,
speed : .5,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
And in your Projectile constructor you assign this.velocity = config.velocity; You are assigning this.velocity to be the same object instance for all Projectiles. To fix it, try copying the object, like:
this.velocity = {...config.velocity};
That will make a copy of the object rather than sharing the same object instance.
Also note that you have a bug that you didn't ask about in this line:
projectilesArray.splice(pIndex, 1);
If two timeouts are queued in the same loop, the array will shift when the first timeout fires, and the second timeout will be operating on the wrong index.
I want to animate a rectangle on html canvas. when the user will click the canvas, the rectangle will start it's animation, and go to the clicked position. I used delta x and y to add and subtract pixels from the x and y position of the rectangle.
But the problem with this solution is, I can't find a way to make the rectangle animate in a straight path.
My code:
'use strict'
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const ratio = Math.ceil(window.devicePixelRatio)
let height = window.innerHeight
let width = window.innerWidth
canvas.height = height * ratio
canvas.width = width * ratio
canvas.style.height = `${height}px`
canvas.style.width = `${width}px`
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
let position = {
x: 0,
y: 0,
deltaX: 0,
deltaY: 0,
size: 20
}
let move = {
x: 0,
y: 0,
}
function animate() {
if (position.x === move.x && position.y === move.y) {
cancelAnimationFrame()
}
if (position.x !== move.x) {
ctx.fillRect(position.x, position.y, position.size, position.size)
position.x += position.deltaX
}
if (position.y !== move.y) {
ctx.fillRect(position.x, position.y, position.size, position.size)
position.y += position.deltaY
}
requestAnimationFrame(animate)
}
function moveTo(x, y) {
move.x = x
move.y = y
position.deltaX = position.x > x ? -1 : 1
position.deltaY = position.y > y ? -1 : 1
animate()
}
canvas.addEventListener('click', (event) => {
moveTo(event.clientX, event.clientY)
})
ctx.fillRect(position.x, position.y, position.size, position.size)
<canvas id="canvas">
</canvas>
If you click in the canvas the rectangle will start moving but it'll go in a weird path, I can't find a way to properly go straight at the clicked position.
see demo at Github page
Here is the sample Math from something I did a while ago...
If there is something you don't understand there ask
In your code it was moving "weird" because your delta values where always 1 or -1, no fractions, that limits the way the object can travel, instead we do our calculations using the angle.
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
let player = {
x: 0, y: 0, size: 10,
delta: { x: 0, y: 0 },
move: { x: 0, y: 0 }
}
function animate() {
let a = player.x - player.move.x;
let b = player.y - player.move.y;
if (Math.sqrt( a*a + b*b ) < 2) {
player.delta = { x: 0, y: 0 }
}
player.x += player.delta.x
player.y += player.delta.y
ctx.fillRect(player.x, player.y, player.size, player.size)
requestAnimationFrame(animate)
}
function moveTo(x, y) {
player.move.x = x
player.move.y = y
let angle = Math.atan2(y - player.y, x - player.x)
player.delta.x = Math.cos(angle)
player.delta.y = Math.sin(angle)
}
canvas.addEventListener('click', (event) => {
moveTo(event.clientX, event.clientY)
})
animate()
<canvas id="canvas"></canvas>
Im making simple arena shooter in JS and HTML.
right now I wanted to add shooting mechanic,I thought i would use code from my enemy class with bits of tweaking
here:
//differences between objects
let dirx = mouse.x - player.x,
diry = mouse.y - player.y;
//normalizing
let dist = Math.sqrt(dirx * dirx + diry * diry);
dirx = dirx / dist;
diry = diry / dist;
this.x += dirx * 5;
this.y += diry * 5;
Since my player is moving the player.x& player.y coords in dirX & dirY will change, meaning bullet wont go to original coords.
So I would be pleased for some help.
You could use vectors to determine the trajectory of the bullets.
In the example below, when I fire a bullet:
Initialize the position at the source of the bullet
The speed is determined by:
Subtracting the mouse position from the source
Normalizing the distance
Applying a speed constant
class Bullet {
constructor(source, target, created) {
this.position = source.clone();
this.speed = this.position.clone()
.subtract(Victor.fromObject(target))
.normalize()
.multiply(new Victor(-2, -2));
this.size = 3;
this.timeLeft = created + 500;
}
update() {
this.position = this.position.add(this.speed);
}
draw(ctx) {
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.size / 2, 0, 2 * Math.PI);
ctx.fill();
}
}
Note: I also added a timeLeft variable to remove the bullet from the game after a set period of time e.g. 500ms.
Demo
Here is an example using the vector logic explained above.
Controls
W (Up) - Move up
A (Left) - Move left
S (Down) - Move down
D (Right) - Move right
Mouse 1 - Hold to fire
const VK_W = 87;
const VK_A = 65;
const VK_S = 83;
const VK_D = 68;
const VK_UP = 38;
const VK_DOWN = 40;
const VK_LEFT = 37;
const VK_RIGHT = 39;
const canvas = document.querySelector('.game'),
stateBtn = document.querySelector('.state-btn');
let game;
const main = () => {
stateBtn.addEventListener('click', changeState);
const ctx = canvas.getContext('2d');
game = new Game(ctx, { width: 400, height: 300 });
game.run();
};
const changeState = (e) => {
const btn = e.currentTarget, running = btn.dataset.running === 'true';
if (running) {
game.pause();
} else {
game.run();
}
btn.textContent = running ? 'Pause' : 'Resume';
btn.dataset.running = !running;
};
class Game {
constructor(ctx, options) {
const { width, height } = options;
this.lastRender = 0;
this.ctx = ctx;
this.state = { keySet: new Set() };
this.bullets = [];
Object.assign(ctx.canvas, { width, height });
const origin = new Victor(width / 2, height / 2);
this.player = new Player({
name: 'Bob',
position: origin
});
ctx.canvas.addEventListener('mousemove', (e) => this.followMouse(e));
ctx.canvas.addEventListener('mousedown', (e) => this.mouseDown(e));
ctx.canvas.addEventListener('mouseup', (e) => this.mouseUp(e));
document.addEventListener('keydown', (e) => this.addKey(e), false);
document.addEventListener('keyup', (e) => this.removeKey(e), false);
}
followMouse(e) {
this.state.mousePos = getMousePos(e.currentTarget, e);
}
mouseDown(e) {
this.state.mouseDown = true;
}
mouseUp(e) {
this.state.mouseDown = false;
}
addKey(e) {
const key = e.which || e.keyCode || 0;
this.state.keySet.add(key);
}
removeKey(e) {
const key = e.which || e.keyCode || 0;
this.state.keySet.delete(key);
}
update(progress) {
const ks = this.state.keySet;
const x = (ks.has(VK_D) || ks.has(VK_RIGHT))
? 1 : (ks.has(VK_A) || ks.has(VK_LEFT)) ? -1 : 0;
const y = (ks.has(VK_S) || ks.has(VK_DOWN))
? 1 : (ks.has(VK_W) || ks.has(VK_UP)) ? -1 : 0;
this.player.position.add(new Victor(x, y));
this.bullets.forEach((bullet, index) => {
if (this.lastRender > bullet.timeLeft) {
this.bullets.splice(index, 1);
}
bullet.update(this.lastRender);
});
if (this.state.mousePos && this.state.mouseDown) {
this.bullets.push(new Bullet(this.player.position,
this.state.mousePos, this.lastRender));
}
}
draw() {
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.player.draw(this.ctx);
this.bullets.forEach(bullet => bullet.draw(this.ctx));
}
run(timestamp) {
const progress = timestamp - this.lastRender;
this.update(progress);
this.draw();
this.lastRender = timestamp;
this.req = window.requestAnimationFrame((t) => this.run(t));
}
pause() {
const { width, height } = this.ctx.canvas;
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
this.ctx.fillRect(0, 0, width, height);
this.ctx.font = '32px Arial';
this.ctx.fillStyle = '#FFF';
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText('Paused', width / 2, height / 2);
cancelAnimationFrame(this.req);
this.req = null;
}
isRunning() {
return this.req !== null;
}
}
class Player {
constructor(options) {
const opts = { ...Player.defaultOptions, ...options };
this.name = opts.name;
this.position = opts.position;
this.size = opts.size;
}
draw(ctx) {
ctx.fillStyle = '#00FF00';
ctx.fillRect(this.position.x - this.size / 2,
this.position.y - this.size / 2, this.size, this.size);
}
};
Player.defaultOptions = {
size: 10,
position: new Victor(0, 0)
};
class Bullet {
constructor(source, target, created) {
this.position = source.clone();
this.speed = this.position.clone()
.subtract(Victor.fromObject(target))
.normalize()
.multiply(new Victor(-2, -2));
this.size = 3;
this.timeLeft = created + 500;
}
update() {
this.position = this.position.add(this.speed);
}
draw(ctx) {
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.size / 2, 0, 2 * Math.PI);
ctx.fill();
}
}
const getMousePos = (canvas, event) =>
(({ top, left }) => ({
x: event.clientX - left,
y: event.clientY - top
}))(canvas.getBoundingClientRect());
main();
.game {
background: #111;
}
.as-console-wrapper { max-height: 4em !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/victor/1.1.0/victor.min.js"></script>
<canvas class="game"></canvas>
<div class="controls">
<button class="state-btn" data-running="true">Pause</button>
</div>
I am new to canvas and developing a game where a car moves straight and now I want to rotate the image of the car only to rotate anti clockwise when the left key is pressed and clockwise when right key is pressed.
Currently I am trying with
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var heroReady = false;
var heroImage = new Image();
heroImage.onload = function () {
heroReady = true;
};
heroImage.src = "images/car.png";
if (37 in keysDown) { // Player holding left
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.translate(canvas.width,canvas.height);
ctx.rotate(90*Math.PI/180);
ctx.drawImage(heroImage,hero.x,hero.y);
}
But this rotates the whole screen .I only want the heroImage to be rotated and not the screen.Any help is appreciated.
My source code: working pen
To get key input and rotate paths and what not on the canvas.
/** SimpleUpdate.js begin **/
// short cut vars
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
ctx.font = "18px arial";
var cw = w / 2; // center
var ch = h / 2;
var focused = false;
var rotated = false;
var angle = 0;
// Handle all key input
const keys = { // key input object
ArrowLeft : false, // only add key names you want to listen to
ArrowRight : false,
keyEvent (event) {
if (keys[event.code] !== undefined) { // are we interested in this key
keys[event.code] = event.type === "keydown";
rotated = true; // to turn off help
}
}
}
// add key listeners
document.addEventListener("keydown", keys.keyEvent)
document.addEventListener("keyup", keys.keyEvent)
// check if focus click
canvas.addEventListener("click",()=>focused = true);
// main update function
function update (timer) {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.clearRect(0,0,w,h);
// draw outside box
ctx.fillStyle = "red"
ctx.fillRect(50, 50, w - 100, h - 100);
// rotate if input
angle += keys.ArrowLeft ? -0.1 : 0;
angle += keys.ArrowRight ? 0.1 : 0;
// set orgin to center of canvas
ctx.setTransform(1, 0, 0, 1, cw, ch);
// rotate
ctx.rotate(angle);
// draw rotated box
ctx.fillStyle = "Black"
ctx.fillRect(-50, -50, 100, 100);
// set transform to center
ctx.setTransform(1, 0, 0, 1, cw, ch);
// rotate
ctx.rotate(angle);
// move to corner
ctx.translate(50,50);
// rotate once more, Doubles the rotation
ctx.rotate(angle);
ctx.fillStyle = "yellow"
ctx.fillRect(-20, -20,40, 40);
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default
// draw center box
ctx.fillStyle = "white"
ctx.fillRect(cw - 25, ch - 25, 50, 50);
if(!focused){
ctx.lineWidth = 3;
ctx.strokeText("Click on canvas to get focus.",10,20);
ctx.fillText("Click on canvas to get focus.",10,20);
}else if(!rotated){
ctx.lineWidth = 3;
ctx.strokeText("Left right arrow to rotate.",10,20);
ctx.fillText("Left right arrow to rotate.",10,20);
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleUpdate.js end **/
<canvas id = canvas></canvas>
Looking at you pen bellow is a function that will help.
The following function will draw a scaled and rotated sprite on the canvas
// draws a image as sprite at x,y scaled and rotated around its center
// image, the image to draw
// x,y position of the center of the image
// scale the scale, 1 no scale, < 1 smaller, > 1 larger
// angle in radians
function drawSprite(image, x, y, scale = 1,angle = 0){
ctx.setTransform(scale, 0, 0, scale, x, y); // set scale and center of sprite
ctx.rotate(angle);
ctx.drawImage(image,- image.width / 2, - image.height / 2);
ctx.setTransform(1,0,0,1,0,0); // restore default transform
// if you call this function many times
// and dont do any other rendering between
// move the restore default line
// outside this function and after all the
// sprites are drawn.
}
OK more for you.
Your code is all over the place and the only way to work out what was happening was to rewrite it from the ground up.
The following does what I think you want your pen to do. Not I squash the width to fit the snippet window.
Code from OP pen and modified to give what I think the OP wants.
// Create the canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 1024;
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.forEach(hero => hero.update());
}
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,
reset(){
this.x = canvas.width / this.spawnPos;
this.y = canvas.height / this.spawnPos;
},
update(){
if (keys.ArrowUp) { // Player holding up
this.y -= this.speed * frameTime;
this.dir = Math.PI * 0; // set direction
}
if (keys.ArrowDown) { // Player holding down
this.y += this.speed * frameTime;
this.dir = Math.PI * 1; // set direction
}
if (keys.ArrowLeft) { // Player holding left
this.x -= this.speed * frameTime;
this.dir = Math.PI * 1.5; // set direction
}
if (keys.ArrowRight) { // 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;
}
});
if (this.x >= canvas.width || this.y >= canvas.height || this. y < 0 || this.x < 0) {
this.reset();
}
}
}
// 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] = {...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] = {...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/v1491958481/road_lrjihy.jpg");
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/v1491959220/m_n1rbem.png");
// create all objects
createHero("hero", {image : images.hero, spawnPos : 1.5});
createHero("hero3", {image : images.hero, spawnPos : 2, speed : -256});
createMonster("monster", {image : images.monster});
createMonster("monster3", {image : images.monster});
createMonster("monster9", {image : images.monster});
createMonster("monster12", {image : images.monster});
// add key listeners
document.addEventListener("keydown", keys.keyEvent);
document.addEventListener("keyup", keys.keyEvent);
canvas {
width : 100%;
height : 100%;
}
I want to modify the platformer game that I find in codepen.
http://codepen.io/loktar00/pen/JEdqD
the original is like the below image.
I want to change it to be:
I want to zoom in the viewport to player and making a camera-like view where I could scroll a level within a canvas element.
Like this game http://www.html5quintus.com/quintus/examples/platformer_full/
The viewport and camera is tracking the player.
This is my code:
(function () {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
width = 100,
height = 100,
player = {
x: width / 2,
y: height - 15,
width: 5,
height: 5,
speed: 3,
velX: 0,
velY: 0,
jumping: false,
grounded: false
},
keys = [],
friction = 0.8,
gravity = 0.3;
var boxes = [];
// player's position
var playerX = 20;
var playerY = 20;
// how far offset the canvas is
var offsetX = 0;
var offsetY = 0;
// dimensions
boxes.push({
x: 0,
y: 0,
width: 10,
height: height
});
boxes.push({
x: 0,
y: height - 2,
width: width,
height: 50
});
boxes.push({
x: width - 10,
y: 0,
width: 50,
height: height
});
boxes.push({
x: 120,
y: 10,
width: 80,
height: 80
});
boxes.push({
x: 170,
y: 50,
width: 80,
height: 80
});
boxes.push({
x: 220,
y: 100,
width: 80,
height: 80
});
boxes.push({
x: 270,
y: 150,
width: 40,
height: 40
});
canvas.width = width;
canvas.height = height;
function update() {
// check keys
if (keys[38] || keys[32] || keys[87]) {
// up arrow or space
if (!player.jumping && player.grounded) {
player.jumping = true;
player.grounded = false;
player.velY = -player.speed * 2;
}
}
if (keys[39] || keys[68]) {
// right arrow
if (player.velX < player.speed) {
player.velX++;
offsetX--;
}
}
if (keys[37] || keys[65]) {
// left arrow
if (player.velX > -player.speed) {
player.velX--;
offsetX++;
}
}
player.velX *= friction;
player.velY += gravity;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "black";
ctx.beginPath();
player.grounded = false;
for (var i = 0; i < boxes.length; i++) {
ctx.rect(boxes[i].x, boxes[i].y, boxes[i].width, boxes[i].height);
var dir = colCheck(player, boxes[i]);
if (dir === "l" || dir === "r") {
player.velX = 0;
player.jumping = false;
} else if (dir === "b") {
player.grounded = true;
player.jumping = false;
} else if (dir === "t") {
player.velY *= -1;
}
}
if(player.grounded){
player.velY = 0;
}
player.x += player.velX;
player.y += player.velY;
ctx.save();
ctx.translate(offsetX, offsetY);
// clear the viewport
ctx.clearRect(-offsetX, -offsetY, 100,100);
ctx.fill();
ctx.fillStyle = "red";
ctx.fillRect(player.x, player.y, player.width, player.height);
ctx.fillRect(playerX-offsetX, playerY-offsetY, 8, 8);
// draw the other stuff
var l = boxes.length;
for (var i = 0; i < l; i++) {
// we should really only draw the things that intersect the viewport!
// but I am lazy so we are drawing everything here
var x = boxes[i][0];
var y = boxes[i][1];
ctx.fillStyle = 'lightblue';
ctx.fillRect(x, y, 8, 8);
ctx.fillStyle = 'black';
ctx.fillText(x + ', ' + y, x, y) // just to show where we are drawing these things
}
ctx.restore();
requestAnimationFrame(update);
}
function colCheck(shapeA, shapeB) {
// get the vectors to check against
var vX = (shapeA.x + (shapeA.width / 2)) - (shapeB.x + (shapeB.width / 2)),
vY = (shapeA.y + (shapeA.height / 2)) - (shapeB.y + (shapeB.height / 2)),
// add the half widths and half heights of the objects
hWidths = (shapeA.width / 2) + (shapeB.width / 2),
hHeights = (shapeA.height / 2) + (shapeB.height / 2),
colDir = null;
// if the x and y vector are less than the half width or half height, they we must be inside the object, causing a collision
if (Math.abs(vX) < hWidths && Math.abs(vY) < hHeights) {
// figures out on which side we are colliding (top, bottom, left, or right)
var oX = hWidths - Math.abs(vX),
oY = hHeights - Math.abs(vY);
if (oX >= oY) {
if (vY > 0) {
colDir = "t";
shapeA.y += oY;
} else {
colDir = "b";
shapeA.y -= oY;
}
} else {
if (vX > 0) {
colDir = "l";
shapeA.x += oX;
} else {
colDir = "r";
shapeA.x -= oX;
}
}
}
return colDir;
}
document.body.addEventListener("keydown", function (e) {
keys[e.keyCode] = true;
});
document.body.addEventListener("keyup", function (e) {
keys[e.keyCode] = false;
});
window.addEventListener("load", function () {
update();
});
<h3>A, D or Arrow keys to move, W or space to jump</h3>
<canvas id="canvas"></canvas>
But this does not work.
You can use the same code that you had in the codepen, but add a couple lines in the load event, before the first update().
Let's say something like:
window.addEventListener("load", function () {
ctx.scale(2,2);
ctx.translate(-100,-100);
update();
});
This will zoom by two times and center on new coordinates, BUT keep in mind that you have to do it yourself if you want to re-center when the player is going out of the view.
As a partial way of doing this, you can check if the player moved and translate the canvas using the opposite values of player.velX and player.velY. Something like:
var playerMoved = false;
if (keys[38] || keys[32] || keys[87]) {
playerMoved = true;
//...Other stuff
}
if (keys[39] || keys[68]) {
playerMoved = true;
//...Other stuff
}
if (keys[37] || keys[65]) {
playerMoved = true;
//...Other stuff
}
if (playerMoved) {
// You still need to check quite a few things, like
// the player being out of bounds, the view not translating
// when x less than or bigger then a specific value, and so on
ctx.translate(-player.velX, -player.velY);
}
This is not a complete solution because that would require quite some code, but it should get you started.