Glitchy collision resolution between mouse-controlled player and obstacle squares - javascript

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.

Related

How do i make a collision in pong game in canvas to work?

I have tried to make a collision so whenever the ball touches the paddles it will bounce back. but there is a problem that i am stuck with and cant figure it out. the problem is that the ball bounces back not just on the paddle but also over and under it. it bounces over the whole yaxis. i'm new to this so i thought maybe start with a simple game. any help could be helpful. thanks
let x = canvas.width / 2
let y = canvas.height / 2
let ballX = 3
let ballY = -3
let player1 = {
x: 50,
y: canvas.height / 2.5,
height: 80,
width: 15,
speed: 10
}
function left_paddle() {
ctx.beginPath();
ctx.strokeStyle = "white";
ctx.rect(player1.x, player1.y, player1.width, player1.height);
ctx.stroke();
ctx.fill()
}
function draw_ball() {
ctx.beginPath()
ctx.arc(x, y, radius, 0, Math.PI * 2)
ctx.fillStyle = "white"
ctx.fill()
}
window.addEventListener("keydown", left)
window.addEventListener("keydown", right)
function left(e) {
if (e.keyCode == 83) {
player1.y += player1.speed
if (player1.y + player1.height > canvas.height) { // left paddle down
player1.y = canvas.height - player1.height
}
} else if (e.keyCode == 87) {
player1.y -= player1.speed
if (player1.y < 0) { // left paddle up
player1.y = 0
}
}
}
function detectCollision() {
if (y + ballY > canvas.height - radius || y + ballY < radius) { // top and down
ballY = -ballY
}
if (x + ballX > canvas.width - radius) { // right and left
ballX = 0
ballY = 0
score1++
console.log(score1)
} else if (x + ballX < radius) {
ballX = 0
ballY = 0
score2++
}
if (x - radius < (player1.x + player1.width)) { // left paddle collision
ballX = - ballX
}
if (x + radius > player2.x) { // right paddle collision
ballX = -ballX
}
}
You have to check the height and y position of the paddles. There are 3 cases for collision between ball and paddle:
top corner
middle
bottom corner
Middle:
if (x - radius < (player1.x + player1.width) && y <= (player1.y + player1.height) && y >= player1.y)
Top corner:
if (Math.sqrt((player1.x + player1.width - x) ** 2 + (player1.y - y) ** 2) < radius)
Bottom corner:
if (Math.sqrt((player1.x + player1.width - x) ** 2 + (player1.y + player1.height - y) ** 2) < radius)
There's much space to improve the performance. That's just the basic checks. You shouldn't do these checks in each step, only when the x distance and y distance is small enough. radius ** 2 is usually better than Math.sqrt.
There are whole books only about the topic collision detection.

How to fix performance lag in canvas html5?

