You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 11:32:35