복셀 애니메이션은(는) 무엇을 하나요?
블록 조립 게임처럼 보이는 장면을, 실제 게임 애셋 없이 만듭니다. 모든 텍스처는 런타임에 코드로 생성되며 캐릭터는 독창적인 디자인입니다. 출력물은 하나의 독립적인 HTML 페이지(대화형, 공유 가능)이며, 요청 시 같은 페이지에서 프레임 정확한 비디오로 렌더링됩니다.
이 스킬의 파일
assets/voxel-kit.js: 키트(텍스처, 재질, 앱 루프, 궤도 카메라, 낮/밤 하늘, 섬 지형, 스튜디오 플랫폼, 캐릭터 릭, 파티클, 팝인, 폭발, 횃불, 라우팅 헬퍼, 레코더, 캡처 모드). 장면을 작성하기 전에 반드시 읽으세요: 아래 API 참조는 요약이며, 파일이 진실입니다.assets/template.html: 키트 마커, 섬, 캐릭터 및 작은 타임라인이 있는 시작 페이지.examples/house-builder.html: 건축가가 집을 블록 하나씩 짓고, 환호하고, 폭발하고, 반복합니다.examples/melting-crystal.html: 물리 예제(6×6×6 얼음 격자의 Lindemann 용융, 복셀 온도계, 웅덩이, 재결빙). 과학 설명을 위해 이 구조를 복사하세요.scripts/inline_kit.py: 페이지의<!-- VOXEL_KIT -->를 키트로 대체하여 하나의 파일로 만듭니다.scripts/render.py: PNG 프레임 및.mov/.mp4/.webm으로 프레임 정확한 렌더링.
이 SKILL.md만 사용 가능하고 폴더가 없는 경우, 빌드 전에 https://github.com/normix93/voxel-animations 에서 전체 스킬을 받으세요.
워크플로
- 아이디어를 한 줄로 정리: 무엇을 만들고, 변환하거나 설명하며, 루프가 어떻게 끝나는지(리셋, 폭발, 정지, 카메라 비행 아웃) 결정합니다. 대화형 페이지, 편집용 비디오(투명 배경 여부), 세로 숏(1080×1920) 등 목적지를 결정합니다.
- 타임라인으로 스토리보드 작성: 3~6개의 비트와 지속 시간(예:
0–2초 빈 부지 → 2–40초 건설 → 40–44초 환호 → 44–49초 폭발 → 반복). 각 비트는 하나의app.onUpdate상태 머신에서mode입니다(집 예제 참조). assets/template.html에서 시작하여 키트 마커를 유지하고 SCENE 섹션을 작성합니다. 키트 조각을 재사용하고 다시 작성하지 마세요. 모든 것을 한 페이지에 유지합니다.- 페이지 빌드:
python3 scripts/inline_kit.py scene.src.html scene.html. - 확인: 페이지를 열거나(또는
render.py로 몇 프레임을 폴더에 렌더링하고 확인)합니다. 블록이 떠 있거나 겹치는지, 캐릭터가 벽을 통과하는지, 카메라가 나무에 걸리는지, 처음 몇 초가 비어 있는지, 투명 블록이 깜빡이는지 확인합니다. - 전달: 환경이 지원할 때 페이지를 아티팩트로 게시하고 .html 파일도 함께 제공합니다. 요청 시 비디오를 렌더링합니다(내보내기 참조).
스타일 규칙
- 독창성만. 기존 게임의 텍스처, 사운드, 로고, 몹, 캐릭터 또는 이름을 절대 사용하지 마세요. 출력물을 "Minecraft"라고 부르지 마세요: 복셀, 블록, 블록 월드라고 말하세요. 키트의 절차적 텍스처와 안전모 건축가는 안전하게 사용 및 수정할 수 있습니다.
- 픽셀 선명도:
NearestFilter가 있는 16×16 텍스처(키트가 처리). 강한 조명, 선명한 그림자, 재질당 적은 색상, 작은 노이즈. - 움직임에 무게감: 블록은 오버슈트(
app.pop)로 팝인, 잔해는 중력(≈20–22 blocks/s²)과 감쇠 바운스 사용, 캐릭터는 ~5 blocks/s로 걷고 부드럽게 회전합니다. 물리적 동작에 선형 트윈을 피하세요. - 한눈에 읽기 쉬움: 중앙에 하나의 주요 주제, 카메라는 천천히 궤도 회전(
autoSpeed0.05–0.1), 배경은 차분함(하늘 또는studio색상). - 텍스트: 페이지에는 제목과 상태 줄이 있을 수 있습니다. 비디오의 경우
?clean(UI 없음)으로 렌더링합니다. 비디오에 레이블이 필요한 경우 매우 짧은 단어를 사용하고, 설명은 내레이션이나 페이지에 속하며 프레임에 있으면 안 됩니다.
키트 API (요약)
const app = VK.createApp({
title, subtitle, hint, // UI 텍스트 (?clean 또는 키 H로 숨김)
sky: 'day' | 'night' | 'studio', // studio = 평평한 색상(studioColor), 안개 없음
cycle: false, dayLength: 48, tod: .27, // 시간 0..1 (.25 일출 쪽, .75 밤)
camera: { radius, phi, theta, target: [x,y,z], auto: true, autoSpeed: .07 },
speed: 1, seed: 7, fileName: 'my-scene', shadowSize: 28,
});
app.M.grass | dirt | stone | sand | planks | roof | log | leaves | glass | water | ice | snow | cobble | brick | lava
VK.solid(0xff8800, { glow, opacity, noise, border }) // 색상 픽셀 블록 재질
VK.block(app, 'planks' | material, x, y, z, { pop: true }) // 단일 메시 (이동/팝)
VK.instanced(app.scene, material, [[x,y,z], ...]) // 정적 블록, 한 번의 드로우 콜
const world = VK.island(app, { seed, radius: 18, flatRadius: 7.5, trees: 10, water: -1, snowLine })
// → world.groundTopAt(x, z), world.hAt(ix, iz)
VK.platform(app, { size: 16, top: .5, mat: 'stone', topMat: 'cobble' }) // 스튜디오 바닥
const hero = VK.character(app, { ground: world.groundTopAt, position: [x,0,z], yaw, colors: { shirt:[r,g,b], hat: 0xf5c518 | null, ... } })
hero.goTo([[x,z], ...]) · hero.arrived · hero.face(x, z, dt) · hero.jump(v)
hero.pose = 'idle' | 'reach' (set hero.reach 0..1) | 'cheer' | 'panic' | 'wave'
hero.lookTarget = {x,y,z} | null
VK.standOutside(x, z, { halfX, halfZ, cx, cz }) // 상자 블록 작업을 위해 서 있을 위치
VK.routeAround(from, to, { halfX, halfZ, cx, cz }) // 상자 주위를 걷는 경유지
VK.explode(app, meshes, { center, power, ground, onDone })
VK.torch(app, x, y, z) // 밤에 불빛이 강해짐
app.puff(position, material, n, power) · app.pop(mesh) · app.clearParticles()
app.onUpdate((dt, time) => { ... }) // dt는 이미 speed에 따라 스케일링됨; 캡처 모드에서 결정적
app.rand() // 시드 기반 난수: Math.random 대신 사용
app.status(html) · app.button({ label, key, toggle, on, onClick }) · app.standardButtons({ cycle })
app.start()
장면을 올바르게 유지하는 규칙:
app.rand()(시드 기반)를 사용하고Math.random()은 절대 사용하지 마세요. 그래야 렌더링이 반복 가능합니다.- 모든 움직임을
dt를 사용하는app.onUpdate에 넣고, 애니메이션에setTimeout/setInterval을 절대 사용하지 마세요(캡처 모드가 깨집니다). - 블록은 1×1×1이며 정수 좌표에 중심이 있습니다. 높이
h인 기둥의 상단 표면은h + 0.5에 있습니다. 캐릭터는groundTopAt(x, z)에 섭니다. - 정적 배경 →
VK.instanced; 움직이거나, 팝하거나 폭발하는 모든 것 →VK.block. - 대량 카운트: 움직이는 메시를 약 1500개 미만으로 유지; 그 이상은 InstancedMesh로 애니메이션 처리합니다.
레시피
- 빌드 시퀀스: 순서가 있는 계획
[{type,x,y,z}](층별로, 둘레를 따라 걷기; 지붕은 지그재그 줄)를 만듭니다. 각 항목에 대해:standOutside로 경로 지정,pose='reach'설정, 리치의 약 45%에서 스폰, 다음.examples/house-builder.html참조. - 블록으로 물리 설명: 수량을 보이는 것으로 매핑합니다. 온도 → 떨림 진폭 ∝ √T (등분배); 압력 → 블록 충돌; 에너지 레벨 → 탑의 층; 장 → 블록 화살표; 엔트로피 → 순서화된 벽 재섞기; 중력 → 지지되지 않을 때 넘어지는 더미. 화면의 숫자 대신 복셀 게이지(
melting-crystal.html의 온도계)로 제어 변수를 보여줍니다. - 변환:
mesh.material변경(물 ↔ 얼음, 돌 → 용암),scale.y로 평평하게, 두 위치 사이의 이징 lerp로 슬라이드. - 원자 / 분자: 입자용
VK.solid(색상)큐브, 전자나 광자용glow: true, 작은 큐브(mesh.scale.setScalar(.3))는 궤적용. - 반복되는 엔딩: 폭발(
VK.explode) → 리셋, 또는 정지 + 느린 카메라, 또는 횃불과 함께 밤이 내림(app.sky.cycle = true).
내보내기
URL 매개변수(키트로 빌드된 모든 페이지에서 작동):
?clean UI 숨김 · ?alpha 투명 배경(하늘, 안개 없음) · ?seed=3 ·
?speed=2 · ?night · ?capture&fps=30 렌더러가 사용하는 프레임 정확 모드.
키: H UI 숨김 · Space 일시 정지 · V 빠른 실시간 WebM 녹화(로컬 파일에서 페이지를 열 때 작동; 호스팅된 미리보기는 다운로드를 차단할 수 있음).
프레임 정확 비디오(편집에 선호):
pip install playwright && python3 -m playwright install chromium # 한 번만, ffmpeg도 필요
python3 scripts/render.py scene.html --seconds 12 --fps 30 --out scene.mov # ProRes 422 HQ
python3 scripts/render.py scene.html --seconds 12 --alpha --out overlay.mov # 알파 포함 ProRes 4444
python3 scripts/render.py scene.html --size 1080x1920 --warmup 3 --out short.mp4 # 세로 H.264
python3 scripts/render.py scene.html --seconds 5 --out frames/ # PNG 프레임만
--warmup은 녹화 전 장면 초를 건너뜁니다; --params "seed=3&night"는 URL 매개변수를 추가합니다; --headed는 보이는 브라우저를 사용합니다(실제 GPU, 대부분의 노트북에서 더 빠름). --alpha를 사용하면 카메라는 계속 궤도 회전합니다; 정적 오버레이를 위해 camera: { auto: false }로 설정하세요.
전달 전 확인
- 페이지가 콘솔 오류 없이 열리고, 첫 프레임에 이미 주제가 보입니다.
- 루프가 최소 두 번 작동합니다(리셋이 메시, 조명 및 파티클을 지웁니다).
- 캐릭터가 절대 벽을 통과하지 않으며, 의도하지 않은 한 아무것도 떠 있지 않습니다.
- 비디오의 경우:
?clean으로 렌더링, 올바른 크기/fps, 요청 시 알파 채널이 확인되었는지 확인. - 코드, 제목, 파일 이름 어디에도 타사 게임 이름, 텍스처 또는 캐릭터가 없습니다.