I am building a project where a user can type word in the input text and with the input value, canvas draws the particles onto the text. When the mouse hovers over the particles pushed back and comes back(core animation)
However, the performance is just terrible, it's way too slow and I have been looking up online and found stuff like frame rate, display ratio, getImageData, putImageData, new Uint32Array(), bitwise operator etc but after hours of trying different things I noticed I wasn't making any progress rather got stuck deeper
My codes are below, and if anyone can tell me where I should go about fixing it would be great.
in index.html
<canvas> </canvas>
<form>
<input class="text" type="text" value="touch me!" placeholder="type your message.."/>
<div class="input-bottom"></div>
</form>
in app.js - I didn't include any code on form submit since it works fine
let canvas = document.querySelector(".canvas")
let canvasContext2d = canvas.getContext("2d")
let canvasWidth = canvas.width = window.innerWidth
let canvasHeight = canvas.height = window.innerHeight
let form = document.querySelector('form')
let text = form.querySelector(".text")
let textMessage = text.value
let mouse = {x: undefined, y: undefined}
function Particle(x, y, r, accX, accY){
this.x = randomIntFromRange(r, canvasWidth-r)
this.y = randomIntFromRange(r, canvasHeight-r)
this.r = r
this.color = "black"
this.velocity = {
x: randomIntFromRange(-10, 10),
y: randomIntFromRange(-10, 10)
}
this.dest = {x : x, y : y}
this.accX = 5;
this.accY = 5;
this.accX = accX;
this.accY = accY;
this.friction = randomNumDecimal(0.94, 0.98)
this.draw = function(){
canvasContext2d.beginPath()
canvasContext2d.arc(this.x, this.y, this.r, 0, Math.PI * 2)
canvasContext2d.fillStyle = "rgb(250, 250, 247)"
canvasContext2d.fill()
canvasContext2d.closePath()
// mouse ball
canvasContext2d.beginPath()
canvasContext2d.arc(mouse.x, mouse.y, 50, 0, Math.PI * 2)
canvasContext2d.fill()
canvasContext2d.closePath()
}
this.update = function(){
this.draw()
if(this.x + this.r > canvasWidth || this.x - this.r < 0){
this.velocity.x = -this.velocity.x
}
if(this.y + this.r > canvasHeight || this.y - this.r < 0){
this.velocity.y = -this.velocity.y
}
this.accX = (this.dest.x - this.x) / 300;
this.accY = (this.dest.y - this.y) / 300;
this.velocity.x += this.accX;
this.velocity.y += this.accY;
this.velocity.x *= this.friction;
this.velocity.y *= this.friction;
this.x += this.velocity.x;
this.y += this.velocity.y;
if(dist(this.x, this.y, mouse.x, mouse.y) < 70){
this.accX = (this.x - mouse.x) / 30;
this.accY = (this.y - mouse.y) / 30;
this.velocity.x += this.accX;
this.velocity.y += this.accY;
}
}
}
let particles;
function init(){
particles = []
canvasContext2d.font = `bold ${canvasWidth/10}px sans-serif`;
canvasContext2d.textAlign = "center"
canvasContext2d.fillText(textMessage, canvasWidth/2, canvasHeight/2)
let imgData = canvasContext2d.getImageData(0, 0, canvasWidth, canvasHeight)
let data = imgData.data
for(let i = 0; i < canvasWidth; i += 4){
for(let j = 0; j < canvasHeight; j += 4){
if(data[((canvasWidth * j + i) * 4) + 3]){
let x = i + randomNumDecimal(0, 3)
let y = j + randomNumDecimal(0, 3)
let r = randomNumDecimal(1, 1.5)
let accX = randomNumDecimal(-3, 0.2)
let accY = randomNumDecimal(-3, 0.2)
particles.push(new Particle(x, y, r, accX, accY))
}
}
}
}
function animate(){
canvasContext2d.clearRect(0, 0, canvasWidth, canvasHeight)
for(let i = 0; i < particles.length; i++){
particles[i].update()
}
requestAnimationFrame(animate)
}
init()
animate()
First you can consider reducing how much total work is going on for a full screen's number of pixels, for instance:
reduce the canvas size (you can consider using CSS transform: scale to scale it back up if you must),
reduce the number of particles,
use a less expensive/less accurate distance operation like just checking horizontal distance and vertical distance between two objects,
use integer values instead of floats (drawing to canvas with floats is more expensive)
consider using fillRect instead of drawing arcs. (At such a small size it won't make much of a difference visually, but these are generally less expensive to draw- you may want to test if it makes much of a difference),
even consider reducing how often you redraw the canvas (adding a setTimeout to wrap your requestAnimationFrame and increasing the delay between frames (requestAnimationFrame is generally about 17ms))
And some more small optimizations in the code:
store particles.length in a variable after they are created so that you don't calculation particles.length on every for loop iteration in your animate function. But millions of calculations minus this 2048 isn't going to make much of a difference.
only set the context fillStyle once. You never change this color, so why set it on every draw?
remove the closePath() lines. They do nothing here.
draw the particles onto an offscreen "buffer" canvas, and draw that canvas to the onscreen one only after all the particles have been drawn to it. This can be done with a normal <canvas> object, but depending on what browser you are working with, you can also look into OffscreenCanvas. Basic example would look something like this:
var numParticles;
// Inside init(), after creating all the Particle instances
numParticles = particles.length;
function animate(){
// Note: if offscreen canvas has background color drawn, this line is unnecessary
canvasContext2d.clearRect(0, 0, canvasWidth, canvasHeight)
for(let i = 0; i < numParticles; i++){
particles[i].update() // remove .draw() from .update() method
particles[i].drawToBuffer(); // some new method drawing to buffer canvas
}
drawBufferToScreen(); // some new method drawing image from buffer to onscreen canvas
requestAnimationFrame(animate)
}

Detecting edge of canvas and allowing for movement away from it

I have a small game where the player is moving a circle within a canvas. I am having issues with detecting the edges of the canvas, disallowing a player to move past those edges, and allowing the user to navigate away from any given edge.
Currently, the player can hit canvas edges and move away successfully, most of the time. However, with a combination of certain movements and collision along an edge, the player will become "stuck".
How can I go about creating simple canvas edge collision detection and allow the user to move freely after the event?
My code:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height - 30;
var dx = 2;
var dy = -2;
var ballRadius = 10;
var rightPressed = false;
var leftPressed = false;
var upPressed = false;
var downPressed = false;
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) {
if (e.keyCode == 65) {
rightPressed = true;
}
if (e.keyCode == 68) {
leftPressed = true;
}
if (e.keyCode == 87) {
upPressed = true;
}
if (e.keyCode == 83) {
downPressed = true;
}
}
function keyUpHandler(e) {
if (e.keyCode == 65) {
rightPressed = false;
}
if (e.keyCode == 68) {
leftPressed = false;
}
if (e.keyCode == 87) {
upPressed = false;
}
if (e.keyCode == 83) {
downPressed = false;
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.strokeStyle = "black";
ctx.stroke();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius - 3) {
dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius - 3) {
dy = -dy;
}
if (rightPressed) {
x -= dx;
}
if (leftPressed) {
x += dx;
}
if (upPressed) {
y += dy;
}
if (downPressed) {
y -= dy;
}
}
setInterval(draw, 10);
<canvas id="myCanvas"></canvas>
Well i have to admit that have been long time since i don't touch older libraries stored in the bault. Luckley i got the sources at hand, so after taking a look i will tell a method that works for me regarding how to detect the colision of a ball object against a wall.
I guess it will fit to you needs, don't know if the better or worse solution, but works!. Let start by defining how we represent the data for each ball, recall our friends of unix that says that part of the complexity is in the data structure as it is part of the algorithm as a whole... but enought chat, going to the matters.. by using this kind of data structure for representing a Ball
class Ball
{
radio : number
x : number
y : number
}
So you can draw the ball this way:
function draw (Graphics g)
{
int r = ball.getRadio ();
int diam = rad * 2;
int px = (int) ball.getPx () - r;
int py = (int) ball.getPy () - r;
g.setColor (niceColor);
g.fillArc (px,py,diameter,diameter,0,360); // Fills a circular or elliptical arc covering the specified rectangle
}
// Disclaimer, i don't know well the primitives for graphical routines in canvas, but i would assume that you will have somethign to draw a circle with all of that. You got px, py, x, y, radio, diameter.
Anyway the answer for the question comes here where you can use this code:
function isAHorizontalCollision (ball c,int width)
{
return (c.x > width - c.radio || c.x < radio);
}
function isAVerticalCollision (ball c,int height)
{
return (c.y > height - c.radio || c.y < radio);
}
// Assume that enclosing rectangle where the ball can move is between (0,width) for horizontal, and (top,0) vertical.
Is important to advise that this work only if the x values goes incrementally from left to right and the y goes decrementally from top to bottom.
Hope my typescript alike code fits well for explaining. I have a lot of source files in java for this. If need more. (For example collision between two circles).
If you have time i would recommend to check this out, very very powerfull stuff in there.
You complicated things a bit with all the Boolean variables...
The same can be done with just a the dx and dy those are your speed set them to 0 to stop.
Also it look like you where inverting the direction of the keys that was the lines:
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius - 3) {
dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius - 3) {
dy = -dy;
}
When those condition are true the up is now down and the right is left, not sure if that was your intended behavior, when it worked it looked like the ball was bouncing from the edge but from that point on the key was inverted... I remove that from my fix.
Here is my approach, I added a bit of math fun to animate your player:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var dx = 0;
var dy = 0;
var ballRadius = 10;
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) {
if (e.keyCode == 65) dx = -2
if (e.keyCode == 68) dx = 2
if (e.keyCode == 87) dy = -2
if (e.keyCode == 83) dy = 2
}
function keyUpHandler(e) {
if (e.keyCode == 65) dx = 0
if (e.keyCode == 68) dx = 0
if (e.keyCode == 87) dy = 0
if (e.keyCode == 83) dy = 0
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
var fx = x + dx
var fy = y + dy
if (ballRadius < fx && fx < canvas.width - ballRadius) x = fx;
if (ballRadius < fy && fy < canvas.height - ballRadius) y = fy;
}
setInterval(draw, 10);
var hr = ballRadius / 2 - 1
var pi2 = Math.PI * 2
var i = 0
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, pi2);
ctx.strokeStyle = "black";
ctx.stroke();
// draw some small moving circles inside
ctx.beginPath();
ctx.fillStyle = "red";
ctx.arc(x + hr * Math.sin(i), y + hr * Math.cos(i), hr, 0, pi2);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(x - hr * Math.sin(i), y - hr * Math.cos(i), hr, 0, pi2);
ctx.fill();
ctx.closePath();
i += 0.1 + Math.abs(dx)/15 + Math.abs(dy)/15
}
<canvas id="myCanvas" style="border:1px solid #000000;"></canvas>
Thanks for the help from the two of you who answered my question. Because of you two, I was able to successfully find a solution.
Originally, I was using this to determine the edges of the canvas, and adjust direction accordingly:
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius - 3) {
dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius - 3) {
dy = -dy;
}
However, as pointed out, that reverses the movement direction, making it basically useless for my intended purpose. Not only that, but all 4 sides of the canvas act in a slightly different manor from each other.
Using the feedback from the other answer, here's the solution I am using now, which works wonderfully:
if (x + dx > canvas.width - ballRadius) {
var fx = x - dx;
x = fx;
}
if (x + dx < ballRadius) {
var fx = x + dx;
x = fx;
}
if (y + dy > canvas.height - ballRadius) {
var fy = y + dy;
y = fy;
}
if (y + dy < ballRadius) {
var fy = y - dy;
y = fy;
}
This way, I am detecting all 4 sides successfully, stopping user motion on contact, and allowing the player to move away from the side of the canvas.

