mcpField
A
axiom-spritekit-ref

axiom-spritekit-ref

github.com/charleswiltgen開発1.2k154 回インストール今日更新
リポジトリ

axiom-spritekit-ref について

axiom-spritekit-refは、SpriteKit開発者向けのAPIリファレンスです。ノード、物理、アクション、テクスチャ、シーン、パーティクル、シェーダー、SwiftUI連携を整理し、性能上の注意点やデバッグ機能も扱います。

主な機能

  1. 01SpriteKitのノード型とプロパティの一覧
  2. 02物理ボディ、フィールド、ジョイントの解説
  3. 03アクションと組み合わせ方の一覧
  4. 04テクスチャ、アトラス、制約の解説
  5. 05シーン設定とデバッグ表示の説明
  6. 06パーティクル、シェーダー、SwiftUIの項目

活用シーン

  1. 01SpriteKitのAPIやプロパティの確認
  2. 02物理ボディの作成方法の選択
  3. 03アクションの種類と組み合わせの確認
  4. 04シーン描画や診断機能の設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-spritekit-ref

インストール後、Claude Code で/axiom-spritekit-refを入力して読み込まれたか確認します。

詳細

SpriteKit API リファレンス

カテゴリ別にまとめた SpriteKit の完全な API リファレンスです。

このリファレンスの使用場面

次のような場合に、このリファレンスを使用してください。

  • 特定の SpriteKit API のシグネチャやプロパティを調べる
  • 利用可能なノードの種類と、それぞれのパフォーマンス特性を確認する
  • 適切な物理ボディの作成方法を探す
  • アクションカタログ全体を確認する
  • SKView、スケールモード、トランジションを設定する
  • パーティクルエミッターのプロパティを設定する
  • SKRenderer または SKShader を使用する

パート 1:ノード階層

すべてのノードタイプ
ノード用途バッチ処理パフォーマンスに関する注意
SKNodeコンテナ、グループ化該当なし描画コストなし
SKSpriteNodeテクスチャ付きスプライトはい(同じアトラス)ゲームプレイで主要なノード
SKShapeNodeベクターパスいいえそれぞれに 1 回のドローコール — ゲームプレイでは避けてください
SKLabelNodeテキスト描画いいえそれぞれに 1 回のドローコール
SKEmitterNodeパーティクルシステム該当なしGPU 負荷が高いため、発生率を制限してください
SKCameraNodeビューポート制御該当なしHUD を子としてアタッチしてください
SKEffectNodeCore Image フィルターいいえ高コストです — shouldRasterize でキャッシュしてください
SKCropNodeマスキングいいえマスクとコンテンツで 2 回以上のドローコール
SKTileMapNodeタイルベースのマップはい(同じタイルセット)大きなマップに効率的です
SKVideoNode動画再生いいえAVPlayer を使用します
SK3DNodeSceneKit コンテンツいいえSceneKit シーンを描画します
SKReferenceNode再利用可能な .sks ファイル該当なし実行時にアーカイブを読み込みます
SKLightNodeピクセル単位のライティング該当なし制限:シーンあたり 8 個のライト
SKFieldNode物理フィールド該当なし重力、電場、磁場など
SKAudioNode位置オーディオ該当なしAVAudioEngine を使用します
SKTransformNode3D 回転用ラッパー該当なし視点のための xRotation、yRotation
SKSpriteNode のプロパティ
// Creation
SKSpriteNode(imageNamed: "player")           // From asset catalog
SKSpriteNode(texture: texture)                // From SKTexture
SKSpriteNode(texture: texture, size: size)    // Custom size
SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50))  // Solid color

// Key properties
sprite.anchorPoint = CGPoint(x: 0.5, y: 0)   // Bottom-center
sprite.colorBlendFactor = 0.5                  // Tint strength (0-1)
sprite.color = .red                            // Tint color
sprite.normalTexture = normalMap               // For lighting
sprite.lightingBitMask = 0x1                   // Which lights affect this
sprite.shadowCastBitMask = 0x1                 // Which lights cast shadows
sprite.shader = customShader                   // Per-pixel effects
SKLabelNode のプロパティ
let label = SKLabelNode(text: "Score: 0")
label.fontName = "AvenirNext-Bold"
label.fontSize = 24
label.fontColor = .white
label.horizontalAlignmentMode = .left
label.verticalAlignmentMode = .top
label.numberOfLines = 0          // Multi-line (iOS 11+)
label.preferredMaxLayoutWidth = 200
label.lineBreakMode = .byWordWrapping

