별을 보는 어린 왕자[Javascript]

html5의 canvas로 애니메이션을 만드는 과정이 꽤 재밌게 느껴져서 이것저것 만져보던 차에, 간단하게 별을 보는 어린 왕자의 모습을 구현해봤다.

화면 중앙 하단에 행성 위에 올라가 있는 어린 왕자가 있고 그 위로 별들이 회전하고 있는 형태로 구상했다. 그리고 인터랙티브한 요소를 넣어봤는데, 유저가 화면을 클릭하고 있으면 별들의 속도가 점점 증가하게 만들었다.

⭐️ 별 만들기

class Particle {
  constructor(moveRadius, step, position, size) {
    this.moveRadius = moveRadius;
    this.step = step;
    this.position = position;
    this.size = size;
    this.acc = 0.0001;
    this.cnt = 100;
  }
  
  ...
}

기본적으로 별의 속성값을 갖는 Particle 클래스를 정의했다. moveRadius는 회전반경, step은 프레임 별로 이동할 거리, position은 현재 회전 각도, size는 별의 크기이다. 지금생각해보니 position은 변수명을 angle로 하는게 나을 것 같다.

class Particle {
  ...

  draw() {
    let x = Math.cos(this.position) * this.moveRadius + canvas.width / 2;
    let y = Math.sin(this.position) * this.moveRadius + canvas.height;

    ctx.beginPath();
    drawStar(x, y, 5, this.size, this.size / 2);
    ctx.closePath();
    ctx.strokeStyle = "white";
    ctx.stroke();
  }

  update() {
    if (!isAccelerated) {
      if (this.cnt >= 101) {
        this.cnt -= 1;
      }
      this.position += this.step * (this.acc * this.cnt * this.cnt);
      this.draw();
    } else {
      if (this.cnt <= 300) {
      	this.cnt += 1;
      }
      this.position += this.step * (this.acc * this.cnt * this.cnt);
      this.draw();
    }
  }
}

Particle 클래스의 함수들이다. draw 함수를 통해 별을 그리고, update 함수로 별들의 속도를 조절한다. 속이 빈 별을 만들기 위해 stroke를 사용했다.

function drawStar(positionX, positionY, spikes, outerRadius, innerRadius) {
  let rotation = (Math.PI / 2) * 3;
  let x = positionX;
  let y = positionY;
  let step = Math.PI / spikes;

  ctx.beginPath();
  ctx.moveTo(positionX, positionY - outerRadius);
  for (let i = 0; i < spikes; i++) {
    x = positionX + Math.cos(rotation) * outerRadius;
    y = positionY + Math.sin(rotation) * outerRadius;
    ctx.lineTo(x, y);
    rotation += step;

    x = positionX + Math.cos(rotation) * innerRadius;
    y = positionY + Math.sin(rotation) * innerRadius;
    ctx.lineTo(x, y);
    rotation += step;
  }
  ctx.lineTo(positionX, positionY - outerRadius);
  ctx.closePath();
}

Particle 클래스의 draw 함수 내부에서 별을 그리는데 이용되는 drawStar 함수이다. 설정된 각도로 점을 찍어가며 별을 그린다. spikes 인자로 별을 꼭지점 개수를 조절할 수 있는데 아래 그림을 보자.

spike = 3


꼭지점이 3개라 그냥 삼각형처럼 그려진다.

spike = 5


이게 제일 별모양 같다.

spike = 7


이건 별이라기보단 타격이펙트(?) 처럼 생겼다.

어쨌든 꼭지점은 5로 두는게 제일 맘에들었다.

✏️ 초기화 및 애니메이션 구현

function init() {
  particleArray = [];
  for (let i = 0; i < 600; i++) {
    let moveRadius = Math.random() * canvas.width + 250;
    let step = Math.random() * 0.0002 + 0.002;
    let position = Math.random() * Math.PI * 2;
    let size = Math.random() * 8 + 0.5;

    particleArray.push(new Particle(moveRadius, step, position, size));
  }
}