Ball stops bouncing after 4-5 seconds

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>

Is there an elegant way to draw shapes around the edge of a canvas?

I want to create a seamless pattern on a canvas. I simplified the whole process down to simple rectangles. When a rectangle is drawn close to an edge of the canvas and part of it gets cut off, I want that missing part to be repeated at the other side.
I thought I'd just check whether the rectangle to be drawn is too close to the edge, and draw it again + canvas.width/height. Halfway through I realized that this might become quite a few ifs.
This is what I already have:
This is the part where I check for the edges.
// this._draw(); is a convenience method that draws the rectangle with
// the center as its registration point instead of top left corner.
// which is also why I add (this.size/2) to the coordinates.
if(this.x+( this.size/2 ) > canvas.width) {
this._draw(
this.x+canvas.width,
this.y
);
}
if(this.x-( this.size/2 ) < 0){
this._draw(
this.x+canvas.width,
this.y
);
}
if(this.y+( this.size/2 ) > canvas.height) {
this._draw(
this.x-(this.size/2),
this.y-canvas.height-(this.size/2)
)
}
if(this.y-(this.size/2) < 0){
this._draw(
this.x,
this.y+canvas.height
);
}
This is what I want
Is there some clever way to check this more efficiently? I'm certain that there's a more elegant approach than what I'm currently directed to.
The whole example is on codepen.io.
Take a look at this code:
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var LT = new Dot(0, 100, 290, 140);
var RT = new Dot(90, 75, canvas.width, 0);
var RB = new Dot(180, 50, canvas.width, canvas.height);
var LB = new Dot(270, 25, 0, canvas.height);
function Dot(color, size, x, y){
this.size = size;
this.x = x;
this.y = y;
this.color = "hsla("+color+", 100%, 50%, 1)";
this.draw = function() {
context.fillStyle = this.color;
context.strokeStyle = "hsla(0,0%,0%, 0.5)";
this._draw(x, y);
this.drawBorders();
};
this._draw = function(centerX, centerY) {
context.fillRect(
centerX - size/2,
centerY - size/2,
size,
size
);
};
this.drawBorders = function() {
var borders = 0;
var tx, ty;
if(x - size/2 <= 0){
tx = canvas.width + x;
}else if(x + size/2 >= canvas.width){
tx = canvas.width - x;
}
if(y - size/2 <= 0){
ty = canvas.height + y;
}else if(y + size/2 >= canvas.height){
ty = y - canvas.height ;
}
if(x-size/2 <= 0 || x+size/2 >= canvas.width ){
this._draw(tx, y);
}
if(y-size/2 <= 0 || y+size/2 >= canvas.height){
this._draw(x, ty);
}
if(x+size/2 >= canvas.width ||
y+size/2 >= canvas.height ||
x-size/2 <= 0 ||
y-size/2 <= 0){
this._draw(tx, ty);
}
}
this.draw();
}
document.body.appendChild(canvas);
This draws the squares to overlap the corners, but only if they indeed overlap.

Categories