Here is the javascript:
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
ax = 50,
ay = 50,
avx = 5,
avy = 2,
radius = 50;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function drawArc() {
ctx.beginPath();
ctx.fillStyle = "white";
ctx.arc(ax, ay, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
};
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawArc();
ax += avx;
ay -= avy;
avy -= 0.2;
if (ay + radius >= canvas.height) {
avy *= -0.8;
avx *= 0.9;
};
if (ax + radius >= canvas.width) {
avx = -avx;
};
if (ax - radius <= 0) {
avx = -avx;
};
}
setInterval(update, 10);
body, html {
margin: 0;
padding: 0;
}
#canvas {
background-color: black;
}
<canvas id="canvas"></canvas>
Here at Jsfiddle: https://jsfiddle.net/milosdacic/qh1ha085/
I don't know why this is happening, code seems fine.
I have done this thing before but now it won't work.
Any help will be appreciated.
Short answer is you are getting many hits because you are not moving the ball up away from the ground. The next frame it is still impacting the ground and as you reduce its speed it just bogs down..
Add this after line 29 of your fiddle.
ay = canvas.height - radius;
For more on bouncing a ball
I guess You wanted this:
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
ax = 50,
ay = 50,
avx = 5,
avy = 2,
radius = 50;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function drawArc() {
ctx.beginPath();
ctx.fillStyle = "white";
ctx.arc(ax, ay, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
};
var hitTheGround = 0;
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawArc();
ax += avx;
ay -= avy;
avy -= 0.2;
if (ay + radius >= canvas.height) {
avy *= -0.8;
avx *= 0.9;
}
if (ax + radius >= canvas.width) {
avx = -avx;
}
if (ax - radius <= 0) {
avx = -avx;
}
if(ay + radius >= canvas.height - 3) {
hitTheGround++;
}
else {
hitTheGround = 0;
}
if(hitTheGround == 100) { // if it jumps near the ground too frequently
return setTimeout(function() {clearInterval(interval)}, 1000);
}
}
var interval = setInterval(update, 10);
body, html {
margin: 0;
padding: 0;
}
#canvas {
background-color: black;
}
<canvas id="canvas"></canvas>
Little bit dirty fix to stop calling update after than it jumps (vibrates) near the ground N times.
Okay, first of, the code that works.
<html>
<head>
<style>
body, html {
margin: 0;
padding: 0;
}
#canvas {
background-color: black;
}
</style>
<script>
function drawArc() {
ctx.beginPath();
ctx.fillStyle = "white";
ctx.arc(ax, ay, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
};
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawArc();
ax += avx;
ay -= avy;
avy -= 0.2;
if (ay + radius >= canvas.height) {
avy *= -0.8;
avx *= 0.9;
};
if (ax + radius >= canvas.width) {
avx = -avx;
};
if (ax - radius <= 0) {
avx = -avx;
};
}
function onload()
{
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
ax = 50;
ay = 50;
avx = 5;
avy = 2;
radius = 50;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
setInterval(update, 10);
}
</script>
<style>
</style>
</head>
<body onload="onload()">
<canvas id="canvas"></canvas>
</body>
</html>
Second, what the problem is/was. I think the biggest issue I had was that the code you offered had variables and actions in the global context which were dependent on the page elements which had not yet been created. This meant that the variables for canvas and cxt couldn't be created because the canvas tag hadn't been created yet. I put all of that into a function I could call when the page had loaded and everything worked fine from there. Now when that function is called the canvas already exists and you can use it to create the context and do everything else.
Third based on the question title you want to know why it stopped bouncing. I think you have correctly created a bouncing ball which bounces off the walls and floors, but suffers a lost of energy due to gravity and a non-elastic collision with the ground. Personally, I have not worked through the code, but it looks great. Why would you change it. If you want it to keep bouncing forever, then the collisions with the walls and floors need to be 100% elastic--this means that you don't lose any energy and your ball bounces as high as it ever was before. In your interaction with the FLOOR you use the code below. This has a dampening effect and eventually drops your ball to zero energy.
avy *= -0.8;//could be -1
avx *= 0.9;//could be 1
That, however, creates another problem. The code you are using needs dampening or the ball just bounces higher on every bounce. This is because you are accelerating on this line and GAINING ENERGY.
avy -= 0.2;
One of your other answers suggested you reduce the dampening, but not remove it entirely by changing this one line. You would have to tune this to make it have the behavior you want.
avy *= -0.8;//-0.8 is too little? -1 is too much
LAST EDIT, I PROMISE. I actually had a lot of fun with this. The problem is not so trivial that you can just put in psuedo-physics and get a good simulation of a ball bouncing. Even when you put in all the right equations you still get a small perpetual bounce because that is what happens in real life. When the ball is moving slowly enough and is close enough to the floor the other forces (not gravity, but strong, weak and electro-magnetic attraction) dominate and cause the ball to stop moving. So, I took one more swipe at it and improved the physics a lot. It's not perfect, but at some point you have to ask is the FIDELITY of the simulation more important than smoothing the behavior to match what I want to see. Hope this helps.
<html>
<head>
<style>
body, html {
margin: 0;
padding: 0;
}
#canvas {
background-color: black;
}
</style>
<script>
function drawArc() {
ctx.beginPath();
ctx.fillStyle = "white";
ctx.arc(xPos, yPos, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
};
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawArc();
var dx = xVel*timeStep;
var dy = yVel*timeStep;
if(yVel<radius && yVel>-radius && canvas.height-(yPos+radius) < .1)
{
yVel = 0;
yPos = canvas.height-radius;
dy=0;
//friction affects xVel
xVel *= fFloor;
}
else if (yPos + dy + radius >= canvas.height) {
//you will be below the floor; there is a bounce
//find the rest of the falling interval
var remainingY = canvas.height-(yPos+radius);
//find the rest of the time step
var remainingTime = remainingY / yVel;
//add acceleration for that time
yVel += gravity * remainingTime
//friction affects xVel
xVel *= fFloor;
//elasticity affects yVel
yVel *= eFloor;
//now you are bouncing up
//what is time up
remainingTime = timeStep - remainingTime;
//set final position
yPos = canvas.height + (yVel*remainingTime) - radius;
//add acceleration for that time
yVel += gravity * remainingTime;
}
else
{
//do not hit the floor, falling the whole time
yPos += dy;
yVel += gravity * timeStep;
}
if (xPos + dx + radius >= canvas.width)
{
//hit a wall; there is a bounce
//find the rest of the interval
var remainingX = canvas.width-(xPos+radius);
//find the rest of the time step
var remainingTime = remainingX / xVel;
//no horizontal acceleration
//friction affects yVel
yVel *= fWall;
//elasticity affects xVel
xVel *= eWall;
//now you are bouncing back
//what is time up
remainingTime = timeStep - remainingTime;
//set final position
xPos = canvas.width + (xVel*remainingTime) - radius;
//no horizontal acceleration
}
else if (xPos + dx - radius <= 0) {
//hit a wall; there is a bounce
//find the rest of the interval
var remainingX = (xPos - radius);
//find the rest of the time step
var remainingTime = remainingX / xVel;
//no horizontal acceleration
//friction affects yVel
yVel *= fWall;
//elasticity affects xVel
xVel *= eWall;
//now you are bouncing back
//what is time up
remainingTime = timeStep - remainingTime;
//set final position
xPos = xVel*remainingTime+radius;
//no horizontal acceleration
}
else {
//never hit a wall; flying the whole time
xPos += dx;
}
}
function onload()
{
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
radius = 15;
xPos = Math.random()*(canvas.width-(2*radius))+radius;
yPos = Math.random()*(canvas.height-(2*radius))+radius;
xVel = Math.random()*100-50;
yVel = Math.random()*100-50;
gravity = 9.8;
eWall = -1;
eFloor = -.8;
fFloor = .9;
fWall = .9;
interval = 10;
timeStep = .1;//apparent time step
setInterval(update, interval);
}
</script>
<style>
</style>
</head>
<body onload="onload()">
<canvas id="canvas"></canvas>
</body>
</html>
Related
I am trying to make a game with collision detection and resolution. For some reason, when I move the player to the right of the 'enemy blocks', the player moves to the left of the 'enemy'. How can I solve this problem? I have been working on this for hours and cannot find any solution. I am not sure if it is a small problem or if I have to change the whole enemy object.
//declare variables
var body = document.getElementById("body");
var canvas = document.getElementById("canvas");
var iwidth = window.innerWidth;
var iheight = window.innerHeight;
//variable for drawing
var draw = canvas.getContext("2d");
//variables for character paramaters
var playerwidth = 20;
var playerheight = 20;
var playerx = iwidth / 2 - playerwidth / 2;
var playery = iheight / 2 - playerheight / 2;
var playerspeed = 20;
//mouse co-ordinates
var mousex;
var mousey;
//enemy's parameters
var enemyxpositions = [43, 94, 200];
var enemyypositions = [41, 120, 83];
var enemywidths = [12, 43, 45];
var enemyheights = [43, 11, 87];
var i = 0;
var collision = false;
///////////////////////////////////////////////////////////////////////////////////
/////// separating variables and rest of the code ///////
///////////////////////////////////////////////////////////////////////////////////
//puts canvas in top right corner
body.style.margin = "0";
//changes the canvas's style namely color, margin, width and height
canvas.style.backgroundColor = "black";
canvas.style.margin = "0";
canvas.width = iwidth;
canvas.height = iheight;
//the function that the player is drawn in
function drawplayer() {
//allows animation
requestAnimationFrame(drawplayer);
//clears the canvas every time the function runs so that the image doesn't leave a mark
draw.clearRect(0, 0, iwidth, iheight);
//drawing the player
draw.fillStyle = "#ffff00";
draw.fillRect(playerx, playery, playerwidth, playerheight);
draw.fill();
//checking where the mouse is and letting the player follow it
if (mousex > playerx + playerwidth / 2) {
playerx += (mousex - playerx + playerwidth) / playerspeed;
}
if (mousex < playerx + playerwidth / 2) {
playerx -= (playerx - mousex + playerwidth) / playerspeed;
}
if (mousey > playery + playerheight / 2) {
playery += (mousey - playery + playerheight) / playerspeed;
}
if (mousey < playery + playerheight / 2) {
playery -= (playery - mousey + playerheight) / playerspeed;
}
//the obstacles' object
function Enemy(enemyx, enemyy, enemywidth, enemyheight) {
this.enemyx = enemyx;
this.enemyy = enemyy;
this.enemywidth = enemywidth;
this.enemyheight = enemyheight;
this.enemies = function() {
draw.fillStyle = "#0000ff";
draw.fillRect(enemyx, enemyy, enemywidth, enemyheight);
draw.fill();
}
//collision detection
if (mousex + playerwidth / 2 > this.enemyx &&
mousex - playerwidth / 2 < this.enemyx + this.enemywidth &&
mousey + playerheight / 2 > this.enemyy &&
mousey - playerheight / 2 < this.enemyy + this.enemyheight) {
collision = true;
}
else {
collision = false;
}
//collision implementation
//left collision
if (collision == true && mousex + playerwidth / 2 > this.enemyx) {
playerx = this.enemyx - playerwidth;
}
//right collision
else if (collision == true && mousex - playerwidth / 2 < this.enemyx + this.enemywidth) {
playerx = this.enemyx + this.enemywidth + 50;
}
}
//draws all the obstacles
for (i = 0; i < enemyxpositions.length; i++) {
new Enemy( enemyxpositions[i],
enemyypositions[i],
enemywidths[i],
enemyheights[i]).enemies();
}
}
drawplayer();
//gets the mouse co-ordinates
window.onmousemove = function mousepos(event) {
mousex = event.clientX;
mousey = event.clientY;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DUNGE</title>
<style>
::-webkit-scrollbar {
display: none;
}
canvas {
display: block;
}
#obstacles {
opacity: 1;
margin-top: -100vh;
}
</style>
</head>
<body id="body">
<canvas id="canvas"></canvas>
<script src="script.js"></script>
</body>
</html>
Collision resolution is a pretty tricky domain and there are a many approaches you can take. For the purposes of squares with mouse control as in your case, a naive approach might be as follows:
If a collision is detected between a player and an immobile obstacle (enemy, wall, whatever), we can resolve the collision by gradually "undoing" the player's motion until it's no longer colliding with the obstacle.
For example, if on the current frame, the player is moving with a y velocity of 5 and an x velocity of 2 and we detect a collision, then we can avoid the collision by undoing the move. However, this would create an unrealistic air gap between the obstacle and the player that can result in a bouncing effect. Instead, we can slowly move the obstacle's x and y positions by a small value like -0.5 until no collision is detected. However, undoing the move on both axes might be incorrect if only one axis experienced a collision.
Here's an initial attempt at separating the x and y axes into distinct steps:
const canvas = document.createElement("canvas");
canvas.width = 300;
canvas.height = 180;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
const mouse = {x: 0, y: 0};
const enemy = {x: 130, y: 70, width: 40, height: 40};
const player = {
x: 0, y: 0, width: 20, height: 20, vx: 0, vy: 0,
velocityDamp: 0.06, collisionDamp: 0.3
};
const collides = (a, b) =>
a.x + a.width >= b.x && a.x <= b.x + b.width &&
a.y + a.height >= b.y && a.y <= b.y + b.height
;
(function render() {
player.vx = (mouse.x - player.x) * player.velocityDamp;
player.vy = (mouse.y - player.y) * player.velocityDamp;
player.x += player.vx;
player.y += player.vy;
while (collides(player, enemy)) {
player.y -= Math.sign(player.vy) * player.collisionDamp;
}
while (collides(player, enemy)) {
player.x -= Math.sign(player.vx) * player.collisionDamp;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "blue";
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
ctx.fillStyle = "yellow";
ctx.fillRect(player.x, player.y, player.width, player.height);
requestAnimationFrame(render);
})();
onmousemove = e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
};
body {margin: 0;}
canvas {background: #000;}
This works fine when the collision is on the y-axis, but collisions on the x-axis cause the player to "pop" out of the obstacle. Ordering the adjustments so that the least offending velocity adjustment is handled first should fix the problem. We do this by "undoing" the last move on one axis, checking if this single-axis move resolved the collision, and adjusting accordingly.
Putting it all together, here's a proof-of-concept:
const canvas = document.createElement("canvas");
canvas.width = 300;
canvas.height = 180;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
const mouse = {x: 0, y: 0};
const enemy = {x: 130, y: 70, width: 40, height: 40};
const player = {
x: 0, y: 0, width: 20, height: 20, vx: 0, vy: 0,
velocityDamp: 0.06, collisionDamp: 0.3
};
const collides = (a, b) =>
a.x + a.width >= b.x && a.x <= b.x + b.width &&
a.y + a.height >= b.y && a.y <= b.y + b.height
;
const resolveOnAxis = (player, enemy, axis) => {
while (collides(player, enemy)) {
player[axis] -= Math.sign(player["v"+axis]) * player.collisionDamp;
}
};
const resolveCollision = (player, enemy) => {
player.x -= player.vx;
if (collides(player, enemy)) {
player.x += player.vx;
resolveOnAxis(player, enemy, "y");
resolveOnAxis(player, enemy, "x");
}
else {
player.x += player.vx;
resolveOnAxis(player, enemy, "x");
resolveOnAxis(player, enemy, "y");
}
};
(function render() {
player.vx = (mouse.x - player.x) * player.velocityDamp;
player.vy = (mouse.y - player.y) * player.velocityDamp;
player.x += player.vx;
player.y += player.vy;
if (collides(player, enemy)) {
resolveCollision(player, enemy);
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "blue";
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
ctx.fillStyle = "yellow";
ctx.fillRect(player.x, player.y, player.width, player.height);
requestAnimationFrame(render);
})();
onmousemove = e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
};
body {margin: 0;}
canvas {background: #000;}
This isn't perfect collision resolution by any means, but it introduces a few fundamental concepts and should be sufficient for simple games.
Note that I'm only handling one enemy; it's left to the reader to create an array of enemies and loop over them to detect and resolve collisions. Problems can arise if multiple enemies are close together; resolving one collision could push the player into another collision. It gets worse if the obstacles are also moving. If you're making a platformer, a collision grid might be worth looking into to circumvent some of these issues.
If dealing with collision becomes increasingly complicated and overwhelming, there's no shame in using a library like matter.js.
Be careful when using while to resolve these collisions as an infinite loop can easily occur. Consider adding a tries counter to these loops and bail if they exceed more than 20 or 30 iterations (this is a bit unsatisfactory and reveals that this solution is not industrial-strength; this prevents infinite loops but may result in incorrect behavior).
Capping the player's maximum velocity is another important preventative measure: it can avoid situations where the velocity becomes so high the player clips right through obstacles. Explore other ad-hoc solutions to problems as they arise.
Beyond collision detection, I have a few other suggestions:
Use objects to encapsulate all properties associated with a game entity. This makes the code much easier to manage than loose variables like playerwidth, playerheight, playerspeed, etc.
Avoid pointless and noisy comments that reiterate what the code clearly does.
Instead of adding comments to delimit logical parts of a function, create helper functions with the appropriate names. My POC above is not great in this regard--as the game expands, objects, functions and overall design become increasingly important; inlining everything in the update loop makes for a painful coding experience as soon as you want to add features or run into bugs.
Put Enemy's constructor function outside of the game loop. Create enemies one time in an initialization function and scope constructors appropriately.
Use camelCased variables instead of everythinginlowercase.
I started learning JavaScript a few weeks ago and I decided to try and make my first game from scratch, in the code below I am trying to make the game so the balls fall each time with different position, color and speed and after that there will be another ball that ill be able to move with my mouse and will try to dodge the balls, so my question is is there a better way than the one I did to spawn more balls , because if I want to spawn like 5-6 more the code will look so bad and I am sure there is a better way of doing that, I am still learning so if you can hit me with a simple solution and explain it.
var canvas = document.getElementById("Canv");
var ctx = canvas.getContext("2d");
var x = random(1, 801);
var x2 = random(1, 801);
var y = 10;
var y2 = 10;
var ballRadius = random(2, 51);
var ballRadius2 = random(2, 51);
var color = "#" + ((1 << 24) * Math.random() | 0).toString(16);
var color2 = "#" + ((1 << 24) * Math.random() | 0).toString(16);
var dy = random(1, 6);
var dy2 = random(1, 6);
function random(min, max) {
return Math.random() * (max - min) + min;
}
function drawBall() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
function drawBall2() {
ctx.beginPath();
ctx.arc(x2, y2, ballRadius2, 0, Math.PI * 2);
ctx.fillStyle = color2;
ctx.fill();
ctx.closePath();
}
function draw() {
drawBall();
drawBall2();
y += dy;
y2 += dy2;
}
setInterval(draw, 10);
I want to know methods to simplify my code so I know for future projects.
Consider the following code:
The main changes I made are the addition of the function createBall, allowing the creation of multiple new balls per tick, and the removal of balls that are out of view. I tried to add comments to the new parts, if you have any questions, let me know!
var canvas = document.getElementById("Canv");
var ctx = canvas.getContext("2d");
const desiredNumberOfBalls = 10;
let i = 0;
let balls = []; // Track each individual ball
const viewLimit = canvas.height; // The vertical distance a ball has when leaving sight
// For now we create a static paddle
const paddle = {
x: 200,
y: viewLimit - 10,
width: 50,
height: 10,
}
// This is a very rough calculation based on https://math.stackexchange.com/a/2100319 by checking first the corners and then the top edge
paddle.hitsBall = function(ball) {
let hit = false;
if (ball.y + ball.radius >= paddle.y) {
// the ball is at the same level as the paddle
if (Math.sqrt((ball.x - paddle.x) ** 2 + (ball.y - paddle.y) ** 2) < ball.radius) {
// the upper left part of the paddle touches the ball
hit = true;
} else if (Math.sqrt((ball.x - paddle.x - paddle.width) ** 2 + (ball.y - paddle.y) ** 2) < ball.radius) {
// the upper right part of the paddle touches the ball
hit = true;
} else if (ball.x >= paddle.x && ball.x <= paddle.x + paddle.width) {
// the top edge of the paddle touches the ball
hit = true;
}
}
if (hit) console.log("Hit!");
return hit;
}
function createBall() {
let ball = {
id: i++,
x: random(1, canvas.width),
y: 10,
radius: random(2, 51),
color: "#" + ((1 << 24) * Math.random() | 0).toString(16),
speed: random(1, 6)
};
return ball;
}
function random(min, max) {
return Math.random() * (max - min) + min;
}
function drawBall(ball) {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddle.x, paddle.y, paddle.width, paddle.height);
ctx.fillStyle = 'darkred';
ctx.fill();
ctx.closePath();
}
function clearView() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function draw() {
clearView();
// Move all existing balls by their designated speed
balls.forEach((ball) => {
ball.y += ball.speed;
});
// Implicitly delete all balls that have exited the viewport
// by only retaining the balls that are still inside the viewport
balls = balls.filter((ball) => ball.y <= viewLimit);
// Implicitly delete all balls that touch the paddle
// by only retaining the balls that don't
balls = balls.filter((ball) => !paddle.hitsBall(ball));
// Create newBallsPerTick new balls
while (balls.length < desiredNumberOfBalls) {
balls.push(createBall());
}
// Draw the paddle
drawPaddle();
// Draw the position of every ball - both the pre-existing ones
// and the ones we just generated
balls.forEach((ball) => {
drawBall(ball);
});
}
setInterval(draw, 1000 / 60);
canvas {
width: 600px;
height: 400px;
}
<canvas id="Canv" height="400" width="600"></canvas>
I'm trying to resize a rotated shape on canvas. My problem is that when I call the rendering function, the shape starts "drifting" depending on the shape angle. How can I prevent this?
I've made a simplified fiddle demonstrating the problem, when the canvas is clicked, the shape is grown and for some reason it drifts upwards.
Here's the fiddle: https://jsfiddle.net/x5gxo1p7/
<style>
canvas {
position: absolute;
box-sizing: border-box;
border: 1px solid red;
}
</style>
<body>
<div>
<canvas id="canvas"></canvas>
</div>
</body>
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = w2;
var y = h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.translate(x, y);
ctx.rotate(Math.PI / 180 * 15);
ctx.translate(-x, -y);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
In the "real" code the shape is resized when the resize-handle is clicked and moved but I think this example demonstrates the problem sufficiently.
EDIT: updated fiddle to clarify the issue:
https://jsfiddle.net/x5gxo1p7/9/
Always use local coordinates to define shapes.
When rendering content that is intended to be transformed the content should be in its own (local) coordinate system. Think of a image. the top left pixel is always at 0,0 on the image no matter where you render it. The pixels are at their local coordinates, when rendered they are moved to the (world) canvas coordinates via the current transformation.
So if you make your shape with coordinates set to its local, making the rotation point at its local origin (0,0) the display coordinates are stored separately as world coordinates
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
Now you don't have to mess about with translating forward and back... blah blah total pain.
Just
ctx.save();
ctx.translate(shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height)
ctx.restore();
or event quicker and eliminating the need to use save and restore
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
The local shape origin (0,0) is where the transformation places the translation.
This greatly simplifies a lot of the work that has to be done
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
ctx.fillStyle = "black";
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
function draw() {
ctx.setTransform(1,0,0,1,0,0); // to clear use default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
// you were scaling the shape, that can be done via a transform
// once you have moved the shape to the world coordinates.
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
// after the transformations have moved the local to the world
// you can ignore the canvas coordinates and work within the objects
// local. In this case showing the unscaled box
ctx.strokeRect(shape.left, shape.top, shape.width, shape.height);
// and a line above the box
ctx.strokeRect(shape.left, shape.top - 5, shape.width, 1);
ctx.scale(0.5,0.5); // the scaling you were doing
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
}
canvas.addEventListener('click', function() {
shape.width += 15;
shape.left -= 15 / 2;
shape.world.rot += Math.PI / 45; // rotate to illustrate location
// of local origin
var distToMove = 15/2;
shape.world.x += Math.cos(shape.world.rot) * distToMove;
shape.world.y += Math.sin(shape.world.rot) * distToMove;
draw();
});
// no need to use requestAnimationFrame (RAF) if you are not animation
// but its not wrong. Nor do you need to bind this (in this case
// this = window) to the callback RAF does not bind a context
// to the callback
/*window.requestAnimationFrame(draw.bind(this));*/
requestAnimationFrame(draw); // functionaly identical
// or just
/*draw()*/ //will work
body { font-family : Arial,"Helvetica Neue",Helvetica,sans-serif; font-size : 12px; color : #242729;} /* SO font currently being used */
canvas { border: 1px solid red; }
<canvas id="canvas"></canvas>
<p>Click to grow "and rotate" (I add that to illustrate the local origin)</p>
<p>I have added a red box and a line above the box, showing how using the local coordinates to define a shape makes it a lot easier to then manipulate that shape when rendering "see code comments".</p>
Try this. You had ctx.translate() used where it was not entirely necessary. That caused the problems.
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.rotate(Math.PI / 180 * 15);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
This is happening because the x and y are set as the half value of the shape size, which completely changes its position.
You should set a point for the center of the shape, anyway. I set this point as ctx.canvas.[width or height] / 2, the half of the canvas.
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = (ctx.canvas.width / 2) - w2;
var y = (ctx.canvas.height / 2) - h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x + (shape.width / 2), y + (shape.height / 2));
ctx.rotate(((shape.rotation * Math.PI) / 180) * 15);
ctx.fillStyle = '#000';
ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
ctx.restore();
Fiddle.
Found a solution, problem was that I wasn't calculating the new center point coordinates.
The new fiddle with solution: https://jsfiddle.net/HTxGb/151/
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width =500;
canvas.height = 500;
var x = canvas.width/2;
var y = canvas.height/2;
var rectw = 20;
var recth = 20;
var rectx = -rectw/2;
var recty = -recth/2;
var rotation = 0;
var addedRotation = Math.PI/12;
var addedWidth = 20;
var addedHeight = 10;
var draw = function() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.fillRect(rectx, recty, rectw, recth);
ctx.restore();
}
document.getElementById('growXRight').addEventListener('click', function() {
rectx -= addedWidth/2;
x += addedWidth/2 * Math.cos(rotation);
y -= addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growXLeft').addEventListener('click', function() {
rectx -= addedWidth/2;
x -= addedWidth/2 * Math.cos(rotation);
y += addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growYTop').addEventListener('click', function() {
recty -= addedHeight/2;
x += addedHeight/2 * Math.sin(rotation);
y -= addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('growYBottom').addEventListener('click', function() {
recty -= addedHeight/2;
x -= addedHeight/2 * Math.sin(rotation);
y += addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('rotatePlus').addEventListener('click', function() {
rotation += addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
document.getElementById('rotateMinus').addEventListener('click', function() {
rotation -= addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
draw();
I want to move from having a ball bounce back and forth around a canvas to having some gravity and eventually dropping.
I know i need to change the velocity when it reaches the bottom but i have no idea how this should be done and coded.
I am a completely new JS student, with no physics background - how hard is this going to be? I'm quite happy to learn etc. I tried having balls collide and come off at correct angles but that seems way above me for now.
var canvas,
ctx,
cx = 100,
cy = 150,
vx = 0,
vy = 5,
radius = 30;
function init() {
canvas = document.getElementById("gameCanvas");
ctx = canvas.getContext("2d");
circle();
}
function circle() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
requestAnimationFrame(circle);
if (cx + radius > canvas.width || cx - radius < 0)
vx = -vx;
if (cy + radius > canvas.height || cy - radius < 0)
vy = -vy;
cx += vx;
cy += vy;
}
I've taken out cx movement just for up/down animation and the circle draw codes for space
What would be the next step?
Will i be multiplying its current velocity by a number like 0.8 on collision and where/how?
Forgive basicness/horrible written code - gotta start somewhere!
You were very close, think of the gravity as a constant downwards velocity increment, so in each step you need to add that to your vy calculation.
"I know i need to change the velocity when it reaches the bottom"`
That is not true because gravity affects objects ALL the time. When you touch the bottom, things like material dampening and surface friction can happen.
var canvas,
ctx,
cx = 100,
cy = 100,
vx = 2,
vy = 5,
radius = 5,
gravity = 0.2,
damping = 0.9,
traction = 0.8,
paused = false;
;
function init() {
canvas = document.getElementById("gameCanvas");
ctx = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 150;
circle();
}
function circle() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!paused)
requestAnimationFrame(circle);
if (cx + radius >= canvas.width) {
vx = -vx * damping;
cx = canvas.width - radius;
} else if (cx - radius <= 0) {
vx = -vx * damping;
cx = radius;
}
if (cy + radius >= canvas.height) {
vy = -vy * damping;
cy = canvas.height - radius;
// traction here
vx *= traction;
} else if (cy - radius <= 0) {
vy = -vy * damping;
cy = radius;
}
vy += gravity; // <--- this is it
cx += vx;
cy += vy;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'green';
ctx.fill();
}
init();
// fancy/irrelevant mouse grab'n'throw stuff below
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mouseup', handleMouseUp);
function handleMouseDown(e) {
cx = e.pageX - canvas.offsetLeft;
cy = e.pageY - canvas.offsetTop;
vx = vy = 0;
paused = true;
}
function handleMouseUp(e) {
vx = e.pageX - canvas.offsetLeft - cx;
vy = e.pageY - canvas.offsetTop - cy;
paused = false;
circle();
}
canvas {border: 1px solid black; cursor: crosshair;}
p {margin: 0;}
<canvas id="gameCanvas"></canvas>
<p>Throw the ball by holding and releasing the left mouse button on the canvas (reverse slingshot)</p>
I'm trying to make a ball bounce inside a canvas and it doesn't work. The ball is getting stuck against the "walls" and I don't understand why. Anyone know how I can fix this?
var can = document.querySelector("canvas");
var ctx = can.getContext("2d");
var canvasWidth = 500;
var canvasHeight = 400;
var radius = 30;
var pX = 60;
var pY = 50;
function draw() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.fillStyle = "red";
ctx.arc(pX, pY, radius, 0, Math.PI * 2, false);
ctx.fill();
ctx.closePath();
}
function animate() {
var vX = 3;
var vY = 3;
if (pY >= canvasHeight - radius) {
vY = -vY;
}
if (pX >= canvasWidth - radius) {
vX = -vX;
}
pX += vX;
pY += vY;
draw();
requestAnimationFrame(animate);
}
animate();
Your conditions for collision detection only consider the bottom wall and the right-side wall. You need to add conditions for the top wall and the left-side wall. Something like
if (pY >= canvasHeight - radius || pY <= radius) {
vY = -vY;
}
if (pX >= canvasWidth - radius || pX <= radius) {
vX = -vX;
}
You are seeing the strange behaviour with your collision detection because vX and vY are locally declared and initialized in animate. This means, every time animate is called, it will be re-initialized.
Simply move vX and vY declarations out of the animate function.
EDIT: JsFiddle if you want to see it in action: http://jsfiddle.net/y45fhko7/