Movatterモバイル変換


[0]ホーム

URL:


Anurag Hazra

Evolution Aquerium

How It Works?

These Creatures are based onCraig Reynold's Steering Behaviors andFlocking System

It also implements Genetic Algorithm and mutations.

You can learn more about them on Daniel Shiffman's YouTube ChannelThe Coding Train


Evolution Aquerium Codeflow Visualized (click to enlarge)
Evolution Aquerium Codeflow Visualized (click to enlarge)

AgentBuilder Class

I make use of the "builder" pattern to create different varieties of creatures with different traits. And AgentBuilder class helps me do this easily see how it works.

Learn more how builder pattern works

classAgentBuilder{constructor(type){this.acc=newVector(0,0)this.vel=newVector(0,-2)this.type= type}setPos(x, y){this.pos=newVector(x, y)returnthis}setRadius(r=5){this.radius= rreturnthis}setColor(color){return(this.color= color)}setDNA(dna){this.dna= dnareturnthis}// ... more setFunctionsbuild(){// returns a new BaseAgent based on the valuesreturnnewBaseAgent(this.pos.x,this.pos.y,this.radius,this.dna,this.color,this)}}

And now, the builder pattern is ready to be used. Let us see how we can build creatures with different traits.

// later onlet Predator=newAgentBuilder("PREDATOR").setRadius(10).setMaxSpeed(2).setMaxForce(0.05).setHealthDecrease(0.002).setColor([255,0,0]).setFoodMultiplier([0.5,-0.5])let Avoider=newAgentBuilder("AVOIDER").setRadius(5).setMaxRadius(8).setMaxSpeed(4).setMaxForce(0.2).setHealthDecrease(0.003).setColor([255,165,0]).setFoodMultiplier([0.5,-0.5])

BaseAgent Class

BaseAgent class handles all the logic for the update, render, and physics, it's the heart of the codebase. the basic idea is to give eachagent some essential traits likehealth,radius,maxSpeed,maxSpeed etc etc.

see the full BaseAgent class'scode at github

classBaseAgent{constructor(x, y, radius, dna, color, builder){// for flocking behaviourthis.pos=newVector(x, y);this.acc=newVector(0,0);this.vel=newVector(0,-2);// i used builder pattern to create more variaty agents easilyif(builder===undefined) builder={};this.builder= builder;// traitsthis.age=1;this.health=1;this.radius= radius||5;this.maxSpeed= builder.maxSpeed||1.5;this.maxForce= builder.maxForce||0.05;// amout of health will decrease over timethis.healthDecrease= builder.healthDecrease||0.003;// amount of health will increase after eating foodthis.goodFoodMultiplier= builder.goodFoodMultiplier||0.5;// amount of health will decrease after eating poisonthis.badFoodMultiplier= builder.badFoodMultiplier||-0.4;this.color= color;this.hasReproduced=0;// randomly choose male or femalethis.sex=(random(1)<0.5)?'MALE':'FEMALE';// females will be a bit bigger than malethis.maxRadius= builder.maxRadius||((this.getGender()==='FEMALE')?15:10);// more code in between...// colors based on their genderif(!this.color&&this.getGender()==='MALE')this.color=[0,170,0];if(!this.color&&this.getGender()==='FEMALE')this.color=[255,39,201];}

Later on, I also gave them names, haha yes

...let names_female=['hanna','mona','cutie','sweety','sofia','rose','laisy','daisy','mia'];let names_male=['joe','jim','kim','keo','shaun','morgan','jery','tom','anu','brian','ninja','daniel'];// names based on genderthis.name=(this.getGender()==='MALE')?this.getRandomName(names_male):this.getRandomName(names_female);...

And now, let's talk only about the main meat of the code, not any other unnecessary code.so in the base class we have some methods

The primary function which defines the fear behavior and we can also use this inversely

  • eat()

    seeks the nearby food

  • Behaviour()

    applies the force which returns from eat()

  • reproduce()

    Reproduction System checks for male and female agents, and if their radius is greater than 8 and they are close enough to each other, then they can reproduce with their specific DNA and creates a small Agent based on their DNA data and with some mutation.

Flock Class

Flock class takes an agent and do the calculations for flocking behaviors likeseparate,align,cohesion,seek.

classFlock{constructor(currentAgent){this.currentAgent= currentAgentthis.wandertheta=0}seek(target){}_returnSteer(sum){}wander(){}separate(agents){}align(agents){}cohesion(agents){}}

EcoSystem Class

EcoSystem class manages all theagents andbehaviors, basically it is like a state manager

it has some methods which are

  • addEntities

    adds all entities to the entities (food, poison) object

  • registerAgents

    registers Agents to the state and also creates corresponding Arrays for each of them which you can use by calling ecoSys.groups[yourgivenname]

  • initialPopulation

    initializes the groups of population by the given amount

  • addBehavior

    specifies the behavior of the agent declaratively

  • batchUpdateAgents

    updates all the agents

classEcoSystem{constructor(){this.groups={}// agentsthis.entities={}// generic container (food, poison)this.agents={}// agent classesthis.behaviors={}// calculated behaviors}addEntities(names){}registerAgents(agents){}initialPopulation(init){}addBehavior(config){}batchUpdateAgents(list, foodPoison, weight, callback){}}

Setting up everything

And the last step is to assemble every part of the code to create these boids like creatures and gave each of them behaviors

// Globallet canvas= document.querySelector('#c');letWIDTH= canvas.width= window.innerWidth;letHEIGHT= canvas.height=600;let ctx= canvas.getContext('2d');letMAX_CREATURES=300;constREPRODUCTION_RATE=0.5;constENABLE_SUPER_DEBUG=false;// constants for flexibiltyconstCREATURE='CREATURE';constPREDATOR='PREDATOR';constAVOIDER='AVOIDER';constEATER='EATER';constFOOD='FOOD';constPOISON='POISON';functionload(){const ecoSys=newEcoSystem();// creates a Array which you can access with ecoSys.entities  ecoSys.addEntities({FOOD:[],POISON:[]});// register classes it will also create corresponding Arrays// which you can use by calling ecoSys.groups[your_given_name]  ecoSys.registerAgents({CREATURE: Agent,PREDATOR: Predator,AVOIDER: Avoider,EATER: Eater,});// initialPopulation have to use the same name// which you configure in registerAgents  ecoSys.initialPopulation({CREATURE:150,PREDATOR:randomInt(5,10),AVOIDER:randomInt(10,20),EATER:randomInt(1,4),});let add= document.getElementById('addnew');  canvas.addEventListener('click',function(e){    ecoSys.add(add.value, e.offsetX, e.offsetY)})//  ANIMATE LOOPfunctionanimate(){let grd= ctx.createRadialGradient(WIDTH/2,HEIGHT/2,0,WIDTH/2,HEIGHT/2,WIDTH);    grd.addColorStop(0,"rgba(25,25,25,1)");    grd.addColorStop(1,"rgba(0,0,25,1)");// Fill with gradient    ctx.fillStyle= grd;    ctx.fillRect(0,0,WIDTH,HEIGHT);/**     * likes food dislikes poison     * run away form predators and eaters     * cloneItSelf     */    ecoSys.addBehavior({      name:CREATURE,      like:FOOD,      dislike:POISON,      fear:{PREDATOR:[-4,75],EATER:[-2,100]},      cloneItSelf:0.0015,callback:function(){if(ecoSys.groups.CREATURE.length<MAX_CREATURES&&random(1)<REPRODUCTION_RATE){this.reproduce(ecoSys.groups.CREATURE);}}});/**     * likes poison dislikes food     * seeks and eats creatures     * run away from eaters     */    ecoSys.addBehavior({      name:PREDATOR,      like:POISON,      dislike:FOOD,      likeDislikeWeight:[1,-1],      fear:{EATER:[-10,50],CREATURE:[1,200,function(agents, i){          agents.splice(i,1);this.health+=this.goodFoodMultiplier;this.radius+=this.goodFoodMultiplier;}]},});/**     * likes food dislikes poison     * run away form predators, eaters, creatures     */    ecoSys.addBehavior({      name:AVOIDER,      like:FOOD,      dislike:POISON,      cloneItSelf:0.0005,// likeDislikeWeight: [1, -1],      fear:{CREATURE:[-0.9,100],EATER:[-1,100],PREDATOR:[-1,100,function(){this.health+=this.badFoodMultiplier;}]},});/**     * likes poison     * emits food as waste compound     * seeks creatures, predators, avoiders and EATS THEM     */    ecoSys.addBehavior({      name:EATER,      like:POISON,      dislike:POISON,      likeDislikeWeight:[1,1],      fear:{CREATURE:[1.0,100,function(list, i){          list.splice(i,1);this.health+=this.goodFoodMultiplier;this.radius+=this.goodFoodMultiplier;}],PREDATOR:[1.0,100,function(list, i){          list.splice(i,1);this.health+=this.goodFoodMultiplier;this.radius+=this.goodFoodMultiplier;}],AVOIDER:[1.0,100,function(list, i){          list.splice(i,1);this.health+=this.goodFoodMultiplier;this.radius+=this.goodFoodMultiplier;}],},callback:function(){if(random(0,1)<0.05){addItem(ecoSys.entities.FOOD,1,this.pos.x,this.pos.y)}}});// UPDATE & RENDER    ecoSys.render();    ecoSys.update();renderItem(ecoSys.entities.FOOD,'white',1,true);renderItem(ecoSys.entities.POISON,'crimson',2);requestAnimationFrame(animate);}animate();}window.onload= load;

And that's it, phew, that was a lot of work. But in the end, we have beautiful flocking creatures playing around and interacting with each other. have fun watching them all day. <3


[8]ページ先頭

©2009-2025 Movatter.jp