Communitygithub.com

dembsky/PropMotion

Claude Code 스킬: SwiftUI 앱에서 하나의 3D 객체가 연기하도록 만듭니다. SceneKit 무대, 구체화된 안무, 수제 물리, 실제 그림자.

PropMotion란 무엇인가요?

PropMotion은 SwiftUI 앱 내에서 하나의 3D 객체가 마치 연기하듯 움직이도록 설계된 Claude Code 스킬입니다. 투명한 SceneKit 무대 위에서 제품 액터의 등장, 퇴장, 던지기, 그림자, 햅틱 등 실제 제품 출시를 위한 프로덕션 패턴을 제공합니다. SceneKit은 RealityKit과 비교하면 새로운 앱에서 무거운 3D 요구 사항을 처리하기엔 부적합하지만, SwiftUI 내에서 장식적인 3D 액터를 빠르게 구현하는 데는 여전히 가장 빠른 경로입니다. 이 스킬은 키프레임 애니메이션을 기반으로 하는 무대 구축, 조명, 그림자, 물리 시뮬레이션을 다루며, Metal 파이프라인 컴파일 히치, 그림자 렌더링 이슈, 모션 재현의 함정 등 개발 시간을 크게 소모하는 문제들을 해결하는 방법을 제시합니다. 또한 제스처, 햅틱, 시퀀싱, 카메라 안무, 인스턴싱 등 다양한 참조 문서를 포함하고 있어, 하나의 3D 객체가 등장하는 모든 종류의 SwiftUI 앱에서 활용할 수 있습니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add dembsky/PropMotion

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

PropMotion은(는) 무엇을 하나요?

SwiftUI 앱에서 하나의 3D 객체가 연기하도록 만듭니다. 특정하고 일반적인 작업을 위한 프로덕션 패턴: 투명한 SceneKit 무대 위에서의 세련된 제품 액터 - 등장, 퇴장, 던지기, 그림자, 햅틱, 그리고 며칠을 소모하는 조용한 함정들.

언제 SceneKit을 사용할 것인가

프레임워크의 위치에 대해 솔직해집시다:

  • SceneKit은 유지보수 모드에 있습니다. 새로운 앱에 대규모 3D 요구 사항(에셋 파이프라인, USD/USDZ, AR, 대규모 월드)이 있다면 RealityKit을 선호하세요.
  • SceneKit은 여전히 SwiftUI 앱 내에서 장식적인 3D 액터를 구현하는 가장 빠른 경로입니다: 투명한 SCNView는 모든 SwiftUI 레이아웃 위에 합성되며, 지오메트리 셰이더 수정자는 단일 MSL 문자열이고, CoreAnimation 상호 운용성은 성숙했으며, 여기의 모든 것은 세션 설정 없이 일반 UIKit 뷰에서 실행됩니다.
  • 3D 요소가 안무된 동작을 가진 하나의 히어로 객체라면 이 스킬의 레시피가 직접 적용됩니다. 완전한 상호 작용형 세계라면 RealityKit을 먼저 고려하고 중단하세요.

핵심 무대 레시피

무대는 다음과 같습니다: 투명한 SCNView, 씬 그래프를 소유하는 @MainActor 코디네이터, 서 있는 눈높이의 카메라, 세 개의 조명 리그에 전용 그림자 조명, 접촉 블롭, 그림자 포수. 액터는 구워진 키프레임 애니메이션을 통해 연기합니다.

struct ProductStage: UIViewRepresentable {
    let item: Item

    // 키는 씬이 시각적으로 변경되어야 할 때만 변경되어야 합니다.
    private var stateKey: String { "\(item.id)" }

    func makeCoordinator() -> Coordinator { Coordinator() }

