Matter.js로 브라우저에서 물리엔진 구현하기

Seoyong Lee·2023년 12월 8일
2

개발 공부

목록 보기
18/21

Matter.js

Matter.js is a 2D physics engine for the web

Matter.js는 브라우저 환경에서 물리 엔진 구현을 위한 라이브러리입니다. canvas로 그린 요소에 대한 중력 및 충돌, 드래그를 이용한 조작 등을 구현할 수 있습니다.

기본 요소들

Engine

  • 화면에 구현될 세계를 조작하기 위한 기본적인 엔진을 생성하고 조작하는 방법이 포함된 모듈입니다.
  • 엔진은 뒤에서 설명할 world의 시뮬레이션 업데이트를 관리하는 컨트롤러입니다.
  • Matter.Engine으로 선언합니다.

Render

  • Engine 인스턴스를 시각화하기 위한 간단한 HTML5 캔버스 기반 렌더러입니다.
  • 와이어프레임, 스프라이트 및 뷰포트를 지원하는 벡터 등 다양한 드로잉 옵션을 포함합니다.
  • Matter.Render로 선언합니다.

Body

  • body 모델을 생성하고 조작하는 방법이 포함된 모듈입니다.
  • body는 render 모듈이 그리는 대상이 되는 삼각형, 사각형, SVG 등의 요소입니다.
  • 실제 구현에는 자주 사용되는 요소들을 모아 만든 factory methods인 Bodies를 사용할 수 있습니다.

World

  • render를 통해 그리는 대상물을 모은 world composite 생성과 조작을 위한 모듈입니다.
  • body로 만든 모든 요소들은 world라는 세계 위에서 표현됩니다.
  • world에는 중력을 조절하는 gravity와 axis-aligned bounding boxes (AABB) 조절을 위한 bounds 프로퍼티가 추가로 존재합니다.

사용법

먼저 canvas와 이를 감싼 container를 선언하고 useRef을 이용해 참조를 구성합니다.

import { useRef } from "react";

const Canvas = () => {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);

  return (
    <section
      ref={containerRef}
    >
      <canvas
        ref={canvasRef}
        id="viewport"
        width="500"
        height="500"
      />
    </section>
  );
};

export default Canvas;

이후 Matter.Bodies모듈을 통해 요소를 생성합니다. 사각형, 원, SVG, 이미지 등의 생성이 가능합니다.

const Bodies = Matter.Bodies;

const element = Bodies.rectangle(
  x,
  y,
  width,
  height
  {
    chamfer: {
      radius: [10, 10] // border-radius 설정이 가능합니다.
    },
    render: {
      fillStyle: "#fff", 
      sprite: {
          texture: "/assets/main/logo.png", // 이미지를 입힐 수 있습니다.
          xScale: 0.75,
          yScale: 0.75,
       },
    },
    restitution: 0.6, // 충격을 받으면 튀어오르는 정도를 조절합니다.
  }
);

이제 이를 엔진을 통해 그려봅니다.

useEffect(() => {
  const Engine = Matter.Engine;
  const Render = Matter.Render;
  const World = Matter.World;
  const engine = Engine.create();
  engine.gravity.y = 1.5; // 중력의 세기를 설정합니다.

  const render = Render.create({
    element: containerRef.current,
    engine: engine,
    canvas: canvasRef.current,
    bounds: {
      min: { x: 0, y: 0 },
      max: { x: canvasWidth, y: canvasHeight },
    },
    options: {
      showSeparations: true,
      width: canvasWidth,
      height: canvasHeight,
      background: "",
      wireframes: false,
    },
  });

  // 마우스를 이용해 조작을 가능하게 해줍니다.
  const mouse = Matter.Mouse.create(render.canvas),
    mouseConstraint = Matter.MouseConstraint.create(engine, {
      mouse: mouse,
      constraint: {
        stiffness: 0.2,
        render: {
          visible: false,
        },
      },
    });

  // 그릴 요소들을 world에 모읍니다.
  World.add(engine.world, [
    element,
    mouseConstraint,
  ]);

  Matter.Runner.run(engine); // 엔진을 구동합니다.
  Render.run(render); // 렌더를 진행합니다.
  Body.rotate(element, Math.PI / 6);

  return () => {
    Render.stop(render);
    World.clear(engine.world, false);
    Engine.clear(engine);
    render.canvas.remove();
  };
}, []);

구현이 완료되면 아래와 같이 아래로 떨어지는 요소를 확인할 수 있습니다.

만약 요소가 떨어져서 사라지는 것이 아닌 특정 지점에 고정하고 싶다면 아래에 벽을 만들어 떨어지지 않도록 막아야합니다.

좌/우/위/아래를 막을 벽을 세워줍니다.

// props: (x: number, y: number, width: number, height: number)
const floor = Bodies.rectangle(clientWidth / 2, clientHeight, clientWidth, 100, {
  isStatic: true,
  render: {
    fillStyle: "#121212",
  },
});

const floorLeft = Bodies.rectangle(0, clientHeight / 2, 50, clientHeight, {
  isStatic: true,
  render: {
    fillStyle: "#121212",
  },
});

const floorRight = Bodies.rectangle(clientWidth, clientHeight / 2, 50, clientHeight, {
  isStatic: true,
  render: {
    fillStyle: "#121212",
  },
});

const floorTop = Bodies.rectangle(clientWidth / 2, -10, clientWidth, 50, {
  isStatic: true,
  render: {
    fillStyle: "#121212",
  },
});

useEffect(()=>{
  // ...
    World.add(engine.world, [
      // 벽을 world에 추가합니다.
      floor,
      floorLeft,
      floorRight,
      floorTop,
      logo,
      mouseConstraint,
    ]);
  // ...
}, [])

이제 다음과 같이 특정 공간 안에 요소를 가둬 둘 수 있게됩니다.

여러 구현 사례들

만약 구현하고 싶은 대상을 찾아보고 싶다면 공식으로 지원하는 데모를 확인해 보실 수 있습니다.

Matter.js Demo · code by @liabru

여러가지 옵션들을 제공하며 직접 만져보면서 느낌을 확인해 볼 수 있습니다.
공식 깃허브를 통해 Matter.js를 사용해 만든 다양한 사이트 예시도 확인 가능합니다.

Google - Game of the Year
Fuse.kiwi – Interesting Internet
Patrick Heng - Creative Developer Portfolio
USELESS

참고
GitHub - liabru/matter-js
Matter.js

0개의 댓글