Processing 模拟池塘生态系统

博文简介

使用Processing实现了简单的池塘生态系统。
主要是鱼——水蚤——水草三类生物的行为。
关键技术是遗传算法和自治智能体

生物知识

生态系统

生态系统指在自然界的一定的空间内,生物与环境构成的统一整体,在这个统一整体中,生物与环境之间相互影响、相互制约,并在一定时期内处于相对稳定的动态平衡状态。

组成成分:非生物的物质和能量、生产者、消费者、分解者。

这里模拟了简单的池塘生态系统。忽略掉非生物的物质和能量,还有分解者。只有 生产者–水草、低级消费者–水蚤(以水草为食)、高级消费者–鱼(以水蚤为食)

核心规则:
1、·能量传递的过程中逐级递减,传递率为10%~20%
这点问题不大,简单来说就是 鱼:水蚤:水草 =100:10:1,当然实际上没有这么简单,实现的过程中没有严格按照这点,大概就是注意鱼的数量少于水蚤少于水草,并且鱼的寿命大于水蚤大于水草,生命值也不同。
2、生物进化
这里用到了遗传算法,实现这点的方法是寿命,生物活得越久繁殖概率越大,DNA也就会传递下去
3、动态平衡
生态系统通常具有自我调节能力,可以实现动态平衡。

生物进化和动态平衡在实现的过程中都出现了问题,比我一开始想的出入很大,在下文 问题与思考 中详细阐述。

实现内容

DNA
寿命 (有最大最小限制)
速度 (进化需有一定随机性 、突变性)
大小

交叉
复制
变异

水草:
生命值(被吃会变透明)
寿命
大小(与寿命相关)
速度

死亡
无性繁殖
种子扩散
有微弱的移动能力

水蚤(指低级消费者):
寿命(性成熟)
性别
大小
速度

躲避行为(躲鱼)
寻偶
群集行为(主要是对齐)
吸收养分(吃植物)
有性繁殖

鱼(指高级消费者):
寿命(性成熟)
性别
大小
速度
性别
大小

捕食(追水蚤)
寻偶
群集行为(主要是分离)
吸收养分(吃虾)
有性繁殖

效果展示

完整视频:https://www.bilibili.com/video/av50958125
在这里插入图片描述

操作说明

可以通过单击鼠标左键、右键、滑轮添加鱼、水蚤、水草,或单击左上角得“-”号减少
拖拽可以持续生成(后期生物过多,单击生成减少太慢)
通过交互人为调节生态平衡。

相关类

类间关系如图
在这里插入图片描述

下面是各类的域和方法,具体实现和全部代码见 https://github.com/sssal/EcoSystem

主类

World world;

Boolean isOpenIntroduce;
void setup() {
  size(800, 600);
  frameRate(60);

  world = new World(100, 20, 0);
  textFont(createFont("KaiTi-48.vlw", 48));
  isOpenIntroduce = true;
}

void draw() {
  background(200);

  world.update();

  textSize(15);
  fill(50);
  text("Plants:" + (int)world.getPlantNum(), 20, 20);
  text("Fishes:" + (int)world.getFishNum(), 20, 40);
  text("Fleas:" + (int)world.getFleaNum(), 20, 60);
  rect(100, 7, 20, 12);
  rect(100, 27, 20, 12);
  rect(100, 47, 20, 12);
  fill(250);
  rect(105, 12, 10, 3);
  rect(105, 32, 10, 3);
  rect(105, 52, 10, 3);

  //说明界面
  if (isOpenIntroduce) {
    fill(0, 200);
    rect(width/4, height/4, width/2, height/2, 20);
    textSize(30);
    fill(250);
    text("说明", width/2-50, height/4+40);
    textSize(20);
    text("按 “ i ” 退出/显示说明界面", width/4+20, height/4+70);
    text("左上角表示示植物、水蚤、鱼数量", width/4+20, height/4+100);
    text("鼠标单击左键生成鱼,右键水蚤,中键植物", width/4+20, height/4+130);
    text("鼠标拖拽持续生成",width/4+20,height/4+160);
    text("鼠标单击左上角“-”减少",width/4+20,height/4+190);
    text("鼠标在左上角“-”处拖拽持续减少",width/4+20,height/4+220);
}
}

