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

Processing兔群模拟问题:草瞬移至原点,求排查与优化建议

兔群模拟中草瞬移至原点的问题排查与改进

问题描述

用Processing模拟草地兔群,设置兔子匀速移动、带分离半径保持个体间距,采用贪心算法让兔子向最近的草对象移动,但运行时所有草都会瞬移到原点,无法定位原因。同时作为编程新手,希望指出代码中的实现不足并获得改进建议。


代码清单

主文件

ArrayList<Rabbit> R = new ArrayList<Rabbit>();
ArrayList<Grass> G = new ArrayList<Grass>();
ArrayList<Grass> K = G;
int grass = 25;
int r_Size = 10;
int day = 0;
int i = 0;

void setup(){
 size(1000,8000);
 for(int i = 0;i<r_Size;i++){
   R.add(new Rabbit());
 }
  for(int i = 0;i<grass;i++){
   G.add(new Grass());
 }
}

void draw(){
  background(120);
  i++;
  
  for(Grass r : G){
    r.show();
    r.find();
  }
  for(Rabbit r : R){
    r.show();
    r.move();
  }
  
  fill(255);
  text(day,10,100);
  if(i >= 10){
    day++;
    i = 0;
   ArrayList<Grass> d = new ArrayList<Grass>();
   for(Grass g : G){
    if(g.isEaten){
      d.add(g);
    }
   }
   for(Grass g : d){
    G.remove(g); 
   }
  }
  
  if(day >= 30){
   System.out.println(R.size());
   for(Grass r : G){
    r.update();
   }
   for(int i = 0;i<10;i++){
    G.add(new Grass()); 
   }

   ArrayList<Rabbit> dead = new ArrayList<Rabbit>();
   for (Rabbit r : R){
    r.update();
    if(r.isDead == true) 
      dead.add(r);
   }
   for(Rabbit r : dead){
    R.remove(r); 
   }
   day = 0;
  }
}

Grass类

class Grass{
 PVector pos;
 int age = 0;
 int side = 20;
 boolean isEaten = false;
 int perTurn = 10;
 Grass(){
  pos = new PVector(random(0,width-side),random(0,height-side)); 
 }
 
 void update(){
  age++;
 }
 
 boolean find(){
  if(isEaten)
    return true;
  for(Rabbit r : R){
   if(pos.dist(r.pos) < side){
    isEaten = true;
    r.hasBeenHungry = 0;
    return true;
   }
  }
  return false;
 }
 
 
 void show(){
  float c = map(age,0,10,100,255);
  fill(0,0+c,0);
  noStroke();
  square(pos.x,pos.y,side);
 }
}

Animal基类(后续将扩展至狐狸)

class Animal{
  PVector pos;
  PVector vel;
  boolean isDead;
  int age;
  int gender;
  int max_age;
  float max_speed;
  int hasBeenHungry = 0;
  
  //gender 0 means female and 1 means male;
  
  // update means age in months;
  void update(){
   this.age++;
   hasBeenHungry++;
   if(this.age >= max_age){
    isDead = true; 
   }
   float k = (int)random(10);
   if(k == 0){
    isDead = true; 
   }
   if(hasBeenHungry >= 5){
    isDead = true; 
   }
  }
  
  void move(){
   pos.add(this.vel);
   checkBound();
   //this.vel.mult(0);
  }
  
  void checkBound(){
   if(pos.x < 0 || pos.x > width){
       vel.x *= -1;
   }
   if(pos.y < 0|| pos.y > height){
    vel.y *= -1;
   }
  }
  
}

Rabbit类

class Rabbit extends Animal{
  
  Rabbit(){
   pos = new PVector(random(0,width),random(0,height));
   vel = new PVector(2,1);
   int k = (int)(random(0,2));
   this.isDead = false;
   this.gender = k;
   this.max_age = 10;
   this.max_speed = 3;
  }
 
  
  void show(){
   if(this.gender == 0){
    fill(120,0,0 );
   }
   else{
    fill(0,0,120); 
   }
   circle(this.pos.x,this.pos.y,15);
  }
  
