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
相关产品推荐
相关产品推荐

