mcpField
A
axiom-spritekit-ref

axiom-spritekit-ref

github.com/charleswiltgen开发1.2k154 次安装今天更新
代码仓库

关于 axiom-spritekit-ref

axiom-spritekit-ref 为 SpriteKit 开发者提供 API 参考,按节点、物理、动作、纹理、场景、粒子、着色器和 SwiftUI 集成组织内容,并包含性能提示与调试选项。

主要功能

  1. 01整理 SpriteKit 节点类型与属性
  2. 02说明物理体、场和关节
  3. 03列出动作及组合模式
  4. 04涵盖纹理、图集与约束
  5. 05介绍场景设置和调试叠加层
  6. 06包含粒子、着色器和 SwiftUI

适用场景

  1. 01查询 SpriteKit 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允许透明背景(速度较慢)
调试选项
选项显示内容
.showsFPS每秒帧数
.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 观察其变化。避免在视图主体中重新创建场景——使用 @State 持久保存场景实例。


资源

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

文档:/spritekit/skspritenode、/spritekit/skphysicsbody、/spritekit/skaction、/spritekit/skemitternode、/spritekit/skrenderer

技能:axiom-spritekit、axiom-spritekit-diag

评论

支持 Markdown