본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 26. 03:05

심층 분석: "Gravity Paint" 제작하기 - React, Matter.js, p5.js를 활용한 촉각적 물리 도구

요약

React 19, Matter.js, p5.js를 결합하여 물리 기반 브라우저 게임인 'Gravity Paint'를 구축하는 아키텍처를 분석합니다. React의 상태 관리와 물리 엔진의 고주파수 루프 간의 충돌을 해결하기 위한 관심사 분리 및 이벤트 기반 통신 전략을 다룹니다.

핵심 포인트

  • React의 가상 DOM과 물리 엔진의 고주파수 루프 간 아키텍처 조정 방법
  • CustomEvent를 활용한 React와 물리 루프 간의 효율적인 통신
  • Matter.js를 이용한 로프 제약 조건 및 물리 시뮬레이션 구현
  • 사용자 경험을 고려한 반복적 디자인 삭제(Iterative Design Clearing) 기능

물리 기반 브라우저 게임은 새로운 것이 아니지만, 진정으로 유기적이고, 상호작용적이며, 감각적으로 만족스러운 무언가를 만드는 것은 여전히 보람 있는 엔지니어링 과제로 남아 있습니다. 이 기술 블로그 포스트에서는 Gravity Paint의 내부 구조를 살펴봅니다. 이는 플레이어가 단단한 로프 케이블을 그려서 색색의 유리, 강철, 버블 구슬의 흐름을 목표 수집 버킷으로 유도하는 중력 인식 샌드박스 퍼즐입니다.

우리는 React 19의 상태 관리(state-management), Matter.js의 고성능 강체 시뮬레이션(rigid-body simulations), p5.js의 부드러운 시각적 캔버스 루프(canvas loops), 그리고 자체 제작한 Web Audio API 신디사이저를 어떻게 원활하게 결합하는지 분석할 것입니다.

세 가지 머리를 가진 아키텍처: React, P5, & Matter.js

복잡한 상호작용 캔버스를 만들 때 개발자가 직면하는 가장 큰 설계 문제는 **아키텍처 조정(Architectural Coordination)**입니다.

  • React는 구조화된 데이터, 레이아웃 트리, 레벨 진행 상황 및 사이드바를 관리하는 데 탁월합니다. 하지만 React의 가상 DOM (virtual-DOM) 렌더링은 고주파수(60fps) 물리 단계(physics steps)에는 전혀 적합하지 않습니다.
  • Matter.js는 세계, 벡터, 충돌, 모양, 힘 및 제약 조인트(constraint joints)를 나타내는 분석 엔진을 실행합니다.
  • **p5.js (인스턴스 모드, instance-mode)**는 그래픽 파이프라인, 픽셀 그리기, 마우스 입력, 프레임 초기화 및 UI 애니메이션을 처리합니다.

이들을 함께 사용하려면 세심한 관심사 분리(boundary of concerns)가 필요합니다:

+------------------------------------------+
|                 REACT UI                 |
| (Campaign Progress, Sound Settings, HUD) |
...

메모리 누수를 방지하고 깨끗한 상태 전환(레벨이 리셋되거나 체인이 제거될 때와 같은 경우)을 조정하기 위해, 우리는 전체 캔버스 설정을 단일 React useEffect() ref 훅 컨테이너 내에 래핑합니다. React 래퍼와 내부 루프 간의 통신은 커스텀 Window 이벤트 (CustomEvent)를 통해 깔끔하게 조정되며, 이를 통해 물리 사이클을 방해하는 지저분한 React 리렌더링(re-renders)을 피할 수 있습니다.

그릴 수 있는 로프(체인)의 물리 법칙

무게에 따라 늘어지고 처지며, 그릴 수 있는 페인팅 가능한 로프 제약 조건(rope constraints)을 어떻게 구축할까요?

Matter.js에는 내장된 "로프(rope)\

플레이테스트(playtesting)를 진행하는 동안, 디자인을 미세하게 조정하고 싶어 하는 플레이어들이 전체 시스템 리셋(full system resets)으로 인해 좌절감을 느낀다는 점을 관찰했습니다. 리셋을 하면 현재 레벨의 수집 점수가 삭제되고 레벨 진행 상황이 초기화되었습니다.

