javaScript 气泡 class 示例使用 P5.js 库,代码在一段时间后崩溃。我该如何规避和克服这个问题?
javaScript bubble class example using P5.js lib, code crashes after a while. how can I circumvent and overcome this?
一直在关注 Dan Shiffman 的视频,尝试使用 类 重温我的面向对象编程。
我写了一些代码,在随机位置生成随机直径的气泡,使用 p5 的噪声函数让气泡运动。
我的目的是让气泡弹出 (使用 splice() 从数组中移除) 每当气泡到达 canvas 的边缘或当两个或多个气泡相交。
代码 运行 根据需要,但过了一会儿它崩溃并抛出错误“Uncaught TypeError: Cannot read 属性 'x' of undefined (sketch: line 15)”
我试过四处乱砍,但没有任何乐趣,如果有人能阐明为什么会发生此错误,或者对我的方法提出一般性建议,我将不胜感激。这是有问题的代码。
var balls = [];
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
for (var i = 0; i < balls.length; i++) {
balls[i].showBall();
balls[i].moveBall();
for (var j = 0; j < balls.length; j++) {
if (balls[i].x < 0 + balls[i].r ||
balls[i].x > width - balls[i].r ||
balls[i].y < 0 + balls[i].r ||
balls[i].y > height - balls[i].r ||
balls[i].life >= 220 ||
i != j && balls[i].intersect(balls[j])) {
balls.splice(i, 1);
}
}
}
}
class Ball {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.t = 0.0;
this.t2 = 107.0;
this.life = 0;
}
showBall() {
noFill();
stroke(this.life);
strokeWeight(2);
ellipse(this.x, this.y, this.r * 2);
}
moveBall() {
this.x += map(noise(this.t), 0, 1, -1, 1) * 0.5;
this.y += map(noise(this.t2), 0, 1, -1, 1) * 0.5;
this.t += 0.02;
this.life += 0.15;
}
intersect(other) {
if (dist(this.x, this.y, other.x, other.y) < this.r + other.r) {
return true;
} else {
return false;
}
}
}
function randBubbleGen() {
let foo = floor(random(11));
console.log(foo);
if (foo >= 5) {
let b = new Ball(random(41, 359),
random(41, 359),
random(5, 40));
balls.push(b);
}
}
setInterval(randBubbleGen, 1000);
<script src="https://cdn.jsdelivr.net/npm/p5@1.3.1/lib/p5.js"></script>
谢谢!
P
第一部分问题是经典splice
问题:
const arr = [..."abcde"];
for (let i = 0; i < arr.length; i++) {
if (i === 2) { // some arbitrary condition
arr.splice(i, 1);
}
else {
console.log(i, arr[i]);
}
}
这里发生了什么?拼接索引2处的元素"c"
后,arr
的长度变为4,但i++
仍然发生,跳过一个元素"d"
,该元素从未被打印或访问过环形。解决办法是每次拼接操作后i--
或者反向迭代,这样splice
就不会导致未访问的元素被跳过
至于你的错误,问题是你的内循环遍历所有 j
拼接出一个元素,然后继续,就好像 balls[i]
没有被删除一样。根据上面的演示,我们知道在 balls.splice(i, 1)
之后, balls[i]
成为 下一个 元素之后的原始 i
的其余迭代外循环体。这是一个错误,因为拼接元素后的 i+1
会跳过一些碰撞,但不会导致错误,除非 i
恰好是 balls
数组中的最后一个元素。在这种情况下,balls[i+1]
是未定义的,您无法访问未定义的属性。
解决方法是在拼接出一个元素后break
跳出内层j
循环。这是在每次拼接调用后反向迭代或使用 i--
以避免跳球的补充。
从时间复杂度的角度来看,splice
是一个糟糕的选择,因为它是 O(n)。如果球数组中有 n
次碰撞,则需要将其循环 n
次,导致更新代码中出现三重嵌套循环 运行。
一个更好的通用方法是创建一个在一轮中幸存下来的新元素数组,然后在该帧之后将该新数组重新分配给旧的 balls
数组。这涉及一些分配开销。
其他提示:
- 尽可能使用
const
而不是 let
。
- 对循环计数器和可变变量使用
let
而不是 var
。理想情况下,never use var
or let
,尽管 p5 由于其 window 附加函数而提高了可变性。
- 首选
forEach
和 for ... of
循环而不是经典的 C 风格 for
循环。
- 你可以
return dist(this.x, this.y, other.x, other.y) < this.r + other.r
因为它已经是一个布尔值,不需要 if bool return true else return false
冗长。
- 尽可能将渲染和位置更新分开。对于这个动画来说,这可能无关紧要,但随着事情变得越来越复杂,当某些东西消失但仍然为一帧渲染时,这可能会很奇怪,就像这里的情况一样。
- 将碰撞检测和边缘检测移至外部函数 --
if (balls[i].x < 0 + balls[i].r ...
条件难以阅读。
一直在关注 Dan Shiffman 的视频,尝试使用 类 重温我的面向对象编程。
我写了一些代码,在随机位置生成随机直径的气泡,使用 p5 的噪声函数让气泡运动。
我的目的是让气泡弹出 (使用 splice() 从数组中移除) 每当气泡到达 canvas 的边缘或当两个或多个气泡相交。
代码 运行 根据需要,但过了一会儿它崩溃并抛出错误“Uncaught TypeError: Cannot read 属性 'x' of undefined (sketch: line 15)”
我试过四处乱砍,但没有任何乐趣,如果有人能阐明为什么会发生此错误,或者对我的方法提出一般性建议,我将不胜感激。这是有问题的代码。
var balls = [];
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
for (var i = 0; i < balls.length; i++) {
balls[i].showBall();
balls[i].moveBall();
for (var j = 0; j < balls.length; j++) {
if (balls[i].x < 0 + balls[i].r ||
balls[i].x > width - balls[i].r ||
balls[i].y < 0 + balls[i].r ||
balls[i].y > height - balls[i].r ||
balls[i].life >= 220 ||
i != j && balls[i].intersect(balls[j])) {
balls.splice(i, 1);
}
}
}
}
class Ball {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.t = 0.0;
this.t2 = 107.0;
this.life = 0;
}
showBall() {
noFill();
stroke(this.life);
strokeWeight(2);
ellipse(this.x, this.y, this.r * 2);
}
moveBall() {
this.x += map(noise(this.t), 0, 1, -1, 1) * 0.5;
this.y += map(noise(this.t2), 0, 1, -1, 1) * 0.5;
this.t += 0.02;
this.life += 0.15;
}
intersect(other) {
if (dist(this.x, this.y, other.x, other.y) < this.r + other.r) {
return true;
} else {
return false;
}
}
}
function randBubbleGen() {
let foo = floor(random(11));
console.log(foo);
if (foo >= 5) {
let b = new Ball(random(41, 359),
random(41, 359),
random(5, 40));
balls.push(b);
}
}
setInterval(randBubbleGen, 1000);
<script src="https://cdn.jsdelivr.net/npm/p5@1.3.1/lib/p5.js"></script>
谢谢! P
第一部分问题是经典splice
问题:
const arr = [..."abcde"];
for (let i = 0; i < arr.length; i++) {
if (i === 2) { // some arbitrary condition
arr.splice(i, 1);
}
else {
console.log(i, arr[i]);
}
}
这里发生了什么?拼接索引2处的元素"c"
后,arr
的长度变为4,但i++
仍然发生,跳过一个元素"d"
,该元素从未被打印或访问过环形。解决办法是每次拼接操作后i--
或者反向迭代,这样splice
就不会导致未访问的元素被跳过
至于你的错误,问题是你的内循环遍历所有 j
拼接出一个元素,然后继续,就好像 balls[i]
没有被删除一样。根据上面的演示,我们知道在 balls.splice(i, 1)
之后, balls[i]
成为 下一个 元素之后的原始 i
的其余迭代外循环体。这是一个错误,因为拼接元素后的 i+1
会跳过一些碰撞,但不会导致错误,除非 i
恰好是 balls
数组中的最后一个元素。在这种情况下,balls[i+1]
是未定义的,您无法访问未定义的属性。
解决方法是在拼接出一个元素后break
跳出内层j
循环。这是在每次拼接调用后反向迭代或使用 i--
以避免跳球的补充。
从时间复杂度的角度来看,splice
是一个糟糕的选择,因为它是 O(n)。如果球数组中有 n
次碰撞,则需要将其循环 n
次,导致更新代码中出现三重嵌套循环 运行。
一个更好的通用方法是创建一个在一轮中幸存下来的新元素数组,然后在该帧之后将该新数组重新分配给旧的 balls
数组。这涉及一些分配开销。
其他提示:
- 尽可能使用
const
而不是let
。 - 对循环计数器和可变变量使用
let
而不是var
。理想情况下,never usevar
orlet
,尽管 p5 由于其 window 附加函数而提高了可变性。 - 首选
forEach
和for ... of
循环而不是经典的 C 风格for
循环。 - 你可以
return dist(this.x, this.y, other.x, other.y) < this.r + other.r
因为它已经是一个布尔值,不需要if bool return true else return false
冗长。 - 尽可能将渲染和位置更新分开。对于这个动画来说,这可能无关紧要,但随着事情变得越来越复杂,当某些东西消失但仍然为一帧渲染时,这可能会很奇怪,就像这里的情况一样。
- 将碰撞检测和边缘检测移至外部函数 --
if (balls[i].x < 0 + balls[i].r ...
条件难以阅读。