Recently i've embarked on learning canvas animations and have been following a few tutorials etc online. However the maths aspect i've started to hit a few boundaries with. One paticular issue involves reversing the x and y coordinates of a ball once it hits a div element inside the canvas. A picture representation can be seen below of the expected desired movements of the ball.
As you can see, i would like the balls x and y cordinates change based on the side of the div thats touched.
At the present moment the ball does bounce of the sides of the canvas correctly and also does bounce correctly off of the left and right side of the main div. However once the ball hits the top or bottom of the main div, the logic breaks. My code for the project can be seen below.
var canvas = document.querySelector('canvas');
var content = document.querySelector('.main-content h1');
var contentPosition = content.getBoundingClientRect();
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var radius = 10;
var maxRadius = 20;
var mouse = {
x: undefined,
y: undefined
}
var colour = [
'#FF530D',
'#E82C0C',
'#FF0000',
'#E80C7A',
'#FF0DFF'
];
window.addEventListener('mousemove', function(event){
mouse.x = event.x;
mouse.y = event.y;
});
//c = context
var c = canvas.getContext('2d');
function circle(x, y, dx, dy, radius){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.minRadius = Math.floor(Math.random() * 10 + 1);
this.fillColour = colour[Math.floor(Math.random() * colour.length )];
this.draw = function(){
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI*2);
c.fillStyle = this.fillColour;
c.fill();
c.closePath();
}
this.update = function(){
if(this.x + this.radius > innerWidth || this.x - this.radius < 0){
this.dx =- this.dx;
}
if(this.y + this.radius >= innerHeight || this.y - this.radius < 0){
this.dy =- this.dy;
}
if(this.x + this.radius > contentPosition.left && this.x - this.radius < contentPosition.right && this.y + this.radius > contentPosition.top && this.y - this.radius < contentPosition.bottom){
this.dx =- this.dx;
}
if(this.x - mouse.x < 50 && (this.x - mouse.x) > -50
&& this.y - mouse.y < 50 && this.y - mouse.y > -50){
if(this.radius < maxRadius){
this.radius +=2;
}
}
else if(this.radius > this.minRadius){
this.radius -= 2;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
}
circleArray = [];
function getDistance(x1, y1, x2, y2){
var xDistance = x2 - x1;
var yDistance = y2 - y1;
return Math.sqrt( Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
function randomIntFromInterval(min,max)
{
return Math.floor(Math.random()*(max-min+1)+min);
}
function init(){
for(i = 0; i < 100; i++){
x = randomIntFromInterval(radius, innerWidth - radius);
y = randomIntFromInterval(radius, innerHeight - radius);
dx = (Math.random() - 0.5) * 2;
dy = (Math.random() - 0.5) * 2;
circleArray.push(new circle(x, y, dx, dy, radius));
}
}
function animate(){
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight);
for(i = 0; i < circleArray.length; i++){
circleArray[i].update();
}
}
init();
animate();
canvas {
position: fixed;
z-index: -9999;
}
body {
margin: 0;
}
.main-content{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.main-content h1{
font-weight: 700;
font-size: 40px;
text-shadow: 0 0 2px #464646;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="http://code.jquery.com/jquery-3.3.1.slim.js" integrity="sha256-fNXJFIlca05BIO2Y5zh1xrShK3ME+/lYZ0j+ChxX2DA=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<link rel="stylesheet" href="styles.css">
<title>Canvas Test</title>
</head>
<body>
<canvas></canvas>
<div class="main-content">
<h1 class="text-center">Example Text For Main Div</h1>
</div>
<script src="canvas.js"></script>
</body>
</html>
The above demonstrates the whole working logic. The code that detects the collision with the main div from the above snippet is as below.
if(this.x + this.radius > contentPosition.left && this.x - this.radius < contentPosition.right && this.y + this.radius > contentPosition.top && this.y - this.radius < contentPosition.bottom){
this.dx =- this.dx;
}
At the present moment, only the x coordinate of the ball changes once the left or right side of the div is hit by the ball. I need this adapted to also include the change of Y coordinate if the top or bottom of the main div is hit.
Any advice on how to correct my code or any knowledge of online maths based material which will point to the answer would greatly be appreciated.
Thanks in advance
Update
updated circle projected path based on code given in answer
答案 0 :(得分:1)
已更新以通知网格问题已得到修复,并且包含一个显示正在使用的解决方案的代码段。
基于给定的图像,我假设圆圈总是在矩形之外开始,并且矩形将始终是轴对齐的。您可以根据相应的墙对分隔x和y否定。下面的解决方案测试一个圆圈在div的一侧发现自己的那一刻。
// Aliases to avoid thinking of offsetting positions
var circleTop = this.y - this.radius;
var circleBottom = this.y + this.radius;
var circleLeft = this.x - this.radius;
var circleRight = this.x + this.radius;
// For uniformity with circle
var rectTop = contentPosition.top;
var rectBottom = contentPosition.bottom;
var rectLeft = contentPosition.left;
var rectRight = contentPosition.right;
// Circle penetration on the div's left and right walls
if (((circleRight > rectLeft && circleLeft < rectLeft) ||
(circleLeft < rectRight && circleRight > rectRight)) &&
circleTop < rectBottom && circleBottom > rectTop) {
this.dx = -this.dx;
}
// Circle penetration on the div's top and bottom walls
if (((circleBottom > rectTop && circleTop < rectTop) ||
(circleTop < rectBottom && circleBottom > rectBottom)) &&
circleLeft < rectRight && circleRight > rectLeft) {
this.dy = -this.dy;
}
以下是我移植的代码。
var canvas = document.querySelector('canvas');
var content = document.querySelector('.main-content h1');
var contentPosition = content.getBoundingClientRect();
// For uniformity with circle
var rectTop = contentPosition.top;
var rectBottom = contentPosition.bottom;
var rectLeft = contentPosition.left;
var rectRight = contentPosition.right;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var radius = 10;
var maxRadius = 20;
var mouse = {
x: undefined,
y: undefined
}
var colour = [
'#FF530D',
'#E82C0C',
'#FF0000',
'#E80C7A',
'#FF0DFF'
];
window.addEventListener('mousemove', function(event){
mouse.x = event.x;
mouse.y = event.y;
});
//c = context
var c = canvas.getContext('2d');
function circle(x, y, dx, dy, radius){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.minRadius = Math.floor(Math.random() * 10 + 1);
this.fillColour = colour[Math.floor(Math.random() * colour.length )];
this.draw = function(){
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI*2);
c.fillStyle = this.fillColour;
c.fill();
c.closePath();
}
this.update = function(){
if(this.x + this.radius > innerWidth || this.x - this.radius < 0){
this.dx =- this.dx;
}
if(this.y + this.radius >= innerHeight || this.y - this.radius < 0){
this.dy =- this.dy;
}
// Aliases to avoid thinking of offsetting positions
var circleTop = this.y - this.radius;
var circleBottom = this.y + this.radius;
var circleLeft = this.x - this.radius;
var circleRight = this.x + this.radius;
// Circle penetration on the div's left and right walls
if (((circleRight > rectLeft && circleLeft < rectLeft) ||
(circleLeft < rectRight && circleRight > rectRight)) &&
circleTop < rectBottom && circleBottom > rectTop) {
this.dx = -this.dx;
}
// Circle penetration on the div's top and bottom walls
if (((circleBottom > rectTop && circleTop < rectTop) ||
(circleTop < rectBottom && circleBottom > rectBottom)) &&
circleLeft < rectRight && circleRight > rectLeft) {
this.dy = -this.dy;
}
if(this.x - mouse.x < 50 && (this.x - mouse.x) > -50
&& this.y - mouse.y < 50 && this.y - mouse.y > -50){
if(this.radius < maxRadius){
this.radius +=2;
}
}
else if(this.radius > this.minRadius){
this.radius -= 2;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
}
circleArray = [];
function getDistance(x1, y1, x2, y2){
var xDistance = x2 - x1;
var yDistance = y2 - y1;
return Math.sqrt( Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
function randomIntFromInterval(min,max)
{
return Math.floor(Math.random()*(max-min+1)+min);
}
function init(){
for(i = 0; i < 100; i++){
x = randomIntFromInterval(radius, innerWidth - radius);
y = randomIntFromInterval(radius, innerHeight - radius);
dx = (Math.random() - 0.5) * 2;
dy = (Math.random() - 0.5) * 2;
circleArray.push(new circle(x, y, dx, dy, radius));
}
}
function animate(){
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight);
for(i = 0; i < circleArray.length; i++){
circleArray[i].update();
}
}
init();
animate();
&#13;
canvas {
position: fixed;
z-index: -9999;
}
body {
margin: 0;
}
.main-content{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.main-content h1{
font-weight: 700;
font-size: 40px;
text-shadow: 0 0 2px #464646;
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="http://code.jquery.com/jquery-3.3.1.slim.js" integrity="sha256-fNXJFIlca05BIO2Y5zh1xrShK3ME+/lYZ0j+ChxX2DA=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<link rel="stylesheet" href="styles.css">
<title>Canvas Test</title>
</head>
<body>
<canvas></canvas>
<div class="main-content">
<h1 class="text-center">Example Text For Main Div</h1>
</div>
<script src="canvas.js"></script>
</body>
</html>
&#13;
应该注意的是,如果你的圈子的速度(你的this.dx
和this.dy
)足够大,并且你的div和圆圈足够小,那么圆圈就有可能穿过div的墙壁,而不会触发上面的碰撞条件。在视频游戏碰撞编程中,这个问题被称为隧道,并且有更多涉及的算法来减轻影响,例如及时提前投射运动形状并针对其阴影进行测试。
对于更复杂的情况,您可能会发现一些更有用的碰撞算法,例如Separating Axis Theorem(SAT算法),当您的形状不再是轴对齐时。
答案 1 :(得分:1)
您要处理的问题是,您需要知道圆圈越过框的边界:通过水平边框或垂直边框。否则你可能会翻转错误的变量符号。
如果您检查圆圈当前最接近的四边中的哪一边变得更容易,并让该边确定弹跳效果。
另外,我不会只是翻转标志,而是明确说明标志应该是什么,以避免你不停地来回翻转并让那些摇摇晃晃的圆圈走路。
以下是它的外观:
if (this.x + this.radius > innerWidth || this.x - this.radius < 0) {
this.dx = (this.x < this.radius || -1) * Math.abs(this.dx);
}
if (this.y + this.radius > innerHeight || this.y - this.radius < 0) {
this.dy = (this.y < this.radius || -1) * Math.abs(this.dy);
}
if (this.y + this.radius > contentPosition.top
&& this.y - this.radius < contentPosition.bottom
&& this.x + this.radius > contentPosition.left
&& this.x - this.radius < contentPosition.right) {
// Choose which side of the box is closest to the circle's centre
var dists = [Math.abs(this.x - contentPosition.left),
Math.abs(this.x - contentPosition.right),
Math.abs(this.y - contentPosition.top),
Math.abs(this.y - contentPosition.bottom)];
// Get minimum value's index in array
var i = dists.indexOf(Math.min.apply(Math, dists));
// ... that will be the side that dictates the bounce
if (i < 2) {
this.dx = (i || -1) * Math.abs(this.dx);
} else {
this.dy = (i > 2 || -1) * Math.abs(this.dy);
}
}
在这个片段中,我还添加了一些代码,以避免圆圈的初始位置在框内:
var canvas = document.querySelector('canvas');
var content = document.querySelector('.main-content h1');
var contentPosition = content.getBoundingClientRect();
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var radius = 10;
var maxRadius = 20;
var mouse = {
x: undefined,
y: undefined
}
var colour = [
'#FF530D',
'#E82C0C',
'#FF0000',
'#E80C7A',
'#FF0DFF'
];
window.addEventListener('mousemove', function(event){
mouse.x = event.x;
mouse.y = event.y;
});
//c = context
var c = canvas.getContext('2d');
function circle(x, y, dx, dy, radius){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.minRadius = Math.floor(Math.random() * 10 + 1);
this.fillColour = colour[Math.floor(Math.random() * colour.length )];
this.draw = function(){
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI*2);
c.fillStyle = this.fillColour;
c.fill();
c.closePath();
}
this.update = function(){
if (this.x + this.radius > innerWidth || this.x - this.radius < 0) {
this.dx = (this.x < this.radius || -1) * Math.abs(this.dx);
}
if (this.y + this.radius > innerHeight || this.y - this.radius < 0) {
this.dy = (this.y < this.radius || -1) * Math.abs(this.dy);
}
if (this.y + this.radius > contentPosition.top && this.y - this.radius < contentPosition.bottom
&& this.x + this.radius > contentPosition.left && this.x - this.radius < contentPosition.right) {
// Choose which side of the box is closest to the circle's centre
var dists = [Math.abs(this.x - contentPosition.left),
Math.abs(this.x - contentPosition.right),
Math.abs(this.y - contentPosition.top),
Math.abs(this.y - contentPosition.bottom)];
var i = dists.indexOf(Math.min.apply(Math, dists)); // Get minimum value's index in array
// ... that will be the side that dictates the bounce
if (i < 2) {
this.dx = (i || -1) * Math.abs(this.dx);
} else {
this.dy = (i > 2 || -1) * Math.abs(this.dy);
}
}
if(this.x - mouse.x < 50 && (this.x - mouse.x) > -50
&& this.y - mouse.y < 50 && this.y - mouse.y > -50){
if(this.radius < maxRadius){
this.radius +=2;
}
}
else if(this.radius > this.minRadius){
this.radius -= 2;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
}
circleArray = [];
function getDistance(x1, y1, x2, y2){
var xDistance = x2 - x1;
var yDistance = y2 - y1;
return Math.sqrt( Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}
function randomIntFromInterval(min,max){
return Math.floor(Math.random()*(max-min+1)+min);
}
function init(){
for(var i = 0; i < 100; i++){
do { // repeat until not in box
var x = randomIntFromInterval(radius, innerWidth - radius);
var y = randomIntFromInterval(radius, innerHeight - radius);
} while (x + radius > contentPosition.left && x - radius < contentPosition.right
&& y + radius > contentPosition.top && y - radius < contentPosition.bottom);
var dx = (Math.random() - 0.5) * 2;
var dy = (Math.random() - 0.5) * 2;
circleArray.push(new circle(x, y, dx, dy, radius));
}
}
function animate(){
requestAnimationFrame(animate);
c.clearRect(0, 0, innerWidth, innerHeight);
for(var i = 0; i < circleArray.length; i++){
circleArray[i].update();
}
}
init();
animate();
canvas {
position: fixed;
z-index: -9999;
}
body {
margin: 0;
}
.main-content{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.main-content h1{
font-weight: 700;
font-size: 40px;
text-shadow: 0 0 2px #464646;
border: 1px solid
}
<canvas></canvas>
<div class="main-content">
<h1 class="text-center">Example Text For Main Div</h1>
</div>