matter js로 2d 애니메이션 적용해보기

이수연·2023년 6월 7일
7

포트폴리오를 기획하면서 우주로 테마를 잡게 되었다. 따라서 우주의 무중력 상태를 표현하고자 기획하게 되어 matter js를 이용하여 기능을 구현하게 되었다.

matter js란?

지금까지 canvas 공부하며 구현하였던 fps,requestAnimationFrame(frame) 재귀함수,gravity 등등의 직접 구현하기 어려운 물리 알고리즘을 쉽게 사용가능하게끔 만든 Javascript용 2D 물리 엔진이다.

matter js에는 engine,render,runner,body,bodies,composite 등의 api가 있다.

engine

물리 시뮬레이션을 담당하는 api로, Body 들의 list들을 관리 한다.
Body 들의 위치, 속도, 충돌 등을 계산 할수 있으며 gravity, enableSleeping, positionIterations 등등의 기능을 수행할수 있다.

Render

말그대로 Body 들의 시각적인 부분을 담당 한다. Canvas의 width, height를 받아오거나 color, pixelRatio, showVelocity, showBlaBla등의 기능을 수행할수 있다.

Runner

Engine, Render의 업데이트 loop 관리 한다. beforeTick, tick, afterTick, fps, requestAnimationFrame(frame) 등의 기능을 수행할수 있다.

Body

단일 물리적 객체 ( 시각적 x ) 이다. 한 객체의 position, velocity, force, mass 등의 기능을 수행 할수 있다.

Bodies

물리값만 가진 Body를 시각적으로 쉽게 표현위한 모듈 이다. 원, 사각형, 다각형, SVG Paths 등을 쉽게 제작 할수 있다.

Composite

world에 body를 추가해주는 역할을 한다. Body들을 하나로 묶는 그룹의 역할도 가능 하다고 한다.

Mouse

Canvas 내에서의 포지션값을 위하여 사용하는 api다. 말그대로 마우스 이벤트시 값을 받아오기 때문에 캔버스의 Body들과 상호작용은 할 수 없는 특징이 있다.

MouseContraint

mouse api와 다르게 각 Body들과 상호작용을 할 수있게 해준다.(들기 등)
Mouse를 이 MouseConstraint와 연결하여 사용한다.

matter js를 이용하여 리액트 프로젝트 만들어 보기

설치

npm i matter-js
  1. 기존에 canvas를 활용하여 프로젝트를 만들었던것 처럼, useRef를 활용하여 canvas의 dom요소를 참조한다.
  const canvasRef = useRef(null);
  
 <canvas style={{ cursor: "pointer" }} ref={canvasRef}></canvas>
  1. useEffect를 세팅한후, canvas의 current값을 받아 온다. 그이후, 초기 옵션을 initScene 이라는 함수에서 세팅한다. Engine.create()를 선언하여 engine을 먼저 만들고, render에서 기본으로 세팅되어야 되는 값들을 Render.create로 세팅해준다. 그이후 Runner.create()를 선언해 Engine, Render의 업데이트 loop를 관리 할수 있도록 생성한후 Render.run(render), Runner.run(runner, engine)를 세팅하여 렌더 그리고 엔진을 실행할수 있도록 한다.
 useEffect(() => {
      const canvas = canvasRef.current;
      
      initScene();
       function initScene() {
      engine = Engine.create();

      render = Render.create({
        canvas: canvas,
        engine: engine,
        options: {
          width: cw,
          height: ch,
          wireframes: false,
        },
      });
      runner = Runner.create();

      Render.run(render);
      Runner.run(runner, engine);
    }
      
 },[])
  1. 위와 같이 생성한후 요소들이 원안에서만 애니메이션 할수 있도록 경계 도형을 만들어야 된다. 아래 사진과 같이 다각형의 한변의 길이를 구할수 있는 공식을 r tanθ/2 2 로 계산할수 있다.

  1. 위에서 설명한 공식을 적용하는 로직은 아래와 같다. segments는 다각형의 변의 개수를 의미한다. for문을 이용하여 변의 개수만큼 도형을 만든다 addRect에서 세팅된 isStatic 옵션은 중력값을 세팅하고 싶지 않을때 세팅해주면 된다.
    function initGround() {
      const segments = 32;
      const deg = (Math.PI * 2) / segments;
      const width = 50;
      const radius = cw / 2 + width / 2;
      const height = radius * Math.tan(deg / 2) * 2;

      for (let i = 0; i < segments; i++) {
        const theta = deg * i;
        const x = radius * Math.cos(theta) + cw / 2;
        const y = radius * Math.sin(theta) + ch / 2;
        addRect(x, y, width, height, { isStatic: true, angle: theta });
      }
    }
  1. 다음은 애니메이션이 적용되는 영역을 지정했으니, 내부의 요소들을 세팅해야된다. 포토샵으로 작업한 이미지의 크기가 250px이기때문에 가로 세로 250으로 지정했으나, addRect의 render 속성내부에서 이미지의 크기를 세팅된 크기보다 0.7사이즈로 세팅을 했기때문에 t1의 초기값에서도 동일하게 적용해줬다. addRect x,y 값은 처음 세팅된 사각형의 한변의 길이 만큼 더한값, 뺀값으로 세팅해서 위치가 겹치지 않도록 한다.
   function initImageBoxes() {
      const scale = 0.7;
      const t1 = { w: 250 * scale, h: 250 * scale };

      addRect(cw / 2, ch / 2, t1.w, t1.h, {
        label: "JS",
        chamfer: { radius: 20 },
        render: { sprite: { texture: IconJS, xScale: scale, yScale: scale } },
      });

      addRect(cw / 2, ch / 2 + t1.h, t1.w, t1.h, {
        label: "StyledComponent",
        chamfer: { radius: 20 },
        render: {
          sprite: { texture: IconStyled, xScale: scale, yScale: scale },
        },
      });
      addRect(cw / 2 - t1.w, ch / 2 + t1.h, t1.w, t1.h, {
        label: "REACT",
        chamfer: { radius: 75 },
        render: {
          sprite: { texture: IconREACT, xScale: scale, yScale: scale },
        },
      });
      addRect(cw / 2, ch / 2 - t1.h, t1.w, t1.h, {
        label: "TypeScript",
        chamfer: { radius: 20 },
        render: {
          sprite: { texture: IconTypeScript, xScale: scale, yScale: scale },
        },
      });
      addRect(cw / 2, ch / 2 - t1.h * 2, t1.w, t1.h, {
        label: "ReduxToolkit",
        chamfer: { radius: 20 },
        render: {
          sprite: { texture: IconRedux, xScale: scale, yScale: scale },
        },
      });
    }
    
  
    }
  1. 다음으로 원안의 요소들이 중력값에 따라 움직여야 되기 때문에 중력값 세팅을 한다. Matter.Events api를 이용하여 세팅할수 있다. 요소들을 감싸는 원의 중력 x, y 값은 이전에 계산해왔던 공식과 마찬가지로 x = r sinθ, y= r sinθ가 되고, gravityDeg를 1씩 올려주면서 이 값들에 곱하면 원 안에서 요소들이 돌게 된다.
  const garvityPower = 0.5;
  
    Events.on(runner, "tick", () => {
      gravityDeg += 1;
      engine.world.gravity.x =
        garvityPower * Math.cos((Math.PI / 180) * gravityDeg);
      engine.world.gravity.y =
        garvityPower * Math.sin((Math.PI / 180) * gravityDeg);
    });

참고 문헌
21개 프로젝트로 완성하는 인터랙티브 웹 개발 with Three.js & Canvas

0개의 댓글

관련 채용 정보