パート 2:物理 API

SKPhysicsBody の作成
// Volume bodies (have mass, respond to forces)
SKPhysicsBody(circleOfRadius: 20)                    // Cheapest
SKPhysicsBody(rectangleOf: CGSize(width: 40, height: 60))
SKPhysicsBody(polygonFrom: path)                     // Convex only
SKPhysicsBody(texture: texture, size: size)          // Pixel-perfect (expensive)
SKPhysicsBody(texture: texture, alphaThreshold: 0.5, size: size)
SKPhysicsBody(bodies: [body1, body2])                // Compound

// Edge bodies (massless boundaries)
SKPhysicsBody(edgeLoopFrom: rect)                    // Rectangle boundary
SKPhysicsBody(edgeLoopFrom: path)                    // Path boundary
SKPhysicsBody(edgeFrom: pointA, to: pointB)          // Single edge
SKPhysicsBody(edgeChainFrom: path)                   // Open path
物理ボディのプロパティ
// Identity
body.categoryBitMask = 0x1          // What this body IS
body.collisionBitMask = 0x2         // What it bounces off
body.contactTestBitMask = 0x4       // What triggers didBegin/didEnd

// Physical characteristics
body.mass = 1.0                     // kg
body.density = 1.0                  // kg/m^2 (auto-calculates mass)
body.friction = 0.2                 // 0.0 (ice) to 1.0 (rubber)
body.restitution = 0.3              // 0.0 (no bounce) to 1.0 (perfect bounce)
body.linearDamping = 0.1            // Air resistance (0 = none)
body.angularDamping = 0.1           // Rotational damping

// Behavior
body.isDynamic = true               // Responds to forces
body.affectedByGravity = true       // Subject to world gravity
body.allowsRotation = true          // Can rotate from physics
body.pinned = false                 // Pinned to parent position
body.usesPreciseCollisionDetection = false  // For fast objects

// Motion (read/write)
body.velocity = CGVector(dx: 100, dy: 0)
body.angularVelocity = 0.0

// Force application
body.applyForce(CGVector(dx: 0, dy: 100))           // Continuous
body.applyImpulse(CGVector(dx: 0, dy: 50))          // Instant
body.applyTorque(0.5)                                 // Continuous rotation
body.applyAngularImpulse(1.0)                         // Instant rotation
body.applyForce(CGVector(dx: 10, dy: 0), at: point)  // Force at point
SKPhysicsWorld
scene.physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
scene.physicsWorld.speed = 1.0        // 0 = paused, 2 = double speed
scene.physicsWorld.contactDelegate = self

// Ray casting
let body = scene.physicsWorld.body(at: point)
let bodyInRect = scene.physicsWorld.body(in: rect)
scene.physicsWorld.enumerateBodies(alongRayStart: start, end: end) { body, point, normal, stop in
    // Process each body the ray intersects
}
物理ジョイント
// Pin joint (pivot)
let pin = SKPhysicsJointPin.joint(
    withBodyA: bodyA, bodyB: bodyB,
    anchor: anchorPoint
)

// Fixed joint (rigid connection)
let fixed = SKPhysicsJointFixed.joint(
    withBodyA: bodyA, bodyB: bodyB,
    anchor: anchorPoint
)

// Spring joint
let spring = SKPhysicsJointSpring.joint(
    withBodyA: bodyA, bodyB: bodyB,
    anchorA: pointA, anchorB: pointB
)
spring.frequency = 1.0    // Oscillations per second
spring.damping = 0.5       // 0 = no damping

// Sliding joint (linear constraint)
let slide = SKPhysicsJointSliding.joint(
    withBodyA: bodyA, bodyB: bodyB,
    anchor: point, axis: CGVector(dx: 1, dy: 0)
)