void mouseDragged() {
  //print(0);
  if (mouseX>100&&mouseX<120&&mouseY>7&&mouseY<19) {
    world.reducePlant();
  } else if (mouseX>100&&mouseX<120&&mouseY>27&&mouseY<39) {
    world.reduceFish();
  } else if (mouseX>100&&mouseX<120&&mouseY>47&&mouseY<59) {
    world.reduceFlea();
  } else {
    if (mouseButton == LEFT) {
      print(1);
      world.addFish(new PVector(mouseX, mouseY));
    } else if (mouseButton == RIGHT) {
      world.addFlea(new PVector(mouseX, mouseY));
    } else if (mouseButton == CENTER) {
      world.addPlant(new PVector(mouseX, mouseY));
    }
  }
}

void mouseClicked() {
  //print(0);
  if (mouseX>100&&mouseX<120&&mouseY>7&&mouseY<19) {
    world.reducePlant();
  } else if (mouseX>100&&mouseX<120&&mouseY>27&&mouseY<39) {
    world.reduceFish();
  } else if (mouseX>100&&mouseX<120&&mouseY>47&&mouseY<59) {
    world.reduceFlea();
  } else {
    if (mouseButton == LEFT) {
      print(1);
      world.addFish(new PVector(mouseX, mouseY));
    } else if (mouseButton == RIGHT) {
      world.addFlea(new PVector(mouseX, mouseY));
    } else if (mouseButton == CENTER) {
      world.addPlant(new PVector(mouseX, mouseY));
    }
  }
}

void keyPressed() {
  if (key == 'i') {
    if (isOpenIntroduce) {
      isOpenIntroduce = false;
    } else {
      isOpenIntroduce = true;
    }
  }
}
class World {
  ArrayList<Flea> fleas;
  ArrayList<Plant> plants;
  ArrayList<Fish> fishes;

  World(int plantsNum, int fleasNum, int fishNum) 
  void update() 
  float getFishNum() 
  float getFleaNum()
  float getPlantNum() 
  void addFish(PVector pvector) 
  void addFlea(PVector pvector)
  void addPlant(PVector pvector) 
  void reduceFish() 
  void reducePlant()
  void reduceFlea() 
}
class Creature {
  //生物类 所有生物的父类
  PVector position;  //位置
  PVector acceleration; //加速度
  PVector velocity;  //速度

  float lifetime;     //寿命 
  float maxspeed;     //速度
  float maxforce;    //转向力
  float size;         //大小
  float r;            //画图大小

  float maxLifetime; //用来保存生物的最大生命和尺寸
  float maxSize;     
  float health;      //生命值
  float maxHealth;

  DNA dna;          
  DNA fatherDNA;

  //设置不同行为的权重
  float separateWeight;
  float cohesionWeight;
  float alignWeight;

  float breedProbability; //繁殖概率
  float matingProbability; //交配概率

  float xoff;
  float yoff;  //控制随机移动速度

  float periphery = PI/2; //视野角度

  Boolean gender;  //性别
  Boolean isRut;   //是否处于发情期
  Boolean isPregnancy; //怀孕

  color col;      //颜色