이를 해결하기 위해, 우리는 반복적 디자인 삭제 (Iterative Design Clearing) 기능을 구현했습니다.

우리는 체인 삭제 (Clear Chains) 기능을 도입했습니다. 이 기능은 사용자가 직접 그린 Matter 복합체 체인(composite chains)만을 대상으로 하여 삭제하며, 흐르는 입자의 물리적 진행 상태, 레벨 타이머, 그리고 수집 합계 점수는 완전히 온전하게 유지합니다:

// Matter 물리 범위 내에서 삭제 로직을 깔끔하게 정의
const handleClearChainsEvent = () => {
  if (!world) return;
...

고주파수(High-Hz) Canvas 루프에서의 React 클로저(Closure) 함정 완화

이 런타임(runtime)에서 해결한 가장 까다로운 버그 중 하나는 **부정확한 실행 상태 캡처 프리즈 (Sloppy Run State Capture Freeze)**였습니다.

증상:

플레이어가 5개 이상의 구슬을 떨어뜨리면, 화면에 "Sloppy Run! Game Over" 카드가 나타나며 재시도를 유도합니다. 오버레이(overlay)가 활성화되어 있음에도 불구하고, 구슬 수도꼭지(faucet spigot)는 계속해서 공을 생성했고, 떨어진 공 카운터는 계속 증가하여 UI 상태가 부정확해지는 현상이 발생했습니다.

근본 원인:

P5의 고주파수 p.draw 루프(60fps로 실행) 내부에서 React 상태 값인 showSloppyMessage를 참조하면, P5 초기화 시점에 캡처된 **클로저 값 (closure value)**이 반환됩니다. 즉, 캔버스 루프가 오래된(stale) 상태를 확인하고 있었기 때문에, 게임 오버 모달이 표시되었다는 사실을 인지하지 못했던 것입니다!

해결책:

우리는 React와 P5 페인트 루틴 사이의 핫와이어링(hot-wiring) 브릿지 역할을 하는 **동기화된 가변 참조 (Synchronized Mutable Refs)**를 구현했습니다:

// 1. 상태 및 추적용 Ref 선언
const [showSloppyMessage, setShowSloppyMessage] = useState<boolean>(false);
const showSloppyMessageRef = useRef(showSloppyMessage);
...

p.draw() 내부에서 이 Hot-Ref를 쿼리함으로써, 물리 루프는 클로저 지연(closure lags)을 겪거나 캔버스의 재마운트(re-mount)를 강제하지 않고도 상태 변화에 즉각적으로 반응할 수 있습니다.

사운드 합성: 인터랙티브 Web Audio API

감각적인 만족감을 제공하기 위해, Gravity Paint는 무거운 .mp3 에셋을 사용하는 대신 동적인 Web Audio Synthesizers (Web Audio 합성기)를 활용합니다. 구슬이 충돌하거나 떨어질 때마다, 우리는 합성 톤 트리거 (synthetic tone triggers)를 실행합니다:

export class Synthesizer {
  private ctx: AudioContext | null = null;

...

이를 통해 단순한 캔버스 샌드박스(canvas sandbox)로부터 생성적인 시각-악기 (visual-instrument) 경험을 만들어냅니다!

Matter.js와 같은 강체 물리 엔진 (rigid-body physics engines)을 p5.js 레이아웃과 결합하여 React 19와 같이 반응성이 매우 높은 프레임워크 내부에서 구현하는 것은 도전적인 과제일 수 있습니다. 우리는 다음과 같은 방법을 통해 이를 해결했습니다:

  1. React 렌더링으로부터 물리 계산을 분리하고,
  2. 동적 루프 (dynamic loops)를 hot-ref 레지스터로 연결하며,
  3. Web Audio API를 통한 분산형 사운드 생성 (decentralized sound generation)을 사용함으로써,

고성능 웹 게임을 구축했습니다. 이 게임은 표준 프로덕션 서버부터 샌드박스화된 모바일 프레임에 이르기까지 모든 환경에서 깔끔하게 실행됩니다.

소스 코드를 탐색하고, 게임을 직접 확인하며, 그림을 그려보세요!

스크린샷

How to play 1

How to play 2

How to play 3

코드 및 기타 정보: https://www.dailybuild.xyz/project/143-gravity-paint

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0