Related
I am trying to make a game in which a player has an equippable weapon. As of now, I have set this weapon to be an image of a bow, and I want to have said weapon to move around the player while facing the mouse. This is similar to buildroyale.io, where the player rotates along with his weapon to face the mouse.
As of now, with the help of #Justin, I've got the bow rotating (somewhat) on the screen. It only shows up when the left click is down, as desired, but does not rotate as expected. Here is a clip showcasing how it moves: clip
Here is the code I use:
class EventHandler {
equip_weapon() {
if (holding) {
player.weapon = player.bag.slot_1;
canv.globalAlpha = 1;
player.weapon.equip();
player.weapon.update();
}
}
}
class Weapon {
image_path;
constructor(image_path) {
this.x = player.x + 30;
this.y = player.y + 30;
this.width = 120;
this.height = 120;
this.angle = 0;
this.distance = 50;
this.image = image_path;
}
equip() {
this.angle = Math.atan2(mouse.y - this.y, mouse.x - this.x)
canv.save();
canv.translate(this.x, this.y);
canv.rotate(this.angle);
canv.drawImage(this.image, this.distance, -this.height/2, this.width, this.height);
canv.restore();
}
update() {
this.x = player.x + player.width / 2;
this.y = player.y + player.height / 2;
}
}
bow = new Weapon(bow_image);
player.bag.slot_1 = bow;
aim_bounds = document.documentElement.getBoundingClientRect();
class Player {
constructor() {
this.name = null;
this.speed = 5;
this.skin = player_sheet;
this.can_move = true;
this.is_moving = false;
this.width = 68;
this.height = 68;
this.scale = 1;
this.x = 566;
this.y = 316;
this.direction = facing.down;
this.frame = 0;
this.shadow_offset = 25;
this.frame_rate = 10;
this.health = 100;
this.clip_amount = 10;
this.weapon = null;
// Player inventory
this.bag = {
slot_1 : null,
slot_2 : null,
slot_3 : null,
slot_4 : null,
slot_5 : null,
offhand : null,
armor : null
}
this.is_menu_open = false;
console.log("Player constructed!");
}
update() {
// Animation updates
if (game.tick % this.frame_rate == 0 && this.is_moving && !this.is_menu_open) {
this.frame += 68;
if (this.frame >= 272) { this.frame = 0; }
} else if (!this.is_moving) { this.frame = 0; }
// Movement updates
if (this.can_move) {
if (controller.up) { this.direction = facing.up; this.y -= this.speed; this.is_moving = true; }
else if (controller.down) { this.direction = facing.down; this.y += this.speed; this.is_moving = true; }
else if (controller.left) { this.direction = facing.left; this.x -= this.speed; this.is_moving = true; }
else if (controller.right) { this.direction = facing.right; this.x += this.speed; this.is_moving = true; }
if (!controller.up && !controller.down && !controller.left && !controller.right) { this.is_moving = false; }
}
// Checks
if (this.is_menu_open) { this.can_move = false; } else { this.can_move = true; }
document.getElementById("health_bar").value = this.health;
if (this.is_menu_open) { menu.style.display = "block"; } else { menu.style.display = "none"; }
}
animate() {
// Player shadow
canv.drawImage(player_shadow, this.x, this.y + this.shadow_offset);
// Player
canv.globalAlpha = 1;
canv.drawImage(player_sheet, (sprite.x + this.frame), (sprite.y * this.direction),
sprite.width, sprite.height,
this.x, this.y,
this.width, this.height);
}
}
Here is a download to my current code in case you need more to debug: game.zip
The way I handle this is to make sure my image is first oriented correctly. In my case my bow image would look like this (ignore the quality). Just see that it faces right.
In the draw function I use translate() to position the image and I use the x and y inside the drawImage(img, x, y, w, h) to draw the image centered along the top edge of the canvas. The x (set to 50 in this example) position is essentially the radius of the image rotation and the y is just to center my bow's arrow on the y axis of the canvas.
Using Math.atan2() I can rotate the image
this.angle = Math.atan2(mouse.y - this.y, mouse.x - this.x)
In this snippet I have two lines that are commented out. If you uncomment them you will better see what is happening.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 600;
let mouse = {x: 10, y: 10}
let canvasBounds = canvas.getBoundingClientRect();
canvas.addEventListener('mousemove', e => {
mouse.x = e.x - canvasBounds.x
mouse.y = e.y - canvasBounds.y
})
let bow = new Image();
bow.src = "https://lh3.googleusercontent.com/g5Sr3HmGZgWx07sRQMvgvtxZ-ErhWNT0_asFdhLIlw-EQMTuUq3BV3YY8d5rrIrZBiJ-Uo2l836Qlmr8dmaCi-dcCCqN6veS6xnE8jSrmdtRtZKnmF5FQ5aTxuVBgB28n6ICoxSlpA=w2400";
class Weapon {
constructor() {
this.x = 200;
this.y = 200;
this.w = 60;
this.h = 60;
this.angle = 0
}
draw() {
this.angle = Math.atan2(mouse.y - this.y, mouse.x - this.x)
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
//ctx.fillStyle = 'lightgrey';
//ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(bow, 50, -this.h/2, this.w, this.h)
ctx.restore();
}
}
let bowArrow = new Weapon();
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
bowArrow.draw();
requestAnimationFrame(animate)
}
animate();
<canvas id="canvas"></canvas>
UPDATE:
Here is what I changed in your files and it works well on my end.
engine.controller.js change mousemove and add listener for resize. I'm also pretty sure there's a way you can get rid of the getMouse function since the listener below gets mouse coordinates for you.
canvas.addEventListener("mousemove", function (event) {
mouse_position = event
mouse.x = event.x - canvas_bounds.x;
mouse.y = event.y - canvas_bounds.y;
})
window.addEventListener('resize', () => {
canvas_bounds = canvas.getBoundingClientRect();
})
engine.eventhandler.js
class Weapon {
image_path;
constructor(image_path) {
this.x = player.x + player.width/2;
this.y = player.y + player.height/2;
this.w = 60;
this.h = 60;
this.angle = 0;
this.image = image_path;
}
equip() {
this.x = player.x + player.width/2;
this.y = player.y + player.height/2;
this.angle = Math.atan2(mouse.y - this.y, mouse.x - this.x)
canv.save();
canv.translate(this.x, this.y);
canv.rotate(this.angle);
canv.drawImage(this.image, 50, -this.h/2, this.w, this.h);
canv.restore();
}
}
engine.main.js
function setup() {
game = new Game;
player = new Player;
controller = new Controller;
event_handler = new EventHandler;
canvas.width = 1200;
canvas.height = 700;
canvas_bounds = canvas.getBoundingClientRect();
// REMOVE LATER
bow = new Weapon(bow_image);
player.bag.slot_1 = bow;
document.getElementById("bag").style.display = "block";
}
engine.setup.js
// Weapon stuff
var weapon_equiped = false;
var canvas = document.getElementById("canvas");
let mouse = {
x : 10,
y : 10
}
let canvas_bound;
var mouse_position, holding;
var rect, mouse_x, mouse_y;
...and
//you have a space between canvas. height in both fillRect
canv.fillRect(0, 0, canvas.width, canvas. height);
I think that's all I changed. Pretty sure you can use your mouse_x or the mouse.x but you probably don't need both in your code.
I've tried for the last few days without too much success to rotate, scale and translate shapes on the canvas.
I've read everything I could find on internet about similar issues but still I cannot seem to be able to adapt it to my own problem.
If everything is drawn on the same scale, I can still drag and drop. If I rotate the shapes, then the mouseOver is messed up since the world coordinates don't correspond anymore with the shape coordinates.
If I scale, then it's impossible to select any shape.
I look at my code and do not understand what I'm doing wrong.
I read some really nice and detailed stackoverflow solutions to similar problems.
For example, user #blindman67 made a suggestion of using a setTransform helper and a getMouseLocal helper for getting the coordinates of the mouse relative to the transformed shape.
inverse transform matrix
I spent some time with that and could not fix my issue.
Here is an example of what I tried. Any suggestion is appreciated.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth - 40;
canvas.height = window.innerHeight - 60;
const canvasBounding = canvas.getBoundingClientRect();
const offsetX = canvasBounding.left;
const offsetY = canvasBounding.top;
let scale = 1;
let selectedShape = '';
let startX = 0;
let startY = 0;
let endX = 0;
let endY = 0;
let mouseIsDown = false;
let mouseIsMovingShape = false;
let selectedTool = 'SELECT';
let shapes = {};
const selectButton = document.getElementById('select');
const rectangleButton = document.getElementById('rectangle');
canvas.addEventListener('mousedown', canvasMouseDown);
canvas.addEventListener('mouseup', canvasMouseUp);
canvas.addEventListener('mousemove', canvasMouseMove);
function canvasMouseDown(e) {
e.preventDefault();
const mouseX = e.clientX - offsetX;
const mouseY = e.clientY - offsetY;
startX = mouseX;
startY = mouseY;
mouseIsDown = true;
selectedShape = '';
if (selectedTool === 'SELECT') {
for (const shapeId in shapes) {
const shape = shapes[shapeId];
if (shape.mouseIsOver(mouseX, mouseY)) {
selectedShape = shape.id;
shapes[shape.id].isSelected = true;
} else {
shapes[shape.id].isSelected = false;
}
}
}
draw();
}
function canvasMouseUp(e) {
e.preventDefault();
const mouseX = e.clientX - offsetX;
const mouseY = e.clientY - offsetY;
endX = mouseX;
endY = mouseY;
mouseIsDown = false;
const tooSmallShape = Math.abs(endX) - startX < 1 || Math.abs(endY) - startY < 1;
if (tooSmallShape) {
return;
}
if (selectedTool === 'RECTANGLE') {
const newShape = new Shape(selectedTool.toLowerCase(), startX, startY, endX, endY);
shapes[newShape.id] = newShape;
selectedShape = '';
setActiveTool('SELECT');
}
draw();
}
function canvasMouseMove(e) {
e.preventDefault();
const mouseX = e.clientX - offsetX;
const mouseY = e.clientY - offsetY;
const dx = e.movementX;
const dy = e.movementY;
if (mouseIsDown) {
draw();
if (selectedTool === 'SELECT' && selectedShape !== '') {
const shape = shapes[selectedShape];
shape.x += dx;
shape.y += dy;
}
if (selectedTool === 'RECTANGLE') {
drawShapeGhost(mouseX, mouseY);
}
}
}
function draw() {
clear();
for (const shapeId in shapes) {
const shape = shapes[shapeId];
shape.drawShape(ctx);
}
}
function clear() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(0, 0, canvas.width, canvas.height)
}
function drawShapeGhost(x, y) {
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
ctx.strokeRect(startX, startY, x - startX, y - startY);
ctx.stroke();
}
function setActiveTool(tool) {
selectedTool = tool;
if (tool === 'RECTANGLE') {
rectangleButton.classList.add('active');
selectButton.classList.remove('active');
selectedTool = tool;
}
if (tool === 'SELECT') {
rectangleButton.classList.remove('active');
selectButton.classList.add('active');
selectedTool = tool;
}
}
function degreesToRadians(degrees) {
return (Math.PI * degrees) / 180;
};
class Shape {
constructor(shapeType, startX, startY, endX, endY, fill, stroke) {
this.id = shapeType + Date.now();
this.type = shapeType;
this.x = startX;
this.y = startY;
this.width = Math.abs(endX - startX);
this.height = Math.abs(endY - startY);
this.fill = fill || 'rgba(149, 160, 178, 0.8)';
this.stroke = stroke || 'rgba(0, 0, 0, 0.8)';
this.rotation = 0;
this.isSelected = false;
this.scale = 1;
}
drawShape(ctx) {
switch (this.type) {
case 'rectangle':
this._drawRectangle(ctx);
break;
}
}
_drawRectangle(ctx) {
ctx.save();
ctx.scale(this.scale, this.scale);
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
ctx.rotate(degreesToRadians(this.rotation));
if (this.fill) {
ctx.fillStyle = this.fill;
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
}
if (this.stroke !== null) {
ctx.strokeStyle = this.stroke;
ctx.strokeWidth = 1;
ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height);
ctx.stroke();
}
if (this.isSelected) {
ctx.strokeStyle = 'rgba(254, 0, 0, 1)';
ctx.strokeWidth = 1;
ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height)
ctx.stroke();
ctx.closePath();
}
ctx.restore();
}
mouseIsOver(mouseX, mouseY) {
if (this.type === 'rectangle') {
return (mouseX > this.x && mouseX < this.x + this.width && mouseY > this.y && mouseY < this.y + this.height);
}
}
}
const menu = document.getElementById('menu');
const rotation = document.getElementById('rotation');
const scaleSlider = document.getElementById('scale');
menu.addEventListener('click', onMenuClick);
rotation.addEventListener('input', onRotationChange);
scaleSlider.addEventListener('input', onScaleChange);
function onMenuClick(e) {
const tool = e.target.dataset.tool;
if (tool && tool === 'RECTANGLE') {
rectangleButton.classList.add('active');
selectButton.classList.remove('active');
selectedTool = tool;
}
if (tool && tool === 'SELECT') {
rectangleButton.classList.remove('active');
selectButton.classList.add('active');
selectedTool = tool;
}
}
function onRotationChange(e) {
if (selectedShape !== '') {
shapes[selectedShape].rotation = e.target.value;
draw();
}
}
function onScaleChange(e) {
scale = e.target.value;
for (const shapeId in shapes) {
const shape = shapes[shapeId];
shape.scale = scale;
}
draw();
}
function setTransform(ctx, x, y, scaleX, scaleY, rotation) {
const xDx = Math.cos(rotation);
const xDy = Math.sin(rotation);
ctx.setTransform(xDx * scaleX, xDy * scaleX, -xDy * scaleY, xDx * scaleY, x, y);
}
function getMouseLocal(mouseX, mouseY, x, y, scaleX, scaleY, rotation) {
const xDx = Math.cos(rotation);
const xDy = Math.sin(rotation);
const cross = xDx * scaleX * xDx * scaleY - xDy * scaleX * (-xDy) * scaleY;
const ixDx = (xDx * scaleY) / cross;
const ixDy = (-xDy * scaleX) / cross;
const iyDx = (xDy * scaleY) / cross;
const iyDy = (xDx * scaleX) / cross;
mouseX -= x;
mouseY -= y;
const localMouseX = mouseX * ixDx + mouseY * iyDx;
const localMouseY = mouseX * ixDy + mouseY * iyDy;
return {
x: localMouseX,
y: localMouseY,
}
}
function degreesToRadians(degrees) {
return (Math.PI * degrees) / 180
};
function radiansToDegrees(radians) {
return radians * 180 / Math.PI
};
let timer;
function debounce(fn, ms) {
clearTimeout(timer);
timer = setTimeout(() => fn(), ms);
}
canvas {
margin-top: 1rem;
border: 1px solid black;
}
button {
border: 1px solid #adadad;
background-color: transparent;
}
.active {
background-color: lightblue;
}
.menu {
display: flex;
}
.d-flex {
display: flex;
margin-left: 2rem;
}
.rotation input {
margin-left: 1rem;
max-width: 50px;
}
<div id="menu" class="menu">
<button id="select" data-tool="SELECT">select</button>
<button id="rectangle" data-tool="RECTANGLE">rectangle</button>
<div class="d-flex">
<label for="rotation">rotation </label>
<input type="number" id="rotation" value="0">
</div>
<div class="d-flex">
<label for="scale">scale </label>
<input type="range" step="0.1" min="0.1" max="10" value="1" name="scale" id="scale">
</div>
</div>
<canvas id="canvas"></canvas>
If I have time tomorrow I will try to implement the following to your code but I can provide you with a working example of how to get mouse collision precision on a rotated rectangle. I had the same struggle with this and finally found a good explanation and code that I was able to get to work. Check out this website
Now for my own implementation I did not use the method on that website to get my vertices. As you'll see in my code I have a function called updateCorners() in my Square class. I also have objects called this.tl.x and this.tl.y (for each corner).
The formulas are what I use to get vertices of a translated and rotated rectangle and the corner objects are what are used to determine collision. From there I used the distance() function (Pythagorean theorem), the triangleArea() function, and then the clickHit() function which I renamed to collision() and changed some things.
Example in the snippet below
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let shapes = [];
let mouse = {
x: null,
y: null
}
canvas.addEventListener('mousemove', e => {
mouse.x = e.x - canvas.getBoundingClientRect().x;
mouse.y = e.y - canvas.getBoundingClientRect().y;
})
class Square {
constructor(x, y, w, h, c) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
this.a = 0;
this.r = this.a * (Math.PI/180);
this.cx = this.x + this.w/2;
this.cy = this.y + this.h/2;
//used to track corners
this.tl = {x: 0, y: 0};
this.tr = {x: 0, y: 0};
this.br = {x: 0, y: 0};
this.bl = {x: 0, y: 0};
}
draw() {
ctx.save();
ctx.translate(this.x, this.y)
ctx.rotate(this.r);
ctx.fillStyle = this.c;
ctx.fillRect(-this.w/2,-this.h/2,this.w,this.h);
ctx.restore();
}
updateCorners() {
this.a += 0.1
this.r = this.a * (Math.PI/180);
let cos = Math.cos(this.r);
let sin = Math.sin(this.r)
//updates Top Left Corner
this.tl.x = (this.x-this.cx)*cos - (this.y-this.cy)*sin+(this.cx-this.w/2);
this.tl.y = (this.x-this.cx)*sin + (this.y-this.cy)*cos+(this.cy-this.h/2)
//updates Top Right Corner
this.tr.x = ((this.x+this.w)-this.cx)*cos - (this.y-this.cy)*sin+(this.cx-this.w/2)
this.tr.y = ((this.x+this.w)-this.cx)*sin + (this.y-this.cy)*cos+(this.cy-this.h/2)
//updates Bottom Right Corner
this.br.x = ((this.x+this.w)-this.cx)*cos - ((this.y+this.h)-this.cy)*sin+(this.cx-this.w/2)
this.br.y = ((this.x+this.w)-this.cx)*sin + ((this.y+this.h)-this.cy)*cos+(this.cy-this.h/2)
//updates Bottom Left Corner
this.bl.x = (this.x-this.cx)*cos - ((this.y+this.h)-this.cy)*sin+(this.cx-this.w/2)
this.bl.y = (this.x-this.cx)*sin + ((this.y+this.h)-this.cy)*cos+(this.cy-this.h/2)
}
}
let square1 = shapes.push(new Square(250, 70, 25, 25, 'red'));
let square2 = shapes.push(new Square(175,210, 100, 50, 'blue'));
let square3 = shapes.push(new Square(50,100, 30, 50, 'purple'));
let square4 = shapes.push(new Square(140,120, 120, 20, 'pink'));
//https://joshuawoehlke.com/detecting-clicks-rotated-rectangles/
//pythagorean theorm using built in javascript hypot
function distance(p1, p2) {
return Math.hypot(p1.x-p2.x, p1.y-p2.y);
}
//Heron's formula used to determine area of triangle
//in the collision() function we will break the rectangle into triangles
function triangleArea(d1, d2, d3) {
var s = (d1 + d2 + d3) / 2;
return Math.sqrt(s * (s - d1) * (s - d2) * (s - d3));
}
function collision(mouse, rect) {
//area of rectangle
var rectArea = Math.round(rect.w * rect.h);
// Create an array of the areas of the four triangles
var triArea = [
// mouse posit checked against tl-tr
triangleArea(
distance(mouse, rect.tl),
distance(rect.tl, rect.tr),
distance(rect.tr, mouse)
),
// mouse posit checked against tr-br
triangleArea(
distance(mouse, rect.tr),
distance(rect.tr, rect.br),
distance(rect.br, mouse)
),
// mouse posit checked against tr-bl
triangleArea(
distance(mouse, rect.br),
distance(rect.br, rect.bl),
distance(rect.bl, mouse)
),
// mouse posit checked against bl-tl
triangleArea(
distance(mouse, rect.bl),
distance(rect.bl, rect.tl),
distance(rect.tl, mouse)
)
];
let triArea2 = Math.round(triArea.reduce(function(a,b) { return a + b; }, 0));
if (triArea2 > rectArea) {
return false;
}
return true;
}
function animate() {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = 'black';
ctx.fillText('x: '+mouse.x+',y: '+mouse.y, 50, 50);
for (let i=0; i< shapes.length; i++) {
shapes[i].draw();
shapes[i].updateCorners();
if (collision(mouse, shapes[i])) {
shapes[i].c = 'red';
} else {
shapes[i].c = 'green'
}
}
requestAnimationFrame(animate)
}
animate();
<canvas id="canvas"></canvas>
I'm sure there's many other ways to do this but this is what I was able to understand and get to work. I haven't really messed with scale so I can't help much there.
UPDATE:
Here is a snippet using the method you wanted. Now you can rotate, scale, and translate and still click inside the shape. Be aware I changed your mouse to a global mouse object vice making it a variable in every mouse... function.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 40;
canvas.height = window.innerHeight - 60;
const canvasBounding = canvas.getBoundingClientRect();
const offsetX = canvasBounding.left;
const offsetY = canvasBounding.top;
let scale = 1;
let selectedShape = "";
let startX = 0;
let startY = 0;
let endX = 0;
let endY = 0;
let mouseIsDown = false;
let mouseIsMovingShape = false;
let selectedTool = "SELECT";
let localMouse = { x: null, y: null };
let mouse = { x: null, y: null };
let shapes = {};
const selectButton = document.getElementById("select");
const rectangleButton = document.getElementById("rectangle");
canvas.addEventListener("mousedown", canvasMouseDown);
canvas.addEventListener("mouseup", canvasMouseUp);
canvas.addEventListener("mousemove", canvasMouseMove);
function canvasMouseDown(e) {
e.preventDefault();
mouse.x = e.clientX - offsetX;
mouse.y = e.clientY - offsetY;
startX = mouse.x;
startY = mouse.y;
mouseIsDown = true;
selectedShape = "";
if (selectedTool === "SELECT") {
for (const shapeId in shapes) {
const shape = shapes[shapeId];
if (shape.mouseIsOver()) {
selectedShape = shape.id;
shapes[shape.id].isSelected = true;
} else {
shapes[shape.id].isSelected = false;
}
}
}
draw();
}
function canvasMouseUp(e) {
e.preventDefault();
mouse.x = e.clientX - offsetX;
mouse.y = e.clientY - offsetY;
endX = mouse.x;
endY = mouse.y;
mouseIsDown = false;
const tooSmallShape =
Math.abs(endX) - startX < 1 || Math.abs(endY) - startY < 1;
if (tooSmallShape) {
return;
}
if (selectedTool === "RECTANGLE") {
const newShape = new Shape(
selectedTool.toLowerCase(),
startX,
startY,
endX,
endY
);
shapes[newShape.id] = newShape;
selectedShape = "";
setActiveTool("SELECT");
}
draw();
}
function canvasMouseMove(e) {
e.preventDefault();
mouse.x = e.clientX - offsetX;
mouse.y = e.clientY - offsetY;
const dx = e.movementX;
const dy = e.movementY;
if (mouseIsDown) {
draw();
if (selectedTool === "SELECT" && selectedShape !== "") {
const shape = shapes[selectedShape];
shape.x += dx;
shape.y += dy;
}
if (selectedTool === "RECTANGLE") {
drawShapeGhost(mouse.x, mouse.y);
}
}
}
function draw() {
clear();
for (const shapeId in shapes) {
const shape = shapes[shapeId];
shape.drawShape(ctx);
}
}
function clear() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = "rgba(255, 255, 255, 1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function drawShapeGhost(x, y) {
ctx.strokeStyle = "rgba(0, 0, 0, 0.5)";
ctx.strokeRect(startX, startY, x - startX, y - startY);
ctx.stroke();
}
function setActiveTool(tool) {
selectedTool = tool;
if (tool === "RECTANGLE") {
rectangleButton.classList.add("active");
selectButton.classList.remove("active");
selectedTool = tool;
}
if (tool === "SELECT") {
rectangleButton.classList.remove("active");
selectButton.classList.add("active");
selectedTool = tool;
}
}
function degreesToRadians(degrees) {
return (Math.PI * degrees) / 180;
}
class Shape {
constructor(shapeType, startX, startY, endX, endY, fill, stroke) {
this.id = shapeType + Date.now();
this.type = shapeType;
this.x = startX;
this.y = startY;
this.width = Math.abs(endX - startX);
this.height = Math.abs(endY - startY);
this.fill = fill || "rgba(149, 160, 178, 0.8)";
this.stroke = stroke || "rgba(0, 0, 0, 0.8)";
this.rotation = 0;
this.isSelected = false;
this.scale = { x: 1, y: 1 };
}
drawShape(ctx) {
switch (this.type) {
case "rectangle":
this._drawRectangle(ctx);
break;
}
}
_drawRectangle(ctx) {
ctx.save();
setTransform(
this.x + this.width / 2,
this.y + this.height / 2,
this.scale.x,
this.scale.y,
degreesToRadians(this.rotation)
);
if (this.fill) {
ctx.fillStyle = this.fill;
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
}
if (this.stroke !== null) {
ctx.strokeStyle = this.stroke;
ctx.strokeWidth = 1;
ctx.strokeRect(
-this.width / 2,
-this.height / 2,
this.width,
this.height
);
ctx.stroke();
}
if (this.isSelected) {
ctx.strokeStyle = "rgba(254, 0, 0, 1)";
ctx.strokeWidth = 1;
ctx.strokeRect(
-this.width / 2,
-this.height / 2,
this.width,
this.height
);
ctx.stroke();
ctx.closePath();
}
ctx.restore();
}
mouseIsOver() {
localMouse = getMouseLocal(
mouse.x,
mouse.y,
this.x + this.width / 2,
this.y + this.height / 2,
this.scale.x,
this.scale.y,
degreesToRadians(this.rotation)
);
if (this.type === "rectangle") {
if (
localMouse.x > 0 - this.width / 2 &&
localMouse.x < 0 + this.width / 2 &&
localMouse.y < 0 + this.height / 2 &&
localMouse.y > 0 - this.height / 2
) {
return true;
}
}
}
}
const menu = document.getElementById("menu");
const rotation = document.getElementById("rotation");
const scaleSlider = document.getElementById("scale");
menu.addEventListener("click", onMenuClick);
rotation.addEventListener("input", onRotationChange);
scaleSlider.addEventListener("input", onScaleChange);
function onMenuClick(e) {
const tool = e.target.dataset.tool;
if (tool && tool === "RECTANGLE") {
rectangleButton.classList.add("active");
selectButton.classList.remove("active");
selectedTool = tool;
}
if (tool && tool === "SELECT") {
rectangleButton.classList.remove("active");
selectButton.classList.add("active");
selectedTool = tool;
}
}
function onRotationChange(e) {
if (selectedShape !== "") {
shapes[selectedShape].rotation = e.target.value;
draw();
}
}
function onScaleChange(e) {
scale = e.target.value;
for (const shapeId in shapes) {
const shape = shapes[shapeId];
shape.scale.x = scale;
shape.scale.y = scale;
}
draw();
}
function setTransform(x, y, sx, sy, rotate) {
var xdx = Math.cos(rotate); // create the x axis
var xdy = Math.sin(rotate);
ctx.setTransform(xdx * sx, xdy * sx, -xdy * sy, xdx * sy, x, y);
}
function getMouseLocal(mouseX, mouseY, x, y, sx, sy, rotate) {
var xdx = Math.cos(rotate); // create the x axis
var xdy = Math.sin(rotate);
var cross = xdx * sx * xdx * sy - xdy * sx * -xdy * sy;
var ixdx = (xdx * sy) / cross; // create inverted x axis
var ixdy = (-xdy * sx) / cross;
var iydx = (xdy * sy) / cross; // create inverted y axis
var iydy = (xdx * sx) / cross;
mouseX -= x;
mouseY -= y;
var localMouseX = mouseX * ixdx + mouseY * iydx;
var localMouseY = mouseX * ixdy + mouseY * iydy;
return { x: localMouseX, y: localMouseY };
}
function radiansToDegrees(radians) {
return (radians * 180) / Math.PI;
}
let timer;
function debounce(fn, ms) {
clearTimeout(timer);
timer = setTimeout(() => fn(), ms);
}
canvas {
margin-top: 1rem;
border: 1px solid black;
}
button {
border: 1px solid #adadad;
background-color: transparent;
}
.active {
background-color: lightblue;
}
.menu {
display: flex;
}
.d-flex {
display: flex;
margin-left: 2rem;
}
.rotation input {
margin-left: 1rem;
max-width: 50px;
}
<div id="menu" class="menu">
<button id="select" data-tool="SELECT">select</button>
<button id="rectangle" data-tool="RECTANGLE">rectangle</button>
<button id="triangle" data-tool="TRIANGLE">triangle</button>
<div class="d-flex">
<label for="rotation">rotation </label>
<input type="number" id="rotation" value="0">
</div>
<div class="d-flex">
<label for="scale">scale </label>
<input type="range" step="0.1" min="0.1" max="10" value="1" name="scale" id="scale">
</div>
</div>
<canvas id="canvas"></canvas>
I want to draw quite a few dots. That's what I do:
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
function render () {
window.requestAnimationFrame(render);
// clear screen
ctx.clearRect(0, 0, cwidth, cheight);
for (let p of particles) {
p.rmove();
p.render(ctx);
}
}
render();
The drawing function for my point looks like this:
class Point {
x: number;
y: number;
constructor(x, y) {
this.x = x;
this.y = y;
}
rmove() {
this.x += Math.round(Math.random() < 0.5 ? -1 : 1);
this.y += Math.round(Math.random() < 0.5 ? -1 : 1);
}
render (ctx) {
ctx.fillStyle = "gray";
ctx.beginPath();
ctx.rect(this.x, this.y, 1.5,1.5);
ctx.fill();
ctx.stroke();
}
}
Note that I round the values in the rmove() function, as canvas draws points with integer coordinates more quickly.
I'd like to somehow put all these drawing calls together.
Make your points trace on the given context (could even be a Path2D), and keep the actual drawing for the renderer.
All your points have to do is to make the context moveTo their own coords before tracing the rect.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
rmove() {
this.x += Math.round(Math.random() < 0.5 ? -1 : 1);
this.y += Math.round(Math.random() < 0.5 ? -1 : 1);
}
trace (ctx) {
ctx.moveTo( this.x, this.y );
ctx.rect(this.x, this.y, 1.5, 1.5);
}
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const cwidth = canvas.width = 300;
const cheight = canvas.height = 300;
const particles = Array.from(
{ length: 5000 },
()=> new Point( cwidth/2, cheight/2 )
);
function animate () {
update();
draw();
window.requestAnimationFrame(animate);
}
function update() {
for (let p of particles) {
p.rmove();
}
}
function draw() {
// clear screen
ctx.clearRect(0, 0, cwidth, cheight);
// define our single path
ctx.beginPath();
for (let p of particles) {
p.trace(ctx);
}
ctx.fillStyle = "gray";
ctx.stroke(); // OP has it reversed, but then the fill-color is almost not visible
// (1.5 width - 2*0.5 stroke leaves only 0.5 for the fill => antialiased...
ctx.fill();
}
window.requestAnimationFrame( animate );
<canvas id="canvas"></canvas>
But this works only because all your particles share the same color. If they didn't, then you'd need a bit more logic:
const colors = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow'];
class Point {
constructor(x, y, color=0) {
this.x = x;
this.y = y;
this.color = color;
}
rmove() {
this.x += Math.round(Math.random() < 0.5 ? -1 : 1);
this.y += Math.round(Math.random() < 0.5 ? -1 : 1);
}
trace (ctx) {
ctx.moveTo( this.x, this.y );
ctx.rect(this.x, this.y, 1.5, 1.5);
}
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const cwidth = canvas.width = 300;
const cheight = canvas.height = 300;
const particles = Array.from(
{ length: 5000 },
()=> new Point( cwidth/2, cheight/2, (Math.random()*colors.length-1)|0 )
);
function animate () {
update();
draw();
window.requestAnimationFrame(animate);
}
function update() {
for (let p of particles) {
p.rmove();
}
}
function draw() {
// clear screen
ctx.clearRect(0, 0, cwidth, cheight);
// define our single path
let last_color = -1;
for (let p of particles) {
let p_color = p.color;
if( p_color !== last_color ) {
paint();
last_color = p_color;
}
p.trace(ctx);
}
paint(); // the last
function paint() {
ctx.fillStyle = colors[ last_color ];
ctx.strokeStyle = colors[ (last_color + 1) % colors .length ];
ctx.stroke();
ctx.fill();
ctx.beginPath();
}
}
window.requestAnimationFrame( animate );
<canvas id="canvas"></canvas>
Though doing this, you may very well end up with a lot of drawings, so a final trick which might not work everywhere is to sort your particles by their color. This results in a different graphic since this one color will always be at the top, but it might work in some cases and the performance gain can outfit the downside.
const colors = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow'];
class Point {
constructor(x, y, color=0) {
this.x = x;
this.y = y;
this.color = color;
}
rmove() {
this.x += Math.round(Math.random() < 0.5 ? -1 : 1);
this.y += Math.round(Math.random() < 0.5 ? -1 : 1);
}
trace (ctx) {
ctx.moveTo( this.x, this.y );
ctx.rect(this.x, this.y, 1.5, 1.5);
}
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const cwidth = canvas.width = 300;
const cheight = canvas.height = 300;
const particles = Array.from(
{ length: 5000 },
()=> new Point( cwidth/2, cheight/2, (Math.random()*colors.length-1)|0 )
);
particles.sort( (a, b) => a.color - b.color );
function animate () {
update();
draw();
window.requestAnimationFrame(animate);
}
function update() {
for (let p of particles) {
p.rmove();
}
}
function draw() {
// clear screen
ctx.clearRect(0, 0, cwidth, cheight);
// define our single path
let last_color = -1;
for (let p of particles) {
let p_color = p.color;
if( p_color !== last_color ) {
paint();
last_color = p_color;
}
p.trace(ctx);
}
paint(); // the last
function paint() {
ctx.fillStyle = colors[ last_color ];
ctx.strokeStyle = colors[ (last_color + 1) % colors .length ];
ctx.stroke();
ctx.fill();
ctx.beginPath();
}
}
window.requestAnimationFrame( animate );
<canvas id="canvas"></canvas>
And nothing prevents you to generate chunks from these sorted particles so it looks more random.
var car;
var front;
var back;
var img = new Image();
img.src = 'https://cdn4.iconfinder.com/data/icons/transportation-2-4/60/transportation-2-flat-036-racing-car-top-512.png';
function startGame() {
car = new move(12, 20, "red", 600, 300);
pg.start();
}
var pg = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 1200;
this.canvas.height = 600;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateframe, 20);
window.addEventListener('keydown', function(e) {
e.preventDefault();
pg.keys = (pg.keys || []);
pg.keys[e.keyCode] = (e.type == "keydown");
})
window.addEventListener('keyup', function(e) {
pg.keys[e.keyCode] = (e.type == "keydown");
})
},
stop: function() {
clearInterval(this.interval);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function move(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speed = 0;
this.angle = 0;
this.moveAngle = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = pg.context;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.drawImage(img, this.width / -2, this.height / -2,20,40);
ctx.beginPath();
ctx.moveTo(this.width / -2, this.height / -2);
ctx.lineTo(this.width / -2, 30);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(this.width / -2, 30);
ctx.lineTo(13, (this.height / -2)+40);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(14, (this.height / -2)+40);
ctx.lineTo(14, this.height / -2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(14, this.height / -2);
ctx.lineTo(this.width / -2, this.height / -2);
ctx.stroke();
ctx.restore();
ctx.beginPath();
ctx.moveTo(300, 150);
ctx.lineTo(600, 800);
ctx.stroke();
}
this.newPos = function() {
this.angle += this.moveAngle * Math.PI / 180;
this.x += this.speed * Math.sin(this.angle);
this.y -= this.speed * Math.cos(this.angle);
}
}
function updateframe() {
pg.clear();
car.moveAngle = 0;
car.speed = 0;
if (pg.keys && pg.keys[37]) {
if (pg.keys && pg.keys[40]) {
car.moveAngle = 5;
}
if (pg.keys && pg.keys[38]) {
car.moveAngle = -5;
}
}
if (pg.keys && pg.keys[39]) {
if (pg.keys && pg.keys[40]) {
car.moveAngle = -5;
}
if (pg.keys && pg.keys[38]) {
car.moveAngle = 5;
}
}
if (pg.keys && pg.keys[38]) {
car.speed = 5;
}
if (pg.keys && pg.keys[40]) {
car.speed = -5;
}
car.newPos();
car.update();
}
startGame();
canvas {
border: 1px solid #d3d3d3;
background-color: #f1f1f1;
}
There is a car and a line, now i want to make an alert whenever the car get touched by the the line.
I know how to do this logically. like if i convert the car image in four lines (border) then use line intersection formula to get if there is a intersection but i am new to this canvas drawing and i cant figure how to get the border line equation of car.
update: i have made lines of border around the car, now i just need help with getting if these lines intersects with each other...
code is updated plz check it now...
one more thing, at this point there is only one line and i am going to add more lines in it... so i need a function to call it more often and get if the lines are intersecting with car border...
Note : use arrow keys to move car in snippet
I'm making an eCard with falling confetti and fireworks flying up from the bottom of the screen. The logic for both are almost identical:
I make an empty array for fireworks and confetti, and then fill them with objects, using my GetRandom(mix,max) function to create each one with a random value:
//fill confetti object array
for (i = 0; i < NUM_CONFETTI; i++) {
ConfettiArray.push(new Confetti(GetRandom(0,canvas.width), GetRandom(-200,-10), GetRandom(10,30), GetRandom(10,30), colorChoices[GetRandom(0,NUM_COLORS)], GetRandom(0,2*Math.PI),GetRandom(50,80)));
}
//fill firework object array
for (i = 0; i < NUM_FIREWORKS; i++) {
FireworkArray.push(new Firework(GetRandom(0,canvas.width), canvas.height, GetRandom(4,20), colorChoices[GetRandom(0,NUM_COLORS)], GetRandom(30,100), GetRandom(-10,10), GetRandom(50,200)));
}
Then they are drawn and updated in their specific ways, each one starting by setting context.fillStyle and .strokeStyle as the current object's color value:
//confetti.draw
Draw: function(x, y, width, height, color, rotationAngle) {
context.translate(x,y)
context.rotate(rotationAngle)
context.strokeStyle = color;
context.fillStyle = color;
context.beginPath();
context.rect(0, 0, width, height);
context.fill();
context.closePath();
context.resetTransform();
},
//firework.draw
Draw: function(x, y, lineWidth, color, speedY, swayX, blastRadius) {
context.fillStyle = color;
context.strokeStyle = color;
context.moveTo(x,y);
context.lineTo(x-swayX,y-speedY);
context.stroke();
},
Then they are each updated:
//confetti.update
Update: function(modifier) {
this.y = this.y + (this.fallSpeed * modifier);
if (this.y > canvas.height) {this.x = GetRandom(0,canvas.width);this.y = GetRandom(-100,0);}
},
//firework.update
Update: function(modifier) {
this.x = this.x - this.swayX;
this.y = this.y - this.speedY;
if (this.y < -10) {this.x = GetRandom(0,canvas.width);this.y = canvas.height;this.speedY = GetRandom(30,100);this.swayX = GetRandom(-10,10)}
},
I have scoured the code over and over and I just can't seem to get a reason as to why the confetti all has random sizes, angles, fallrates, and colors, AND the fireworks all have random swayX and speedY values but all the same colors. The full code is below if anyone wants to run it in full:
//james gossling multimedia for web design spring 2018
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d');
var GetRandom = function(min, max) {
//set mins and maxes for ball speed to change angle slightly after ball reset
min = min;
max = max;
return Math.floor(Math.random() * (max - min + 1) + min);
};
//game classes go here
var Background = function(context,color) {
this.context = context;
this.color = color;
};
Background.prototype = {
DrawBackground: function() {
//for testing
//console.log('here')
context.strokeStyle = this.color;
context.fillStyle = this.color;
context.beginPath();
context.rect(0,0,canvas.width,canvas.height);
context.stroke(); // invoke stroke
context.fill(); // invoke fill
context.closePath();
},
};
Confetti = function(x, y, width, height, color, rotationAngle, fallSpeed) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
this.rotationAngle = rotationAngle;
this.fallSpeed = fallSpeed;
};
Confetti.prototype = {
Draw: function(x, y, width, height, color, rotationAngle) {
context.translate(x,y)
context.rotate(rotationAngle)
context.strokeStyle = color;
context.fillStyle = color;
context.beginPath();
context.rect(0, 0, width, height);
context.fill();
context.closePath();
context.resetTransform();
},
GetX: function() {
return this.x;
},
GetY: function() {
return this.y;
},
GetWidth: function() {
return this.width;
},
GetHeight: function() {
return this.height;
},
GetColor: function() {
return this.color;
},
GetRotationAngle: function() {
return this.rotationAngle;
},
GetFallSpeed: function() {
return this.fallSpeed;
},
Update: function(modifier) {
this.y = this.y + (this.fallSpeed * modifier);
if (this.y > canvas.height) {this.x = GetRandom(0,canvas.width);this.y = GetRandom(-100,0);}
},
};
var DrawConfetti = function() {
for (i = 0; i < NUM_CONFETTI; i++) {
ConfettiArray[i].Draw(ConfettiArray[i].GetX(),ConfettiArray[i].GetY(),ConfettiArray[i].GetWidth(),ConfettiArray[i].GetHeight(),ConfettiArray[i].GetColor(),ConfettiArray[i].GetRotationAngle());
}
}
var UpdateConfetti = function(modifier) {
for (i = 0; i < NUM_CONFETTI; i++) {
ConfettiArray[i].Update(modifier);
}
};
Firework = function(x, y, lineWidth, color, speedY, swayX, blastRadius) {
this.x = x;
this.y = y;
this.lineWidth = lineWidth;
this.color = color;
this.speedY = speedY;
this.swayX = swayX;
//this.rotationAngle = rotationAngle;
this.blastRadius = blastRadius;
};
Firework.prototype = {
Draw: function(x, y, lineWidth, color, speedY, swayX, blastRadius) {
context.fillStyle = color;
context.strokeStyle = color;
context.moveTo(x,y);
context.lineTo(x-swayX,y-speedY);
context.stroke();
},
GetX: function() {
return this.x;
},
GetY: function() {
return this.y;
},
GetLineWidth: function() {
return this.lineWidth;
},
GetColor: function() {
return this.color;
},
GetSpeedY: function() {
return this.speedY;
},
GetSwayX: function() {
return this.swayX;
},
GetRotationAngle: function() {
return this.rotationAngle;
},
GetBlastRadius: function() {
return this.blastRadius;
},
Update: function(modifier) {
this.x = this.x - this.swayX;
this.y = this.y - this.speedY;
if (this.y < -10) {this.x = GetRandom(0,canvas.width);this.y = canvas.height;this.speedY = GetRandom(30,100);this.swayX = GetRandom(-10,10)}
},
};
var DrawFireworks = function() {
//create confetti object array
for (i = 0; i < NUM_FIREWORKS; i++) {
FireworkArray[i].Draw(FireworkArray[i].GetX(), FireworkArray[i].GetY(), FireworkArray[i].GetLineWidth(), FireworkArray[i].GetColor(), FireworkArray[i].GetSpeedY(), FireworkArray[i].GetSwayX(), FireworkArray[i].GetBlastRadius());
}
};
var UpdateFireworks = function(modifier) {
for (i = 0; i < NUM_FIREWORKS; i++) {
FireworkArray[i].Update(modifier);
}
};
UncleSam = function() {
};
UncleSam.prototype = {
};
Text = function(context,title,x,y,color) {
this.context = context;
this.title = title;
this.x = x;
this.y = y;
this.color = color;
this.lineWidth = 2;
this.lineHeight =(this.context.measureText('W').width) + Math.pow((this.context.measureText('W').width),2);
this.font = '70pt Times New Roman';
//GET METRICS
this.metrics = this.context.measureText(this.title);
this.width = this.metrics.width;
this.maxWidth = canvas.width;
this.GradChangeSpeed = .5;
this.GradChangeOffset = .3;
this.debugCounter = 0;
};
Text.prototype = {
SetAttributes: function() {
context.font
context.textAlign = 'center'
context.font = this.font;
context.lineWidth = this.lineWidth;
context.lineHeight = this.GetHeight;
context.fillStyle = TextGradient;
context.strokeStyle = this.color;
//shadow attributes
context.shadowColor = undefined;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 0;
},
//GETTTERS SETTERS///////////////////
GetTextX: function() {
return this.x;
},
GetTextY: function() {
return this.y;
},
GetTextLineHeight: function() {
return this.lineHeight;
},
GetTextWidth: function() {
return this.width;
},
//GETTERS SETTERS////////////
SetColorGradient: function() {
TextGradient.addColorStop(0,"red");
TextGradient.addColorStop(this.GradChangeOffset,"white");
TextGradient.addColorStop(.8,"blue");
},
Update: function(modifier) {
this.GradChangeOffset = this.GradChangeOffset + (this.GradChangeSpeed * modifier);
//GRADIENT DEBUGGERS
context.strokeText(this.GradChangeOffset.toFixed(2),canvas.width/2,canvas.height/2);
//GRADIENT DEBUGGERS
if (this.GradChangeOffset > .7 || this.GradChangeOffset < .2) {this.GradChangeSpeed = -(this.GradChangeSpeed);}
},
DrawText: function() {
this.WrapText(this.context, this.title, this.x, this.y, this.maxWidth, this.lineHeight);
},
WrapText: function(context, title, x, y, maxWidth, lineHeight) {
var words = title.split(' ');
var line = '';
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
context.fillText(line, x, y);
context.strokeText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
context.fillText(line, x, y);
context.strokeText(line, x, y);
},
};
//other functions
var ClearScreen = function() {
context.clearRect(0, 0, canvas.width, canvas.height);
};
var DrawObjects = function() {
ClearScreen();
Background1.DrawBackground();
Text1.SetAttributes();
Text1.SetColorGradient();
Text1.DrawText();
DrawFireworks();
DrawConfetti();
};
var UpdateObjects = function(modifier) {
//Text1.Update(modifier);
UpdateFireworks(modifier);
UpdateConfetti(modifier);
};
var Reset = function() {
};
//MAIN GAME LOOP FXN///////////////////
// The main game loop
var main = function() {
var now = Date.now();
var delta = now - then;
var frameRateAdjust = delta/1000;
DrawObjects(frameRateAdjust);
UpdateObjects(frameRateAdjust);
then = now;
//possibly do RESET
// Request to do this again ASAP
requestAnimationFrame(main);
};
// Cross-browser support for requestAnimationFrame
var w = window;
requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
//START ECARD
//create variables
var then = Date.now();
var colorChoices = ["red","white","blue","deeppink","orange","limegreen","darkred"];
var NUM_CONFETTI = 25;
var NUM_FIREWORKS = 10;
var NUM_COLORS = colorChoices.length;
//create arrays for objects
var ConfettiArray = [];
var FireworkArray = [];
//create objects
Background1 = new Background(context,'black');
var Text1 = new Text(context,'Happy 4th of July!',canvas.width/2,canvas.height/10,'red');
var TextGradient = context.createLinearGradient(Text1.GetTextX(),Text1.GetTextY(),Text1.GetTextX()+Text1.GetTextWidth(),Text1.GetTextY()+Text1.GetTextLineHeight()/2)
//fill confetti object array
for (i = 0; i < NUM_CONFETTI; i++) {
ConfettiArray.push(new Confetti(GetRandom(0,canvas.width), GetRandom(-200,-10), GetRandom(10,30), GetRandom(10,30), colorChoices[GetRandom(0,NUM_COLORS)], GetRandom(0,2*Math.PI),GetRandom(50,80)));
}
//fill firework object array
for (i = 0; i < NUM_FIREWORKS; i++) {
FireworkArray.push(new Firework(GetRandom(0,canvas.width), canvas.height, GetRandom(4,20), colorChoices[GetRandom(0,NUM_COLORS)], GetRandom(30,100), GetRandom(-10,10), GetRandom(50,200)));
}
//start eCard animations
Reset();
main();
Begin a new path with ctx.beginPath()
The reason is because you are creating one path out of all the firework lines.
//firework.draw
Draw: function(x, y, lineWidth, color, speedY, swayX, blastRadius) {
context.fillStyle = color;
context.strokeStyle = color; // sets the stroke color
context.moveTo(x,y); // adds another line
context.lineTo(x-swayX,y-speedY);
context.stroke(); // stroke all lines up to this point
},
Each time stroke is called all the firework lines are render up to that point.
The result is that the last stroke call draws all the lines again the color of the last firework.
The fix
The fix is simple. Just start a new path with ctx.beginPath()
//firework.draw
Draw: function(x, y, lineWidth, color, speedY, swayX, blastRadius) {
context.fillStyle = color;
context.strokeStyle = color; // sets the stroke color
//========================================================
context.beginPath(); // start a new path
//========================================================
context.moveTo(x,y);
context.lineTo(x-swayX,y-speedY);
context.stroke();
},