  Creature(PVector pos, DNA initDNA)
  //更新
  void update()
  //移动
  void move() 
  //画图
  void display() 
  //添加力改变加速度
  void applyForce(PVector force)
  //三种群集规则
  //分离  避免碰撞
  //对齐  转向力与邻居一致
  //聚集  朝邻居中心转向 (留在群体内)
  void flock(ArrayList<? extends Creature> Creatures) 
  //寻找
  PVector seek(PVector target) 
  // Cohesion 聚集行为
  PVector cohesion (ArrayList<? extends Creature> creatures) 
  //分离行为
  PVector separate (ArrayList<? extends Creature> creatures) 
  //对齐行为
  PVector align (ArrayList<? extends Creature> creatures)  
  //设置发情期
  void rut()
  //寻偶
  PVector mating(ArrayList<? extends Creature> creatures)
  //觅食
  PVector foraging(ArrayList<? extends Creature> creatures)
  //繁殖
  public Creature  breed() 
  // 避免超出画板范围
  void borders()
  //判断死亡
  boolean dead()
}
class Plant extends Creature {
  //水草类
  Plant(PVector pos, DNA initDNA)
  @Override
    void display() 
  @Override
    Plant breed()
  //移动
  @Override
    void move()
  //判断死亡
  @Override
    boolean dead()
class Flea extends Creature {
  //水蚤类
  Flea(PVector pos, DNA initDNA)
  //更新
  @Override
    void update() 
  @Override
    void display()
  @Override //加入寻偶行为
    void flock(ArrayList<? extends Creature> Creatures)
  //躲避动作
  void moveElude(ArrayList<Fish> fishes)
  //躲避
  PVector elude(ArrayList<Fish> fishes)
  @Override //有性繁殖
    Flea breed()
  //移动
  @Override
    void move()
  //对齐行为
  @Override  //添加视野角度
    PVector align (ArrayList<? extends Creature> creatures)
  //吃水草
  void eat(ArrayList<Plant> plants)
  //判断死亡
  @Override
    boolean dead()
class Fish extends Creature {
  //鱼类
  Fish(PVector pos, DNA initDNA)
  //更新
  @Override
    void update()
  @Override
    void display()
  @Override   //有性繁殖
    Fish breed()
  @Override //加入寻偶行为
    void flock(ArrayList<? extends Creature> Creatures)
  //捕食运动
  void moveForaging(ArrayList<Flea> fleas)
  //移动
  @Override
    void move() 
  //吃水蚤
  void eat(ArrayList<Flea> fleas)
  //判断死亡
  @Override
    boolean dead()
class DNA {
  private HashMap<String, Float> genes=new HashMap<String,Float>();
  DNA()
  DNA(HashMap newgenes)
  //复制
  public DNA dnaCopy()
  //交叉
  public DNA dnaCross(DNA fatherDNA)
  //变异
  public void mutate(float m)

技术讲解

向量、力之类的比较基础,这里主要说一下遗传和自治智能体。

遗传

这部分的代码比较简单。只需要把遗传相关的属性和方法实现就可以了。
在DNA类中,使用 HashMap<String, Float> 用作储存基因型,键(String)保存基因型的名字,值(FLoat)保存基因间的差别,在Creature类中实例化DNA类并通过值来实现表现型。

之后事基因的复制、交叉和变异。
复制主要用在无性繁殖,直接复制DNA就可以了

  //复制
  public DNA dnaCopy() {
    HashMap<String, Float> childGenes = (HashMap<String,Float>)genes.clone();
    
    return new DNA(childGenes);
  }

交叉则是有性繁殖,父亲把DNA传递给母亲,然后生成孩子的DNA

  //交叉
  public DNA dnaCross(DNA fatherDNA){
    HashMap<String, Float> childGenes = (HashMap<String,Float>)genes.clone();
    HashMap<String, Float> fatherGenes = fatherDNA.genes;
    float lifetime = (fatherGenes.get("lifetime") + genes.get("lifetime"))/2;
    childGenes.put("lifetime",lifetime);
    float speed = (fatherGenes.get("speed") + genes.get("speed"))/2;
    childGenes.put("speed",speed);
    float size = (fatherGenes.get("size") + genes.get("size"))/2;
    childGenes.put("size",size);
    
    return new DNA(childGenes);
  }

变异在有性繁殖和无性繁殖中都会出现

  //变异
  public void mutate(float m) {
    for (String key : genes.keySet()) {
      if (random(1)<m) {
        genes.put(key, random(0, 1));
      }
    }
  }

自治智能体

这部分是代码的核心,控制生物的行为。
生物的行为可以分成两类,一类是群集行为,一类是个体行为。
运动会受到不同权重,生物的视野和角度影响。

群集行为

群集行为包括聚集,分离,对齐。
水蚤的对齐行为权重更大,会与视野内的水蚤速度保持一致。
统计视野内其他水蚤的速度均值,处理后作为加速度。

  //对齐行为
  @Override  //添加视野角度
    PVector align (ArrayList<? extends Creature> creatures) {
    float neighbordist = size * 2;
    PVector sum = new PVector(0, 0);
    int count = 0;
    for (Creature other : creatures) {
      //从一个生物到另一个生物的向量
      PVector comparison = PVector.sub(other.position, position);
      //距离
      float d = PVector.dist(position, other.position);
      //角度
      float  diff = PVector.angleBetween(comparison, velocity);
      if ((diff < periphery) && (d > 0) && (d < neighbordist)) {
        sum.add(other.velocity);
        count++;
      }
    }
    if (count > 0) {
      sum.div((float)count);
      sum.normalize();
      sum.mult(maxspeed);
      PVector steer = PVector.sub(sum, velocity);
      steer.limit(maxforce);
      return steer;
    } else {
      return new PVector(0, 0);
    }
  }

聚集则是计算视野内其他生物的位置的均值,生成从当前位置指向均值的向量处理后用作加速度