  void seperate(){
   float desiredRadius = 30;
   int count = 0;
   PVector c = new PVector(0,0);
   for(Rabbit r : R){
     if(r != this){
      float a = pos.dist(r.pos);
      if(a > 0 && a < desiredRadius){
        PVector diff = PVector.sub(pos, r.pos);
        //diff.normalize();
        diff.div(a);        // Weight by distance
        c.add(diff);
        count++;  
      }
     }
   }
    if (count > 0){
      c.div((float)count);
      vel.add(c);
      vel.normalize();
      vel.mult(max_speed);
    }
  }
  
  PVector target(){
   //Each rabbit eats grass so we could make a greedy approach where the rabbit focuses on the grass closest to it
   PVector mi = new PVector(0,0);
   //int distance =  1000;
   for(Grass g : G){
    float a = this.pos.dist(g.pos);
     if(a < pos.dist(mi)){
      mi = g.pos;
     }
    }
    return mi;
  }
  
  void move(){
   super.move();
   vel = target();
   seperate();
   vel.normalize();
   vel.mult(max_speed);
  }
}

问题根源:引用赋值导致的坐标篡改

Rabbit类的target()方法中,mi = g.pos是直接将草的pos对象引用赋值给mi,而非复制坐标值。后续兔子的vel = target()会让vel和草的pos指向同一个PVector对象,兔子移动时执行的pos.add(vel),实际上修改的是草的pos坐标,最终所有草被拉到原点。


修复方案

1. 修正target()函数

改为复制草的坐标,而非引用,同时优化逻辑跳过已被吃掉的草:

PVector target(){
  PVector mi = new PVector(width * 2, height * 2);
  float minDist = Float.MAX_VALUE;
  for(Grass g : G){
    if(g.isEaten) continue; // 跳过已被吃掉的草
    float a = this.pos.dist(g.pos);
    if(a < minDist){
      minDist = a;
      mi = g.pos.copy(); // 复制坐标,避免引用篡改
    }
  }
  // 所有草被吃完时,返回当前位置避免兔子乱跑
  if(minDist == Float.MAX_VALUE){
    return pos.copy();
  }
  // 返回从当前位置到目标草的方向向量,而非草的位置
  return PVector.sub(mi, pos);
}

2. 调整move()方法逻辑顺序

先计算目标方向和分离行为,再执行移动:

void move(){
  vel = target(); // 获取到草的方向向量
  seperate(); // 叠加分离行为
  vel.normalize();
  vel.mult(max_speed);
  super.move(); // 最后执行移动操作
}

其他代码改进建议

  • 移除无用的ArrayList引用:主文件中ArrayList<Grass> K = G;只是复制引用,无实际作用,可直接删除;若需创建副本,改为new ArrayList<>(G)。
  • 修正边界检测逻辑:Animal类的checkBound()中,先将兔子位置拉回边界内再反转速度,避免反复弹跳:
void checkBound(){
  if(pos.x < 0){
    pos.x = 0;
    vel.x *= -1;
  } else if(pos.x > width){
    pos.x = width;
    vel.x *= -1;
  }
  if(pos.y < 0){
    pos.y = 0;
    vel.y *= -1;
  } else if(pos.y > height){
    pos.y = height;
    vel.y *= -1;
  }
}
  • 优化分离行为权重:Rabbit的seperate()方法中,给分离行为设置合理权重,避免覆盖目标方向:
void seperate(){
  float desiredRadius = 30;
  int count = 0;
  PVector c = new PVector(0,0);
  for(Rabbit r : R){
    if(r != this){
      float a = pos.dist(r.pos);
      if(a > 0 && a < desiredRadius){
        PVector diff = PVector.sub(pos, r.pos);
        diff.normalize();
        diff.div(a); // 距离越近,排斥力越强
        c.add(diff);
        count++;  
      }
    }
  }
  if (count > 0){
    c.div((float)count);
    c.normalize();
    c.mult(max_speed * 0.5f); // 分离行为权重设为速度的50%
    vel.add(c);
  }
}
  • 修正草的绘制原点:Grass的show()方法中,以草的中心绘制正方形,让碰撞检测更准确:
square(pos.x - side/2, pos.y - side/2, side);
  • 修复随机死亡概率:Animal类的update()中,将float k = (int)random(10);改为int k = (int)random(10);,确保10%的随机死亡概率正确生效。

内容的提问来源于stack exchange,提问作者Kaushal Tiwari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 15:24:25