// Limit joint (distance constraint)
let limit = SKPhysicsJointLimit.joint(
    withBodyA: bodyA, bodyB: bodyB,
    anchorA: pointA, anchorB: pointB
)

// Add joint to world
scene.physicsWorld.add(joint)
// Remove: scene.physicsWorld.remove(joint)
物理フィールド
// Gravity (directional)
let gravity = SKFieldNode.linearGravityField(withVector: vector_float3(0, -9.8, 0))

// Radial gravity (toward/away from point)
let radial = SKFieldNode.radialGravityField()
radial.strength = 5.0

// Electric field (charge-dependent)
let electric = SKFieldNode.electricField()

// Noise field (turbulence)
let noise = SKFieldNode.noiseField(withSmoothness: 0.5, animationSpeed: 1.0)

// Vortex
let vortex = SKFieldNode.vortexField()

// Drag
let drag = SKFieldNode.dragField()

// All fields share:
field.region = SKRegion(radius: 100)     // Area of effect
field.strength = 1.0                      // Intensity
field.falloff = 0.0                       // Distance falloff
field.minimumRadius = 10                  // Inner dead zone
field.isEnabled = true
field.categoryBitMask = 0xFFFFFFFF        // Which bodies affected

パート 3:アクションカタログ

移動
SKAction.move(to: point, duration: 1.0)
SKAction.move(by: CGVector(dx: 100, dy: 0), duration: 0.5)
SKAction.moveTo(x: 200, duration: 1.0)
SKAction.moveTo(y: 300, duration: 1.0)
SKAction.moveBy(x: 50, y: 0, duration: 0.5)
SKAction.follow(path, asOffset: true, orientToPath: true, duration: 2.0)
回転
SKAction.rotate(byAngle: .pi, duration: 1.0)        // Relative
SKAction.rotate(toAngle: .pi / 2, duration: 0.5)    // Absolute
SKAction.rotate(toAngle: angle, duration: 0.5, shortestUnitArc: true)
スケーリング
SKAction.scale(to: 2.0, duration: 0.5)
SKAction.scale(by: 1.5, duration: 0.3)
SKAction.scaleX(to: 2.0, y: 1.0, duration: 0.5)
SKAction.resize(toWidth: 100, height: 50, duration: 0.5)
フェード
SKAction.fadeIn(withDuration: 0.5)
SKAction.fadeOut(withDuration: 0.5)
SKAction.fadeAlpha(to: 0.5, duration: 0.3)
SKAction.fadeAlpha(by: -0.2, duration: 0.3)
合成
SKAction.sequence([action1, action2, action3])       // Sequential
SKAction.group([action1, action2])                    // Parallel
SKAction.repeat(action, count: 5)                     // Finite repeat
SKAction.repeatForever(action)                         // Infinite
action.reversed()                                      // Reverse
SKAction.wait(forDuration: 1.0)                       // Delay
SKAction.wait(forDuration: 1.0, withRange: 0.5)      // Random delay
テクスチャと色
SKAction.setTexture(texture)
SKAction.setTexture(texture, resize: true)
SKAction.animate(with: [tex1, tex2, tex3], timePerFrame: 0.1)
SKAction.animate(with: textures, timePerFrame: 0.1, resize: false, restore: true)
SKAction.colorize(with: .red, colorBlendFactor: 1.0, duration: 0.5)
SKAction.colorize(withColorBlendFactor: 0, duration: 0.5)
サウンド
SKAction.playSoundFileNamed("explosion.wav", waitForCompletion: false)
ノードツリー
SKAction.removeFromParent()
SKAction.run(block)
SKAction.run(block, queue: .main)
SKAction.customAction(withDuration: 1.0) { node, elapsed in
    // Custom per-frame logic
}
物理
SKAction.applyForce(CGVector(dx: 0, dy: 100), duration: 0.5)
SKAction.applyImpulse(CGVector(dx: 50, dy: 0), duration: 1.0/60.0)  // ~1 frame
SKAction.applyTorque(0.5, duration: 1.0)
SKAction.changeCharge(to: 1.0, duration: 0.5)
SKAction.changeMass(to: 2.0, duration: 0.5)
タイミングモード
action.timingMode = .linear          // Constant speed
action.timingMode = .easeIn          // Slow → fast
action.timingMode = .easeOut         // Fast → slow
action.timingMode = .easeInEaseOut   // Slow → fast → slow

