[패스트캠퍼스] 챌린지 1일차 2.20

2023. 2. 20. 22:41개발 일지/패스트캠퍼스\챌린지

Part2. Canvas 챕터 01 - 4. 파티클 애니메이션 시키기 강의 시작!

강의 시작!

기존에 파티클 그리기까지 진행을 해서 그 이후에 파티클에 애니메이션 효과를 넣는 강의를 진행했다.
애니메이션 효과를 넣기 전에 우선 requestAnimationFrame이 실행되는 원리부터 설명을 들었다.
requestAnimationFrame 함수는 사용하는 모니터의 성능에 따라 실행되는 횟수가 다른데 60hz의 주사율을 가진 모니터를 기준으로 설명을 했다.
60hz의 모니터에서는 1초에 약 60번 정도 실행이되고 이는 약 16ms마다 실행된다는 뜻이다.

  현재시간 now - ( delta % interval ) now - then 1000/10  
횟수 now then delta interval fps
0 1000 1000 0 100 10
1 1016 1000 16 100 10
2 1032 1000 32 100 10
3 1048 1000 48 100 10
4 1064 1000 64 100 10
5 1080 1000 80 100 10
6 1096 1000 96 100 10
7 1112 1100 12 100 10
8 1128 1100 28 100 10
9 1144 1100 44 100 10
10 1160 1100 60 100 10
11 1176 1100 76 100 10
12 1192 1100 92 100 10
13 1208 1200 8 100 10
14 1224 1200 24 100 10
15 1240 1200 40 100 10
16 1256 1200 56 100 10
17 1272 1200 72 100 10
18 1288 1200 88 100 10
19 1304 1300 4 100 10

위의 표는 60hz 모니터를 기준에서 애니메이션을 동작시키기 위해서 requestAnimationFrame 함수가 호출될 때마다 현재시간을 가져와 (처음에 1000이라는 가정하에) 설정했던 목표 interval인 ( 표의 1000/10은 100ms로 fps를 10으로 맞출 수 있는 기준이 된다.) 100을 기준으로 now - then (then은 초기값을 now와 동일하게 설정해 준다.)의 값이 인터벌보다 커지게 되면 애니메이션을 실행하게 해주는 것이다. 이는 fps를 10으로 실행하고 싶을 때의 기준이다.

최근에 나오는 대부분의 모니터는 60hz이라 강의에선 60fps로 맞추기 위해 1000 / 60으로 interval을 설정해 줬다.

let interval = 1000 / 60;
let now, delta;
let then = Date.now();

function animate() {
	...
    now = Date.now();
    delta = now - then;
    
    if (delta < interval) return;
    
    ...
    
    // x를 1px 이동시키기
    particle.y += 1
    
    ...
    
    then = now - (delta % interval);
}

이렇게 코드를 추가해 주면 기존 강의에서 캔버스에 그려줬던 particle이 아래로 움직인다!
이제 이를 응용 해서 파티클을 단일 인스턴스가 아닌 다중 인스턴스로 만들어 생성할 수 있다.

...
const TOTAL = 5;
const randomNumBetween = (min, max) => {
	return Math.random() * (max - min + 1) + min
};

for (let i = 0; i < TOTAL; i++) {
    const x = randomNumBetween(0, canvasWidth);
    const y = randomNumBetween(0, canvasHeight);
    const radius = randomNumBetween(50, 100);
    const particle = new Particle(x, y, radius);
    particles.push(particle);
}

console.log(particles);

function animate(){
    window.requestAnimationFrame(animate);
    now = Date.now();
    delta = now - then;

    if (delta < interval) return;

    ctx.clearRect(0, 0, canvasWidth, canvasHeight);

    particles.forEach(particle => {
        particle.draw();
    });

    then = now - (delta % interval);
}
...

