Silverback9

#야생으로

Creative Coding 독학 제329일 2025년02월19일(수)

네~^^* 오늘은 vehicle 서식처의 환경 요소를 개선하고 food와 poison 인식 레이더를 볼 수도 있고 안 볼 수도 있도록 해보는 날~^^* Yeah~^^*

아침의 종소리에 귀를 기울여 보고~^^*

움직임의 통로에 울려 퍼지는 음악에도 귀 기울여 보고 계셔요~^^* 공부 정리해서 돌아올게요~ 쓩우웅~^^*

음…자연의 생태계에서는 어떤 개체가 죽으면, 그 사체의 영양분이 생태계 속으로 다시 녹아들어 생태계를 풍성하게 하는 것이 기여하게 되지요?

이 순환의 과정을 우리들의 태초의 vehicle 부족의 서식처에 담아보면 어떨까요?

네~^^* vehicle이 죽으면, 그 자리에 food가 생겨나도록 만들어 보겠습니다~^^*

function draw() {
   .
   .
   .
 if (vehicles[i].dead()) {
      const x = vehicles[i].position.x;
      const y = vehicles[i].position.y;
      food.push(createVector(x, y));
    
      vehicles.splice(i, 1);
    }
//만약 vehicles[i].dead()가 "true 참"을 반환한다면, 
//vehicle이 있던 좌표 위치에 벡터를 생성하여 food 배열에 담습니다.
//네 vehicle의 사체의 영양분이 순환되어 음식으로 태어납니다. 
   .
   .
   .
}

vehicle의 영양분이 순환되게 되었으니, poison의 독성은 좀더 높여보겠습니다. 건강 지수가 -1 감소하도록, 감소폭을 늘여 보겠습니다.

class Vehicle {
   .
   .
   .
  behaviours(good, bad) {
     .
     .
     . 
    const steerB = this.eat(bad, -1, this.dna[3]);
    //몸에 나쁜 음식에 대한 반응행동 벡터 steerB입니다.
    //건강 상태 지수가 1 줄어들겠네요...독성이 매우 강해졌어요...
    //poison 인식 범위 dna[3]를 perception 값으로 전해주겠습니다.
     .
     .
     .
  }
   .
   .
   .
} 

음…그리고….

원래는 생태계에 우리가 개입을 하면 좋지 않지만…

vehicle 부족의 개체수가 너무 적어져서 멸족의 위기가 생겨 신의 가호가 필요하다면, 우리가 vehicle 부족의 guardian angel 수호천사 역할을 하면 어떨까요?

필요할 때면, 마우스/손가락을 drag 주욱 끌면서 vehicle 개체를 생성해 보겠습니다.

함수 mouseDragged()는 독립적으로 작용하기 때문에, 다른 함수 안에 넣지 않겠습니다~^^*

function mouseDragged() {
  vehicles.push(new Vehicle(mouseX, mouseY));
}

food와 poison을 인식하는 레이더를 볼 수도 있고 안 볼 수도 있도록 선택할 수 있는 박스를 만들어 보겠습니다.

let debug;

function setup() {
   .
   .
   . 
  debug = createCheckbox();
}

debug 박스가 체크가 되면, food와 poison 인식 범위 레이더를 보여주겠습니다.

인식 범위 레이더를 보여주는 작업문 구간을 조건문 안에 담아 보겠습니다.

 class Vehicle() {
     .
     .
     . 
   display() { 
      .
      .
      .
     if(debug.checked()) {
        //debug 박스가 체크되면
        //food와 poison 인식 범위 레이더를 보여주겠습니다~^^* 
        noFill();
        stroke(0, 255, 0);
        line(0, 0, 0,-this.dna[0] * 20);
        ellipse(0, 0, this.dna[2] * 2);
        //food를 인식할 수 있는 범위를 원으로 표현합니다~^^*
        stroke(255, 0, 0);
        line(0, 0, 0,-this.dna[1] * 20);
        ellipse(0, 0, this.dna[3] * 2);
        //food를 인식할 수 있는 범위를 원으로 표현합니다~^^*
     }
      .
      .
      .
   }
    .
    .
    .
}    

네, 그럼 이제 nutrient cycle 영양 순환이 이루어 지고, poison의 독성은 강해졌고, 인구 절벽 위기에 우리가 vehicle 부족의 guardian angel 수호천사 역할을 할 수 있으며, 캔버스 화면 바로 아래에 있는 debug 박스를 check/uncheck 체크/체크해제 하여 food와 poison 인식 범위 레이더를 보기도 하고 안보기도 할 수 있는 우리의 최종 프로그램을 플레이 해 볼까요~~~^^* Yeah~~~^^*

