본문으로 건너뛰기
cccskills
무료GitHub에 공개

scenekit

Maintain and extend existing SceneKit 3D scenes and visualizations. Use when working with SCNView, SCNScene, SCNNode scene graphs, SceneKit geometry/materials/lights/cameras, SCNAction animation, SCNPhysicsBody physics, SCNParticleSystem effects, .scn/.dae/.abc SceneKit assets, shader modifiers, or SwiftUI SceneView. SceneKit is soft-deprecated and in maintenance mode; route new apps, significant updates, USD/USDZ pipelines, and migration planning toward RealityKit.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(3)

  • SKILL.md15.7 KB
  • evals/evals.json3.4 KB
  • references/scenekit-patterns.md18.5 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

SceneKit

Maintain existing SceneKit scenes only. Apple deprecated SceneKit at WWDC 2025 and limits it to maintenance; route new projects, major modernization, and USD/USDZ pipelines to RealityKit. Existing apps continue to work.

Contents

Scene Setup

SCNView in UIKit

import SceneKit

let sceneView = SCNView(frame: view.bounds)
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = .black
view.addSubview(sceneView)

allowsCameraControl adds built-in orbit, pan, and zoom gestures. Typically disabled in production where custom camera control is needed.

Creating an SCNScene

let scene = SCNScene()                                          // Empty
guard let scene = SCNScene(named: "art.scnassets/ship.scn")     // .scn in .scnassets
    else { fatalError("Missing scene asset") }
let url = Bundle.main.url(forResource: "ship", withExtension: "dae")!
let scene = try SCNScene(url: url, options: [.checkConsistency: true])

Nodes and Geometry

Every scene has a rootNode. All content exists as descendant nodes. Nodes define position, orientation, and scale in their parent's coordinate system. SceneKit uses a right-handed coordinate system: +X right, +Y up, +Z toward the camera.

let parentNode = SCNNode()
scene.rootNode.addChildNode(parentNode)

let childNode = SCNNode()
childNode.position = SCNVector3(0, 1, 0)  // 1 unit above parent
parentNode.addChildNode(childNode)

Transforms

node.position = SCNVector3(x: 0, y: 2, z: -5)
node.eulerAngles = SCNVector3(x: 0, y: .pi / 4, z: 0)  // 45-degree Y rotation
node.scale = SCNVector3(2, 2, 2)
node.simdPosition = SIMD3<Float>(0, 2, -5)  // Prefer simd for performance

Built-in Primitives

SCNBox, SCNSphere, SCNCylinder, SCNCone, SCNTorus, SCNCapsule, SCNTube, SCNPlane, SCNFloor, SCNText, SCNShape (extruded Bezier path).

let node = SCNNode(geometry: SCNSphere(radius: 0.5))

Finding Nodes

let maxNode = scene.rootNode.childNode(withName: "Max", recursively: true)
let enemies = scene.rootNode.childNodes { node, _ in
    node.name?.hasPrefix("enemy") == true
}

Materials

SCNMaterial defines surface appearance. Use firstMaterial for single-material geometries or the materials array for multi-material.

Color and Texture

let material = SCNMaterial()
material.diffuse.contents = UIColor.systemBlue     // Solid color
material.diffuse.contents = UIImage(named: "brick") // Texture
material.normal.contents = UIImage(named: "brick_normal")
sphere.firstMaterial = material

Physically Based Rendering (PBR)

let pbr = SCNMaterial()
pbr.lightingModel = .physicallyBased
pbr.diffuse.contents = UIImage(named: "albedo")
pbr.metalness.contents = 0.8       // Scalar or texture
pbr.roughness.contents = 0.2       // Scalar or texture
pbr.normal.contents = UIImage(named: "normal")
pbr.ambientOcclusion.contents = UIImage(named: "ao")

Lighting Models

.physicallyBased (metalness/roughness), .blinn (default), .phong, .lambert (diffuse-only), .constant (unlit), .shadowOnly.

Each material property is an SCNMaterialProperty accepting UIColor, UIImage, CGFloat scalar, SKTexture, CALayer, or AVPlayer.

Transparency