    func makeUIView(context: Context) -> SCNView {
        let view = SCNView()
        view.backgroundColor = .clear   // 무대가 SwiftUI 위에 합성됩니다
        view.isOpaque = false
        view.antialiasingMode = .multisampling4X
        view.scene = context.coordinator.buildScene()
        view.pointOfView = context.coordinator.cameraNode
        context.coordinator.install(item)
        context.coordinator.markState(stateKey)
        // 첫 프레임 웜업, 두 키 점화: 셰이더를 중요 경로 밖에서 컴파일하고,
        // 액터를 무대 밖에 대기시키며, 첫 등장을 최소 지연 시간과 prepare의 완료
        // 모두에 게이트합니다.
        context.coordinator.parkOffstage()
        if let scene = view.scene {
            view.prepare([scene]) { _ in
                DispatchQueue.main.async { context.coordinator.markPipelinesWarm() }
            }
        }
        context.coordinator.scheduleFirstEntrance()
        return view
    }

    func updateUIView(_ view: SCNView, context: Context) {
        // 상태 키 Diff: SwiftUI 재렌더링은 절대 등장을 재생하지 않아야 합니다.
        guard context.coordinator.stateKey != stateKey else { return }
        context.coordinator.markState(stateKey)
        context.coordinator.transition(to: item)
    }
}

구축 순서대로 조각들:

  1. 투명 뷰 플래그 (backgroundColor = .clear, isOpaque = false), MSAA 4x. 호스트 화면이 배경을 제공하며 씬에는 박스가 없습니다.
  2. 코디네이터가 노드 계층을 소유하며, 모션 관심사(이동, 상승, 요, 회전)당 하나의 노드로 분해되어 독립적인 애니메이션이 단일 변환을 두고 싸우지 않습니다.
  3. 카메라: projectionDirection = .horizontal로 액터의 크기가 무대 너비를 따르도록 합니다. 약간 내려다보는 각도로 올려진 위치는 서 있는 관찰자처럼 읽힙니다. 정적으로 유지하세요; 움직이는 카메라는 인위적으로 읽힙니다.
  4. 조명: 따뜻한 키, 차가운 낮은 필, 하드 림, 바닥 값으로만 앰비언트, 그리고 액터의 뒤-위에서 향하는 별도의 그림자 투사 방향광으로 보이는 조각을 방해하지 않습니다.
  5. 지면 접촉: 액터 아래의 부드러운 어두운 그라데이션 평면(접촉 블롭)과 실제 그림자를 위한 보이지 않는 .shadowOnly 포수 평면.
  6. 반사 재질은 사진이 아닌 작은 프로그래밍 방식 환경 맵을 샘플링합니다. 카메라 뒤 영역은 어둡게 유지하세요.
  7. 모든 안무는 구워진 CAKeyframeAnimation이며, 세대 토큰으로 보호되어 새로운 비트가 보류 중인 작업을 무시할 수 있습니다.

전체 세부 사항은 코드와 함께: references/stage-recipe.md

가장 많은 시간을 소모하는 함정