네~^^* 그럼 우리 복습 삼아 전체 코드 한 번 다시 보구요~~^^*

var mr = 0.01;
//mutaion rate 돌연변이 확률을 0.01로 설정해 보겠습니다. 

class Vehicle {
  constructor(x, y, dna) {
    this.acceleration = createVector(0, 0);
    this.velocity = createVector(0, -2);
    this.position = createVector(x, y);

    this.health = 1;

    this.r = 4;
    this.maxspeed = 4;
    this.maxforce = 0.2;
    
    this.dna = [];
    
    if (dna === undefined) { 
      this.dna[0] = random(-2, 2);
      //food에 대한 멀어지고픔 최대치 -2 끌림 최대치 +2 사이 무작위 값
      this.dna[1] = random(-2, 2);
      //poison에 대한 멀어지고픔 최대치 -2 끌림 최대치 +2 사이 무작위 값
    
      //음식과 독약에 대한 감정기억을 좀더 차분하게 만들어 보겠습니다. 
      //[-5] ~ [5] 사이의 감정기억을 [-2] ~ [2] 사이로, 감정기억의 간극을 줄여보겠습니다~^^* 평정심~~^^*
    
      this.dna[2] = random(0,100);
      //food에 대해 인식할 수 있는 범위 반지름 0에서 100사이 무작위 값
      this.dna[3] = random(0,100);
     //poison에 대해 인식할 수 있는 범위 반지름 0에서 100사이 무작위 
    
    } else {
      //선조로부터 미리 정의된 dna을 물려 받으면
      this.dna[0] = dna[0];
      if (random(1) < mr) {
        this.dna[0] += random(-0.1, 0.1);
      }
      this.dna[1] = dna[1];
      if (random(1) < mr) {
        this.dna[1] += random(-0.1, 0.1);
      }
      this.dna[2] = dna[2];
      if (random(1) < mr) {
        this.dna[2] += random(-10, 10);
      }
      this.dna[3] = dna[3];
      if (random(1) < mr) {
        this.dna[3] += random(-10, 10);
      }
      //선조의 dna 구성요소들을 그대로 물려받되
      //dna 구성요소들을 각각 0.01 확률로 살짝 변화시켜 보겠습니다. 
    }
  }


  update() {
    
    this.health -= 0.005;
    //건강상태 지수는 계속 낮아집니다.
    
    this.velocity.add(this.acceleration);
    this.velocity.limit(this.maxspeed);
    this.position.add(this.velocity);
    this.acceleration.mult(0);
  }

  applyForce(force) {
    this.acceleration.add(force);
  }
  
  behaviors(good, bad) {
     const steerG = this.eat(good, 0.2, this.dna[2]);
     //몸에 좋은 음식에 대한 반응행동 벡터 steerG입니다.
     //건강 상태 지수가 0.2 증가하겠네요~^^*
     //food 인식 범위 dna[2]를 perception 값으로 전해주겠습니다. 
     const steerB = this.eat(bad, -1, this.dna[3]);
     //몸에 나쁜 음식에 대한 반응행동 벡터 steerB입니다.
     //건강 상태 지수가 1 줄어들겠네요...독성이 매우 강해졌어요...
     //poison 인식 범위 dna[3]를 perception 값으로 전해주겠습니다. 


     steerG.mult(this.dna[0]);
     //몸에 좋은 것에 대한 기억인 dna[0]에 저장된 값을 좋은 음식에 대한 반응행동 벡터 steerG의 크기에 곱하겠습니다.  
     steerB.mult(this.dna[1]);
     //몸에 나쁜 것에 대한 기억인 dna[1]에 저장된 값을 나쁜 음식에 대한 반응행동 벡터 steerB의 크기에 곱하겠습니다. 

     this.applyForce(steerG);
     this.applyForce(steerB);
      //vehicle의 행동에 이 반응벡터 steerG와 steerB가 영향을 끼칩니다.     
  }
  
  clone() {
    if (random(1) < 0.002) {
      return new Vehicle(this.position.x, this.position.y, this.dna);
      //0.002확률로
      //선조 vehicle의 위치좌표와 dna을 그대로 물려받은 복제 후세를 반환합니다.  
    } else {
      return null;
    }
  }
  
