오늘은 어린이들의 슈퍼 스타 공Dragon의 깃털이 차르르 펼쳐지는 애니메이션을 만들어 보겠습니다~~^^*
네!! 그렇습니다!!!
할 수 있습니다!!! 해낼 수 있습니다!!!
찬찬히 생각해서 코드를 개선해 보겠습니다~!!!
하나. 클래스 Segment의 구성 요소를 animation 구현에 맞게 수정해 보겠습니다.
(1) 처음 받은 a와 b의 위치에서 점점 회전을 해야 하니~
(i) 처음 받은 a와 b를 startA와 startB로 지정하고
(ii) 애니메이션으로 위치 변경이 자주 되므로, a와 b를 따로 복사해서, a와 b로 쓰고
(iii) origin도 넘겨 받아, 따로 복사하여, origin으로 사용하고
(iv) 회전이 마무리 되었나를 알 수 있는 상태 진리값 completed를 준비하고
(v) 회전각을 지정하여 받을 수 있는 변수 angle도 마련하여
클래스 내장 구성 변수들로 지정해 보겠습니다.
constructor(a, b, origin) {
this.startA = a;
this.startB = b;
this.a = a.copy();
this.b = b.copy();
this.origin = origin.copy();
this.completed = false;
this.angle = 0;
}
//copy를 하여 사용하면, 원래의 변수를 그대로 따로 보존할 수 있어서, 안정성이 높아질 것 같습니다~^^*
둘. 기존의 rotation 함수는 애니메이션 효과를 주는 것에 집중한 함수 update()와 애니메이션이 적용된 결과물을 반환하는 것에 집중한 함수 duplicate()로 분리하여 보겠습니다.
(1) 함수 update()는 회전각을 점차 증가시켜 회전하여 선을 만들어 냅니다. 회전각이 PI/2가 되면, 회전이 complete되었다고 상태 알림을 하고, animation을 멈출 것입니다.
update() {
this.angle += 0.1;
if (this.angle > PI/2) {
this.angle = PI/2;
this.completed = true;
}
let va = p5.Vector.sub(this.startA, this.origin);
//this.origin에서 this.startA로 향하는 벡터 va입니다.
let vb = p5.Vector.sub(this.startB, this.origin);
//this.origin에서 this.startB로 향하는 벡터 vb입니다.
//이 프로그램에서 벡터this.origin는 벡터this.startB와 동일할 것입니다.
//그래서 동일 벡터 뺄셈의 결과인 벡터 vb의 크기는 0가 될 것입니다.
va.rotate(-this.angle);
vb.rotate(-this.angle);
//반시계방향 angle 크기만큼 회전하겠습니다.
//두 벡터의 방향은 일치합니다.
this.a = p5.Vector.add(this.origin, va);
//크기(0 + va = va)인 새로운 벡터 this.a입니다.
this.b = p5.Vector.add(this.origin, vb);
//크기(0 + 0 = 0)인 새로운 벡터 this.b입니다.
//원래 this.startB의 위치 그대로입니다.
}
(2) 이렇게 해서 update된 깃털은, 마우스가 클릭될 떄마다 보여질 수 있도록, 함수 duplicate()가 함수 mousePressed()에게 반환할 예정입니다~^^*.
duplicate(origin) {
return new Segment(this.a.copy(), this.b.copy(), origin);
}
셋. 모든 함수에서 값을 변화시킬 수 있는 글로벌 변수들을 준비해 보겠습니다~^^*
let segments = [ ];
let endSegment;
let firstTime = true;
//segments[]는 전체 깃털들을 담아내는 배열입니다~^^*.
//태초의 깃털은 회전해서 생성되는 새로운 깃털의 끝자락이 되어 또다시 회전의 중심이 되므로, 태초의 깃털을 endSegment라고 이름지어 보겠습니다~^^*.
//맨 처음의 회전의 origin과 두번째부터의 회전의 origin은 달라지지요? 그래서,상태를 나타내는 진리값 변수 firstTime로 맨 처음 회전을 특정해 보겠습니다~^^*
넷. mousePressed()도 전체 코드 변화에 맞추어 살짝 고쳐볼까요~~^^*? 기능의 변화는 없습니다^^* 첫 번째 회전 origin은 b이고 두 번째부터의 회전 origin은 a가 되는 기능을 그대로 가지고 있습니다~^^* 새롭게 만들어진 깃털은 기존의 깃털에 더해지는 것도 그대로 입니다~^^*
function mousePressed() {
let newSegments = [];
for (let s of segments) {
let newS = s.duplicate(endSegment.a);
if(firstTime) {
newS.origin = endSegment.b.copy();
firstTime = false;
}
//첫 번째 회전만 endSegment.b를 origin으로 삼겠습니다.
//두 번째 회전부터는 모두 endSegment.a를 origin을 삼을게요~^^*
newSegments.push(newS);
}
endSegment= newSegments[0];
//태초의 깃털이, 앞으로의 모든 회전의 중심이 되는 끝자락 깃털이 됩니다~^^*
segments = segments.concat(newSegments);
//마우스클릭으로 생성 회전 변화된 newSegments[]를 전체 segments[]에 덧붙입니다~^^*
}
다섯. 태초의 깃털을 만들어 볼 차례인데요~^^* 함수 setup()를 개선해 볼게요~~^^*
function setup(){
createCanvas(400,400);
let a = createVector(180, 130);
let b = createVector(180, 125);
endSegment = new Segment(a,b,b);
endSegment.completed = true;
//b를 origin으로 가지고 a와 b사이를 잇는 태초의 깃털을 글로벌 변수 endSegment에 저장합니다~^^*
//태초의 깃털 endSegment는 animation이 완결되었다고 미리 설정하여, 움직임 update 없이 바로 고정해서 보여주겠습니다~^^*.
segments.push(endSegment);
//태초의 깃털 endSegment를 전체 깃털 모음 배열인 글로벌 변수 segments에 담겠습니다~^^*
}
여섯. 마지막으로, 함수도 draw()도 수정해 보겠습니다~^^*
애니메이션을 넣지 않는 태초의 깃털과 회전각이 PI/2에 다다른 깃털은 상태 진리변수 completed의 값이 true였지요? 그래서 update()함수를 호출하지 않습니다.
태초의 깃털이 아니며 회전각이 PI/2 미만인 깃털만 completed의 값이 false이기 떄문에, 이떄만 함수 update()를 호출하여 회전각을 변화시키게 됩니다.
function draw(){
background(220);
for (let s of segments) {
if(!s.completed) {
s.update();
//태초의 깃털 endSegment는 이미 completed가 true이기 떄문에, animation을 위한 update 과정을 거치지 않습니다~^^*
//회전을 해서 생성되는 두 번째 깃털부터는 completed가 false상태로 시작하기 때문에 update()를 호출하여 animation 효과를 주겠습니다.
//회전각이 PI/2에 다다르면, completed가 true가 되기 때문에 update()를 호출하지 않게 되어, animation이 멈추게 되겠네요~^^*
}
s.show();
}
}
//클래스 Segment를 저장한 배열 segments의 구성요소인 모든 s들을 보여줍니다.
이제 전체 코드를 저와 함께 살펴 보시죠~~^^* 위의 내용과 거의 같기 떄문에 가벼운 마음으로 보셔도 좋을 것 같습니다~^^*
class Segment {
constructor(a, b, origin) {
this.startA = a;
this.startB = b;
this.a = a.copy();
this.b = b.copy();
this.origin = origin.copy();
this.completed = false;
this.angle = 0;
}
show(){
stroke(0);
strokeWeight(2);
line(this.a.x, this.a.y, this.b.x, this.b.y);
}
duplicate(origin) {
return new Segment(this.a.copy(), this.b.copy(), origin);
}
update() {
this.angle += 0.1;
if (this.angle > PI/2) {
this.angle = PI/2;
this.completed = true;
}
let va = p5.Vector.sub(this.startA, this.origin);
//this.origin에서 this.startA로 향하는 벡터 va입니다.
let vb = p5.Vector.sub(this.startB, this.origin);
//this.origin에서 this.startB로 향하는 벡터 vb입니다.
//이 프로그램에서 벡터this.origin는 벡터this.startB와 동일할 것입니다.
//그래서 동일 벡터 뺄셈의 결과인 벡터 vb의 크기는 0가 될 것입니다.
va.rotate(-this.angle);
vb.rotate(-this.angle);
//반시계방향 angle 크기만큼 회전하겠습니다.
//두 벡터의 방향은 일치합니다.
this.a = p5.Vector.add(this.origin, va);
//크기(0 + va = va)인 새로운 벡터 this.a입니다.
this.b = p5.Vector.add(this.origin, vb);
//크기(0 + 0 = 0)인 새로운 벡터 this.b입니다.
//원래 this.startB의 위치 그대로입니다.
}
}
let segments = [ ];
let endSegment;
let firstTime = true;
//segments[]는 전체 깃털들을 담아내는 배열입니다~^^*.
//태초의 깃털은 회전해서 생성되는 새로운 깃털의 끝자락이 되어 또다시 회전의 중심이 되므로, 태초의 깃털을 endSegment라고 이름지어 보겠습니다~^^*.
//맨 처음의 회전의 origin과 두번째부터의 회전의 origin은 달라지지요? 그래서,상태를 나타내는 진리값 변수 firstTime로 맨 처음 회전을 특정해 보겠습니다~^^*
//이 세 가지 변수는 글로별 변수로 지정하여, 모든 함수에서 그 값을 변화시킬 수 있도록 해보겠습니다~^^*
function mousePressed() {
let newSegments = [];
for (let s of segments) {
let newS = s.duplicate(endSegment.a);
if(firstTime) {
newS.origin = endSegment.b.copy();
firstTime = false;
}
//첫 번째 회전만 endSegment.b를 origin으로 삼겠습니다.
//두 번째 회전부터는 모두 endSegment.a를 origin으로 삼을게요~^^*
newSegments.push(newS);
}
endSegment= newSegments[0];
//태초의 깃털이, 앞으로의 모든 회전의 중심이 되는 끝자락 깃털이 됩니다~^^*
segments = segments.concat(newSegments);
//마우스클릭으로 생성 회전 변화된 newSegments[]를 전체 segments[]에 덧붙입니다~^^*
}
function setup(){
createCanvas(400,400);
let a = createVector(180, 130);
let b = createVector(180, 125);
endSegment = new Segment(a,b,b);
endSegment.completed = true;
//b를 origin으로 가지고 a와 b사이를 잇는 태초의 깃털을 글로벌 변수 endSegment에 저장합니다~^^*
//태초의 깃털 endSegment는 animation이 완결되었다고 미리 설정하여, 움직임 update 없이 바로 고정해서 보여주겠습니다~^^*.
segments.push(endSegment);
//태초의 깃털 endSegment를 전체 깃털 모음 배열인 글로변 변수 segments에 담겠습니다~^^*
}
function draw(){
background(220);
for (let s of segments) {
if(!s.completed) {
s.update();
//태초의 깃털 endSegment는 이미 completed가 true이기 떄문에, animation을 위한 update 과정을 거치지 않습니다~^^*
//회전을 해서 생성되는 두 번째 깃털부터는 completed가 false상태로 시작하기 때문에 update()를 호출하여 animation 효과를 주겠습니다.
//회전각이 PI/2에 다다르면, completed가 true가 되기 때문에 update()를 호출하지 않게 되어, animation이 멈추게 되겠네요~^^*
}
s.show();
}
}
//클래스 Segment를 저장한 배열 segments의 구성요소인 모든 s들을 보여줍니다.
와~~^^* 우리가 애니메이션을 만들어 내었어요!!!
오늘 멋진 애니메이션을 저와 함께 완성해 주셔서 감사합니다~~!!!
내일은 우리 깃털이 점점 펼쳐질 떄마다 전체 크기를 줄여서 화면에 다 담길 수 있도록 애니메이션을 개선해 볼까요~~?
우린 해낼 수 있어요~~!!
오늘도 이 큰 도전을 해낸 우리니까요~~!!
도전과 도전을 훨훨 넘어가는 Dragonfly처럼요~~!!
오늘도 점심 맛있게 드시고요~~^^*
멋진 하루 보내시고요~~^^*
우리 내일 또 만나는 거예요~~^^*
네~~!!! 꿈은 이루어 집니다~~!!!
댓글 남기기