함정수정
지연 그림자는 MSAA가 켜져 있을 때 절대 렌더링되지 않으며 콘솔 오류도 없습니다shadowMode = .forward와 .shadowOnly 포수를 사용하세요. 누락된 그림자를 디버그하려면 먼저 씬에 보이는 회색 램버트 바닥을 제공하세요.
새 SCNView의 첫 프레임은 등장 애니메이션 도중에 Metal 파이프라인을 컴파일합니다백그라운드에서 prepare([scene])를 실행하고, 액터를 무대 밖에 대기시키며, 첫 등장을 지연시키세요. 절대 첫 프레임에 애니메이션을 시작하지 마세요.
fillMode = .forwards + isRemovedOnCompletion = false는 프레젠테이션을 고정하며, 제거는 객체별로 이루어집니다모든 진입점에서 호출되는, 노드, 모든 자식 지오메트리, 조명, 실행 중인 액션을 정리하는 하나의 중앙 clearAnimations를 사용하세요.
정면의 실제 금속은 회색 또는 검은 구멍으로 렌더링됩니다정면에서 본 거울은 카메라 뒤의 환경 영역을 반사합니다. 승인된 페인트 베이스를 유지하고 자체 반사 맵으로 얇은 추가 레이어를 추가하세요.
castsShadow를 토글하면 한 프레임에 흐릿한 반음영이 나타납니다. shadowBias와 조명의 categoryBitMask는 전방 방향 그림자에 대해 무시됩니다castsShadow를 영구적으로 켜두고 동기화된 모션과 함께 shadowColor 알파를 애니메이트하세요.
고정된 웜업 지연은 차가운 기기와 시뮬레이터에서 여전히 히칩니다. 파티클 효과의 첫 프레임은 자체 파이프라인을 컴파일하고 정확히 발사될 때 드롭됩니다두 키 점화: 등장을 최소 지연 시간 AND prepare의 완료 핸들러에 게이트하세요. 실제 효과의 플래그와 일치하는 투명도가 0인 버스트로 파티클 파이프라인을 웜업하세요.
기본 UIGraphicsImageRenderer 포맷은 화면 배율을 상속하여 생성된 모든 텍스처를 픽셀에서 9배로 부풀립니다 - 실시간으로 재렌더링되는 텍스처(키 입력당 조각)에 치명적입니다렌더러 포맷의 배율을 1로 고정하고 캔버스 크기를 액터의 화면 투영에 맞추세요. 다중 입력 리타겟을 업데이트 패스당 하나의 렌더로 병합하세요.
사진에서 실제 객체의 동작을 재현하면 확신에 찬 리그가 만들어지지만 옆으로 실패합니다 - 각 수정은 새로운 오류를 드러냅니다스틸은 포즈를 전달하지만 경로나 메커니즘은 전달하지 않습니다. 최종 포즈 맞춤이 궤적을 결정하지 않습니다. 경로를 모델링하세요: 직접 포즈 컨트롤이 있는 보정 리그, 소유자가 물리적 객체에 대한 키프레임을 작성합니다.
키프레임 모션이 키프레임에서 리드미컬하게 딸꾹질하며 모든 렌더링 및 타이밍 수정에서 살아남습니다딸꾹질은 곡선에 구워져 있습니다: 균일하지 않은 간격의 키프레임에 있는 균일 Catmull-Rom 기저는 모든 매듭에서 속도를 밟습니다. 거리 기반 위상에 대해 스팬 가중 Hermite 탄젠트(또는 자연 3차)로 보간하고 수치 연속성 검사에 게이트하세요.
더 큰 모델에서 잘라낸 서브메시는 오류 없이 전체 객체인 것처럼 측정됩니다잘라내기는 인덱스 버퍼만 트리밍했습니다. 버텍스 버퍼는 여전히 원본의 모든 버텍스를 보유합니다. 서브메시 인덱스가 참조하는 버텍스만 측정하세요.
scene.lightingEnvironment에 할당된 정사각형 이미지는 조용히 무시됩니다 - 0 반사, 모든 거울 재질이 검은색으로 렌더링되며 콘솔 출력이 없습니다인식된 큐브 맵 레이아웃으로 환경 맵을 칠하세요. 가장 쉬운 것은 2:1 구면 캔버스(1024x512)입니다. material.reflective만 정사각형 구면 맵을 허용합니다.