  eat(list, nutrition, perception) {
    let record = Infinity;
    let closest = null;
    //먼저, 현재 가장 가까운 것은 없는 상태로 시작하겠습니다.
    for (let i = list.length - 1; i >= 0; i--) {
      //가까이 있는 것은 일단 먹기로 할 것이라서,
      //해당 구성 요소가 배열에서 삭제하고 뒤편에 있는 구성 요소를 앞으로 당겨 재정열을 할 예정이라,
      //배열의 끝부분부터 앞으로 나아가며 반복 작업을 하도록 하겠습니다. 

      const d = this.position.dist(list[i]);

      if (d < this.maxspeed) {
        //가까운 거리 값을 this.maxspeed로 설정하여
        //가까운 거리 값 변경이 쉬워지도록 하겠습니다. 
        //가까운 거리 안에 들어 왔다면, 먹습니다!
        list.splice(i, 1);
        this.health += nutrition;
      } else {
        //가까운 거리에 있지 않다면
        if (d < record && d < perception) {
        //인식 범위에 들어오면서 현재까지의 기록보다 더 가까운 경우
          record = d;
          closest = list[i];
        //기록을 갱신합니다. 
        }
      }
    }


    if (closest != null) {
      return this.seek(closest);
      //가장 가까운 것이 있다면 그것을 찾으러 갑니다.
    }

    return createVector(0, 0);
    //가장 가까운 것이 없다면, 가만히 있습니다. 
  } 
   
//<nutrition 영양가와 this.health 건강상태 지수 관계>
// positive 값을 가진 nutrition은 this.health 값을 증가시키고
// negative 값을 가진 nutrition은 this.health 값을 감소시킵니다. 

 seek(target) {
    const desired = p5.Vector.sub(target, this.position); 
    desired.setMag(this.maxspeed);
    let steer = p5.Vector.sub(desired, this.velocity);
    steer.limit(this.maxforce); 
    return steer;
  }  
//target으로 몸을 트는 벡터를 생성하여 변수 steer에 저장한 후 maxforce 값 안으로 힘의 크기를 조정하여 steer를 반환합니다.  
  
  dead() {
    return (this.health < 0);
  }
//vehicle의 health 값이 0보다 작은 경우에 진리값 "true 참"을 반환합니다. 
//vehicle의 health 값이 0 이상인 경우에 진리값 "false 거짓"을 반환합니다.

  display() {
    let angle = this.velocity.heading() + PI / 2;
    
    push();
    translate(this.position.x, this.position.y);
    rotate(angle);
    
    if(debug.checked()) {
      //debug 박스가 체크되면
      //food와 poison 인식 범위 레이더를 보여주겠습니다~^^* 
      noFill();
      stroke(0, 255, 0);
      line(0, 0, 0,-this.dna[0] * 20);
      ellipse(0, 0, this.dna[2] * 2);
      //food를 인식할 수 있는 범위를 원으로 표현합니다~^^*
      stroke(255, 0, 0);
      line(0, 0, 0,-this.dna[1] * 20);
      ellipse(0, 0, this.dna[3] * 2);
      //food를 인식할 수 있는 범위를 원으로 표현합니다~^^*
    }  
    
    var rd = color(255, 0, 0);
    var gr = color(0, 255, 0);
    var col = lerpColor(rd, gr, this.health);
    
    fill(col);
    stroke(col);
    strokeWeight(1);
    
    beginShape();
    vertex(0, -this.r * 2);
    vertex(-this.r, this.r * 2);
    vertex(this.r, this.r * 2);
    endShape(CLOSE);
    pop();
    
  }
  
   boundaries() {
    //캔버스 밖을 벗어나서 끝없는 광야에서 헤매다 생을 마감하는 경우를 예방해 보겠습니다. 
    //캔버스 테두리 밖에 나가게 되면 다시 캔버스 안으로 들어오도록 하겠습니다.
    //왼쪽/오른쪽 테두리를 벗어나려고 하면 움직임의 x축 방향을 반대로 하고,
    //위쪽/아래쪽 테두리를 벗어나려고 하면 움직임의 y축 방향을 반대로 하여, 
    //테두리에 빛이 반사되듯 되돌아오는 움직임을 가지도록 해보겠습니다~
      
      const d = 25;

      let desired = null;

      if (this.position.x < d) {
        desired = createVector(this.maxspeed, this.velocity.y);
      } else if (this.position.x > width - d) {
        desired = createVector(-this.maxspeed, this.velocity.y);
      }

      if (this.position.y < d) {
        desired = createVector(this.velocity.x, this.maxspeed);
      } else if (this.position.y > height - d) {
        desired = createVector(this.velocity.x, -this.maxspeed);
      }

      if (desired !== null) {
        //테두리에 닿아서 변수 desired에 테두리 반사 움직임 벡터가 저장이 되어 변수 desired가 null이 아닐 때, 즉 변수 desired가 어떤 값을 갖게 되었을 때 
       desired.normalize();
       desired.mult(this.maxspeed);
       const steer = p5.Vector.sub(desired, this.velocity);
       steer.limit(this.maxforce);
       this.applyForce(steer);
        //테두리 반사 움직임 벡터 desired가 vehicle의 움직임에 영향을 주어
       //vehicle이 테두리 안으로 돌아가도록 합니다.  
      }
    }
}

