Files
2nd/10_Wiki/Topics/Coding/Game_Skia_Native_2D.md
T
2026-05-09 21:08:02 +09:00

6.3 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
game-skia-native-2d Skia / Native 2D — Mobile / Cross-platform 그림 Coding draft B conceptual 2026-05-09 2026-05-09
game
skia
2d
mobile
vibe-coding
language applicable_to
TS / Dart
Mobile
Frontend
Skia
react-native-skia
CanvasKit
Flutter
Path
paint
fast 2D

Skia / Native 2D

Google Skia = Chrome/Flutter/Android/Firefox 의 그림 엔진. GPU-accelerated 2D. RN Skia / CanvasKit / Flutter Canvas 가 wrapper.

📖 핵심 개념

  • Path: 선 + 곡선.
  • Paint: 색 + style.
  • Canvas: drawable surface.
  • Shader / image filter: Path 위 효과.

💻 코드 패턴

React Native Skia

yarn add @shopify/react-native-skia
import { Canvas, Circle, Path, Skia, useClockValue, useComputedValue, useValue } from '@shopify/react-native-skia';

function App() {
  const clock = useClockValue();
  const cx = useComputedValue(() => 100 + Math.sin(clock.current / 500) * 50, [clock]);
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Circle cx={cx} cy={100} r={40} color="hotpink" />
    </Canvas>
  );
}

Path (복잡 도형)

const path = Skia.Path.Make();
path.moveTo(50, 50);
path.lineTo(150, 50);
path.quadTo(200, 100, 150, 150);
path.close();

<Path path={path} color="lightblue" style="fill" />
<Path path={path} color="darkblue" style="stroke" strokeWidth={2} />

Gradient

import { LinearGradient, vec } from '@shopify/react-native-skia';

<Rect x={0} y={0} width={200} height={200}>
  <LinearGradient
    start={vec(0, 0)}
    end={vec(200, 200)}
    colors={['#00ffff', '#ff00ff']}
  />
</Rect>

Image filter (blur, etc)

import { Blur, ColorMatrix } from '@shopify/react-native-skia';

<Image image={img} x={0} y={0} width={300} height={300}>
  <Blur blur={10} />
  <ColorMatrix matrix={[
    1, 0, 0, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 0, 0, 0.5, 0,  // 50% alpha
  ]} />
</Image>

Shader (GPU)

import { Shader, Skia } from '@shopify/react-native-skia';

const source = Skia.RuntimeEffect.Make(`
  uniform float2 iResolution;
  uniform float iTime;
  
  half4 main(float2 fragCoord) {
    float2 uv = fragCoord / iResolution;
    return half4(uv.x, uv.y, sin(iTime), 1.0);
  }
`);

const clock = useClockValue();
const uniforms = useComputedValue(() => ({
  iResolution: [width, height],
  iTime: clock.current / 1000,
}), [clock]);

<Canvas>
  <Fill>
    <Shader source={source!} uniforms={uniforms} />
  </Fill>
</Canvas>

→ Web 의 Three.js shader 같은 power.

Animation (declarative)

import { useSpring, withTiming, withRepeat, withSequence } from '@shopify/react-native-skia';

const x = useSharedValue(0);
useEffect(() => {
  x.value = withRepeat(
    withSequence(
      withTiming(200, { duration: 1000 }),
      withTiming(0, { duration: 1000 }),
    ),
    -1
  );
}, []);

<Circle cx={x} cy={100} r={20} color="red" />

→ Reanimated 통합 — UI thread 60fps.

Text

import { Text, useFont } from '@shopify/react-native-skia';

const font = useFont(require('./Roboto.ttf'), 24);
if (!font) return null;

<Text x={50} y={100} text="Hello" font={font} color="white" />

CanvasKit (web)

import CanvasKitInit from 'canvaskit-wasm';

const CanvasKit = await CanvasKitInit({ locateFile: f => `/${f}` });
const surface = CanvasKit.MakeWebGLCanvasSurface('canvas')!;
const canvas = surface.getCanvas();

const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.Color4f(0.9, 0.5, 0.2, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Fill);

canvas.drawCircle(100, 100, 50, paint);
surface.flush();

→ Web 에서 Skia 그대로 (Flutter web 이 사용).

Flutter Canvas

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;
    
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
    
    final path = Path()
      ..moveTo(0, 0)
      ..quadraticBezierTo(100, 200, 200, 0);
    canvas.drawPath(path, paint);
  }
  
  @override
  bool shouldRepaint(covariant CustomPainter old) => false;
}

CustomPaint(painter: MyPainter(), size: Size.infinite);

Game-like usage (RN Skia)

function Game() {
  const clock = useClockValue();
  const playerY = useComputedValue(() => 200 + Math.sin(clock.current / 500) * 50, [clock]);
  
  // Bullets — array of values
  const bullets = useMemo(() => Array.from({ length: 20 }, () => ({ x: 0, y: 0 })), []);
  
  return (
    <Canvas style={{ flex: 1, backgroundColor: 'black' }}>
      <Circle cx={100} cy={playerY} r={20} color="green" />
      {bullets.map((b, i) => (
        <Circle key={i} cx={b.x} cy={b.y} r={5} color="red" />
      ))}
    </Canvas>
  );
}

Performance

  • Path / shape 재사용 (useMemo).
  • Reanimated 의 SharedValue 가 UI thread.
  • useComputedValue 가 derive.
  • 큰 image = Image component + cache.
  • 너무 많은 element = Canvas 가 한 번에 그림.

Web Canvas API (대안)

const ctx = canvas.getContext('2d')!;
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 100);
ctx.beginPath();
ctx.arc(150, 150, 50, 0, Math.PI * 2);
ctx.fill();

→ Skia 만큼 강력 X. 단순 OK.

vs WebGL (Three.js)

2D = Skia / Canvas API
3D = WebGL / WebGPU / Three.js

Skia 도 GPU 가속 — Canvas API 보다 빠름 (canvaskit).

🤔 의사결정 기준

상황 추천
RN custom drawing RN Skia
Flutter Canvas / CustomPainter
Web 2D Canvas API (간단) / CanvasKit (강력)
Game UI Skia / 자체 canvas
차트 Recharts / Visx (위 문서)
매우 simple shape SVG

안티패턴

  • 매 frame Path 새로: useMemo / 외부.
  • JS thread 에 animation: reanimated SharedValue.
  • 큰 Bitmap 매번 decode: cache.
  • 너무 많은 Element (1000+): 한 Canvas 안 묶기.
  • Shader 매 frame compile: useMemo.
  • CanvasKit web bundle 인지: 큰 (~3MB). Lazy.

🤖 LLM 활용 힌트

  • RN = RN Skia (modern).
  • Flutter = built-in Canvas.
  • Web 2D = Canvas API 보통 충분.
  • Reanimated 통합으로 60fps.

🔗 관련 문서