이전 글 : [Canvas-불꽃놀이-14] 원형 불꽃 효과 생성
CircleParticle 클래스를 완성하여 화면에 렌더 해보기
이전 글에 원형 불꽃 효과를 생성하였다.
이번에는 이 효과를 적용할 CircleParticle 클래스를 완성하여 실제로 화면에 렌더 해보겠다.
1. CircleParticle 클래스 구현
이전의 원형 불꽃 효과의 내용을 바탕으로 CircleParticle을 구현하도록 하자.
1-1. 생성자와 멤버변수
- 부모 클래스인 Particle에 공통 속성을 넘겨 관리하고, CircleParticle에 중력 효과를 적용하기위해 고유 속성을 추가한다.
- 재사용성을 높이기 위해 마찰력을 초기화 상태(initialState)에 저장하고 추후 재사용시 원래 상태로 복원하도록 한다.
// constants.js
export const CIRCLE = {
// 생략...
FRICTION: 0.94, // 마찰력
GRAVITY: 0.01, // 중력
};
class CircleParticle extends Particle {
constructor({ ctx, isSmallScreen, x, y, vx, vy, radius, opacity, color }) {
// 부모 클래스를 이용하여 공통 멤버 변수 초기화
super({ ctx, isSmallScreen, x, y, vx, vy, radius, opacity, friction: CIRCLE.FRICTION, color });
// CircleParticle 고유 멤버 변수 : 중력을 추가하여 하강 효과 적용
this.gravity = CIRCLE.GRAVITY;
// 재사용을 위한 초기 상태 저장
this.initialState = { friction: CIRCLE.FRICTION };
}
}
1-2. draw
- 파티클을 그릴때 빛번짐 효과를 주기위해 ctx.shadowBlur 속성을 추가했다.
- 캔버스는 상태 기반으로 동작하기 때문에 한번 설정한 속성은 이후에 그려지는 모든 도형에 동일하게 적용된다.
이러한 상태를 개별적으로 관리하려면 ctx.save() 와 ctx.restore() 메서드를 사용해야 한다. - ctx.save()
-. 현재 캔버스의 drawing state(그리기 상태)를 저장하여, 복원 시 이전의 상태를 사용할 수 있게 한다.
-. 캔버스 상태(drawing state)란 scale, rotate, fillStyle, shadowBlur, shadowColor, lineWidth등 과 같은 속성값을 포함한다. - ctx.restore()
-. 이전의 drawing state을 복원한다.
-. 캔버스 상태와 픽셀 데이터가 분리되어 있기 때문에, 복원을 해도 화면에 그려진 픽셀 데이터는 사라지지 않고 블러가 적용된 상태로 남아있으며 이전에 저장한 drawing state만 복원된다.
-. 만약 복원하지 않는다면 이후에 그려지는 text나 tail 파티클에도 블러 효과가 적용되게 된다.
따라서 save를 통해 저장한 이전의 상태를 꼭 restore 시켜줘야 파티클의 블러 효과를 개별적으로 적용할 수 있다. - 아래와 같은 절차로 블러 효과를 파티클에 개별적으로 적용할 수 있다.
-. 현재 상태 저장 -> 파티클 a를 그리기 위해 블러 설정 -> 파티클 a 그리기 -> 블러 설정 전 상태로 복원
-. 이런 복원 작업 덕분에 다음 파티클을 그릴때 새로운 블러 설정을 독립적으로 적용할 수 있다.
// constants.js
export const CIRCLE = {
// 생략...
// 번짐의 정도
SHADOW_BLUR: 10,
};
// CircleParticle.js
draw() {
// 현재 캔버스 상태 저장: 이후 복원을 위해 기록
this.ctx.save();
// 블러 효과 적용 : 파티클 주변에 빛 번짐같은 느낌 추가
this.ctx.shadowBlur = CIRCLE.SHADOW_BLUR;
this.ctx.shadowColor = this.fillColor; // 파티클 색상과 동일한 그림자 색상 적용
// 부모 클래스의 메서드를 사용하여 화면에 파티클을 그린다.
super.draw();
// 복원: 블러 효과 적용 후 이전에 저장한 캔버스 상태 복원하여 효과를 개별적으로 적용하도록 한다.
this.ctx.restore();
}
1-3. update
- 파티클의 반지름은 점점 크게 증가시켜 입체감을 주다가, 마지막에 살짝 감소시켜 소멸 전 단계에서 자연스러움을 더했다.
- 투명도를 점진적으로 줄여 파티클이 화면에 자연스럽게 사라지도록 한다.
- y축 속도에 중력을 더해 아래로 자연스럽게 하강하도록 한다.
// constants.js
export const CIRCLE = {
// 생략...
RADIUS_ADJUST_OFFSET: 0.15, // 반지름 조정 간격
OPACITY_ADJUST_OFFSET: 0.01, // 투명도 조정 간격
RADIUS_ADJUST_RATE: 0.99, // 반지름 조정 비율
};
// CircleParticle.js
update() {
// 반지름을 점진적으로 증가시켜 입체감을 더함
this.radius += CIRCLE.RADIUS_ADJUST_OFFSET;
// 투명도를 점진적으로 감소시켜 자연스럽게 사라지도록 함
this.opacity -= CIRCLE.OPACITY_ADJUST_OFFSET;
// y좌표값에 중력을 더해 하강 효과 적용
this.vy += this.gravity;
// 부모의 업데이트 메서드를 사용하여 공통 속성 업데이트하기
super.update();
// 반지름이 너무 커지지 않도록 마지막에 일정 비율로 감소
this.radius *= CIRCLE.RADIUS_ADJUST_RATE;
}
2. CircleParticle 애니메이션 적용
이제 원형효과를 위한 CircleParticle 생성 메서드를 실행하고 업데이트시켜 애니메이션에 적용해 보도록 하겠다.
2-1. CircleParticle 생성 메서드 실행
- 원형 효과 또한 꼬리가 사라진 자리에 생성되어야 하므로 꼬리 파티클을 활성화 배열에서 제거할 시 CircleParticle을 생성한다.
- 사라진 꼬리의 x, y위치를 생성 메서드에 전달한다.
// Canvas.js
updateTailParticle() {
for (let i = this.tailParticles.length - 1; i >= 0; i--) {
const tail = this.tailParticles[i];
tail.update();
tail.draw();
if (tail.belowOpacityLimit(TAIL.OPACITY_LIMIT)) {
this.createTextParticle(tail.x, tail.y);
// CircleParticle 꼬리가 사라진 x, y 위치에 생성
this.createCircleParticle(tail.x, tail.y);
this.tailParticles.splice(i, 1);
this.pm.returnToPool(TYPE_TAIL, tail);
}
}
}
2-2. 업데이트 메서드 구현
- 이제 CircleParticle 클래스의 구현도 끝나서 애니메이션 중 파티클의 상태를 업데이트 하는 메서드를 구현해보겠다.
- CircleParticle도 활성화 배열에서 제거 시 인덱스 밀림 문제를 방지하기 위해 역순으로 순회한다.
- CircleParticle을 제거하는 기준은 투명도가 0 이하일떄, 파티클이 캔버스 영역에서 벗어날 때 제거하고 풀에 반납한다.
// Canvas.js
updateCircleParticle() {
// 인덱스 밀림 문제를 방지하기 위해 역순으로 순회
for (let i = this.circleParticles.length - 1; i >= 0; i--) {
// 개별 파티클들의 상태를 업데이트하고 화면에 그린다.
const circle = this.circleParticles[i];
circle.update();
circle.draw();
// 투명도가 제한선 아래이거나 캔버스 영역을 벗어날 때 활성화 배열에서 제거하고 풀에 반납
if (circle.belowOpacityLimit() || this.isOutOfCanvasArea()) {
this.circleParticles.splice(i, 1);
this.pm.returnToPool(TYPE_CIRCLE, circle);
}
}
}
2-3. CircleParticle 애니메이션 실행
- 애니메이션을 관리하는 animateFireworks 메서드에 CircleParticle 업데이트 메서드를 실행하여 원형 효과를 구현한다.
// Canvas.js
animateFireworks() {
let then = document.timeline.currentTime;
let frameCount = 0;
const bgCleanUp = setRgbaColor(SCREEN.BG_RGB, SCREEN.ALPHA_CLEANUP);
const addFrameCountDivisor = ANIMATION.TAIL_FPS + this.textLength * ANIMATION.TEXT_LEN_MULTIPLIER;
/**
* requestAnimationFrame에 전달할 콜백 함수
* @param {number} now requestAnimationFrame의 timestamp (밀리 초)
*/
const frame = (now) => {
const delta = now - then;
if (delta >= this.interval) {
// TODO: 애니메이션 코드 업데이트
const alpha = SCREEN.ALPHA_BASE + SCREEN.ALPHA_OFFSET * Math.sin(frameCount / SCREEN.SPEED_CONTROL);
this.fillFullCanvas(setRgbaColor(SCREEN.BG_RGB, alpha));
if (frameCount === 0) {
this.fillFullCanvas(bgCleanUp);
this.createTailParticle();
}
// TailParticle 업데이트 후 활성화 배열 제외할 때 CircleParticle 생성
this.updateTailParticle();
// CircleParticle 상태 업데이트하여 화면에 다시 렌더
this.updateCircleParticle();
this.updateTextParticle();
frameCount = (frameCount + 1) % addFrameCountDivisor;
then = now - (delta % this.interval);
}
this.animationId = requestAnimationFrame(frame);
};
this.animationId = requestAnimationFrame(frame);
}
3. 실행 화면 사진
글자수 별 대략적인 실행화면이다. 잔상효과는 추후 추가할 예정이다.
Github
'Canvas > 불꽃놀이 프로젝트' 카테고리의 다른 글
[Canvas-불꽃놀이-17] Jest 설정과 테스트 진행 순서 (1) | 2024.12.03 |
---|---|
[Canvas-불꽃놀이-16] SparkParticle로 잔상효과 적용하기 (1) | 2024.12.02 |
[Canvas-불꽃놀이-14] 원형 불꽃 효과 생성 (2) | 2024.11.29 |
[Canvas-불꽃놀이-13] TextParticle 구현과 적용 (1) | 2024.11.26 |
[Canvas-불꽃놀이-12] text 픽셀 데이터 생성 (1) | 2024.11.25 |