  // Cohesion 聚集行为
  PVector cohesion (ArrayList<? extends Creature> creatures) {
    float neighbordist = r * 5; //视野  ????
    PVector sum = new PVector(0, 0);   // Start with empty vector to accumulate all positions
    int count = 0;
    for (Creature other : creatures) {
      float d = PVector.dist(position, other.position);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(other.position); // 将其他对象位置相加
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      return seek(sum);  //寻找邻居的平均坐标
    } else {
      return new PVector(0, 0);
    }
  }

分离统计远离其他生物的向量均值,可以用于避免生物间距离过近重叠。

  //分离行为
  PVector separate (ArrayList<? extends Creature> creatures) {
    float desiredseparation = r*1.5; //分离视野
    PVector steer = new PVector(0, 0, 0);
    int count = 0;
    // For every boid in the system, check if it's too close
    for (Creature other : creatures) {
      float d = PVector.dist(position, other.position);
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < desiredseparation)) {
        // Calculate vector pointing away from neighbor
        PVector diff = PVector.sub(position, other.position);
        diff.normalize();
        diff.div(d);        // Weight by distance
        steer.add(diff);
        count++;            // Keep track of how many
      }
    }
    // Average -- divide by how many
    if (count > 0) {
      steer.div((float)count);
    }

    // As long as the vector is greater than 0
    if (steer.mag() > 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.normalize();
      steer.mult(maxspeed);
      steer.sub(velocity);
      steer.limit(maxforce);
    }
    return steer;
  }
个体行为

个体的行为是求偶、觅食、躲避

当个体处于发情期会寻找视野内处于发情期的异性,找到后传递DNA,并结束发情期状态

  //寻偶
  PVector mating(ArrayList<? extends Creature> creatures) {
    float neighbordist = r * 15;
    if (isRut) {
      for (Creature other : creatures) {
        if (other.isRut && gender != other.gender) {
          //都处于发情期且性别不同
          //PVector comparison = PVector.sub(other.position, position);
          //距离
          float d = PVector.dist(position, other.position);
          //角度
          //float  diff = PVector.angleBetween(comparison, velocity);
          if ( (d < neighbordist) && (d>r)) {
            return seek(other.position);
          } else if (d<r) { //当两者足够靠近
            //print(3);
            isRut = false;
            other.isRut = false;
            if (gender) {
              col = color(255, 0, 0);
              other.col = color(0, 255, 0);

              isPregnancy = true;
              fatherDNA = other.dna;
            } else {
              col = color(0, 255, 0);
              other.col = color(255, 0, 0);

              other.isPregnancy = true;
              other.fatherDNA = dna;
            }
          }
        }
      }
    }
    return new PVector(0, 0);
  }

觅食是鱼的行为,主动寻找水蚤

  //觅食
  PVector foraging(ArrayList<? extends Creature> creatures) {
    float neighbordist = r * 10;
    for (Creature c:creatures) {
      //从一个生物到另一个生物的向量
      PVector comparison = PVector.sub(c.position, position);
      //距离
      float d = PVector.dist(position, c.position);
      //角度
      float  diff = PVector.angleBetween(comparison, velocity);
      if ((diff < periphery) && (d < neighbordist)) {
        return seek(c.position);
      }
    }
    return new PVector(0, 0);
  }

躲避与觅食相反,水蚤主动躲避鱼