init 함수를 한 번 실행시켜서 별들을 만들고 particleArray에 넣어준다. 별들의 속성은 랜덤함수를 이용하여 다양하게 만들어 준다.

function animate() {
  requestAnimationFrame(animate);
  if (!isAccelerated) {
    ctx.fillStyle = `rgba(0,10,32,0.1)`;
  } else {
    ctx.fillStyle = `rgba(0,10,32,0.05)`;
  }
  ctx.fillRect(0, 0, innerWidth, innerHeight);

  for (let i = 0; i < particleArray.length; i++) {
    particleArray[i].update();
  }
  drawPrince();
}

requestAnimationFrame 함수로 animate를 계속 불러준다. 그리고 별들이 움직일때, 이전 위치에 잔상 효과를 주기 위해 ctx.fillStyle에 알파값을 주었다. 이후 모든 별을 계속 업데이트 해준다.

🤴 어린 왕자 그리기

무료 이미지 하나를 다운받아서 넣어주었다.

function drawPrince() {
  var img = new Image();
  img.src = "prince.png";
  img.onload = function () {
    ctx.drawImage(img, canvas.width / 2 - 150, canvas.height / 1.4, 300, 300);
  };
}

이까지 하면 아래와 같은 결과를 볼 수 있다.

👆 인터랙티브한 요소 넣기

var isAccelerated = false;

window.addEventListener("mousedown", handleIsAccelerated, false);
window.addEventListener("mouseup", handleIsAccelerated, false);
window.addEventListener("touchstart", handleIsAccelerated, false);

일단 화면을 누르고 있는지, 떼고있는지를 체크하기 위해 이벤트리스너와 변수 하나를 사용하였다. 모바일 환경에서도 돌려보려고 touchstart를 넣어봤는데, 내 아이폰에서는 작동을 안하더라. 뭘 잘못한 것 같긴한데 일단 패스.

화면을 클릭했을 때 별들의 속도를 증가시키고싶었다. 근데 선형으로 업데이트하니 급가속, 급정지하는 것처럼 보여 부자연스러웠다. 그래서 가속도 개념을 사용하였다. 다시 클래스 내부의 update 함수를 보자

class Particle {
  ...
  
  update() {
    if (!isAccelerated) {
      if (this.cnt >= 101) {
        this.cnt -= 1;
      }
      this.position += this.step * (this.acc * this.cnt * this.cnt);
      this.draw();
    } else {
      if (this.cnt <= 300) {
      	this.cnt += 1;
      }
      this.position += this.step * (this.acc * this.cnt * this.cnt);
      this.draw();
    }
  }
}

this.position이 현재 별의 회전반경인데, 이 값을 계속 업데이트하고 있는데, 매 가속도 this.acc에 this.cnt의 제곱을 곱해주고있다. 이렇게 하면, 매 반복마다 화면을 누르고 있던 시간의 제곱에 비례하여 별의 위치가 바뀌므로 상당히 자연스럽게 속도가 바뀐다.

이제 다 구현했으니 결과를 보자.

별의 속도가 빨라지며 꼬리가 길어지는 모션이 제대로 보인다.

😃 후기

항상 canvas로 도형이나 그려보며 끄적대다가 뭔가 그럴듯한 작품(?)을 처음으로 만들어봤다. 구글링 열심히 해가며 후딱후딱 만들어 봤는데, 생각보다 결과물이 괜찮은 것 같아서 만족스럽다.

근데 자바스크립트를 자주 사용하진 않다보니, 코드 구조가 누더기처럼 이리저리 갖다 붙인 느낌이라 좀 불만족스럽다. 다음 번 프로젝트에서는 클래스를 좀 이쁘게 사용해보자.

그리고 깃헙의 페이지 기능이 꽤 괜찮은 것 같다. 이런 가벼운 프로젝트들의 데모를 무료로 편하게 호스팅할 수 있음에 감사한다.

데모 보러가기
코드 보러가기

좋은 웹페이지 즐겨찾기