I'm making a javascript game in which I want enemy ships to be able to rotate towards a specified point. Their movement can then be calculated from their angle. The following code for the rotation works only if the ship is below and to the right of its target. If the ship is to the left, it rotates to ~0 degrees and jitters there. To the top right it continuously rotates counterclockwise.
What the heck am I doing wrong here? Any suggestions as to a better method?
obj.angle %= 360;
// Calculate angle to target point
var targetAngle = Math.atan2(obj.mode.dest.y - obj.y, obj.mode.dest.x - obj.x) * (180 / Math.PI) + 90;
// Get the difference between the current angle and the target angle
var netAngle = Math.abs(obj.angle - targetAngle) % 360;
// Turn in the closest direction to the target
netAngle > 180 ? obj.angle += obj.shipType.turnSpeed : obj.angle -= obj.shipType.turnSpeed;
if(obj.angle < 0) obj.angle += 360;
if(obj.angle > 360) obj.angle -= 360;
My question is very similar to this one, which explains it better but unfortunately is in C#.
EDIT: Here's the working code, for anyone who might find it useful:
obj.angle %= 360;
var targetAngle = Math.atan2(obj.mode.dest.y - obj.y, obj.mode.dest.x - obj.x) * (180 / Math.PI) + 90;
targetAngle = (targetAngle + 360) % 360;
if(obj.angle != targetAngle)
{
var netAngle = (obj.angle - targetAngle + 360) % 360;
var delta = Math.min(Math.abs(netAngle - 360), netAngle, obj.shipType.turnSpeed);
var sign = (netAngle - 180) >= 0 ? 1 : -1;
obj.angle += sign * delta + 360;
obj.angle %= 360;
}
With little change to your code it appears to work:
http://jsfiddle.net/57hAk/11/
This can be done without the conversion to degrees.
The steps are:
Compute the angle theta between the source and destination vectors with Math.atan2.
Normalize theta to the range 0..Tau.
Take the difference between the source angle and theta, taking care of wraparound below -PI during the subtraction.
This produces a diff value between -Pi..+Pi. If it's negative, turn clockwise, otherwise turn counterclockwise.
The formula only works if the player angle is constrained to the range -Pi..+Pi.
Here's a working example.
const {PI} = Math;
const TAU = 2 * PI;
const canvas = document.querySelector("canvas");
canvas.width = canvas.height = 240;
const ctx = canvas.getContext("2d");
const player = {
x: canvas.width / 2,
y: canvas.height / 2,
angle: 0,
radius: 20,
};
const mouse = {x: canvas.width / 2, y: canvas.height / 2};
canvas.addEventListener("mousemove", e => {
mouse.x = e.offsetX;
mouse.y = e.offsetY;
});
(function rerender() {
requestAnimationFrame(rerender);
let theta = Math.atan2(mouse.y - player.y, mouse.x - player.x);
theta = theta < 0 ? theta + TAU : theta;
let diff = player.angle - theta;
diff = diff < -PI ? diff + TAU : diff;
if (Math.abs(diff) > 0.02) {
player.angle -= 0.04 * Math.sign(diff);
if (player.angle > PI) {
player.angle -= TAU;
}
else if (player.angle < -PI) {
player.angle += TAU;
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw player
ctx.save();
ctx.strokeStyle = "black";
ctx.lineWidth = 8;
ctx.translate(player.x, player.y);
ctx.rotate(player.angle);
ctx.beginPath();
ctx.arc(0, 0, player.radius, 0, TAU);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(player.radius * 2, 0);
ctx.stroke();
ctx.restore();
// draw crosshair
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(mouse.x - 10, mouse.y);
ctx.lineTo(mouse.x + 10, mouse.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(mouse.x, mouse.y - 10);
ctx.lineTo(mouse.x, mouse.y + 10);
ctx.stroke();
})();
canvas {
border: 2px solid black;
}
<canvas></canvas>
Another option:
const theta = Math.atan2(mouse.y - player.y, mouse.x - player.x);
let diff = player.angle - theta;
diff = diff > PI ? diff - TAU : diff;
diff = diff < -PI ? diff + TAU : diff;
This can probably be simplified further; feel free to leave a comment.
Related
Implemented a canvas, Drawing a square there and get the calculated coordinates,
Can see on the following pic the drawing:
I'm calculating and getting the upleft point X and Y coordinates,
And for the down right coordinates that i need, I'm adding the height and width, as follows:
{ upLeft: { x: position.x, y: position.y }, downRight: { x: position.x + position.width, y: position.y + position.height } },
Now i want to get the same dimensions when i'm rotating the canvas clockwise or anti-clockwise.
So i have the angle, And i try to calculate via the following function:
function getRotatedCoordinates(cx, cy, x, y, angle) {
let radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) - (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) + (sin * (x - cx)) + cy;
return [nx, ny];
}
And i'm calling the function via the following args and using it.
let newCoords = getRotatedCoordinates(0, 0, position.x, position.y, angle);
position.x = newCoords[0];
position.y = newCoords[1];
So firstly, I'm not sure that the cx and cy points are correct, I'm always entering 0 for both of them.
Secondly, I'm not getting the desired results, They are getting changed but i'm pretty sure that something is wrong with the x and y, So i guess that the function is wrong.
Thanks.
Here is how I would do it:
function getRectangeCoordinates(x, y, width, height, angle) {
let points = [ [x, y] ]
let radians = (Math.PI / 180) * angle;
for (let i = 0; i < 3; i++) {
x += Math.cos(radians) * ((i == 1) ? height : width);
y += Math.sin(radians) * ((i == 1) ? height : width);
points.push([x, y])
radians += Math.PI / 2
}
return points
}
let canvas = document.createElement("canvas");
canvas.width = canvas.height = 140
let ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
function draw(coords, radius) {
for (let i = 0; i < 4; i++) {
ctx.beginPath();
ctx.arc(coords[i][0], coords[i][1], radius, 0, 8);
ctx.moveTo(coords[i][0], coords[i][1]);
let next = (i + 1) % 4
ctx.lineTo(coords[next][0], coords[next][1]);
ctx.stroke();
}
}
let coords = getRectangeCoordinates(20, 10, 120, 40, 15)
console.log(JSON.stringify(coords))
draw(coords, 3)
ctx.strokeStyle = "red";
coords = getRectangeCoordinates(60, 40, 40, 50, 65)
draw(coords, 5)
ctx.strokeStyle = "blue";
coords = getRectangeCoordinates(120, 3, 20, 20, 45)
draw(coords, 2)
In the getRectangeCoordinates I'm returning all corners of a rectangle and the paraments of the function are the top left corner (x, y) the height and width of the rectangle and last the angle.
I'm drawing a few rectangles with different shapes and angles to show how it looks like
The calculations in the function are simple trigonometry here is a visual representation that could help you remember it the next time you need it:
I realize this is a simple Trigonometry question, but my high school is failing me right now.
Given an angle, that I have converted into radians to get the first point. How do I figure the next two points of the triangle to draw on the canvas, so as to make a small triangle always point outwards to the circle. So lets say Ive drawn a circle of a given radius already. Now I want a function to plot a triangle that sits on the edge of the circle inside of it, that points outwards no matter the angle. (follows the edge, so to speak)
function drawPointerTriangle(ctx, angle){
var radians = angle * (Math.PI/180)
var startX = this.radius + this.radius/1.34 * Math.cos(radians)
var startY = this.radius - this.radius/1.34 * Math.sin(radians)
// This gives me my starting point on the outer edge of the circle, plotted at the angle I need
ctx.moveTo(startX, startY);
// HOW DO I THEN CALCULATE x1,y1 and x2, y2. So that no matter what angle I enter into this function, the arrow/triangle always points outwards to the circle.
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
}
Example
You don't say what type of triangle you want to draw so I suppose that it is an equilateral triangle.
Take a look at this image (credit here)
I will call 3 points p1, p2, p3 from top right to bottom right, counterclockwise.
You can easily calculate the coordinate of three points of the triangle in the coordinate system with the origin is coincident with the triangle's centroid.
Given a point belongs to the edge of the circle and the point p1 that we just calculated, we can calculate parameters of the translation from our main coordinate system to the triangle's coordinate system. Then, we just have to translate the coordinate of two other points back to our main coordinate system. That is (x1,y1) and (x2,y2).
You can take a look at the demo below that is based on your code.
const w = 300;
const h = 300;
function calculateTrianglePoints(angle, width) {
let r = width / Math.sqrt(3);
let firstPoint = [
r * Math.cos(angle),
r * Math.sin(angle),
]
let secondPoint = [
r * Math.cos(angle + 2 * Math.PI / 3),
r * Math.sin(angle + 2 * Math.PI / 3),
]
let thirdPoint = [
r * Math.cos(angle + 4 * Math.PI / 3),
r * Math.sin(angle + 4 * Math.PI / 3),
]
return [firstPoint, secondPoint, thirdPoint]
}
const radius = 100
const triangleWidth = 20;
function drawPointerTriangle(ctx, angle) {
var radians = angle * (Math.PI / 180)
var startX = radius * Math.cos(radians)
var startY = radius * Math.sin(radians)
var [pt0, pt1, pt2] = calculateTrianglePoints(radians, triangleWidth);
var delta = [
startX - pt0[0],
startY - pt0[1],
]
pt1[0] = pt1[0] + delta[0]
pt1[1] = pt1[1] + delta[1]
pt2[0] = pt2[0] + delta[0]
pt2[1] = pt2[1] + delta[1]
ctx.beginPath();
// This gives me my starting point on the outer edge of the circle, plotted at the angle I need
ctx.moveTo(startX, startY);
[x1, y1] = pt1;
[x2, y2] = pt2;
// HOW DO I THEN CALCULATE x1,y1 and x2, y2. So that no matter what angle I enter into this function, the arrow/triangle always points outwards to the circle.
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.fillStyle = '#FF0000';
ctx.fill();
}
function drawCircle(ctx, radius) {
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = '#000';
ctx.fill();
}
function clear(ctx) {
ctx.fillStyle = '#fff';
ctx.fillRect(-w / 2, -h / 2, w, h);
}
function normalizeAngle(pointCoordinate, angle) {
const [x, y] = pointCoordinate;
if (x > 0 && y > 0) return angle;
else if (x > 0 && y < 0) return 360 + angle;
else if (x < 0 && y < 0) return 180 - angle;
else if (x < 0 && y > 0) return 180 - angle;
}
function getAngleFromPoint(point) {
const [x, y] = point;
if (x == 0 && y == 0) return 0;
else if (x == 0) return 90 * (y > 0 ? 1 : -1);
else if (y == 0) return 180 * (x >= 0 ? 0: 1);
const radians = Math.asin(y / Math.sqrt(
x ** 2 + y ** 2
))
return normalizeAngle(point, radians / (Math.PI / 180))
}
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.querySelector('canvas');
const angleText = document.querySelector('.angle');
const ctx = canvas.getContext('2d');
ctx.translate(w / 2, h / 2);
drawCircle(ctx, radius);
drawPointerTriangle(ctx, 0);
canvas.addEventListener('mousemove', _.throttle(function(ev) {
let mouseCoordinate = [
ev.clientX - w / 2,
ev.clientY - h / 2
]
let degAngle = getAngleFromPoint(mouseCoordinate)
clear(ctx);
drawCircle(ctx, radius);
drawPointerTriangle(ctx, degAngle)
angleText.innerText = Math.floor((360 - degAngle)*100)/100;
}, 15))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<canvas width=300 height=300></canvas>
<div class="angle">0</div>
reduce the radius, change the angle and call again cos/sin:
function drawPointerTriangle(ctx, angle)
{
var radians = angle * (Math.PI/180);
var radius = this.radius/1.34;
var startX = this.center.x + radius * Math.cos(radians);
var startY = this.center.y + radius * Math.sin(radians);
ctx.moveTo(startX, startY);
radius *= 0.9;
radians += 0.1;
var x1 = this.center.x + radius * Math.cos(radians);
var y1 = this.center.y + radius * Math.sin(radians);
radians -= 0.2;
var x1 = this.center.x + radius * Math.cos(radians);
var y1 = this.center.y + radius * Math.sin(radians);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(startX, startY);
}
the resulting triangle's size is proportional to the size of the circle.
in case you need an equilateral, fixed size triangle, use this:
//get h by pythagoras
h = sqrt( a^2 - (a/2)^2 );)
//get phi using arcustangens:
phi = atan( a/2, radius-h );
//reduced radius h by pythagoras:
radius = sqrt( (radius-h)^2 + (a/2)^2 );
radians += phi;
...
radians -= 2*phi;
...
I'm trying to draw a spiral starting from a "start" point up to an "end point. The spiral also has a given center point so it can draw the spirals around that center point.
I can't make it work, somehow the math is totally wrong.
Any advice on how to solve this?
The jsfiddle of the code I tried is here.
<!DOCTYPE HTML>
<html>
<body>
<canvas id="myCanvas" width="800" height="600" style="border:1px solid #c3c3c3;"></canvas>
<script type="text/javascript">
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
//center of the spiral coords:
var centerX = 400;
var centerY = 300;
//draw the center of spiral point:
drawCirc(centerX, centerY, 10, '#6f0c4f');
var gap = 8;
var STEPS_PER_ROTATION = 50;
var rotations = 4;
var increment = rotations * Math.PI / STEPS_PER_ROTATION;
var theta = increment;
//start point:
var startX = 500;
var startY = 380;
//end point:
var endX = 600
var endY = 300;
//draw the start and end points as small circles:
drawCirc(startX, startY, 6, '#FF0000');
drawCirc(endX, endY, 6, '#00FF00');
//trying to calculate theta start position:
theta = Math.abs(((centerX - startX) / Math.cos(theta)) / gap);
var ind = 0;
while (theta < rotations * Math.PI * 2) {
var newX = centerX + theta * Math.cos(theta) * gap;
var newY = centerY + theta * Math.sin(theta) * gap;
var ukwObj = { x: newX, y: newY };
if (ind == 0) {
//draw start point with differnt color to differentiate
drawCirc(newX, newY, 2, 'orange');
} else {
drawCirc(newX, newY);
}
ind++;
theta = theta + increment;
}
function drawCirc(x, y, radius = 2, stroke = '#000000') {
cxt.beginPath();
cxt.arc(x, y, radius, 0, 2 * Math.PI);
cxt.strokeStyle = stroke;
cxt.stroke();
cxt.fillStyle = stroke;
cxt.fill();
}
cxt.stroke(); // draw the spiral
</script>
</body>
</html>
The principle of plotting a circle is:
Given a center x,y, a radius r, we can plot points belonging to the circle by calcultating their coordinates as follow: px = x + r * Math.cos(angle) and py = y + r * Math.sin(angle) when angle varies from 0 to 2* Math.PI.
If r grows when angle varies, the we get an outward spiral (from angle 0 to angle 2*PI which is equivalent to 0).
For the problem at hand we need to calculate a start and end position of the spiral, in polar coordinates (distance, angle).
So we need to compute the start angle, start distance, end angle and end distance, and plot each point by gradually incrementing both the angle and the distance.
The initial theta calculation was wrong, I've changed it.
Then I needed to calculate the start distance between center and start point, and the end distance between center and end point.
During rotation, you progressivley goes from start distance to end distance.
The total angle distance should be totalTheta = numberOfRotation * 2 * Math.PI + (endAngle - startAngle), I replaced rotations * Math.PI * 2 by totalTheta)
Just for fun and to demonstrate it works for any intial condition, I've randomized start position, end position and number of rotations slightly.
I've also decremented the angle increment at each iteration to make the distance between each point look more even, but you can comment to keep a constant angular speed.
The solution below will randomly choose an orange dot, a green dot, a number of turns to complete the spiral, and will plot the spiral around the fixed purple dot.
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
//center of the spiral coords:
var centerX = 200;
var centerY = 150;
//draw the center of spiral point:
drawCirc(centerX, centerY, 10, '#6f0c4f');
var gap = 8;
var STEPS_PER_ROTATION = 50;
var rotations = 1 + parseInt(Math.random() * 5, 10);
var increment = rotations * Math.PI / STEPS_PER_ROTATION;
var theta = increment;
var dist = 0;
//start point:
var startX = centerX + (Math.random() * 150 - 75);
var startY = centerY + (Math.random() * 150 - 75);
var startAngleOffset = startX > centerX ? (startY > centerY ? 0 : 0) : (startY > centerY ? Math.PI : Math.PI);
var startAngleSign = startX > centerX ? (startY > centerY ? 1 : -1) : (startY > centerY ? -1 : 1);
//end point:
var endX = centerX + (Math.random() * 150 - 75);
var endY = centerY + (Math.random() * 150 - 75);
var endAngleOffset = endX > centerX ? (endY > centerY ? 0 : 0) : (endY > centerY ? Math.PI : Math.PI);
var endAngleSign = endX > centerX ? (endY > centerY ? 1 : -1) : (endY > centerY ? -1 : 1);
//draw the start and end points as small circles:
drawCirc(startX, startY, 6, '#FF0000');
drawCirc(endX, endY, 6, '#00FF00');
var startTheta = theta = startAngleOffset + startAngleSign * Math.atan(Math.abs(startY - centerY)/Math.abs(startX - centerX));
var endTheta = endAngleOffset + endAngleSign * Math.atan(Math.abs(endY - centerY)/Math.abs(endX - centerX));
var totalTheta = rotations * 2 * Math.PI + (endTheta - startTheta)
dist = Math.sqrt(Math.pow(startY - centerY, 2) + Math.pow(startX - centerX, 2));
finalDist = Math.sqrt(Math.pow(endY - centerY, 2) + Math.pow(endX - centerX, 2));
var ind = 0;
while (theta -startTheta < totalTheta) {
var currentDist = (dist + ((finalDist - dist)* ((theta - startTheta) / (totalTheta))));
var newX = centerX + currentDist * Math.cos(theta);
var newY = centerY + currentDist * Math.sin(theta);
var ukwObj = { x: newX, y: newY };
if (ind == 0) {
//draw start point with differnt color to differentiate
drawCirc(newX, newY, 2, 'orange');
} else {
drawCirc(newX, newY);
}
ind++;
theta = theta + increment;
// decrement increment to make the space between points look more regular
increment = Math.max(0.01, increment - 0.00096);
}
function drawCirc(x, y, radius = 2, stroke = '#000000') {
cxt.beginPath();
cxt.arc(x, y, radius, 0, 2 * Math.PI);
cxt.strokeStyle = stroke;
cxt.stroke();
cxt.fillStyle = stroke;
cxt.fill();
}
cxt.stroke(); // draw the spiral
<!DOCTYPE HTML>
<html>
<body>
<canvas id="myCanvas" width="800" height="600" style="border:1px solid #c3c3c3;"></canvas>
</body>
</html>
I'm currently trying to make a version of Breakout for university. Thanks for helping me to draw the paddle. I now find myself unable to cause the ball to bounce on the edge of the canvas - except in the middle. I've tried both adding and subtracting fractions to ball.x and ball.y (greater than or equal to canvas.width and canvas.height both work) but for less than or equal to 0, nothing seems to be successful. Here's the javascript code:
var canvas = document.getElementById("breakout");
var ctx = canvas.getContext("2d");
var PADDLE_WIDTH_PX = canvas.width / 5;
var PADDLE_HEIGHT_PX = 10;
var PADDLE_SPEED = 450;
var ball = {
x: canvas.width / 2, //pixels
y: canvas.height / 2, //pixels
xSpeed: 500, //pixels per second
ySpeed: 500, //pixels per second
radius: 100 //the ball is exceptionally large so that I can see what part of the ball is surpassing the canvas edge before the motion is reversed
}
var paddle = {
//radius: 5,
/*speed: 500,
TopRight: ctx.moveTo(canvas.width / 1.35, canvas.height - (canvas.height / 12.5)),
TopSide: ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5)),
RightSide: ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5)),
BottomLeft: ctx.moveTo(canvas.width / 2, canvas.height - (canvas.height / 27.5)),
LeftSide: ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5)),
BottomSide: ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5))*/
xSpeed: 450,
x: (canvas.width - PADDLE_WIDTH_PX) / 2,
y: canvas.height - PADDLE_HEIGHT_PX
}
var keysDown = {};
window.addEventListener("keydown",function(e) {
keysDown[e.keyCode] = true;
});
window.addEventListener("keyup",function(e) {
delete keysDown[e.keyCode];
});
function render() {
//clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// draw the ball
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
//ctx.fillStyle = "red";
/*ctx.moveTo(canvas.width - (2*paddle.x), canvas.height - (2*paddle.y));
/*ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5));
ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5));
ctx.moveTo(canvas.width / 2, canvas.height - (canvas.height / 27.5));
ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5));
ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5));
ctx.fill();
ctx.closePath();*/
/*ctx.lineTo(canvas.width - (2*paddle.x), canvas.height - paddle.y);
ctx.moveTo(canvas.width - paddle.x, canvas.height - paddle.y);
ctx.lineTo(canvas.width - paddle.x, canvas.height - (2*paddle.y));
ctx.lineTo(canvas.width - (2*paddle.x), (canvas.height -paddle.y));*/
ctx.fillRect(paddle.x, paddle.y, PADDLE_WIDTH_PX, PADDLE_HEIGHT_PX);
/*ctx.closePath();
ctx.fill();*/
}
function update(elapsed) {
//update the ball position according to the elapsed time
ball.y += ball.ySpeed * elapsed;
ball.x += ball.xSpeed * elapsed;
/*paddle.TopRight += paddle.speed * elapsed;
paddle.BottomLeft += paddle.speed * elapsed;
paddle.RightSide += paddle.speed * elapsed;
paddle.LeftSide += paddle.speed * elapsed;
paddle.TopSide += paddle.speed * elapsed;
paddle.BottomSide += paddle.speed * elapsed;*/
/*paddle.x += paddle.xSpeed * elapsed;
paddle.y += paddle.xSpeed * elapsed;*/
//bounce the ball of all edges
if (37 in keysDown && paddle.x > 0)
paddle.x -= PADDLE_SPEED * elapsed;
if (39 in keysDown && paddle.x + PADDLE_WIDTH_PX < canvas.width)
paddle.x += PADDLE_SPEED * elapsed;
if (ball.x+(ball.x/7) >= canvas.width) {
ball.x -= 5;
ball.xSpeed *= -1;
}
if (ball.x-(ball.x/7) <= 0) {
ball.x += 5;
ball.xSpeed *= -1;
}
if (ball.y+(ball.y/100) <= 0) {
ball.y += 5;
ball.ySpeed *= -1;
}
if (ball.y+(ball.y/3) >= canvas.height) {
ball.y -= 5;
ball.ySpeed *= -1;
}
/*
The problem here is that sometimes the ball gets 'stuck' to an edge.
This can occur when the ball passes beyond an edge in a frame when the
elapsed time is relatively large. In some cases, when the elapsed time in the
next frame is relatively short, the ball doesn't reach the edge to get back
into play. This results in another flip of the velocity and the ball becomes
'trapped' on the edge.
e.g.
xSpeed = -500, x = 10, elapsed = 0.2 => xSpeed = 500, x = -90 (xMovement = -100)
xSpeed = 500, x = -90, elapsed = 0.1 => xSpeed = -500, x = -40 (xMovement = +50)
xSpeed = -500, x = -40, elapsed = 0.1 => xSpeed = 500, x = -40 (xMovement = -50)
and so on ...until a larger elapsed time occurs in the right direction
The fix for this is to move the ball to the edge when the velocity is flipped.
*/
}
var previous;
function run(timestamp) {
if (!previous) previous = timestamp; //start with no elapsed time
var elapsed = (timestamp - previous) / 1000; //work out the elapsed time
update(elapsed); //update the game with the elapsed time
render(); //render the scene
previous = timestamp; //set the (globally defined) previous timestamp ready for next time
window.requestAnimationFrame(run); //ask browser to call this function again, when it's ready
}
//trigger the game loop
window.requestAnimationFrame(run);
Thanks for taking the time to read this
--ConfusedStudent
There is a number of issues with the paddle.
First of all you probably want it to be of a fixed size, so let's define
its dimensions at the beginning of the file (put it after the first two lines of your code because it uses the canvas to set the paddle width to 1/5th of its width - I think that's what you tried to do):
var PADDLE_WIDTH_PX = canvas.width / 5;
var PADDLE_HEIGHT_PX = 10;
With this you can initialize the paddle to be at the bottom of the canvas and in the middle:
var paddle = {
x: (canvas.width - PADDLE_WIDTH_PX) / 2,
y: canvas.height - PADDLE_HEIGHT_PX
}
x and y are the top-left corner of the paddle, so the right side is at x + PADDLE_WIDTH_PX and the bottom side is at y + PADDLE_HEIGHT_PX.
Knowing this, you can draw a path throught all the four corners like this:
ctx.beginPath();
ctx.moveTo(paddle.x, paddle.y);
ctx.lineTo(paddle.x + PADDLE_WIDTH_PX, paddle.y);
ctx.lineTo(paddle.x + PADDLE_WIDTH_PX, paddle.y + PADDLE_HEIGHT_PX);
ctx.lineTo(paddle.x, paddle.y + PADDLE_HEIGHT_PX);
ctx.lineTo(paddle.x, paddle.y);
ctx.closePath();
But since the paddle is just a rectangle, it's easier to use a method for drawing rectangles - fillRect, like this:
ctx.fillRect(paddle.x, paddle.y, PADDLE_WIDTH_PX, PADDLE_HEIGHT_PX);
Either way, all the four corners of the paddle move toggether so it doesn't grow or shrink.
So if you put it in your render function, it looks like this:
function render() {
//clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// draw the ball
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
// draw the paddle
ctx.fillStyle = "red";
ctx.fillRect(paddle.x, paddle.y, PADDLE_WIDTH_PX, PADDLE_HEIGHT_PX);
}
The last thing is to get the paddle to move when the left and right arrow keys are pressed.
The paddle only moves when either arrow left or arrow right is pressed. Otherwise its speed is 0 and it sits in its place. Therefore the paddle object doesn't need the xSpeed member variable.
Also, the paddle only moves horizontally so only its x variable changes, y is always the same.
Let's first define the speed of the paddle at the beginning of the file:
var PADDLE_SPEED = 300;
and then let's put the movement logic in the update function:
if (37 in keysDown && paddle.x > 0)
paddle.x -= PADDLE_SPEED * elapsed;
else if (39 in keysDown && paddle.x + PADDLE_WIDTH_PX < canvas.width)
paddle.x += PADDLE_SPEED * elapsed;
You can notice that the paddle position is only changed if an arrow key is pressed and the paddle is not at the edge.
All the other code that deals with the paddle should be removed from the update function so that it looks like this (I have removed most of the comments):
function update(elapsed) {
//update the ball position according to the elapsed time
ball.y += ball.ySpeed * elapsed;
ball.x += ball.xSpeed * elapsed;
if (37 in keysDown && paddle.x > 0)
paddle.x -= PADDLE_SPEED * elapsed;
else if (39 in keysDown && paddle.x + PADDLE_WIDTH_PX < canvas.width)
paddle.x += PADDLE_SPEED * elapsed;
//bounce the ball of all edges
if/*(*/(ball.x /*- (ball.x / 2))*/<= 0) {
ball.x = 1;
ball.xSpeed *= -1;
}
if /*(*/(ball.x /*+ (ball.x / 2))*/>= canvas.width) {
ball.x = ball.x -1;
ball.xSpeed *= -1;
}
if/*(*/(ball.y /*- (ball.y / 2))*/<= 0) {
ball.y = 1;
ball.ySpeed *= -1;
}
if /*(*/(ball.y /*+ (ball.y / 2))*/>= canvas.height) {
ball.y = ball.y -1;
ball.ySpeed *= -1;
}
}
Being relatively new to the HTML5 game, just wanted to ask if anyone knew if it was possible to animate a dashed line along a path? Think snake from Nokia days, just with a dashed line...
I've got a dashed line (which represents electrical current flow), which I'd like to animate as 'moving' to show that current is flowing to something.
Thanks to Rod's answer on this post, I've got the dashed line going, but am not sure where to start to get it moving. Anyone know where to begin?
Thanks!
Got it working here, based on this post by phrogz.
What i did:
Add a start parameter which is a number between 0 and 99
Calculate the dashSize summing the contents of the dash array
Calculate dashOffSet as a fraction of dashSize based on start percent
Subtracted the offset from x, y and added to dx, dy
Only started drawying after the offset been gone (it´s negative, remember)
Added a setInterval to update the start from 0 to 99, step of 10
Update
The original algorithm wasn't working for vertical or negative inclined lines. Added a check to use the inclination based on the y slope on those cases, and not on the x slope.
Demo here
Updated code:
if (window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype.lineTo) {
CanvasRenderingContext2D.prototype.dashedLine = function(x, y, x2, y2, dashArray, start) {
if (!dashArray) dashArray = [10, 5];
var dashCount = dashArray.length;
var dashSize = 0;
for (i = 0; i < dashCount; i++) dashSize += parseInt(dashArray[i]);
var dx = (x2 - x),
dy = (y2 - y);
var slopex = (dy < dx);
var slope = (slopex) ? dy / dx : dx / dy;
var dashOffSet = dashSize * (1 - (start / 100))
if (slopex) {
var xOffsetStep = Math.sqrt(dashOffSet * dashOffSet / (1 + slope * slope));
x -= xOffsetStep;
dx += xOffsetStep;
y -= slope * xOffsetStep;
dy += slope * xOffsetStep;
} else {
var yOffsetStep = Math.sqrt(dashOffSet * dashOffSet / (1 + slope * slope));
y -= yOffsetStep;
dy += yOffsetStep;
x -= slope * yOffsetStep;
dx += slope * yOffsetStep;
}
this.moveTo(x, y);
var distRemaining = Math.sqrt(dx * dx + dy * dy);
var dashIndex = 0,
draw = true;
while (distRemaining >= 0.1 && dashIndex < 10000) {
var dashLength = dashArray[dashIndex++ % dashCount];
if (dashLength > distRemaining) dashLength = distRemaining;
if (slopex) {
var xStep = Math.sqrt(dashLength * dashLength / (1 + slope * slope));
x += xStep
y += slope * xStep;
} else {
var yStep = Math.sqrt(dashLength * dashLength / (1 + slope * slope));
y += yStep
x += slope * yStep;
}
if (dashOffSet > 0) {
dashOffSet -= dashLength;
this.moveTo(x, y);
} else {
this[draw ? 'lineTo' : 'moveTo'](x, y);
}
distRemaining -= dashLength;
draw = !draw;
}
// Ensure that the last segment is closed for proper stroking
this.moveTo(0, 0);
}
}
var dashes = '10 20 2 20'
var c = document.getElementsByTagName('canvas')[0];
c.width = 300;
c.height = 400;
var ctx = c.getContext('2d');
ctx.strokeStyle = 'black';
var drawDashes = function() {
ctx.clearRect(0, 0, c.width, c.height);
var dashGapArray = dashes.replace(/^\s+|\s+$/g, '').split(/\s+/);
if (!dashGapArray[0] || (dashGapArray.length == 1 && dashGapArray[0] == 0)) return;
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.dashedLine(10, 0, 10, c.height, dashGapArray, currentOffset);
ctx.dashedLine(0, 10, c.width, 10, dashGapArray, currentOffset);
ctx.dashedLine(0, 0, c.width, c.height, dashGapArray, currentOffset);
ctx.dashedLine(0, c.height, c.width, 0, dashGapArray, currentOffset);
ctx.closePath();
ctx.stroke();
};
window.setInterval(dashInterval, 500);
var currentOffset = 0;
function dashInterval() {
drawDashes();
currentOffset += 10;
if (currentOffset >= 100) currentOffset = 0;
}
You can create the dashed line animation using SNAPSVG library.
Please check the tutorial here DEMO