material.transparency = 0.5
material.transparencyMode = .dualLayer
material.isDoubleSided = true

Lighting

Attach an SCNLight to a node. The light's direction follows the node's negative Z-axis.

Light Types

// Ambient: uniform, no direction
let ambient = SCNLight()
ambient.type = .ambient
ambient.color = UIColor(white: 0.3, alpha: 1)

// Directional: parallel rays (sunlight)
let directional = SCNLight()
directional.type = .directional
directional.castsShadow = true

// Omni: point light, all directions
let omni = SCNLight()
omni.type = .omni
omni.attenuationEndDistance = 20

// Spot: cone-shaped
let spot = SCNLight()
spot.type = .spot
spot.spotInnerAngle = 20
spot.spotOuterAngle = 60

Attach to a node:

let lightNode = SCNNode()
lightNode.light = directional
lightNode.eulerAngles = SCNVector3(-Float.pi / 3, 0, 0)
lightNode.position = SCNVector3(0, 10, 10)
scene.rootNode.addChildNode(lightNode)

Shadows

light.castsShadow = true
light.shadowMapSize = CGSize(width: 2048, height: 2048)
light.shadowSampleCount = 8
light.shadowRadius = 3.0
light.shadowColor = UIColor(white: 0, alpha: 0.5)

Category Bit Masks

light.categoryBitMask = 1 << 1     // Category 2
node.categoryBitMask = 1 << 1      // Only lit by category-2 lights

SceneKit renders a maximum of 8 lights per node. Use attenuationEndDistance on point/spot lights so SceneKit skips them for distant nodes.

Cameras

Attach an SCNCamera to a node to define a viewpoint.

let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
cameraNode.look(at: SCNVector3Zero)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode

Configuration

camera.fieldOfView = 60                        // Degrees
camera.zNear = 0.1
camera.zFar = 500
camera.automaticallyAdjustsZRange = true

// Orthographic
camera.usesOrthographicProjection = true
camera.orthographicScale = 10

Depth-of-field (wantsDepthOfField, focusDistance, fStop) and HDR effects (wantsHDR, bloomIntensity, bloomThreshold, screenSpaceAmbientOcclusionIntensity) are configured directly on SCNCamera.

Animation

SceneKit provides three animation approaches.

SCNAction (Declarative, Game-Oriented)

Reusable, composable animation objects attached to nodes.

let move = SCNAction.move(by: SCNVector3(0, 2, 0), duration: 1)
let rotate = SCNAction.rotateBy(x: 0, y: .pi, z: 0, duration: 1)
node.runAction(.group([move, rotate]))

// Sequential
node.runAction(.sequence([.fadeOut(duration: 0.3), .removeFromParentNode()]))

// Infinite loop
let pulse = SCNAction.sequence([
    .scale(to: 1.2, duration: 0.5),
    .scale(to: 1.0, duration: 0.5)
])
node.runAction(.repeatForever(pulse))

SCNTransaction (Implicit Animation)

SCNTransaction.begin()
SCNTransaction.animationDuration = 1.0
node.position = SCNVector3(5, 0, 0)
node.opacity = 0.5
SCNTransaction.completionBlock = { print("Done") }
SCNTransaction.commit()

Explicit Animations (Core Animation)

let animation = CABasicAnimation(keyPath: "rotation")
animation.toValue = NSValue(scnVector4: SCNVector4(0, 1, 0, Float.pi * 2))
animation.duration = 2
animation.repeatCount = .infinity
node.addAnimation(animation, forKey: "spin")

Physics

Physics Bodies

node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)   // Forces + collisions
floor.physicsBody = SCNPhysicsBody(type: .static, shape: nil)    // Immovable
platform.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil) // Code-driven

When shape is nil, SceneKit derives it from geometry. For performance, use simplified shapes:

let shape = SCNPhysicsShape(
    geometry: SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0),
    options: nil
)
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
node.physicsBody?.mass = 2.0
node.physicsBody?.restitution = 0.3

Applying Forces

node.physicsBody?.applyForce(SCNVector3(0, 10, 0), asImpulse: false) // Continuous
node.physicsBody?.applyForce(SCNVector3(0, 5, 0), asImpulse: true)   // Instant
node.physicsBody?.applyTorque(SCNVector4(0, 1, 0, 2), asImpulse: true)

