p5.js使用collide2d库时同类别对象跨组碰撞异常求助
Pong游戏跨组碰撞异常问题
我使用p5.js开发一款类似Pong的游戏,此前通过collide2d库实现的玩家paddle与球的碰撞功能运行正常。但在添加「达到特定分数后生成额外玩家paddle、对手paddle和球」的功能后,出现了碰撞异常:初始paddle仅能与初始球产生碰撞,新增paddle无法与初始球发生碰撞,各组对象仅能与自身组内的对象触发碰撞。为便于区分,我将初始球设为白色,新增球设为黑色,以下是我的代码:
原始代码
JavaScript部分
things=[]; var hit=false; let y=0; let b=0; let s=4; let i=0; let l=5; let STARTSCREEN=1; let GAME=2; let mode; function setup() { createCanvas(600, 600); let p1=new Thing(0,0,0,255); things.push(p1); mode = STARTSCREEN; } function draw() { if (mode == STARTSCREEN) { startScreen(); } else if (mode == GAME) { game(); } } function startScreen() { background(255, 194, 209); rectMode(CENTER); noStroke(); fill(255, 229, 236); rect(width/2, height/2, width/2, height/6) // start button box fill(255, 179, 198); textAlign(CENTER); textSize(33); textFont('IBM Plex Mono'); text("CLICK TO START", width / 2, height / 2 + 10); fill(255, 229, 236); text("HIGH SCORE:", width / 2, height / 6); text(b, width / 2, height / 6 * 1.5); textSize(23); text("(ONLY PROS GET PAST 25!)", width / 2, height / 6 * 1.9); textSize(50); text("PONG PERFECT", width / 2, height / 6 * 4.75) textSize(23); text("CAN YOU BEAT THE PERFECT AI?", width / 2, height / 6 * 5.25); } function startClicks() { mode = GAME; } function mousePressed() { if (mode === STARTSCREEN) { startClicks(); } else if (mode === GAME) { gameClicks(); } } function game() { background(255, 194, 209); rectMode(CENTER); textAlign(CENTER); fill(255, 229, 236); rect(width/2, height / 8, 100, 50) // score box textSize(35); noStroke(); fill(255, 229, 236); rect(width/2, height/2, 5, height); // middle line rectMode(CORNER); fill(255, 194, 209); textFont('Press Start 2P'); text(s, width / 2, height / 8+12); // score for(let thing of things) { thing.move(); thing.collide(); thing.show(); } } function keyPressed() { if (keyIsDown(DOWN_ARROW)) y+=l; if (keyIsDown(UP_ARROW)) y-=l; } class Thing { constructor(x,y,z,c) { this.x=x; this.y=y; this.z=z; this.f=60; this.c=c; this.w=width/2; this.h=height/2; this.speedX=5; this.speedY=5; } show() { noStroke(); fill(255, 229, 236); //player rect(0+20, this.y, 10, this.f); //opponent rect(width-30, this.z, 10, 60); //ball fill(this.c); rect(this.w, this.h, 10, 10); } move() { //player paddle movement keyPressed(); if (keyIsDown(DOWN_ARROW)) this.y+=l; if (keyIsDown(UP_ARROW)) this.y-=l; if (this.y>height-this.f) this.y=height-this.f; if (this.y<0) this.y=0; //opponent paddle movement this.z=this.h-30; if (this.z>height-60) this.z=height-60; if (this.z<0) this.z=0; //ball movement if (this.w > width-40) { this.speedX=-this.speedX; } if (this.h > height-10 || this.h<0) { this.speedY=-this.speedY; } this.w+=this.speedX; this.h+=this.speedY; } collide() { hit = collideRectRect(30, this.y, 1, this.f, this.w, this.h, 10, 10); console.log(hit); if(hit) { this.speedX=-this.speedX; s=s+1; this.speedX=this.speedX*1.01; this.speedY=this.speedY*1.01; if (s === 5) { let u=(0); let p1=new Thing(0,0,0,u); things.push(p1); i=i+1; l=10; } } if (s === 5) this.f=100; if (s>5) this.f=100; if (s<5) this.f=60; if (this.w < -10) { gameClicks(); if (s>b) b=s; this.w=width/2; this.h=height/2; this.y=0; this.speedX=5; this.speedY=5; s=0; l=5; if (i === 1) { things.pop(); } i=0; } // print('colliding?', hit); } } function gameClicks() { mode = STARTSCREEN; }
HTML部分
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js"></script> <script src="https://unpkg.com/p5.collide2d"></script>
问题根源
你的Thing类设计存在核心问题:每个Thing实例同时封装了玩家paddle、对手paddle、球三组独立元素,且碰撞检测仅在当前实例的paddle与自身球之间进行。新增Thing实例后,每个实例各自维护独立的paddle和球,自然只会触发自身组内的碰撞,跨实例的paddle和球完全没有交互逻辑。
修复方案
需要拆分类结构,将玩家paddle、对手paddle、球分别封装为独立类,全局维护所有对象的列表,碰撞检测时遍历所有paddle和球的组合:
1. 拆分独立类
// 玩家 paddle 类 class PlayerPaddle { constructor(y, height) { this.y = y; this.height = height; this.width = 10; this.x = 20; } show() { noStroke(); fill(255, 229, 236); rect(this.x, this.y, this.width, this.height); } move(speed) { if (keyIsDown(DOWN_ARROW)) this.y += speed; if (keyIsDown(UP_ARROW)) this.y -= speed; this.y = constrain(this.y, 0, height - this.height); } } // 对手 paddle 类 class OpponentPaddle { constructor(y, height) { this.y = y; this.height = height; this.width = 10; this.x = width - 30; } show() { noStroke(); fill(255, 229, 236); rect(this.x, this.y, this.width, this.height); } move(ballY) { // 跟随对应球的位置移动 this.y = constrain(ballY - this.height/2, 0, height - this.height); } } // 球类 class Ball { constructor(x, y, color) { this.x = x; this.y = y; this.size = 10; this.color = color; this.speedX = 5; this.speedY = 5; } show() { noStroke(); fill(this.color); rect(this.x, this.y, this.size, this.size); } move() { // 边界反弹逻辑 if (this.x > width - 40 || this.x < 10) { this.speedX *= -1; } if (this.y > height - this.size || this.y < 0) { this.speedY *= -1; } this.x += this.speedX; this.y += this.speedY; } // 检测与单个paddle的碰撞 checkCollision(paddle) { return collideRectRect( paddle.x, paddle.y, paddle.width, paddle.height, this.x, this.y, this.size, this.size ); } }
2. 全局维护对象列表
修改全局变量和初始化逻辑:
let players = []; let opponents = []; let balls = []; let highScore = 0; let score = 4; let moveSpeed = 5; const STARTSCREEN = 1; const GAME = 2; let mode; function setup() { createCanvas(600, 600); // 初始化初始游戏对象 players.push(new PlayerPaddle(0, 60)); opponents.push(new OpponentPaddle(height/2 - 30, 60)); balls.push(new Ball(width/2, height/2, 255)); mode = STARTSCREEN; }
3. 更新游戏循环与碰撞检测
修改game函数,实现全局对象的更新、碰撞检测与绘制:
function game() { background(255, 194, 209); rectMode(CENTER); textAlign(CENTER); // 绘制UI元素 fill(255, 229, 236); rect(width/2, height / 8, 100, 50); // 分数框 rect(width/2, height/2, 5, height); // 中线 rectMode(CORNER); fill(255, 194, 209); textFont('Press Start 2P'); textSize(35); text(score, width / 2, height / 8 + 12); // 显示分数 // 更新所有对象状态 players.forEach(paddle => paddle.move(moveSpeed)); opponents.forEach((paddle, idx) => paddle.move(balls[idx].y)); balls.forEach(ball => ball.move()); // 检测所有球与所有玩家paddle的碰撞 balls.forEach(ball => { players.forEach(paddle => { if (ball.checkCollision(paddle)) { ball.speedX *= -1.01; ball.speedY *= -1.01; score += 1; // 分数达到5时新增游戏对象 if (score === 5) { players.push(new PlayerPaddle(0, 100)); opponents.push(new OpponentPaddle(height/2 - 50, 60)); balls.push(new Ball(width/2, height/2, 0)); moveSpeed = 10; } } }); // 检测球与对手paddle的碰撞 opponents.forEach(paddle => { if (ball.checkCollision(paddle)) { ball.speedX *= -1.01; ball.speedY *= -1.01; } }); // 球出界,重置游戏 if (ball.x < -10) { gameClicks(); if (score > highScore) highScore = score; // 重置所有对象 players = [new PlayerPaddle(0, 60)]; opponents = [new OpponentPaddle(height/2 - 30, 60)]; balls = [new Ball(width/2, height/2, 255)]; score = 0; moveSpeed = 5; } }); // 绘制所有游戏对象 players.forEach(paddle => paddle.show()); opponents.forEach(paddle => paddle.show()); balls.forEach(ball => ball.show()); }
4. 适配其他函数
更新startScreen函数中的高分显示变量,删除冗余的keyPressed函数(移动逻辑已整合到PlayerPaddle类):
function startScreen() { background(255, 194, 209); rectMode(CENTER); noStroke(); fill(255, 229, 236); rect(width/2, height/2, width/2, height/6); // 开始按钮框 fill(255, 179, 198); textAlign(CENTER); textSize(33); textFont('IBM Plex Mono'); text("CLICK TO START", width / 2, height / 2 + 10); fill(255, 229, 236); text("HIGH SCORE:", width / 2, height / 6); text(highScore, width / 2, height / 6 * 1.5); textSize(23); text("(ONLY PROS GET PAST 25!)", width / 2, height / 6 * 1.9); textSize(50); text("PONG PERFECT", width / 2, height / 6 * 4.75); textSize(23); text("CAN YOU BEAT THE PERFECT AI?", width / 2, height / 6 * 5.25); }
内容的提问来源于stack exchange,提问作者user21270064
相关产品推荐
相关产品推荐

