네~^^* 오늘은 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를 인식할 수 있는 범위를 원으로 표현합니다~^^*
}
.
.
.
}
.
.
.
}
네~^^* 그럼 우리 복습 삼아 전체 코드 한 번 다시 보구요~~^^*
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 부족의 수호천사가 되어 주셔서 감사합니다!!!
내일은 우리 또 다른 도전을 맞이해 볼까요~^^*?
네~^^* 좋아요~^^* 고마워요~^^*
오늘도 멋진 아침! 맛있는 점심! 따뜻한 저녁! 드시고요!
보람찬 가슴에 손 편안히 얹고 깊은 잠 코~^^* 하시기 바래요~~^^*
네~~^^* 꿈은 이루어 집니다~~^^*
댓글 남기기