Collision Detection

struct PhysicsCategory {
    static let player:     Int = 1 << 0
    static let enemy:      Int = 1 << 1
    static let ground:     Int = 1 << 2
}

playerNode.physicsBody?.categoryBitMask = PhysicsCategory.player
playerNode.physicsBody?.collisionBitMask = PhysicsCategory.ground | PhysicsCategory.enemy
playerNode.physicsBody?.contactTestBitMask = PhysicsCategory.enemy

scene.physicsWorld.contactDelegate = self

func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
    handleCollision(between: contact.nodeA, and: contact.nodeB)
}

Gravity

scene.physicsWorld.gravity = SCNVector3(0, -9.8, 0)
node.physicsBody?.isAffectedByGravity = false

Particle Systems

SCNParticleSystem creates effects like fire, smoke, rain, and sparks.

let particles = SCNParticleSystem()
particles.birthRate = 100
particles.particleLifeSpan = 2
particles.particleSize = 0.1
particles.particleColor = .orange
particles.emitterShape = SCNSphere(radius: 0.5)
particles.particleVelocity = 2
particles.isAffectedByGravity = true
particles.blendMode = .additive

let emitterNode = SCNNode()
emitterNode.addParticleSystem(particles)
scene.rootNode.addChildNode(emitterNode)

Load from Xcode particle editor with SCNParticleSystem(named: "fire.scnp", inDirectory: nil). Particles can collide with geometry via colliderNodes.

Loading Models

SceneKit's documented scene-source formats are .scn, .dae, and .abc. For bundled assets, place scene files in a .scnassets folder and texture images in asset catalogs so Xcode can optimize them for target devices.

USD/USDZ is the RealityKit migration path, not the default SceneKit loading path. For new projects, significant updates, or SCN-to-USD asset conversion, handoff to the RealityKit skill.

enum SceneAssetError: Error { case missingResource, missingNode(String) }

func loadCheckedScene() throws -> SCNScene {
    guard let url = Bundle.main.url(forResource: "model", withExtension: "dae")
    else { throw SceneAssetError.missingResource }

    let scene = try SCNScene(url: url, options: [.checkConsistency: true])
    guard scene.rootNode.childNode(withName: "mesh", recursively: true) != nil
    else { throw SceneAssetError.missingNode("mesh") }
    return scene
}

Use this as an authoring/import gate: stop on a consistency or required-node failure, fix the source asset or import options, then repeat the same check. For generated .scn files, load Scene Serialization and require both export success and a checked reload before commit.

Use SCNReferenceNode with .onDemand loading policy for large models. For import-time unit conversion, use SCNSceneSource.LoadingOption:

let source = SCNSceneSource(url: url, options: nil)!
let scene = try source.scene(options: [.convertUnitsToMeters: 1.0])

Do not use SCNScene.Attribute.unit or UnitMetersPerUnit. SCNScene.Attribute is metadata only: .startTime, .endTime, .frameRate, and .upAxis.

SwiftUI Integration

SceneView embeds SceneKit in SwiftUI:

import SwiftUI
import SceneKit

struct SceneKitView: View {
    let scene: SCNScene = {
        let scene = SCNScene()
        let sphere = SCNNode(geometry: SCNSphere(radius: 1))
        sphere.geometry?.firstMaterial?.lightingModel = .physicallyBased
        sphere.geometry?.firstMaterial?.diffuse.contents = UIColor.systemBlue
        sphere.geometry?.firstMaterial?.metalness.contents = 0.8
        scene.rootNode.addChildNode(sphere)
        return scene
    }()

    var body: some View {
        SceneView(scene: scene,
                  options: [.allowsCameraControl, .autoenablesDefaultLighting])
    }
}

Options: .allowsCameraControl, .autoenablesDefaultLighting, .jitteringEnabled, .temporalAntialiasingEnabled.

For render loop control, wrap SCNView in UIViewRepresentable with an SCNSceneRendererDelegate coordinator. See references/scenekit-patterns.md.

Common Mistakes

