Related
Can somebody fix it script to make it works properly?
What I expects:
Run script
Click at the canvas to set target (circle)
Object (triangle) starts to rotate and move towards to target (circle)
Change target at any time
How it works:
Sometimes object rotates correctly, sometimes isn't
Looks like one half sphere works well, another isn't
Thanks!
// prepare 2d context
const c = window.document.body.appendChild(window.document.createElement('canvas'))
.getContext('2d');
c.canvas.addEventListener('click', e => tgt = { x: e.offsetX, y: e.offsetY });
rate = 75 // updates delay
w = c.canvas.width;
h = c.canvas.height;
pi2 = Math.PI * 2;
// object that moves towards the target
obj = {
x: 20,
y: 20,
a: 0, // angle
};
// target
tgt = undefined;
// main loop
setInterval(() => {
c.fillStyle = 'black';
c.fillRect(0, 0, w, h);
// update object state
if (tgt) {
// draw target
c.beginPath();
c.arc(tgt.x, tgt.y, 2, 0, pi2);
c.closePath();
c.strokeStyle = 'red';
c.stroke();
// update object position
// vector from obj to tgt
dx = tgt.x - obj.x;
dy = tgt.y - obj.y;
// normalize
l = Math.sqrt(dx*dx + dy*dy);
dnx = (dx / l);// * 0.2;
dny = (dy / l);// * 0.2;
// update object position
obj.x += dnx;
obj.y += dny;
// angle between +x and tgt
a = Math.atan2(0 * dx - 1 * dy, 1 * dx + 0 * dy);
// update object angle
obj.a += -a * 0.04;
}
// draw object
c.translate(obj.x, obj.y);
c.rotate(obj.a);
c.beginPath();
c.moveTo(5, 0);
c.lineTo(-5, 4);
c.lineTo(-5, -4);
//c.lineTo(3, 0);
c.closePath();
c.strokeStyle = 'red';
c.stroke();
c.rotate(-obj.a);
c.translate(-obj.x, -obj.y);
}, rate);
This turned out to be a bit more challenging than I first thought and I ended up just re-writing the code.
The challenges:
Ensure the ship only rotated to the exact point of target. This required me to compare the two angle from the ship current position to where we want it to go.
Ensure the target did not rotate past the target and the ship did not translate past the target. This required some buffer space for each because when animating having this.x === this.x when an object is moving is very rare to happen so we need some room for the logic to work.
Ensure the ship turned in the shortest direction to the target.
I have added notes in the code to better explain. Hopefully you can implement this into yours or use it as is. Oh and you can change the movement speed and rotation speed as you see fit.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let mouse = { x: 20, y: 20 };
let canvasBounds = canvas.getBoundingClientRect();
let target;
canvas.addEventListener("mousedown", (e) => {
mouse.x = e.x - canvasBounds.x;
mouse.y = e.y - canvasBounds.y;
target = new Target();
});
class Ship {
constructor() {
this.x = 20;
this.y = 20;
this.ptA = { x: 15, y: 0 };
this.ptB = { x: -15, y: 10 };
this.ptC = { x: -15, y: -10 };
this.color = "red";
this.angle1 = 0;
this.angle2 = 0;
this.dir = 1;
}
draw() {
ctx.save();
//use translate to move the ship
ctx.translate(this.x, this.y);
//angle1 is the angle from the ship to the target point
//angle2 is the ships current rotation angle. Once they equal each other then the rotation stops. When you click somewhere else they are no longer equal and the ship will rotate again.
if (!this.direction(this.angle1, this.angle2)) {
//see direction() method for more info on this
if (this.dir == 1) {
this.angle2 += 0.05; //change rotation speed here
} else if (this.dir == 0) {
this.angle2 -= 0.05; //change rotation speed here
}
} else {
this.angle2 = this.angle1;
}
ctx.rotate(this.angle2);
ctx.beginPath();
ctx.strokeStyle = this.color;
ctx.moveTo(this.ptA.x, this.ptA.y);
ctx.lineTo(this.ptB.x, this.ptB.y);
ctx.lineTo(this.ptC.x, this.ptC.y);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
driveToTarget() {
//get angle to mouse click
this.angle1 = Math.atan2(mouse.y - this.y, mouse.x - this.x);
//normalize vector
let vecX = mouse.x - this.x;
let vecY = mouse.y - this.y;
let dist = Math.hypot(vecX, vecY);
vecX /= dist;
vecY /= dist;
//Prevent continuous x and y increment by checking if either vec == 0
if (vecX != 0 || vecY != 0) {
//then also give the ship a little buffer incase it passes the given point it doesn't turn back around. This allows time for it to stop if you increase the speed.
if (
this.x >= mouse.x + 3 ||
this.x <= mouse.x - 3 ||
this.y >= mouse.y + 3 ||
this.y <= mouse.y - 3
) {
this.x += vecX; //multiple VecX by n to increase speed (vecX*2)
this.y += vecY; //multiple VecY by n to increase speed (vecY*2)
}
}
}
direction(ang1, ang2) {
//converts rads to degrees and ensures we get numbers from 0-359
let a1 = ang1 * (180 / Math.PI);
if (a1 < 0) {
a1 += 360;
}
let a2 = ang2 * (180 / Math.PI);
if (a2 < 0) {
a2 += 360;
}
//checks whether the target is on the right or left side of the ship.
//We use then to ensure it turns in the shortest direction
if ((360 + a1 - a2) % 360 > 180) {
this.dir = 0;
} else {
this.dir = 1;
}
//Because of animation timeframes there is a chance the ship could turn past the target if rotating too fast. This gives the ship a 1 degree buffer to either side of the target to determine if it is pointed in the right direction.
//We then correct it to the exact degrees in the draw() method above once the if statment defaults to 'else'
if (
Math.trunc(a2) <= Math.trunc(a1) + 1 &&
Math.trunc(a2) >= Math.trunc(a1) - 1
) {
return true;
}
return false;
}
}
let ship = new Ship();
class Target {
constructor() {
this.x = mouse.x;
this.y = mouse.y;
this.r = 3;
this.color = "red";
}
draw() {
ctx.strokeStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
ctx.stroke();
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, canvas.width, canvas.height);
ship.draw();
ship.driveToTarget();
if (target) {
target.draw();
}
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
I have a Rectangle class for drawing to HTML Canvas. It has a rotation property that gets applied in its draw method. If the user drags within the canvas, a selection marquee is being drawn. How can I set the Rectangle's active attribute to true when the Rectangle is within the selection marquee using math? This is a problem I'm having in another language & context so I do not have all of Canvas' methods available to me there (e.g. isPointInPath).
I found a StackOverflow post about finding Mouse position within rotated rectangle in HTML5 Canvas, which I am implementing in the Rectangle method checkHit. It doesn't account for the selection marquee, however. It's just looking at the mouse X & Y, which is still off. The light blue dot is the origin around which the rectangle is being rotated. Please let me know if anything is unclear. Thank you.
class Rectangle
{
constructor(x, y, width, height, rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.xOffset = this.x + this.width/2;
this.yOffset = this.y + ((this.y+this.height)/2);
this.rotation = rotation;
this.active = false;
}
checkHit()
{
// translate mouse point values to origin
let originX = this.xOffset;
let originY = this.yOffset;
let dx = marquee[2] - originX;
let dy = marquee[3] - originY;
// distance between the point and the center of the rectangle
let h1 = Math.sqrt(dx*dx + dy*dy);
let currA = Math.atan2(dy,dx);
// Angle of point rotated around origin of rectangle in opposition
let newA = currA - this.rotation;
// New position of mouse point when rotated
let x2 = Math.cos(newA) * h1;
let y2 = Math.sin(newA) * h1;
// Check relative to center of rectangle
if (x2 > -0.5 * this.width && x2 < 0.5 * this.width && y2 > -0.5 * this.height && y2 < 0.5 * this.height){
this.active = true;
} else {
this.active = false;
}
}
draw()
{
ctx.save();
ctx.translate(this.xOffset, this.yOffset);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 2 * Math.PI, true);
ctx.fill();
ctx.rotate(this.rotation * Math.PI / 180);
ctx.translate(-this.xOffset, -this.yOffset);
if (this.active)
{
ctx.fillStyle = 'rgba(255,0,0,0.5)';
} else {
ctx.fillStyle = 'rgba(0,0,255,0.5)';
}
ctx.beginPath();
ctx.fillRect(this.x, this.y, this.width, this.y+this.height);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;
var rect = new Rectangle(50,50,90,30,45);
var marquee = [-3,-3,-3,-3];
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
var start_x,start_y;
let draw = () => {
ctx.clearRect(0,0, canvas.width, canvas.height);
//rect.rotation+=1;
rect.draw();
ctx.fillStyle = "rgba(200, 200, 255, 0.5)";
ctx.fillRect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.strokeStyle = "white"
ctx.lineWidth = 1;
ctx.rect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.stroke()
raf = window.requestAnimationFrame(draw);
}
let dragStart = (e) =>
{
start_x = parseInt(e.clientX-offsetX);
start_y = parseInt(e.clientY-offsetY);
marquee = [start_x,start_y,0,0];
canvas.addEventListener("mousemove", drag);
}
let drag = (e) =>
{
let mouseX = parseInt(e.clientX-offsetX);
let mouseY = parseInt(e.clientY-offsetY);
marquee[2] = mouseX - start_x;
marquee[3] = mouseY - start_y;
rect.checkHit();
}
let dragEnd = (e) =>
{
marquee = [-10,-10,-10,-10];
canvas.removeEventListener("mousemove", drag);
}
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('mouseup', dragEnd);
raf = window.requestAnimationFrame(draw);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>
Do convex polygons overlap
Rectangles are convex polygons.
Rectangle and marquee each have 4 points (corners) that define 4 edges (lines segments) connecting the points.
This solution works for all convex irregular polygons with 3 or more sides.
Points and edges must be sequential either Clockwise CW of Count Clockwise CCW
Test points
If any point of one poly is inside the other polygon then they must overlap. See example function isInside
To check if point is inside a polygon, get cross product of, edge start to the point as vector, and the edge as a vector.
If all cross products are >= 0 (to the left of) then there is overlap (for CW polygon). If polygon is CCW then if all cross products are <= 0 (to the right of) there is overlap.
It is possible to overlap without any points inside the other poly.
Test Edges
If any of the edges from one poly crosses any of the edges from the other then there must be overlap. The function doLinesIntercept returns true if two line segments intercept.
Complete test
Function isPolyOver(poly1, poly2) will return true if there is overlap of the two polys.
A polygon is defined by a set of Point's and Lines's connecting the points.
The polygon can be irregular, meaning that each edge can be any length > 0
Do not pass polygons with an edge of length === 0 or will not work.
Added
I added the function Rectangle.toPoints that transforms the rectangle and returning a set of 4 points (corners).
Example
Example is a copy of your code working using the above methods.
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('mouseup', dragEnd);
requestAnimationFrame(draw);
const Point = (x = 0, y = 0) => ({x, y, set(x,y){ this.x = x; this.y = y }});
const Line = (p1, p2) => ({p1, p2});
const selector = { points: [Point(), Point(), Point(), Point()] }
selector.lines = [
Line(selector.points[0], selector.points[1]),
Line(selector.points[1], selector.points[2]),
Line(selector.points[2], selector.points[3]),
Line(selector.points[3], selector.points[0])
];
const rectangle = { points: [Point(), Point(), Point(), Point()] }
rectangle.lines = [
Line(rectangle.points[0], rectangle.points[1]),
Line(rectangle.points[1], rectangle.points[2]),
Line(rectangle.points[2], rectangle.points[3]),
Line(rectangle.points[3], rectangle.points[0])
];
function isInside(point, points) {
var i = 0, p1 = points[points.length - 1];
while (i < points.length) {
const p2 = points[i++];
if ((p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x) < 0) { return false }
p1 = p2;
}
return true;
}
function doLinesIntercept(l1, l2) {
const v1x = l1.p2.x - l1.p1.x;
const v1y = l1.p2.y - l1.p1.y;
const v2x = l2.p2.x - l2.p1.x;
const v2y = l2.p2.y - l2.p1.y;
const c = v1x * v2y - v1y * v2x;
if(c !== 0){
const u = (v2x * (l1.p1.y - l2.p1.y) - v2y * (l1.p1.x - l2.p1.x)) / c;
if(u >= 0 && u <= 1){
const u = (v1x * (l1.p1.y - l2.p1.y) - v1y * (l1.p1.x - l2.p1.x)) / c;
return u >= 0 && u <= 1;
}
}
return false;
}
function isPolyOver(p1, p2) { // is poly p2 under any part of poly p1
if (p2.points.some(p => isInside(p, p1.points))) { return true };
if (p1.points.some(p => isInside(p, p2.points))) { return true };
return p1.lines.some(l1 => p2.lines.some(l2 => doLinesIntercept(l1, l2)));
}
const ctx = canvas.getContext("2d");
var dragging = false;
const marquee = [0,0,0,0];
const rotate = 0.01;
var startX, startY, hasSize = false;
const BB = canvas.getBoundingClientRect();
const offsetX = BB.left;
const offsetY = BB.top;
class Rectangle {
constructor(x, y, width, height, rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.rotation = rotation;
this.active = false;
}
toPoints(points = [Point(), Point(), Point(), Point()]) {
const xAx = Math.cos(this.rotation) / 2;
const xAy = Math.sin(this.rotation) / 2;
const x = this.x, y = this.y;
const w = this.width, h = this.height;
points[0].set(-w * xAx + h * xAy + x, -w * xAy - h * xAx + y);
points[1].set( w * xAx + h * xAy + x, w * xAy - h * xAx + y);
points[2].set( w * xAx - h * xAy + x, w * xAy + h * xAx + y);
points[3].set(-w * xAx - h * xAy + x, -w * xAy + h * xAx + y);
}
draw() {
ctx.setTransform(1, 0, 0, 1, this.x, this.y);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.strokeStyle = this.active ? 'rgba(255,0,0,1)' : 'rgba(0,0,255,1)';
ctx.lineWidth = this.active ? 3 : 1;
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 2 * Math.PI, true);
ctx.fill();
ctx.rotate(this.rotation);
ctx.beginPath();
ctx.rect(-this.width / 2, - this.height / 2, this.width, this.height);
ctx.stroke();
}
}
function draw(){
rect.rotation += rotate;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect.draw();
drawSelector();
requestAnimationFrame(draw);
}
function drawSelector() {
if (dragging && hasSize) {
rect.toPoints(rectangle.points);
rect.active = isPolyOver(selector, rectangle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = "rgba(200, 200, 255, 0.5)";
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.rect(...marquee);
ctx.fill();
ctx.stroke();
} else {
rect.active = false;
}
}
function dragStart(e) {
startX = e.clientX - offsetX;
startY = e.clientY - offsetY;
drag(e);
canvas.addEventListener("mousemove", drag);
}
function drag(e) {
dragging = true;
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
const left = Math.min(startX, x);
const top = Math.min(startY, y);
const w = Math.max(startX, x) - left;
const h = Math.max(startY, y) - top;
marquee[0] = left;
marquee[1] = top;
marquee[2] = w;
marquee[3] = h;
if (w > 0 || h > 0) {
hasSize = true;
selector.points[0].set(left, top);
selector.points[1].set(left + w, top);
selector.points[2].set(left + w, top + h);
selector.points[3].set(left , top + h);
} else {
hasSize = false;
}
}
function dragEnd(e) {
dragging = false;
rect.active = false;
canvas.removeEventListener("mousemove", drag);
}
const rect = new Rectangle(canvas.width / 2, canvas.height / 2, 90, 90, Math.PI / 4);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>
I made a simple game. The goal is to stay alive by avoiding the lasers. However, every time the player moves across the laser warning, it erases the laser, making it look weird. I have already tried redrawing the warnings, but every time that happens it doesn't work and makes it buggier.
<!DOCTYPE html>
<html>
<head>
<title>
Drift 2
</title>
</head>
<body>
<center>
<h1>Drift 2</h1>
<h2>by Milesman34</h2>
<canvas id="canvas" width="400" height="400"></canvas>
<strong><p id="score">Score</p></strong>
</center>
<script src="https://code.jquery.com/jquery-2.1.0.js"></script>
<script>
//MARK: Set up the canvas + canvas variables and draws the border + background
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
var blockSize = 20;
var widthInBlocks = width / blockSize;
var heightInBlocks = height / blockSize;
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(0, 0, width, height);
//MARK: Defines arrays and variables
var shooterArray = [];
var score = 0;
//MARK: Defines functions
function getRandomFromInterval(interval) {
return Math.floor(Math.random() * interval);
};
//MARK: Defines game over function
var gameEnd = 0;
function gameOver () {
setTimeout (function () {
gameEnd = 1;
clearInterval (scoreEffects);
clearInterval (fireEffects);
ctx.clearRect(blockSize, blockSize, width - (blockSize * 2), height - (blockSize * 2))
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(blockSize, blockSize, width - (blockSize * 2), height - (blockSize * 2));
ctx.font = "60px Courier";
ctx.fillStyle = "Black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Game Over", width / 2, height / 2);
}, 149);
};
//MARK: Defines the player
function Player (x, y) {
this.x = x;
this.y = y;
};
//MARK: Defines the function that draws the player
Player.prototype.draw = function () {
ctx.fillStyle = "rgb(185, 185, 185)";
ctx.fillRect(this.x, this.y, blockSize, blockSize);
};
var player = new Player(width / 2, height / 2);
player.draw();
//MARK: Defines the functions that move the player
Player.prototype.moveLeft = function () {
ctx.clearRect(this.x, this.y, blockSize, blockSize);
this.x = this.x - 20;
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(this.x + 20, this.y, blockSize, blockSize);
};
Player.prototype.moveRight = function () {
ctx.clearRect(this.x, this.y, blockSize, blockSize);
this.x = this.x + 20;
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(this.x - 20, this.y, blockSize, blockSize);
};
Player.prototype.moveUp = function () {
ctx.clearRect(this.x, this.y, blockSize, blockSize);
this.y = this.y - 20;
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(this.x, this.y + 20, blockSize, blockSize);
};
Player.prototype.moveDown = function () {
ctx.clearRect(this.x, this.y, blockSize, blockSize);
this.y = this.y + 20;
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(this.x, this.y - 20, blockSize, blockSize);
};
Player.prototype.checkWallCollision = function () {
if (this.x === 0 || this.x === width - 20 || this.y === 0 || this.y === height - 20) {
gameOver();
};
};
//MARK: Defines the Shooter
function Shooter (x, y, direction) {
this.x = x;
this.y = y;
if (["left", "right", "up", "down"].indexOf(direction) != -1) {
this.direction = direction;
};
shooterArray.push(this);
};
//MARK: Defines the function that draws the Shooter
Shooter.prototype.draw = function () {
ctx.fillStyle = "rgb(185, 185, 185)";
ctx.fillRect(this.x, this.y, blockSize, blockSize);
};
//MARK: Defines the function that fires the Shooter
var timeoutID = null;
function fireLeftRight(y) {
if (gameEnd === 0) {
ctx.fillStyle = "Red";
ctx.fillRect(blockSize, y, width - (blockSize * 2), blockSize);
if (player.y === y) {
gameOver();
};
};
};
function fireLeftRightWarn(y) {
ctx.fillStyle = "Red";
for (i = 1;i < widthInBlocks - 1;i++) {
ctx.fillRect(i * blockSize + (blockSize / 4), y + (blockSize / 4 * 1.5), blockSize / 2, blockSize / 4);
};
timeoutID2 = setTimeout (function () {
clearTimeout (timeoutID);
timeoutID = setTimeout (fireLeftRight(y), 100);
}, 600);
};
function fireUpDown(x) {
if (gameEnd === 0) {
ctx.fillStyle = "Red";
ctx.fillRect(x, blockSize, blockSize, height - (blockSize * 2));
if (player.x === x) {
gameOver();
};
};
};
function fireUpDownWarn(x) {
ctx.fillStyle = "Red";
for (i = 1;i < heightInBlocks - 1;i++) {
ctx.fillRect(x + (blockSize / 4 * 1.5), i * blockSize + (blockSize / 4), blockSize / 4, blockSize / 2);
};
timeoutID2 = setTimeout (function () {
clearTimeout (timeoutID);
timeoutID = setTimeout (fireUpDown(x))
}, 600);
};
Shooter.prototype.fire = function () {
if (this.direction === "left" || this.direction === "right") {
timeoutID = setTimeout (fireLeftRightWarn(this.y), 1);
} else {
timeoutID = setTimeout (fireUpDownWarn(this.x), 1)
};
};
//MARK: Creates the required shooters
for (i = 1;i < heightInBlocks - 1;i++) {
new Shooter(0, i * blockSize, "right");
};
for (i = 1;i < heightInBlocks - 1;i++) {
new Shooter(width - blockSize, i * blockSize, "left");
};
for (i = 1;i < widthInBlocks - 1;i++) {
new Shooter(i * blockSize, 0, "down");
};
for (i = 1;i < widthInBlocks - 1;i++) {
new Shooter(i * blockSize, height - blockSize, "up")
};
for (i = 0;i < shooterArray.length;i++) {
shooterArray[i].draw();
};
ctx.fillStyle = "rgb(185, 185, 185)";
ctx.fillRect(0, 0, blockSize, blockSize);
ctx.fillRect(width - blockSize, 0, blockSize, blockSize);
ctx.fillRect(0, height - blockSize, blockSize, blockSize);
ctx.fillRect(width - blockSize, height - blockSize, blockSize, blockSize);
//MARK: Draws the score
function drawScore () {
$("#score").text("Score: " + Math.floor(score));
};
//MARK: Convert keycodes to directions
var directions = {
37: "left",
38: "up",
39: "right",
40: "down"
};
//MARK: This is the interval loop
var scoreEffects = setInterval (function () {
score += 0.1;
drawScore();
player.draw();
player.checkWallCollision();
}, 100);
$("body").keyup(function (event) {
if (gameEnd != 1) {
var moveDir = directions[event.keyCode];
if (moveDir === "left") {
player.moveLeft();
} else if (moveDir === "right") {
player.moveRight();
} else if (moveDir === "up") {
player.moveUp();
} else if (moveDir === "down") {
player.moveDown();
};
};
});
var fireEffects = setInterval (function () {
ctx.clearRect(blockSize, blockSize, width - (blockSize * 2), height - (blockSize * 2))
ctx.fillStyle = "rgb(225, 225, 225)";
ctx.fillRect(blockSize, blockSize, width - (blockSize * 2), height - (blockSize * 2));
ctx.fillStyle = "rgb(185, 185, 185)";
ctx.fillRect(0, 0, blockSize, blockSize);
ctx.fillRect(width - blockSize, 0, blockSize, blockSize);
ctx.fillRect(0, height - blockSize, blockSize, blockSize);
ctx.fillRect(width - blockSize, height - blockSize, blockSize, blockSize);
for (i = 0;i < shooterArray.length;i++) {
if (getRandomFromInterval(30) === 0) {
shooterArray[i].fire();
};
};
}, 750);
</script>
Working project here: https://jsfiddle.net/ay7kp7yb/1/
I think the best approach to solve this problem is to work with the classic pattern. That way all events that occur in the game need to be chained to a main loop (see: Game Loop).
something like:
while (true)
{
processInputs();
updateObjects();
render();
}
In your code some events happening independently with setTimeout / setInterval, it becomes very complex to "align" events.
I recommend you to use two combined techniques, the first is to centralize all events in a single loop (main game loop) and the second is to create a set of states to control the game.
Game states:
// game states
var STATE_TITLE_SCREEN = 0;
var STATE_GAME_STARTING = 1;
var STATE_RUNNING = 2;
var STATE_PLAYER_DYING = 3;
var STATE_GAME_OVER_SCREEN = 4;
// current state
var gameState = STATE_TITLE_SCREEN;
// time of the current state
var gameStateTime = 0;
// change the state and reset the time of last state
function setGameState(state) {
gameState = state;
gameStateTime = 0;
}
Main loop:
var minIteractionTime = 10;
var mainGameLoop = setInterval (function () {
switch (gameState) {
case STATE_TITLE_SCREEN:
// Title screen (on press space bar, change gameState to STATE_GAME_STARTING)
break;
case STATE_GAME_STARTING:
// Starting game, reset variables, countdown to start, etc...
score = 0.0;
// run the game after 5s
if (gameStateTime>5000)
setGameState(STATE_RUNNING);
break;
case STATE_RUNNING:
score += (0.1/100.0); // 0.1 points after 100 miliseconds
// CLEAR THE SCREEN HERE
drawScore();
player.draw(); // draw the player ONLY here
laserWarning.draw(); // Draws the warnings AFTER draw the player
laserbeam.burn(player); // try to burn the player
break;
case STATE_PLAYER_DYING:
// player dying animation..
// after 5s, change show the game over screen
if (gameStateTime>5000)
setGameState(STATE_GAME_OVER_SCREEN);
break;
case STATE_GAME_OVER_SCREEN:
// draw the game over screen here
// after 5s returns to the title screen
if (gameStateTime>5000)
setGameState(STATE_TITLE_SCREEN);
break;
}
// add the loop time in the gameStateTime variable
gameStateTime += minIteractionTime;
}, minIteractionTime);
LaserWarning class:
LaserWarning.prototype.draw = function () {
this.warningTime += minIteractionTime;
if (this.warningTime>100) {
// draw the warning only after firsts 100ms
}
if (this.warningTime>=750) { // warning time
// timer reset
this.warningTime = 0;
}
}
LaserBeam class:
Laserbeam.prototype.burn = function (player) {
this.warmingUpTime += minIteractionTime;
if (this.warmingUpTime>=750) { // same time of warning
// draw the laser
// check if burns the player
if (player.checkWallCollision()) {
setGameState(STATE_PLAYER_DYING);
}
}
}
Input iteractions:
var gameKeys = {
37: "left",
38: "up",
39: "right",
40: "down",
32: "space"
};
// for all iteractions, check the current state
$("body").keyup(function (event) {
var lastKey = gameKeys[event.keyCode];
switch (gameState) {
case STATE_RUNNING:
if (lastKey === "left") {
player.moveLeft();
} else if (lastKey === "right") {
player.moveRight();
} else if (lastKey === "up") {
player.moveUp();
} else if (lastKey === "down") {
player.moveDown();
};
break;
case STATE_TITLE_SCREEN:
if (lastKey=="space")
setGameState(STATE_GAME_STARTING);
break;
}
});
You should only adjust the positioning variables when moving the player and do not redraw it in the new position, this will happen in player.draw ();, in the STATE_RUNNING, before draw the warning.
If the user press the right arrow two times in less than 10ms (minIteractionTime) the player will be drawn only once! If you need to be more precise just reduce the value of minIteractionTime.
The variable minIteractionTime controls the framerate.
I want to make blur erasing and blur drawing effect in JQuery or Javascript. I have written the code for erasing the blurred image when I hover it and to reveal the unblurred image. Here's the screenshot.
I am able to erase the blur image when I hover over the image, but I couldn't redraw the blurred image at the same position, may be like after 500ms. How do I redraw the blur again over where I hovered with my mouse?
Here's the code for reference:
<!DOCTYPE html>
<html>
<head>
<title>Blur Testing</title>
<meta name="content-type" content="text/html; charset=UTF-8">
<style>
body {
margin: 0;
}
#item {
background: url("http://i66.tinypic.com/2z6uq9f.jpg");
background-size: cover;
background-position: center;
}
</style>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" type="text/javascript"></script>
</head>
<body>
<canvas id="item"></canvas>
<script>
var canvas = document.getElementById('item');
var ctx = canvas.getContext('2d'),
img = new Image,
radius = 30;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
$('#item').css({
"-webkit-filter": "blur(0px)",
"filter": "blur(0px)"
});
$(img).on('load', function () {
$('#item').mouseover(function (e) {
erase(getXY(e));
}).mousemove(function (e) {
erase(getXY(e));
//setTimeout(redraw(getXY(e)), 400);
});
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = 'destination-out';
});
img.src = 'http://i64.tinypic.com/14mt7yx.jpg';
img.width = window.width;
img.height = window.height;
function getXY(e) {
var r = $('#item')[0].getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top};
}
// function redraw(pos) {
// ctx.globalCompositeOperation = 'source-in';
// ctx.beginPath();
// ctx.arc(pos.x, pos.y, radius, 0, 2 * Math.PI);
// ctx.closePath();
// ctx.fill();
// };
function erase(pos) {
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
}
</script>
</body>
</html>
The following is a quick modification on Alpha Mask Filter question. I have simply reversed the blur and sharp images. See the linked question for details.
The extra bit to fade out the blur mask
blurMaskFadeCounter += 1;
if((blurMaskFadeCounter % blurMaskFadeRate) === 0){
maskImage.ctx.globalCompositeOperation = "destination-out";
maskImage.ctx.fillStyle = "#000";
maskImage.ctx.globalAlpha = 0.1;
maskImage.ctx.fillRect(0,0,maskImage.width,maskImage.height);
maskImage.ctx.globalAlpha = 1;
maskImage.ctx.globalCompositeOperation = "source-over";
}
Simply draws over the mask with destination-out composition and alpha set low. It is timed to every so many frames to slow it down. If you set alpha below 0.1 you get some pixels that wont completely clear so the frame skipping give a slower response
var imageLoadedCount = 0;
var error = false;
var maskImage;
var flowerImage;
var flowerImageBlur;
/** ImageTools.js begin **/
var imageTools = (function () {
var tools = {
canvas : function (width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var image = this.canvas(width, height);
image.ctx = image.getContext("2d");
return image;
},
loadImage : function (url, callback) {
var image = new Image();
image.src = url;
image.addEventListener('load', callback);
image.addEventListener('error', callback);
return image;
}
};
return tools;
})();
var mouse;
var demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
/** MouseFull.js begin **/
if(typeof mouse !== "undefined"){ // if the mouse exists
if( mouse.removeMouse !== undefined){
mouse.removeMouse(); // remove previouse events
}
}else{
var mouse;
}
var canvasMouseCallBack = undefined; // if needed
mouse = (function(){
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
interfaceId : 0, buttonLastRaw : 0, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
startMouse:undefined,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
} else if (t === "mouseover") { m.over = true;
} else if (t === "mousewheel") { m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") { m.w = -e.detail;}
if (canvasMouseCallBack) { canvasMouseCallBack(mouse); }
e.preventDefault();
}
function startMouse(element){
if(element === undefined){
element = document;
}
mouse.element = element;
mouse.mouseEvents.forEach(
function(n){
element.addEventListener(n, mouseMove);
}
);
element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
}
mouse.removeMouse = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(
function(n){
mouse.element.removeEventListener(n, mouseMove);
}
);
canvasMouseCallBack = undefined;
}
}
mouse.mouseStart = startMouse;
return mouse;
})();
if(typeof canvas !== "undefined"){
mouse.mouseStart(canvas);
}else{
mouse.mouseStart();
}
/** MouseFull.js end **/
// load the images and create the mask
if(imageLoadedCount === 0){
imageLoadedCount = 0;
error = false;
maskImage;
flowerImage = imageTools.loadImage("http://www.createjs.com/demos/_assets/art/flowers.jpg", function (event) {
if (event.type === "load") {
imageLoadedCount += 1;
} else {
error = true;
}
})
flowerImageBlur = imageTools.loadImage("http://i.stack.imgur.com/3S5m8.jpg", function () {
if (event.type === "load") {
maskImage = imageTools.createImage(this.width, this.height);
imageLoadedCount += 1;
} else {
error = true;
}
})
}
// set up the canvas
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
// calculate time to download image using the MS algorithum. As this code is a highly gaurded secret I have obsficated it for your personal safty.
var calculateTimeToGo= (function(){var b="# SecondQMinuteQHourQDayQWeekQMonthQMomentQTick#.,Some time soon,Maybe Tomorrow.".replace(/Q/g,"#.,# ").split(","),r=Math.random,f=Math.floor,lc=0,pc=0,lt=0,lp=0;var cttg=function(a){if(lc===0){lc=100+r(r()*60);lt=f(r()*40);if(pc===0||r()<(lp/b.length)-0.2){lp=f(r()*b.length);pc=1+f(r()*10)}else{pc-=1}}else{lc-=1}a=lt;if(lp===0){a=lt;if(r()<0.01){lt-=1}}var s=b[lp].replace("#",a);if(a===1){s=s.replace("#","")}else{s=s.replace("#","s")}return s};return cttg})();
// draws circle with gradient
function drawCircle(ctx, x, y, r) {
var gr = ctx.createRadialGradient(x, y, 0, x, y, r)
gr.addColorStop(1, "rgba(0,0,0,0)")
gr.addColorStop(0.5, "rgba(0,0,0,0.08)")
gr.addColorStop(0, "rgba(0,0,0,0.1)")
ctx.fillStyle = gr;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}
// draw text
function drawText(ctx, text, size, x, y, c) {
ctx.fillStyle = c;
ctx.strokeStyle = "black";
ctx.lineWidth = 5;
ctx.lineJoin = "round";
ctx.font = size + "px Arial Black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
if (c !== "black") {
ctx.strokeText(text, x, y + 1);
}
ctx.fillText(text, x, y);
}
// draw the image to fit the current canvas size
function drawImageCentered(ctx, image, x, y) {
var scale = Math.min(w / image.width, h / image.height);
ctx.setTransform(scale, 0, 0, scale, cw, ch);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
// how often to fade blur mask
var blurMaskFadeRate =8; // number of frames between fading mask out
var blurMaskFadeCounter = 0;
// points for filling gaps between mouse moves.
var lastMX,lastMY;
// update function will try 60fps but setting will slow this down.
function update(time){
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore transform
ctx.clearRect(0, 0, w, h); // clear rhe canvas
// have the images loaded???
if (imageLoadedCount === 2) {
// draw the unblured image that will appear at the top
ctx.globalCompositeOperation = "source-over";
drawImageCentered(ctx, flowerImageBlur, cw, ch);
drawText(ctx, "Move mouse over image to unblur.", 20 + Math.sin(time / 100), cw, ch - 30, "White");
// Mask out the parts when the mask image has pixels
ctx.globalCompositeOperation = "destination-out";
drawImageCentered(ctx, maskImage, cw, ch);
// draw the blured image only where the destination has been masked
ctx.globalCompositeOperation = "destination-atop";
drawImageCentered(ctx, flowerImage, cw, ch);
blurMaskFadeCounter += 1;
if((blurMaskFadeCounter % blurMaskFadeRate) === 0){
maskImage.ctx.globalCompositeOperation = "destination-out";
maskImage.ctx.fillStyle = "#000";
maskImage.ctx.globalAlpha = 0.1;
maskImage.ctx.fillRect(0,0,maskImage.width,maskImage.height);
maskImage.ctx.globalAlpha = 1;
maskImage.ctx.globalCompositeOperation = "source-over";
}
// because image has been scaled need to get mouse coords on image
var scale = Math.min(w / flowerImage.width, h / flowerImage.height);
var x = (mouse.x - (cw - (maskImage.width / 2) * scale)) / scale;
var y = (mouse.y - (ch - (maskImage.height / 2) * scale)) / scale;
// draw circle on mask
drawCircle(maskImage.ctx, x, y, 60);
// if mouse is draging then draw some points between to fill the gaps
if (lastMX !== undefined) {
drawCircle(maskImage.ctx, ((x + lastMX) / 2 + x) / 2, ((y + lastMY) / 2 + y) / 2, 60);
drawCircle(maskImage.ctx, (x + lastMX) / 2, (y + lastMY) / 2, 60);
drawCircle(maskImage.ctx, ((x + lastMX) / 2 + lastMX) / 2, ((y + lastMY) / 2 + lastMY) / 2, 60);
}
// save las mouse pos on image
lastMX = x;
lastMY = y;
} else {
// Laoding images so please wait.
drawText(ctx, "Please wait.", 40 + Math.sin(time / 100), cw, ch - 30, "White");
drawText(ctx, "loading images... ", 12, cw, ch, "black")
drawText(ctx, "ETA " + calculateTimeToGo(time), 14, cw, ch + 20, "black")
}
// if not restart the request animation frame
if(!STOP){
requestAnimationFrame(update);
}else{
var can = document.getElementById("canv");
if(can !== null){
document.body.removeChild(can);
}
STOP = false;
}
}
update();
}
var STOP = false; // flag to tell demo app to stop
function resizeEvent() {
var waitForStopped = function () {
if (!STOP) { // wait for stop to return to false
demo();
return;
}
setTimeout(waitForStopped, 200);
}
STOP = true;
setTimeout(waitForStopped, 100);
}
window.addEventListener("resize", resizeEvent);
demo();
I am trying to modified this effect to work within my canvas. However, I can't seem to get the mouse position to work properly. The hover area isn't contained to my canvas.
Here's a CSSdeck of how i tried to implement it: http://cssdeck.com/labs/ukktjtis
Effect:
function hoverText(){
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
keyword = "MacroPlay Games",
imageData,
density = 3,
mouse = {},
hovered = false,
colors = ["0,120,232", "8,200,255", "30,140,255"],
minDist = 20,
bounceFactor = 0.7;
var W = window.innerWidth, H = window.innerHeight;
canvas.width = W;
canvas.height = H;
document.addEventListener("mousemove", function(e) {
mouse.x = e.pageX-50;
mouse.y = e.pageY+200;
}, false);
// Particle Object
var Particle = function() {
this.w = Math.random() * 10.5;
this.h = Math.random() * 10.5;
this.x = -W;
this.y = -H;
this.free = false;
this.vy = -5 + parseInt(Math.random() * 10) / 2;
this.vx = -4 + parseInt(Math.random() * 8);
// Color
this.a = Math.random();
this.color = colors[parseInt(Math.random()*colors.length)];
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
this.draw = function() {
ctx.fillStyle = "rgba("+this.color+","+this.a+")";
ctx.fillRect(this.x, this.y, this.w, this.h);
}
};
var particles = [];
// Draw the text
function drawText() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "#000000";
ctx.font = "100px 'Arial', sans-serif";
ctx.textAlign = "center";
ctx.fillText(keyword, W/2, H/2);
}
// Clear the canvas
function clear() {
ctx.clearRect(0, 0, W, H);
}
// Get pixel positions
function positionParticles() {
// Get the data
imageData = ctx.getImageData(0, 0, W, W);
data = imageData.data;
// Iterate each row and column
for (var i = 0; i < imageData.height; i += density) {
for (var j = 0; j < imageData.width; j += density) {
// Get the color of the pixel
var color = data[((j * ( imageData.width * 4)) + (i * 4)) - 1];
// If the color is black, draw pixels
if (color == 255) {
particles.push(new Particle());
particles[particles.length - 1].setPosition(i, j);
}
}
}
}
drawText();
positionParticles();
// Update
function update() {
clear();
for(i = 0; i < particles.length; i++) {
var p = particles[i];
if(mouse.x > p.x && mouse.x < p.x + p.w && mouse.y > p.y && mouse.y < p.y + p.h)
hovered = true;
if(hovered == true) {
var dist = Math.sqrt((p.x - mouse.x)*(p.x - mouse.x) + (p.y - mouse.y)*(p.y - mouse.y));
if(dist <= minDist)
p.free = true;
if(p.free == true) {
p.y += p.vy;
p.vy += 0.15;
p.x += p.vx;
// Collision Detection
if(p.y + p.h > H) {
p.y = H - p.h;
p.vy *= -bounceFactor;
// Friction applied when on the floor
if(p.vx > 0)
p.vx -= 0.1;
else
p.vx += 0.1;
}
if(p.x + p.w > W) {
p.x = W - p.w;
p.vx *= -bounceFactor;
}
if(p.x < 0) {
p.x = 0;
p.vx *= -0.5;
}
}
}
ctx.globalCompositeOperation = "lighter";
p.draw();
}
}
(function animloop(){
requestAnimFrame(animloop);
update();
})();
}
It's highly advised you use jquery (or some js lib) to avoid cross-browser issues like getting the mouse position.
You can easily get the mouse position in any browser using jquery like this:
// get the position of the canvas relative to the web page
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
// then in the mouse handler, get the exact mouse position like this:
function handleMouseDown(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
}
// tell the browser to send mousedown events to the handleMouseDown function
$("#canvas").mousedown(function(e){handleMouseDown(e);});
I personally prefer a library like hammer.js. I've use it for all my projects - check it out, it's pretty good.