var vehicles = [];
var food = [];
var poison = [];

let debug;

function setup() {
  createCanvas(400, 400);
  
  for( i = 0; i < 10; i++) {
    var x = random(width);
    var y = random(height);  
    vehicles[i] = new Vehicle(x, y);
  }
  
  for( var i = 0; i < 40; i++) {
    var x = random(width);
    var y = random(height);
    food.push(createVector(x, y));
  }
  
  for( var i = 0; i < 20; i++) {
    var x = random(width);
    var y = random(height);
    poison.push(createVector(x, y));
  }
  
  debug = createCheckbox();
}

function mouseDragged() {
  vehicles.push(new Vehicle(mouseX, mouseY));
}

function draw() {
  background(51);
  
  if( random(1) < 0.05) {
     var x = random(width);
     var y = random(height);
     food.push(createVector(x, y));
  }
//0에서 1 사이의 무작위 수를 추출하여 만약 그 수가 0.05보다 작다면
//0에서 캔버스 너비 사이의 무작위 수를 추출하여 x축 좌표로 삼고
//0에서 캔버스 높이 사이의 무작위 수를 추출하여 y축 좌표로 삼아
//(x, y) 위치 좌표를 가진 벡터를 생성하여
//배열 food[]의 구성요소로 추가합니다~^^* 
  
   if( random(1) < 0.01) {
     var x = random(width);
     var y = random(height);
     poison.push(createVector(x, y));
  }
//poison도 살짝 추가해 보겠습니다. 0.01확률로 추가해 볼게요~^^*  
  
  for(var i = 0; i < food.length; i++) {
    fill(0, 255, 0);
    noStroke();
    ellipse(food[i].x, food[i].y, 8, 8);
  }
  
  for(var i = 0; i < poison.length; i++) {
    fill(255, 0, 0);
    noStroke();
    ellipse(poison[i].x, poison[i].y, 8, 8);
  }
  
  for( i = vehicles.length -1; i >= 0; i--) {
    vehicles[i].boundaries();
    vehicles[i].behaviors(food, poison);
    vehicles[i].update();
    vehicles[i].display();
    
    const newVehicle = vehicles[i].clone();
    if (newVehicle != null) {
      vehicles.push(newVehicle);
    }
    //복제 함수 clone()을 호출하여 반환된 값이 null이 아니면, \
    //즉, 복제된 후세 vehicle이 생성되었다면
    //이 복제된 vehicle을 vehicle 부족에 넣습니다. 
  
  
    if (vehicles[i].dead()) {
      const x = vehicles[i].position.x;
      const y = vehicles[i].position.y;
      food.push(createVector(x, y));
    
      vehicles.splice(i, 1);
    }
//만약 vehicles[i].dead()가 "true 참"을 반환한다면, 
//vehicle이 있던 좌표 위치에 벡터를 생성하여 food 배열에 담습니다.
//네 vehicle의 사체의 영양분이 순환되어 음식으로 태어납니다. 
    
//vehicles[i]를 시작으로 1개의 구성요소를 배열 vehicles[]에서 삭제하고 뒤에 있는 구성요소들을 한 칸씩 앞으로 당깁니다. 
//즉, vehicles[i]를 vehicles[]에서 삭제하고 나머지 뒷부분 구성요소들을 한 칸씩 당겨 배치하여 배열 vehicles[]를 재정렬합니다.   
  }
}

와우!!! 어느새 우리가 동영상 강의 2편을 마무리 해내었네요~!!!

네~~^^* 우리들의 귀엽고 총명한 태초의 vehicle 부족이 멋진 역사를 써내려 가면 좋겠어요~~^^*

우리가 guardian angel 수호천사가 되어 응원하면 좋겠어요~~^^*

함께 힘을 모아서~^^*

Reboot! 하는 멋진 하루 하루를 보내보면 좋겠어요~~^^*

오늘 저와 함께, nutrient cycle 영양 순환이 이루어지는 자연스러운 서식처에서 살아가게 된 태초의 vehicle 부족의 수호천사가 되어 주셔서 감사합니다!!!

내일은 우리 또 다른 도전을 맞이해 볼까요~^^*?

네~^^* 좋아요~^^* 고마워요~^^*

오늘도 멋진 아침! 맛있는 점심! 따뜻한 저녁! 드시고요!

보람찬 가슴에 손 편안히 얹고 깊은 잠 코~^^* 하시기 바래요~~^^*

네~~^^* 꿈은 이루어 집니다~~^^*

댓글 남기기