@lemondouble/featheranime2d
v0.1.0-preview.6
Published
그림 한 장으로 만드는, 가벼운 움직이는 애니메이션 캐릭터 렌더러
Maintainers
Readme
@lemondouble/featheranime2d
그림 한 장으로 만드는, 가벼운 움직이는 애니메이션 캐릭터 렌더러입니다. 캐릭터 패키지(.fa2d)를 불러와 표정, 모션, 숨쉬기, 깜빡임, 머리카락 흔들림을 계산하고 PixiJS로 그립니다. 계산은 Rust로 만든 Core를 WASM으로 돌립니다.
설치
pnpm add @lemondouble/featheranime2d pixi.jspixi.js(8.x)는/pixi,/react를 쓸 때 필요합니다.react(19.x)는/react를 쓸 때 필요합니다.- WASM 파일은 번들러(Vite, Next.js 등)가
new URL(..., import.meta.url)을 보고 빌드 결과에 함께 넣습니다. 따로 설정할 것은 없습니다.
React
"use client";
import { useFeatherAnime2D } from "@lemondouble/featheranime2d/react";
import { useEffect } from "react";
export default function CharacterStage({ expression }: { expression?: string }) {
const { containerRef, view, error } = useFeatherAnime2D("/characters/witch.fa2d");
useEffect(() => {
view?.character.setExpression(expression ?? null);
}, [view, expression]);
return <div ref={containerRef} style={{ width: "100%", height: "100%" }} />;
}containerRef를 붙인 요소의 크기에 맞춰, 비율을 유지하며 가운데에 그립니다. 배경은 투명합니다. 화면 밀도(devicePixelRatio)만큼 크게 그리되 2배에서 자릅니다.- 휴대폰처럼 원본보다 훨씬 작게 그리는 곳에서는
useFeatherAnime2D(src, { mipmaps: true })로 밉맵을 켭니다. 가는 선이 깨지지 않는 대신 텍스처 메모리를 약 1/3 더 씁니다. - PixiJS와 WASM은 effect 안에서 불러오므로, Next.js에서 서버 렌더링을 빼는 처리(
dynamic(..., { ssr: false })) 없이 서버 컴포넌트에서 바로 쓸 수 있습니다. - 표정처럼 상태인 것은 위처럼
useEffect로 넣고, 모션처럼 한 번 실행하는 것은view.character.playMotion("wave", "normal")을 직접 부릅니다.
PixiJS
import { CharacterView } from "@lemondouble/featheranime2d/pixi";
const view = await CharacterView.load("/characters/witch.fa2d");
app.stage.addChild(view);
app.ticker.add((ticker) => view.update(ticker.deltaMS / 1000));
view.character.setExpression("happy");- 캔버스 픽셀 좌표로 그리므로 화면 크기에 맞추는 일(
scale,position)은 앱이 합니다. 캔버스 크기는view.info.canvas에 있습니다. - 촘촘한 화면에서 흐려지지 않게 PixiJS
Application을resolution: devicePixelRatio,autoDensity: true로 만듭니다. 작게 그리는 곳이면CharacterView.load(src, { mipmaps: true })로 밉맵을 켭니다. info.backdrop이 색이면 앱이 화면을 그 색으로 칠합니다. 그림 배경은CharacterView가 함께 그립니다.- 부위는 캔버스 영역에서 잘라 냅니다. 캔버스 밖까지 그려 둔 부위(모자, 머리카락 등)를 보여 주려면
view.clipMargin = { left: 1000, top: 1000, right: 1000, bottom: 0 }처럼 넓힐 방향과 크기를 정합니다. 음수면 캔버스 안쪽으로 좁힙니다. - 다 쓰면
view.destroy()로 텍스처와 WASM 메모리를 해제합니다.
Core만 쓰기
import { Character } from "@lemondouble/featheranime2d";
const character = await Character.load(bytes); // .fa2d 파일의 바이트
character.update(1 / 60);
character.vertices; // 모든 slot의 정점 (캔버스 픽셀)
character.slotVisible; // slot마다 보이는지
character.slotTextures; // slot마다 그릴 텍스처 번호 (info.textures 순서)- 다른 그래픽 라이브러리로 그릴 때 씁니다. slot의 정점 수, uv, 삼각형은
character.info.slots에 있습니다. vertices같은 버퍼는 WASM 메모리를 복사 없이 보는 뷰라서, 다음update전까지만 유효합니다.- 시드(
{ seed })를 빼면 무작위 시드를 씁니다. 같은 시드의 캐릭터들은 같은 순간에 깜빡입니다.
API
| 이름 | 설명 |
|---|---|
| setExpression(name \| null) | 표정을 바꿉니다. 즉시 바뀝니다 |
| playMotion(name, "idle" \| "normal" \| "force") | 모션을 재생합니다. 재생 중인 모션보다 우선순위가 높지 않으면 버리고 false를 돌려줍니다 |
| currentExpression, currentMotion | 지금 표정과 재생 중인 모션입니다. 모션이 끝나면 null입니다 |
| setInput(parameter, value) | 얼굴 추적, 마우스 시선 같은 외부 입력입니다. 기본값에서 옮긴 만큼 더합니다 |
| setLipSync(mouthOpen, speaking) | 실제 음성으로 계산한 입 벌림 값입니다 |
| setTalkSimulation(enabled, syllablesPerSecond, loudness) | 음성 없이 말하는 흉내를 냅니다. 켜져 있으면 setLipSync 대신 씁니다 |
| setDriverEnabled(driver, enabled) | 모션, 깜빡임, 표정, 외부 입력, 숨쉬기, 물리, 립싱크를 하나씩 켜고 끕니다 |
캐릭터 패키지를 만드는 방법과 형식은 FeatherAnime2D 레포에 있습니다.
