Сначала вы должны исправить логику столкновений c, затем вы можете смело менять размер холста по мере необходимости.
Всегда разрешать коллизии полностью.
Неразрешенное столкновение означает, что ваш сим находится в невозможном состоянии (например, мяч внутри стены). С этого момента следующие состояния будут иметь меньшее значение.
Fix
Причина, по которой шары выходят из холста, заключается в том, что вы не перемещаете их обратно на холст, если они находятся снаружи.
Также, когда вы изменяете направление мяча, когда он ударяет в сторону, вы не гарантируете, что направление является правильным знаком.
// if the dx = 1 and x > innerWidth + 1000 then dx becomes -1
// Next tine x is still > innerWidth + 1000 and you flip the sign of dx again to 1,
// Then dx to -1 and so on. You never move the ball
if(x + radius > innerWidth || x - radius < 0) {
dx = -dx;
}
Будьте систематизированы c в тесте на столкновение, чтобы когда игровое поле (холст) меняет размер, вы не получите неожиданных результатов
При обновлении сначала переместите, а затем проверьте наличие столкновений. Если произошло столкновение, переместите шар еще раз, чтобы не происходило никакого магического c шара в стене.
update() {
this.x += this.dx;
this.y += this.dy;
if (this.x < this.radius) {
this.x = this.radius;
this.dx = Math.abs(this.dx);
}
if (this.y < this.radius) {
this.y = this.radius;
this.dy = Math.abs(this.dy);
}
if (this.x > ctx.canvas.width - this.radius) {
this.x = ctx.canvas.width - this.radius;
this.dx = -Math.abs(this.dx);
}
if (this.y > ctx.canvas.height - this.radius) {
this.y = ctx.canvas.height - this.radius;
this.dy = -Math.abs(this.dy);
}
}
Изменение размера холста
После решения проблемы столкновения вы можете изменить размер холста, когда вам нужно. Один из способов - сделать это внутри функции анимации.
Если размер холста не соответствует размеру страницы, измените размер холста, чтобы он соответствовал.
function animate () {
if (ctx.canvas.width !== innerWidth || ctx.canvas.height !== innerHeight) {
ctx.canvas.width = innerWidth;
ctx.canvas.height = innerHeight;
} else {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
}
// ... do stuff
requestAnimationFrame(animate);
}
Обратите внимание , что изменение размера холста также очищает холст, поэтому очистка указана в предложении else
.
Демонстрация копирования и вставки
В коде было много плохих моментов. Вы можете скопировать вставленный ниже фрагмент в кодовое перо или использовать приведенную выше информацию, чтобы изменить свой код в своем стиле.
const canvas = document.querySelector('#responsive-canvas')
canvas.width = innerWidth;
canvas.height = innerHeight;
// Set constants in one place so you can make changes quickly and easily
const DEFAULT_RADIUS = 50;
const MAX_SPEED = 5;
const CIRCLE_COUNT = 100;
Math.TAU = Math.PI * 2
// Use function to do repetitive code
Math.rand = (min, max) => Math.random() * (max - min) + min; //
function randomHexColor() {
return "#" + ((Math.random() * 0xFFFFFF | 0).toString(16).padStart(6,"0"));
}
// pulral names for arrays and variables that do not change should be constants
const circles = [];
const ctx = canvas.getContext('2d');
requestAnimationFrame(animate); // start frame renderer with a request, dont call it directly
function Circle( // using default params to set random values
radius = Math.rand(DEFAULT_RADIUS/4, DEFAULT_RADIUS), // radius must be first argument as its used to set random x, and y
x = Math.rand(radius, ctx.canvas.width - radius),
y = Math.rand(radius, ctx.canvas.height - radius),
dx = Math.rand(-MAX_SPEED, MAX_SPEED),
dy = Math.rand(-MAX_SPEED, MAX_SPEED),
colour = randomHexColor()
) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.colour = colour;
}
// Define Circle functions as prototype outside the function Circle (runs faster)
Circle.prototype = {
draw() {
ctx.strokeStyle = ctx.fillStyle = this.colour;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.TAU);
ctx.stroke();
},
update() {
this.x += this.dx;
this.y += this.dy;
if (this.x < this.radius) {
this.x = this.radius;
this.dx = Math.abs(this.dx);
}
if (this.y < this.radius) {
this.y = this.radius;
this.dy = Math.abs(this.dy);
}
if (this.x > ctx.canvas.width - this.radius) {
this.x = ctx.canvas.width - this.radius;
this.dx = -Math.abs(this.dx);
}
if (this.y > ctx.canvas.height - this.radius) {
this.y = ctx.canvas.height - this.radius;
this.dy = -Math.abs(this.dy);
}
}
};
for (let i = 0; i < CIRCLE_COUNT; i++) { circles.push(new Circle()) }
function animate () {
if (ctx.canvas.width !== innerWidth || ctx.canvas.height !== innerHeight) {
ctx.canvas.width = innerWidth;
ctx.canvas.height = innerHeight;
} else {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
}
// use for of loop (saves having to mess with index)
for (const circle of circles) {
circle.update(); // seperate update and draw
circle.draw()
}
requestAnimationFrame(animate);
}