처음 수박 게임에서 영감을 얻었다고 했던 걸 기억하시는가. 실제 수박 게임은 Matter.js 라고 하는 2D 물리엔진 라이브러리를 이용해서 만들었지만, 나는 그런 건 모르겠고 그냥 바닐라 JS로 만들어 버렸다. 만들고 싶었던 건 고무공 같은 오브젝트가 이리저리 튀어다니는 모습이었다.
공끼리 충돌하게 만들고 싶진 않았다. 그건 너무 복잡하니까. 그래서 그냥 바닥에 닿으면 튀어오르게 만들었다. 바닥은 평평한데다가 멈춰 있기까지 하다. 그게 뭐 어쨌는데? x 방향 충돌이 없고 y 방향으로 충돌만 고려하면 되며, 바닥은 절대 움직이지 않기 때문에 충돌 계수만 곱해주면 된다는 엄청난 편의를 제공한다.
그래서 만들기 쉬웠나면... 🤷♂️
일단 외부 라이브러리를 전혀 쓰지 않았고 setInterval이나 마우스 이벤트에서 좌표에 관한 이해가 부족했던 게 큰 걸림돌이었다. 심지어 canvas도 아니고 div를 만들어서 움직이는 방식으로 구현하는 바람에 실시간으로 물리량을 변경하는 데서 애를 먹었다.
if (ball.y0 + ball.y + ball.vy * TICK > HEIGHT - radius) {
if (ball.vy <= LIMIT) {
ball.vy = 0;
ball.ay = 0;
} else {
ball.vy *= -COR;
}
}
충돌을 감지하는 부분인데, 바닥의 높이는 항상 고정이므로 이 값을 넘어가면 충돌이 일어난 것으로 보았고, 속도가 일정 수준 이하로 내려가면 멈추도록 만들었다. 네이밍은 최대한 직관적으로 하는 게 좋다던데, 물리량까지 다 풀네임으로 쓰기는 너무 귀찮았다. 😥
ball.y += ball.vy * TICK;
ball.vy += ball.ay * TICK;
ballDiv.style.transform = `translate(-50%, calc(${ball.y}px - 50%))`;
충돌을 감지하고 나서는 공을 이동시켜 주었다. 모든 공은 div로 만들었기 때문에 transform을 주어서 움직이도록 했다.
공 하나하나를 움직이는 것은 만들었으므로 사용자가 클릭할 때마다 공이 추가되도록 만들었다.
const anchorX = container.getBoundingClientRect().x;
const anchorY = container.getBoundingClientRect().y;
const x = pageX - anchorX;
const y = pageY - anchorY;
const ballDiv = document.createElement("div");
ballDiv.className = "ball";
ballDiv.style.width = `${2 * radius}px`;
ballDiv.style.height = `${2 * radius}px`;
ballDiv.style.top = `${y}px`;
ballDiv.style.left = `${x}px`;
container 영역을 클릭하면 클릭한 위치를 감지하여 새로운 div를 생성한다. 처음에는 offset 좌표를 이용했는데, container 위의 div를 클릭했을 때 엉뚱한 위치에 생성되는 오류 때문에 page 좌표로 바꾸었다.
const interval = setInterval(() => {...}, TICK);
ball.interval = interval;
if (balls.length > MAX_NUM) {
while (balls.length > MAX_NUM) {
const removeBall = balls.shift();
clearInterval(removeBall.interval);
container.removeChild(removeBall.ballDiv);
}
}
그 다음 애니메이션을 부여하고 성능 저하를 막기 위해 일정 개수 이상으로 만들면 오래된 순서로 삭제하는 기능도 넣었다.
const radius = Math.round(Math.random() * 30 + 10);
const h = Math.round(Math.random() * 360);
ballDiv.style.backgroundColor = `hsla(${h}, 100%, 65%, 0.8)`;
약간의 랜덤성을 더해주면 재미있을 것 같아 크기와 색상은 랜덤으로 나오도록 했다. 반지름은 random 함수를 이용해서 10px에서 40px 사이로 만들어지도록 했다. 색상은 랜덤하면서도 밝은 색만 나오도록 하기 위해 HSL로 지정했으며, 채도와 밝기는 높이고 색상만 랜덤으로 바뀌도록 했다. 공끼리 조금씩 겹쳐 보이면 더 예쁠 것 같아 약간 투명하게 만들었다.
움직임이 조금 엉성한 것 같지만 그래도 원하던 요소는 잘 만들어 냈다. 하지만 성능이나 가독성 문제가 있기 때문에 결국은 라이브러리를 사용해서 리팩토링을 해야 하지 않을까 싶다.