Not adding a camera or lights

// DON'T: Scene renders blank or black -- no camera, no lights
sceneView.scene = scene

// DO: Add camera + lights, or use convenience flags
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
sceneView.autoenablesDefaultLighting = true

Using exact geometry for physics shapes

// DON'T
node.physicsBody = SCNPhysicsBody(type: .dynamic,
    shape: SCNPhysicsShape(geometry: complexMesh, options: nil))

// DO: Simplified primitive
node.physicsBody = SCNPhysicsBody(type: .dynamic,
    shape: SCNPhysicsShape(
        geometry: SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0),
        options: nil))

Modifying transforms on dynamic bodies

// DON'T: Resets physics simulation
dynamicNode.position = SCNVector3(5, 0, 0)

// DO: Use forces/impulses
dynamicNode.physicsBody?.applyForce(SCNVector3(10, 0, 0), asImpulse: true)

Review Checklist

  • Scene has at least one camera node set as pointOfView
  • Scene has appropriate lighting (or autoenablesDefaultLighting for prototyping)
  • Physics shapes use simplified geometry, not full mesh detail
  • contactTestBitMask set for bodies that need collision callbacks
  • SCNPhysicsContactDelegate assigned to scene.physicsWorld.contactDelegate
  • Dynamic body transforms changed via forces/impulses, not direct position
  • Lights limited to 8 per node; attenuationEndDistance set on point/spot lights
  • Materials use .physicallyBased lighting model for realistic rendering
  • SceneKit assets use documented .scn, .dae, or .abc scene-source formats
  • Imported and exported assets pass consistency and required-node checks before commit
  • Bundled SceneKit textures/images use asset catalogs or Xcode-optimized resources
  • Scene metadata/import options use documented API; no invented SCNScene.Attribute.unit
  • New USD/USDZ pipelines or significant updates are routed to RealityKit
  • Game Center authentication, leaderboards, achievements, or multiplayer are handed off to GameKit
  • SCNReferenceNode used for large models to enable lazy loading
  • Particle birthRate and particleLifeSpan balanced to control particle count
  • categoryBitMask used to scope lights and cameras to relevant nodes
  • SwiftUI scenes use SceneView or UIViewRepresentable-wrapped SCNView
  • Deprecation acknowledged; RealityKit evaluated for new projects

References

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Discover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

dpearson2699/swift-ios-skills1.2천2026년 8월 1일 업데이트

Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback, or any time-sensitive information that updates in real time. Also use when working with ActivityKit, ActivityAttributes, Activity lifecycle (request/update/end), Dynamic Island layouts (compact/minimal/expanded), push-to-update Live Activities, or Lock Screen live widgets.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

dpearson2699/swift-ios-skills1.2천2026년 8월 1일 업데이트

Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

dpearson2699/swift-ios-skills1.2천2026년 8월 1일 업데이트

alarmkit

무료

Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, StandBy, and paired Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, system Stop and AlarmButton secondary actions, authorization, state observation, countdown widget-extension handoff, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

dpearson2699/swift-ios-skills1.2천2026년 8월 1일 업데이트

app-clips

무료

Build iOS App Clips with invocation URLs, App Clip Codes, NFC, QR codes, Safari banners, Maps, Messages, target setup, App Store Connect experiences, size/capability constraints, NSUserActivity routing, SKOverlay promotion, App Group/keychain handoff, ephemeral notifications, location confirmation, and full-app migration. Use when creating App Clips or wiring App Clip invocation, experience configuration, or full-app handoff.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

dpearson2699/swift-ios-skills1.2천2026년 8월 1일 업데이트

Implement App Intents for Siri, Shortcuts, Spotlight, widgets, Control Center, and Apple Intelligence on iOS. Covers AppIntent actions, AppEntity and EntityQuery models, AppShortcutsProvider phrases, IndexedEntity Spotlight indexing, WidgetConfigurationIntent, SnippetIntent, and assistant schemas. Use when exposing app actions or entities to system surfaces.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

dpearson2699/swift-ios-skills1.2천2026년 8월 1일 업데이트

dpearson2699의 스킬 모두 보기

이 스킬의 문제 신고