action.speed = 2.0                   // 2x speed

パート 4:テクスチャとアトラス

SKTexture
// From image
let tex = SKTexture(imageNamed: "player")

// From atlas
let atlas = SKTextureAtlas(named: "Characters")
let tex = atlas.textureNamed("player_run_1")

// Subrectangle (for manual sprite sheets)
let sub = SKTexture(rect: CGRect(x: 0, y: 0, width: 0.25, height: 0.5), in: sheetTexture)

// From CGImage
let tex = SKTexture(cgImage: cgImage)

// Filtering
tex.filteringMode = .nearest    // Pixel art (no smoothing)
tex.filteringMode = .linear     // Smooth scaling (default)

// Preload
SKTexture.preload([tex1, tex2]) { /* Ready */ }
SKTextureAtlas
// Create in Xcode: Assets.xcassets → New Sprite Atlas
// Or .atlas folder in project bundle

let atlas = SKTextureAtlas(named: "Characters")
let textureNames = atlas.textureNames  // All texture names in atlas

// Preload entire atlas
atlas.preload { /* Atlas ready */ }

// Preload multiple atlases
SKTextureAtlas.preloadTextureAtlases([atlas1, atlas2]) { /* All ready */ }

// Animation from atlas
let frames = (1...8).map { atlas.textureNamed("run_\($0)") }
let animate = SKAction.animate(with: frames, timePerFrame: 0.1)

パート 5:制約

// Orient toward another node
let orient = SKConstraint.orient(to: targetNode, offset: SKRange(constantValue: 0))

// Orient toward a point
let orient = SKConstraint.orient(to: point, offset: SKRange(constantValue: 0))

// Position constraint (keep X in range)
let xRange = SKConstraint.positionX(SKRange(lowerLimit: 0, upperLimit: 400))

// Position constraint (keep Y in range)
let yRange = SKConstraint.positionY(SKRange(lowerLimit: 50, upperLimit: 750))

// Distance constraint (stay within range of node)
let dist = SKConstraint.distance(SKRange(lowerLimit: 50, upperLimit: 200), to: targetNode)

// Rotation constraint
let rot = SKConstraint.zRotation(SKRange(lowerLimit: -.pi/4, upperLimit: .pi/4))

// Apply constraints (processed in order)
node.constraints = [orient, xRange, yRange]

// Toggle
node.constraints?.first?.isEnabled = false
SKRange
SKRange(constantValue: 100)                    // Exactly 100
SKRange(lowerLimit: 50, upperLimit: 200)       // 50...200
SKRange(lowerLimit: 0)                          // >= 0
SKRange(upperLimit: 500)                        // <= 500
SKRange(value: 100, variance: 20)              // 80...120

パート 6:シーンの設定

SKView の設定
let skView = SKView(frame: view.bounds)

// Debug overlays
skView.showsFPS = true
skView.showsNodeCount = true
skView.showsDrawCount = true
skView.showsPhysics = true
skView.showsFields = true
skView.showsQuadCount = true

// Performance
skView.ignoresSiblingOrder = true        // Enables batching optimizations
skView.shouldCullNonVisibleNodes = true  // Auto-hide offscreen (manual is faster)
skView.isAsynchronous = true             // Default: renders asynchronously
skView.allowsTransparency = false        // Opaque is faster

// Frame rate
skView.preferredFramesPerSecond = 60     // Or 120 for ProMotion

