Learning
레슨 6 / 8·20분

커스텀 렌더링

Canvas API를 이용한 커스텀 렌더링

Matter.js의 기본 Render 모듈 대신 Canvas 2D API로 직접 물리 바디를 그릴 수 있습니다. 이를 통해 스프라이트, 파티클, 트레일 효과 등 기본 렌더러로는 불가능한 비주얼을 구현할 수 있습니다. Engine.update()를 호출하여 물리를 진행하고, requestAnimationFrame 루프에서 바디 위치를 읽어 직접 그립니다.

javascript
const { Engine, World, Bodies, Composite, Body } = Matter;

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;

// 엔진만 생성 (기본 Render 사용 안 함)
const engine = Engine.create({
  gravity: { x: 0, y: 1 },
});

// 바디 생성
const balls = [];
for (let i = 0; i < 10; i++) {
  const ball = Bodies.circle(
    100 + Math.random() * 600,
    50 + Math.random() * 100,
    15 + Math.random() * 15,
    {
      restitution: 0.7,
      friction: 0.05,
      label: 'ball-' + i,
    }
  );
  balls.push(ball);
}

const ground = Bodies.rectangle(400, 590, 800, 20, { isStatic: true });
const wallL = Bodies.rectangle(0, 300, 20, 600, { isStatic: true });
const wallR = Bodies.rectangle(800, 300, 20, 600, { isStatic: true });

World.add(engine.world, [...balls, ground, wallL, wallR]);
javascript
// ── 커스텀 렌더 루프 ──
const colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6'];
const trails = new Map(); // 트레일 효과용 위치 기록

function render() {
  // 물리 엔진 업데이트 (60fps 기준 ~16.67ms)
  Engine.update(engine, 1000 / 60);

  // 캔버스 초기화
  ctx.fillStyle = 'rgba(26, 26, 46, 0.3)'; // 반투명으로 잔상 효과
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // 공 그리기
  balls.forEach((ball, i) => {
    const pos = ball.position;
    const radius = ball.circleRadius;
    const angle = ball.angle;

    // 트레일 기록
    if (!trails.has(ball.id)) trails.set(ball.id, []);
    const trail = trails.get(ball.id);
    trail.push({ x: pos.x, y: pos.y });
    if (trail.length > 15) trail.shift();

    // 트레일 그리기
    ctx.beginPath();
    trail.forEach((p, j) => {
      ctx.globalAlpha = j / trail.length * 0.3;
      ctx.beginPath();
      ctx.arc(p.x, p.y, radius * 0.5, 0, Math.PI * 2);
      ctx.fillStyle = colors[i % colors.length];
      ctx.fill();
    });
    ctx.globalAlpha = 1.0;

    // 메인 공 그리기
    ctx.save();
    ctx.translate(pos.x, pos.y);
    ctx.rotate(angle);

    // 그라데이션 효과
    const gradient = ctx.createRadialGradient(
      -radius * 0.3, -radius * 0.3, radius * 0.1,
      0, 0, radius
    );
    gradient.addColorStop(0, '#ffffff');
    gradient.addColorStop(1, colors[i % colors.length]);

    ctx.beginPath();
    ctx.arc(0, 0, radius, 0, Math.PI * 2);
    ctx.fillStyle = gradient;
    ctx.fill();
    ctx.restore();
  });

  // 바닥 그리기
  ctx.fillStyle = '#2c3e50';
  ctx.fillRect(0, 580, 800, 20);

  requestAnimationFrame(render);
}

render();
  • Engine.update(engine, delta) — Render/Runner 없이 수동으로 물리 진행
  • body.position — 바디의 현재 위치 {x, y}
  • body.angle — 바디의 현재 회전 각도 (라디안)
  • body.circleRadius — 원형 바디의 반지름
  • body.vertices — 다각형 바디의 꼭짓점 배열
  • Canvas 2D API로 그라데이션, 트레일, 파티클 등 커스텀 비주얼 구현
  • requestAnimationFrame + Engine.update 조합으로 렌더 루프 구성
💡

커스텀 렌더링 시 ctx.fillRect에 반투명 색상을 사용하면 이전 프레임이 서서히 사라지는 잔상(trail) 효과를 쉽게 만들 수 있습니다. rgba의 알파값(0.1~0.3)을 조절하여 잔상의 길이를 변경하세요.