이러면 다양한 크기의 파티클들이 여러 위치에서 그려진다. 하지만 기존 animate함수에서 particle.y += 1로 원들이 내려가도록 애니메이션을 만들었는데 여러 인스턴스로 파티클을 생성하게 되면서 각 파티클들이 서로 다른 속도로 내려가게 만들고 싶다.
이를 위해서는 Particle class의 멤버 함수에 새로운 함수를 구현해 주면 간단하게 animate함수에서 실행해 줄 수 있다.

class Particle {
    constructor(x, y, radius, vy) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.vy = vy;
    }

    update(){
        this.y += this.vy;
    }

    draw(){
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI / 180 * 360);
        ctx.fillStyle = 'orange';
        ctx.fill();
        ctx.closePath();
    }
}

...

for (let i = 0; i < TOTAL; i++){
    const x = randomNumBetween(0, canvasWidth);
    const y = randomNumBetween(0, canvasHeight);
    const radius = randomNumBetween(50, 100);
    const vy = randomNumBetween(1, 5);
    const particle = new Particle(x, y, radius, vy);
    particles.push(particle);
}

...

function animate(){
    window.requestAnimationFrame(animate);
    now = Date.now();
    delta = now - then;

    if (delta < interval) return;

    ctx.clearRect(0, 0, canvasWidth, canvasHeight);

    particles.forEach(particle => {
        particle.update();
        particle.draw();

		// particle의 위치는 particle의 중심점이다.
        // 그래서 particle.y로만 cavasHeight와 비교하게 된다면
        // 원이 중간까지 땅에 들어가게 되면 사라지고 새로 지정한 위치에
        // 나타나게 된다. 즉 particle.radius만큼 위치 계산을 해주고
        // 반지름 만큼의 위치에서 새로 나오게 만들면 파티클이 보이지 않는
        // 곳에서 생성이 되어 내려오게 된다.
        if(particle.y - particle.radius > canvasHeight) {
            particle.y = -particle.radius;
        }
    });

    then = now - (delta % interval);
}

여러 개의 파티클이 다양한 크기와 위치에서 생성이 되고 각 파티클마다 내려오는 속도도 달라 제각각으로 내려오게 된다.
그리고 파티클이 다 내려오면 다시 보이지 않는 곳에서 생성되어 내려오도록 설정해 주면 파티클들이 계속해서 내려오는 모습을 애니메이션으로 볼 수 있다.

파트2의 챕터4의 결과물

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio;

const canvasWidth = innerWidth;
const canvasHeight = innerHeight;

canvas.style.width = canvasWidth + 'px';
canvas.style.height = canvasHeight + 'px';

canvas.width = canvasWidth * dpr;
canvas.height = canvasHeight * dpr;
ctx.scale(dpr, dpr);

class Particle {
    constructor(x, y, radius, vy) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.vy = vy;
    }

    update(){
        this.y += this.vy;
    }

    draw(){
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI / 180 * 360);
        ctx.fillStyle = 'orange';
        ctx.fill();
        ctx.closePath();
    }
}

const TOTAL = 5;
const randomNumBetween = (min, max) => {
    return Math.random() * (max - min + 1) + min;
}
let particles = [];

for (let i = 0; i < TOTAL; i++){
    const x = randomNumBetween(0, canvasWidth);
    const y = randomNumBetween(0, canvasHeight);
    const radius = randomNumBetween(50, 100);
    const vy = randomNumBetween(1, 5);
    const particle = new Particle(x, y, radius, vy);
    particles.push(particle);
}

console.log(particles);

let interval = 1000 / 60;
let now, delta;
let then = Date.now();

function animate(){
    window.requestAnimationFrame(animate);
    now = Date.now();
    delta = now - then;

    if (delta < interval) return;

    ctx.clearRect(0, 0, canvasWidth, canvasHeight);

    particles.forEach(particle => {
        particle.update();
        particle.draw();

        if(particle.y - particle.radius > canvasHeight) {
            particle.y = -particle.radius;
        }
    });

    then = now - (delta % interval);
}

animate();

챕터의 최종 코드는 이렇다.

강의 완료!

http://bit.ly/3Y34pE0

 

패스트캠퍼스 [직장인 실무교육]

프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.

fastcampus.co.kr

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.