  //躲避
  PVector elude(ArrayList<Fish> fishes) {
    float neighbordist = r * 10;
    for (Fish f:fishes) {
      //从一个生物到另一个生物的向量
      PVector comparison = PVector.sub(f.position, position);
      //距离
      float d = PVector.dist(position, f.position);
      //角度
      float  diff = PVector.angleBetween(comparison, velocity);
      if ((diff < periphery) && (d < neighbordist)) {
        PVector result = seek(f.position);
        result = new PVector(-result.x, -result.y);
        return result;
      }
    }
    return new PVector(0, 0);
  }

问题与思考

1、遗传
遗传算法是计算数学中用于解决最佳化的搜索算法,曾经使用过用来计算函数的最大值,这里考虑把生物的寿命作为适应度,活得越长适应度越大,这样迭代一段时间后就能够得到寿命最长也就是最适应环境的个体,但在尝试的过程中发现这样太理想化了。
一开始想要把各项参数都放在DNA中,例如不同行为的权重,繁殖率等,但是发现变量太多,效果不好。
在系统中只有水蚤和水草时,出现过一种情况是水蚤越来越大,但速度却越来越慢,我猜测因为没有给水蚤添加觅食行为,所以走得慢反而更容易生存,越大觅食范围越大,并且水草不会被直接吃掉,生存概率也更高。但是这种情况我后来并没有复现出来。
这里放了一种能够体现遗传进化的情况:
初始为水草:100 水蚤:20 无鱼
在这里插入图片描述
一段时间后的情况:
在这里插入图片描述
发现水蚤在往大的方向进化,速度并没有越来越慢。

这时候我意识到 适应度 的问题,我希望的适应度是寿命,但实际上想要让水蚤因为各项属性影响寿命是很困难的事,我只把参数修改一点点就可能导致生物灭绝或者过度繁殖,所以寿命的随机性其实挺高的。而真正的适应度其实是交配的概率,吃得多活得长,交配的概率高,但影响交配的概率最大的其实是体积,越大越可能和异性接触并交配。

2、动态平衡
正常的生态系统是具有自我调节的能力的,就像狼-兔-草模型中,兔子多了,狼也会多,草减少,这样兔子就会减少,一开始我也是想让鱼-水蚤-水草达到平衡状态,在只有水蚤-水草时还行,加入鱼后我发现这是不可能的,不添加外在条件时不可能的。

自然界中其实还存在一些规律,实际上当一种生物增多时,首先受影响的是它的下级,生态系统的调节是具有滞后性的,就像兔子多了,首先是草大量减少,然后才是狼多,需要一定的时间跨度,才能恢复平衡。但在我模拟的生态系统中,当存在鱼-水蚤-水草时几乎无法调节平衡,会持续地往极端发展,并最终全部灭绝。

当然这应该有我对生物的模拟不够真实的原因,并且环境太小,没有足够的时间和空间让模拟生态系统运行下去。

参考

参考案例:https://www.openprocessing.org/sketch/687983
参考书籍:《The nature of code》/《代码本色》

其他作品推荐

布料模拟

布料模拟
在这里插入图片描述
这个作品一下子就吸引了我,很简洁又真实,确实有布料运动的感觉,这个技术的实现让我想到曾经制作的愤怒的小鸟的弹弓。
布料的整体运动取决于部分的运动,每一个局部运动逻辑是相似的,但整体表现出来的效果又很棒。

融入动画技术的交互应用

博文地址
在这里插入图片描述
一开始实现的跳一跳看起来就很不错,运动、交互的效果看起来很舒服。后边还看到一些有趣的效果,processing实现的融入动画的漫画场景,很好看。最后还分享了一些动画,特别是这个水生物的自然形态模拟,很喜欢,我自己做的生态系统看上去特别丑,早点看到这个例子,或许能让我的作品更具艺术性。在这里插入图片描述

《Magic Network》:一个小孩都能玩的神经网络交互系统

Magic Netwo
在这里插入图片描述
界面画风很符合主题,在学神经网络、人工智能的时候就经常感觉很抽象,很多东西的中间过程难以理解,这个作品非常好,即便是没有学过相关知识的人也可以操作,帮助理解神经网络。

  • 8
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
要实现模拟水墨晕染的效果,可以使用Processing中的图像处理函数和随机函数来创建墨水晕染的效果。 以下是一个简单的示例代码: ```java PImage img; // 要模拟晕染的图片 int numDrops = 500; // 墨水点数量 float dropSize = 10; // 墨水点大小 float dropAlpha = 100; // 墨水点透明度 float dropSpread = 0.5; // 墨水点扩散速度 float noiseScale = 0.01; // 噪声缩放比例 void setup() { size(600, 400); img = loadImage("example.jpg"); img.resize(width, height); background(255); noStroke(); } void draw() { // 绘制背景图像 background(255); image(img, 0, 0); // 创建墨水点 for (int i = 0; i < numDrops; i++) { float x = random(width); float y = random(height); float dropColor = random(50, 255); fill(dropColor, dropAlpha); ellipse(x, y, dropSize, dropSize); // 计算墨水点的扩散效果 float noiseVal = noise(x * noiseScale, y * noiseScale); x += (random(-1, 1) * dropSpread + sin(millis() * 0.01)) * noiseVal; y += (random(-1, 1) * dropSpread + cos(millis() * 0.01)) * noiseVal; fill(dropColor, dropAlpha / 2); ellipse(x, y, dropSize / 2, dropSize / 2); } // 模糊处理墨水点 filter(BLUR, 10); } ``` 代码中使用了`loadImage()`函数加载要模拟晕染的图片,并使用`image()`函数将其绘制到画布上。然后使用`ellipse()`函数创建墨水点的形状,并使用随机函数来计算墨水点的扩散效果。最后使用`filter()`函数对墨水点进行模糊处理以模拟晕染的效果。 你可以根据需要调整代码中的参数和图片来实现不同的效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值