밝을희 클태

<Canvas> 총알 자국 그리기(사라지는 애니메이션, 회전해서 그리기) 본문

사이드 프로젝트/도라에몽 잡기 [React]

<Canvas> 총알 자국 그리기(사라지는 애니메이션, 회전해서 그리기)

huipark 2024. 11. 3. 14:18

총 게임을 하다 보면 벽에 총알 자국이 생겼다가 시간이 지남에 따라 옅어지면서 사라지는 효과를 구현하고 싶었다.

먼저 총알 자국 PNG 이미지를 준비한다. 이 이미지는 나중에 게임에서 총알 자국을 그릴 때 사용된다.

이전의 총알 자국들을 기억하기 위해 bulletMarks라는 배열을 만들고, 배열 안에 총알 객체를 저장한다. 각 총알 자국 객체는 다음과 같은 속성을 가진다

  • x, y: 총알 자국의 좌표
  • t: 총알 자국이 생긴 시간
  • rotate: 총알 자국을 랜덤한 각도로 회전시키기 위한 값

각도는  라디안(360도)으로 표현할 수 있으며, 랜덤한 각도를 만들기 위해 N (0 <= N < 1)을 곱해준다. 예를 들어

  • N = 0.5
    • 2π * 0.5 = 1π
    • 1π = 180º
  • N = 0.2
    • 2π * 0.2 = 0.4π
    • 0.4π * 180/π = 72º
  • N = 0.8
    • 2π * 0.8 = 1.6π
    • 1.6π * 180/π = 288º

이제 bulletMarks 배열을 초기화한다

let bulletMarks = [];

총알이 발사될 때마다 총알 자국을 배열에 추가하기 위한 함수를 작성한다. 이 함수는 클릭 이벤트에서 호출된다

const handleShoot = (e) => {
    bulletMarks.push({
        x: e.clientX,
        y: e.clientY,
        t: new Date().getTime(),
        rotate: Math.random() * 2 * Math.PI,
    });
};

window.addEventListener("click", handleShoot);

그림을 그릴 때 총알 자국의 경과 시간을 계산하여, 2초 동안은 투명도가 1로 유지되도록 하고, 이후 2초가 경과하면 alpha 값을 계산하여 투명도를 조정한다.

또한, 총알 자국을 회전시켜 그리기 위해 그림판(Canvas)을 translate 메서드로 이동시킨다. 마우스 클릭 좌표로 그림판을 옮기고, 이전에 랜덤으로 생성한 각도로 그림판을 회전시킨다. 이때 이미지의 크기만큼 좌표를 조정해야 한다.

 

그림을 그릴 ctx를 초기화한다:

const ctx = canvas.getContext("2d");

이제 drawBulletMarks 함수를 작성한다. 이 함수는 현재 시간과 각 총알 자국의 경과 시간을 비교하여 그리기 작업을 수행한다:

const drawBulletMarks = () => {
    const now = new Date().getTime();

    bulletMarks = bulletMarks.filter((mark) => {
        const elapsed = now - mark.t;
        const totalLifespan = 4000;
        const visibleDuration = 2000;
        const fadeDuration = totalLifespan - visibleDuration;

        let alpha = 1;

        if (elapsed < totalLifespan) {
            if (elapsed > visibleDuration) {
                const fadeElapsed = elapsed - visibleDuration;
                alpha = 1 - fadeElapsed / fadeDuration;
            }
            ctx.save();
            ctx.globalAlpha = alpha;
            ctx.translate(mark.x, mark.y);
            ctx.rotate(mark.rotate);
            ctx.translate(-25, -25);
            ctx.drawImage(bulletImageRef.current, 0, 0, 50, 50);
            ctx.restore();
            return true;
        }
        return false;
    });
};

마지막으로, 전체 그림을 그리는 drawCanvas 함수를 작성한다. 이 함수는 총알 자국을 그리고 애니메이션 프레임을 요청한다

const drawCanvas = () => {
    drawBulletMarks();
    animationFrameId = requestAnimationFrame(drawCanvas);
};

완성!