// Present scene
skView.presentScene(scene)
skView.presentScene(scene, transition: .fade(withDuration: 0.5))
スケールモードの一覧
モードアスペクト比コンテンツ最適な用途
.aspectFill維持ビュー全体に表示し、端をクロップほとんどのゲーム
.aspectFit維持ビュー内に収め、レターボックスを表示正確なレイアウトが必要な場合
.resizeFill歪む引き伸ばして全体を埋めるほとんど使いません
.fill変動ビューに合わせてシーンをリサイズアダプティブなシーン
SKTransition の種類
SKTransition.fade(withDuration: 0.5)
SKTransition.fade(with: .black, duration: 0.5)
SKTransition.crossFade(withDuration: 0.5)
SKTransition.flipHorizontal(withDuration: 0.5)
SKTransition.flipVertical(withDuration: 0.5)
SKTransition.reveal(with: .left, duration: 0.5)
SKTransition.moveIn(with: .right, duration: 0.5)
SKTransition.push(with: .up, duration: 0.5)
SKTransition.doorway(withDuration: 0.5)
SKTransition.doorsOpenHorizontal(withDuration: 0.5)
SKTransition.doorsOpenVertical(withDuration: 0.5)
SKTransition.doorsCloseHorizontal(withDuration: 0.5)
SKTransition.doorsCloseVertical(withDuration: 0.5)
// Custom with CIFilter:
SKTransition(ciFilter: filter, duration: 0.5)

パート 7:パーティクル

SKEmitterNode の主なプロパティ
let emitter = SKEmitterNode(fileNamed: "Spark")!

// Emission control
emitter.particleBirthRate = 100          // Particles per second
emitter.numParticlesToEmit = 0           // 0 = infinite
emitter.particleLifetime = 2.0           // Seconds
emitter.particleLifetimeRange = 0.5      // ± random

// Position
emitter.particlePosition = .zero
emitter.particlePositionRange = CGVector(dx: 10, dy: 10)

// Movement
emitter.emissionAngle = .pi / 2         // Direction (radians)
emitter.emissionAngleRange = .pi / 4    // Spread
emitter.particleSpeed = 100              // Points per second
emitter.particleSpeedRange = 50          // ± random
emitter.xAcceleration = 0
emitter.yAcceleration = -100             // Gravity-like

// Appearance
emitter.particleTexture = SKTexture(imageNamed: "spark")
emitter.particleSize = CGSize(width: 8, height: 8)
emitter.particleColor = .white
emitter.particleColorAlphaSpeed = -0.5   // Fade out
emitter.particleBlendMode = .add         // Additive for fire/glow
emitter.particleAlpha = 1.0
emitter.particleAlphaSpeed = -0.5

// Scale
emitter.particleScale = 1.0
emitter.particleScaleRange = 0.5
emitter.particleScaleSpeed = -0.3        // Shrink over time

// Rotation
emitter.particleRotation = 0
emitter.particleRotationSpeed = 2.0

// Target node (for trails)
emitter.targetNode = scene               // Particles stay in world space

// Render order
emitter.particleRenderOrder = .dontCare  // .oldestFirst, .oldestLast, .dontCare

// Physics field interaction
emitter.fieldBitMask = 0x1
よく使われるパーティクルプリセット
エフェクト主な設定
炎blendMode: .add、速い alphaSpeed、オレンジから赤への色、上向きの速度
煙blendMode: .alpha、遅い速度、灰色、時間とともにスケールを大きくする
火花blendMode: .add、高い速度と範囲、短い寿命、小さいサイズ
雨下向きの emissionAngle、狭い範囲、長い寿命、薄いテクスチャ
雪遅い下向きの速度、広い位置範囲、わずかな x 加速度
軌跡targetNode をシーンに設定し、狭い放出角度、中程度の寿命
爆発高い発生率、短い numParticlesToEmit、広い速度範囲

パート 8:SKRenderer とシェーダー

SKRenderer(Metal との統合)
import MetalKit

let device = MTLCreateSystemDefaultDevice()!
let renderer = SKRenderer(device: device)
renderer.scene = gameScene
renderer.ignoresSiblingOrder = true