참조 맵

  • references/stage-recipe.md: 전체 무대, 뷰 설정, 코디네이터, 노드 계층, 카메라, 조명 리그, 접촉 블롭 및 포수, 프로그래밍 방식 환경 맵 및 텍스처, 무대 위의 충격 파티클, 무대 액터의 셰이더 수정자(선형 공간 균일 함정), 무대의 SwiftUI 식별성 유지(재마운트 및 업데이트 폭풍 함정).
  • references/first-frame-and-warmup.md: 첫 드로우에서의 Metal 파이프라인 컴파일 히치와 두 가지 치료법, 지연된 등장을 통한 웜업(prapare 완료에 게이트된 두 키 점화로 업그레이드), 또는 마운트된 웜 씬을 유지하고 리타겟팅하기 파티클 파이프라인 웜업; 싸인포스트와 온디바이스 히치 추적으로 치료법 증명하기.
  • references/shadows-and-lights.md: 모든 조용한 그림자 함정, 신뢰할 수 있는 forward + shadowOnly 조합, 그림자 가시성 애니메이팅, 중립 조명 예산, 정면 금속.
  • references/baked-animation.md: 구워진 키프레임이 타이머보다 나은 이유, 굽기 전에 다중 위상 비트를 설계하기 위한 큐 시트, 이음새 분류(C1 vs 접촉 임펄스), 무게 설계, 고밀도 샘플링, 정리 부기, 세대 토큰, 애니메이션 도중 노드 훔치기, 미끄러짐 없이 구르기, 바닥 경로를 따라 구르기(조향, 화면 공간 스테이징, 디버그 트레일), 변환을 넘어선 채널(모퍼 가중치, 렌즈 값, 셰이더 유니폼), 저작 재료로서의 스프링, 하나의 속성 하나의 소유자.
  • references/motion-from-reference.md: 실제 객체의 동작 재현 - 스틸과 비디오가 알려줄 수 있는 것과 없는 것, 메커니즘 대신 경로 모델링, 보정 리그(소유자가 액터를 포즈하고 키프레임 저장), 손으로 저장한 포즈의 거리 매개변수화 및 스팬 가중 보간, 균일 Catmull-Rom 함정, 수치 연속성 게이트, 현재 위상에서 나가기를 사용한 매끄러운 코사인 상태 루프, 트리밍된 서브메시 측정.
  • references/physics-without-engine.md: 키프레임으로 구워진 수제 고정 스텝 통합자, 일반 숫자로서의 벽과 바닥, 접촉 기반 햅틱, 림 피벗 전복, 그리고 이것이 안무된 장면에서 SCNPhysics보다 나은 이유.
  • references/gestures-and-haptics.md: 히트 테스트 없이 팬-투-그랩, 잡고 있는 동안 소프트 클램프, 릴리스 속도, 충격 햅틱, 라이브 손가락에서 살아남는 스크립트된 비트(바운드 재시도, 토큰 폴, 숨겨진 액터 제스처 가드, 스틸이 홀드 계약을 닫음), 분류법으로서의 Reduce Motion, 보이지 않는 무대에 대한 VoiceOver 접근, SwiftUI 제스처와 공존.
  • references/sequencing-stages.md: 하나의 영화로서 여러 무대를 감독 - 데이터로서의 비트를 가진 마스터 클록, 벽 시간 드리프트 함정, 퇴장이 지워진 후 모션에서 컷, 웜업을 위한 다음 무대 사전 마운팅, 자체 컷보다 오래 사는 무대(남아 있는 연기), 사용자 상호 작용에 대한 타임라인 재설정, 녹화 리드, 프레임별로 컷 확인.
  • references/modeling-actors.md: 기본 요소로 믿을 수 있는 히어로 객체 구축 - 눈대중 전의 실제 세계 비율, 오목한 면을 위한 고리, 튜브-플러스-토러스 실루엣, 방사형 패턴 가독성, 오목한 면을 위한 인스턴스당 기울기, 열린 간격, 릴리프 피처를 지오메트리로(절대 페인트로 하지 않음), 어두운 무대에서의 새틴 메탈 알베도, 파일로 도착하는 액터 게이트.
  • references/camera-choreography.md: 연기자로서의 카메라 - 오르빗 리그, 구운 공개 비행, 포커스가 돌리를 타고 감, 관성과 집-홈이 있는 드래그-오르빗, 접지로서의 SCNFloor 반사, 다운라이트 아래의 매트 스테이징, 추적 리그를 위한 제약 조건(그리고 왜 그것들이 액터에 절대 가지 않는지).
  • references/instancing-and-swarms.md: 한 번에 수십 명의 액터 - 무리를 위한 하나의 지오메트리, 물리 대신 슬롯 기반 파일, 착륙 지점에서 역으로 해결된 포물선, 휴식 포즈로 블렌딩된 텀블, 시드된 무작위성, 스케줄링에 의한 지연, 예산 노트.
  • references/multi-actor-physics.md: 하나의 구운 시뮬레이션에서 여러 액터 - 하나의 시계에 N 상태 벡터, 쌍별 충돌(분리 후 접근 시 교환), 세계를 얼리는 잡기, 손가락이 선택한 액터 히트 테스트, 스쿼시 및 햅틱을 위한 액터별 접촉 목록.

