I am trying to implement a fairly simple merge fruit game using canvas, in which the balls must fall into a glass, and if the balls are the same size, they touch each other. They turn into one big ball. But there is a problem with gravity and collision. Over time, they just start to squeeze into each other. Most likely, this is due to gravity, which attracts them until they touch the floor. But I do not know how to manipulate her. And do not lose gravity in a collision. Because it is necessary that the balls still have the opportunity to roll over each other. Please help me!
Sorry if the code looks clumsy. I just tried everything I could and eventually started getting confused in my own code.
you can check it in code pen version
https://codepen.io/njtbmhbh-the-animator/pen/wvLrmdK
//Функция для ограничения количества кадров в секунду
var limitLoop = function (fn, fps) {
// Use var then = Date.now(); if you
// don't care about targetting < IE9
var then = new Date().getTime();
// custom fps, otherwise fallback to 60
fps = fps || 60;
var interval = 1000 / fps;
return (function loop(time){
requestAnimationFrame(loop);
// again, Date.now() if it's available
var now = new Date().getTime();
var delta = now - then;
if (delta > interval) {
// Update time
// now - (delta % interval) is an improvement over just
// using then = now, which can end up lowering overall fps
then = now - (delta % interval);
// call the fn
fn();
}
}(0));
};
//Полезные функции
function randomIntFromRange(min,max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function frameCheck(ball){
if(ball.y + ball.radius + ball.velocity.y > canvas.height){
ball.velocity.y = -ball.velocity.y * 0.6;
} else {
if(!ball.colideState){
ball.velocity.y += gravity;
}
}
if(ball.x + ball.radius + ball.velocity.x >= canvas.width || ball.x - ball.radius + ball.velocity.x<= 0){
ball.velocity.x = -ball.velocity.x;
}
}
function getDistance(x1, y1, x2, y2){
let xDistance = x2 - x1;
let yDistance = y2 - y1;
return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2))
}
//Утилита для рассчета коллизии
/**
* Rotates coordinate system for velocities
*
* Takes velocities and alters them as if the coordinate system they're on was rotated
*
* @param Object | velocity | The velocity of an individual particle
* @param Float | angle | The angle of collision between two objects in radians
* @return Object | The altered x and y velocities after the coordinate system has been rotated
*/
function rotate(velocity, angle) {
const rotatedVelocities = {
x: velocity.x * Math.cos(angle) - velocity.y * Math.sin(angle),
y: velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle)
};
return rotatedVelocities;
}
/**
* Swaps out two colliding particles' x and y velocities after running through
* an elastic collision reaction equation
*
* @param Object | particle | A particle object with x and y coordinates, plus velocity
* @param Object | otherParticle | A particle object with x and y coordinates, plus velocity
* @return Null | Does not return a value
*/
function resolveCollision(particle, otherParticle) {
const xVelocityDiff = particle.velocity.x - otherParticle.velocity.x;
const yVelocityDiff = particle.velocity.y - otherParticle.velocity.y;
const xDist = otherParticle.x - particle.x;
const yDist = otherParticle.y - particle.y;
// Prevent accidental overlap of particles
if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {
// Grab angle between the two colliding particles
const angle = -Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x);
// Store mass in var for better readability in collision equation
const m1 = particle.mass;
const m2 = otherParticle.mass;
// Velocity before equation
const u1 = rotate(particle.velocity, angle);
const u2 = rotate(otherParticle.velocity, angle);
// Velocity after 1d collision equation
const v1 = { x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), y: u1.y };
const v2 = { x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2), y: u2.y };
// Final velocity after rotating axis back to original location
const vFinal1 = rotate(v1, -angle);
const vFinal2 = rotate(v2, -angle);
if(particle.y != particle.pastY){
// Swap particle velocities for realistic bounce effect
if(particle.y + particle.radius + particle.velocity.y < canvas.height){
particle.velocity.y = vFinal2.y * 0.8;
}
if(otherParticle.y + otherParticle.radius + otherParticle.velocity.y < canvas.height){
otherParticle.velocity.y = vFinal2.y * 0.8;
}
}
if(particle.x + particle.radius + particle.velocity.x < canvas.width && particle.x - particle.radius + particle.velocity.x > 0){
particle.velocity.x = vFinal1.x ;
} else {}
if(otherParticle.x + otherParticle.radius + otherParticle.velocity.x < canvas.width && otherParticle.x - otherParticle.radius + otherParticle.velocity.x > 0){
otherParticle.velocity.x = vFinal2.x ;
} else {}
}
}
//game
const canvas = document.querySelector('canvas')
const c = canvas.getContext('2d');
canvas.height = window.screen.height - window.screen.height * 0.6; // window heigh - 20 percents
canvas.width = document.querySelector('.container').clientWidth * 0.5; // container width
let objects = []
//Массив шариков
let ballsTemplates = {
1:{
y: 50,
direction: 0,
radius: 20,
color: "blue",
level: 1
},
2:{
y: 50,
direction: 0,
radius: 25,
color: "green",
level: 2
},
3:{
y: 50,
direction: 0,
radius: 30,
color: "black",
level: 3
},
4:{
y: 50,
direction: 0,
radius: 35,
color: "red",
level: 4
},
}
const gravity = 0.5;
//objects
class Ball {
constructor(x, y, dx, dy, bp, radius, color, level) {
this.x = x
this.y = y
this.pastX = 0
this.pastY = 0
this.colideState = false;
this.velocity = {
x: dx,
y: dy
}
this.level = level
this.bp = bp;
this.radius = radius
this.color = color
this.mass = 1;
}
draw() {
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false)
c.fillStyle = this.color
c.fill()
c.closePath()
}
update = objects => {
this.pastX = this.x;
this.pastY = this.y
this.y += this.velocity.y
this.velocity.x = this.velocity.x * 0.99;
this.x += this.velocity.x;
//Не даём шарику возможности упать за границы карты
frameCheck(this);
for (let i=0; i < objects.length; i++){
if(objects[i]){
if(this === objects[i]) continue;
if(getDistance(this.x, this.y, objects[i].x, objects[i].y) - (this.radius + objects[i].radius) < -0.8){
//Вызов функции коллизии
resolveCollision(this, objects[i]);
let lvl = this.level;
let lvl2 = objects[i].level;
if(lvl === lvl2){
if(lvl < 4){
let x = (this.x + objects[i].x) / 2
let y = (this.y + objects[i].y) / 2
this.x = x;
this.y = y;
this.direction = ballsTemplates[lvl + 1].direction;
this.dy = 0.5;
this.bp = 0.9;
this.radius = ballsTemplates[lvl + 1].radius;
this.color = ballsTemplates[lvl + 1].color;
this.level = ballsTemplates[lvl + 1].level;
delete objects[i];
}
}
if(this.y == this.pastY){
this.colideState = true;
}
} else {
this.colideState = false;
}
}
}
this.draw()
}
}
//функция для создания шарика по клику
canvas.addEventListener('mouseup', function (event) {
// Implementation
const rect = canvas.getBoundingClientRect();
const computedStyle = getComputedStyle(canvas);
const borderLeftWidth = parseInt(computedStyle.borderLeftWidth, 10);
const borderTopWidth = parseInt(computedStyle.borderTopWidth, 10);
let numb = randomIntFromRange(1,4);
let x = event.clientX - rect.left - borderLeftWidth
let y = event.clientY - rect.top - borderTopWidth
let radius = 55;
let dxWerid = 5;
if(x + radius * 2 > canvas.width){
x = canvas.width - radius;
} else if (x - radius * 2 < canvas.width){
x = x + radius;
}
let ball = new Ball( x , y, ballsTemplates[numb].direction , 1 , 0.9,ballsTemplates[numb].radius, ballsTemplates[numb].color, ballsTemplates[numb].level)
objects.push(ball)
});
const animate = async () => {
c.fillStyle="white"
c.fillRect(0, 0, canvas.width, canvas.height) //background :/
objects.forEach(object => {
object.update(objects)
})
}
limitLoop(animate, 60);
<!-- language: lang-css -->
.gameScreen {
border: 10px solid white;
background-color: black;
}
<!-- language: lang-html -->
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"><div class="container" height="200" width="332"><div><div><canvas class="gameScreen"></canvas> <- С попощью левой кнопки мыши заспавни много шариков. Чтобы было несколько рядок</div></div></div></div>
</body>```
I tried to implement a mechanic in which the balls push each other out when they collide. But it really spoils the physics and the external component. I'm trying to figure out how to do the right thing here.