// In Metal render loop:
func draw(in view: MTKView) {
    guard let commandBuffer = commandQueue.makeCommandBuffer(),
          let rpd = view.currentRenderPassDescriptor else { return }

    renderer.update(atTime: CACurrentMediaTime())
    renderer.render(
        withViewport: CGRect(origin: .zero, size: view.drawableSize),
        commandBuffer: commandBuffer,
        renderPassDescriptor: rpd
    )

    commandBuffer.present(view.currentDrawable!)
    commandBuffer.commit()
}
SKShader(カスタム GLSL ES エフェクト)
// Fragment shader for per-pixel effects
let shader = SKShader(source: """
    void main() {
        vec4 color = texture2D(u_texture, v_tex_coord);
        // Desaturate
        float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
        gl_FragColor = vec4(vec3(gray), color.a) * v_color_mix.a;
    }
""")

sprite.shader = shader

// With uniforms
let shader = SKShader(source: """
    void main() {
        vec4 color = texture2D(u_texture, v_tex_coord);
        color.rgb *= u_intensity;
        gl_FragColor = color;
    }
""")
shader.uniforms = [
    SKUniform(name: "u_intensity", float: 0.8)
]

// Built-in uniforms:
// u_texture     — sprite texture
// u_time        — elapsed time
// u_path_length — shape node path length
// v_tex_coord   — texture coordinate
// v_color_mix   — color/alpha mix
// SKAttribute for per-node values

パート 7:SwiftUI との統合

SpriteView
import SpriteKit
import SwiftUI

// Basic embedding
struct GameView: View {
    var body: some View {
        SpriteView(scene: makeScene())
            .ignoresSafeArea()
    }

    func makeScene() -> SKScene {
        let scene = GameScene(size: CGSize(width: 1024, height: 768))
        scene.scaleMode = .aspectFill
        return scene
    }
}

// With options
SpriteView(
    scene: scene,
    transition: .fade(withDuration: 0.5),       // Scene transition
    isPaused: false,                              // Pause control
    preferredFramesPerSecond: 60,                 // Frame rate
    options: [.ignoresSiblingOrder, .shouldCullNonVisibleNodes],
    debugOptions: [.showsFPS, .showsNodeCount]    // Debug overlays
)
SpriteView のオプション
オプション目的
.ignoresSiblingOrder描画順のバッチ処理最適化を有効にします
.shouldCullNonVisibleNodes画面外のノードを自動的に非表示にします
.allowsTransparency透明な背景を許可します (処理が遅くなります)
デバッグオプション
オプション表示内容
.showsFPS1 秒あたりのフレーム数
.showsNodeCount表示中のノードの総数
.showsDrawCountフレームごとの描画呼び出し回数
.showsPhysics物理ボディの輪郭
.showsFields物理フィールドの領域
.showsQuadCountクワッドの分割数
SwiftUI と SpriteKit 間の通信
// Observable model shared between SwiftUI and scene
@Observable
class GameState {
    var score = 0
    var isPaused = false
    var lives = 3
}

// Scene reads/writes the shared model
class GameScene: SKScene {
    var gameState: GameState?

    override func update(_ currentTime: TimeInterval) {
        guard let state = gameState, !state.isPaused else { return }
        // Game logic updates state.score, state.lives, etc.
    }
}

// SwiftUI view owns the model
struct GameContainerView: View {
    @State private var gameState = GameState()
    @State private var scene: GameScene = {
        let s = GameScene(size: CGSize(width: 1024, height: 768))
        s.scaleMode = .aspectFill
        return s
    }()

    var body: some View {
        VStack {
            Text("Score: \(gameState.score)")
            SpriteView(scene: scene, isPaused: gameState.isPaused)
                .ignoresSafeArea()
        }
        .onAppear { scene.gameState = gameState }
    }
}

重要なパターン: @Observable モデルをブリッジとして使用します。シーンがそれを変更し、SwiftUI が変更を監視します。ビューの body 内でシーンを再作成することは避けてください。シーンインスタンスを保持するには @State を使用します。


リソース

WWDC: 2014-608, 2016-610, 2017-609

ドキュメント: /spritekit/skspritenode, /spritekit/skphysicsbody, /spritekit/skaction, /spritekit/skemitternode, /spritekit/skrenderer

スキル: axiom-spritekit, axiom-spritekit-diag

コメント