리뷰 체크리스트

무대를 출시하기 전에 다음을 확인하세요:

  • SCNView에서 backgroundColor = .clear 및 isOpaque = false; 무대가 보이는 박스나 이음새 없이 합성됩니다.
  • 뷰의 첫 프레임에 애니메이션이 시작되지 않습니다; prepare가 실행되고 첫 등장이 지연되거나 씬이 마운트된 웜 상태로 유지됩니다.
  • 그림자는 .shadowOnly 포수와 함께 forward 모드를 사용합니다. deferred 그림자, shadowBias, 또는 조명 categoryBitMask 게이팅에 의존하지 않습니다.
  • 알파 텍스처 평면(레이블, 데칼)은 castsShadow = false입니다. 그렇지 않으면 정사각형 그림자를 드리웁니다.
  • 그림자 가시성 변경은 shadowColor 알파를 램핑합니다. castsShadow는 런타임에 절대 토글되지 않습니다.
  • 씬이 텍스처 색상을 정확하게 렌더링해야 하는 경우, 앰비언트 더하기 방향광 곱하기 cos(입사)가 평평한 표면에 대해 중립(1000)이어야 합니다. 클리핑된 밝은 회색이 없습니다.
  • lightingEnvironment(또는 background)의 모든 이미지는 인식된 큐브 맵 레이아웃을 사용합니다 - 2:1 구면, 6:1 또는 1:6 스트립, 또는 여섯 개의 이미지; 거기에 정사각형 이미지는 조용히 무시되고 반사 재질은 검은색으로 렌더링됩니다.
  • 모든 비동기 연속(지연된 등장, 예약된 햅틱, 연결된 전환)은 세대 토큰을 캡처하고 행동하기 전에 다시 확인합니다.
  • 애니메이션 노드를 훔치는 모든 코드는 removeAllAnimations를 호출하기 전에 .presentation 값을 모델에 스냅샷합니다.
  • 구운 애니메이션은 addAnimation 전에 모델을 최종 포즈로 설정합니다. 어떤 것도 정리 스위프 없이 fillMode = .forwards에 의존하지 않습니다.
  • 다중 위상 비트에는 포즈 함수 위에 위상-경계 구조체와 일치하는 큐 시트 주석이 있습니다. 위상 및 큐 시간은 해당 하나의 구조체에만 있으며, 샘플 루프나 예약된 햅틱에 리터럴로 절대 존재하지 않습니다. 모든 이음새는 분류됩니다: 설계 이음새는 C1이고, 임펄스 이음새는 접촉 이벤트에서만 발생하며 각각 큐를 착지시킵니다.
  • 구르는 객체는 스핀을 거리 나누기 반지름에 종속시킵니다. 어떤 것도 미끄러지지 않습니다.
  • 실제 객체에서 복사된 모션은 보정 리그(직접 포즈 컨트롤, 저장된 포즈, 소유자의 눈)를 통해 키프레임으로 저작되며, 비율을 확인하기 위해 물리적 객체로 측정됩니다. 영상 리버싱이나 인상적인 맞춤으로 경로를 대체하지 않습니다.
  • 뷰가 SwiftUI 업데이트 폭풍(빠른 재렌더링) 동안 재마운트되면 보류 중인 모든 입자, 햅틱, 비동기를 취소하고 새 상태로 즉시 열립니다. 코디네이터의 stateKey가 동일한 업데이트 패스의 중복 updateUIView 호출에 대해 항상 가드를 제공합니다.

references/ 디렉토리 아래의 참조 파일은 각 주제에 대한 전체 세부 사항과 코드를 포함합니다.

관련 스킬