swiftship/internal/skills/data/features/game-ui/SKILL.md
Game UI patterns: SwiftUI HUD overlays on SpriteKit, menus (main/pause/game-over), virtual joystick/d-pad, score displays, health bars, tutorial onboarding.
npx skillsauth add abdullah4ai/apple-developer-toolkit game-uiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use SwiftUI overlays on SpriteView — NOT SKLabelNode/SKSpriteNode for HUD:
struct GameView: View {
@State var gameState = GameState()
var body: some View {
ZStack {
SpriteView(scene: scene, isPaused: gameState.isPaused)
.ignoresSafeArea()
// HUD overlay — uses AppTheme
VStack {
HStack {
ScoreView(score: gameState.score)
Spacer()
LivesView(lives: gameState.lives)
}
.padding(.horizontal, AppTheme.Spacing.lg)
.padding(.top, AppTheme.Spacing.md)
Spacer()
}
.allowsHitTesting(false) // Let touches pass through to SpriteView
// Pause/Game Over modals
if gameState.phase == .paused { PauseMenuView(gameState: gameState) }
if gameState.phase == .gameOver { GameOverView(gameState: gameState) }
}
}
}
Rules:
.allowsHitTesting(false) on non-interactive overlays.allowsHitTesting(true)@Observable GameState shared with SKSceneMainMenu → Game (Playing) → Pause → Resume / Quit
→ GameOver → PlayAgain / MainMenu
Implementation:
@Observable
class GameState {
var phase: GamePhase = .menu
enum GamePhase: Equatable {
case menu
case playing
case paused
case gameOver(winner: String)
}
}
// In MainView
switch gameState.phase {
case .menu:
MainMenuView(gameState: gameState)
case .playing, .paused, .gameOver:
GameView(gameState: gameState)
}
struct ScoreView: View {
let score: Int
var body: some View {
Text("\(score)")
.font(AppTheme.Fonts.largeTitle)
.foregroundStyle(AppTheme.Colors.textPrimary)
.contentTransition(.numericText())
.animation(.spring(duration: 0.3), value: score)
}
}
For games needing continuous directional input (platformers, top-down):
struct JoystickView: View {
@Binding var direction: CGVector
@State private var knobOffset: CGSize = .zero
let radius: CGFloat = 50
var body: some View {
ZStack {
Circle()
.fill(AppTheme.Colors.surface.opacity(0.3))
.frame(width: radius * 2, height: radius * 2)
Circle()
.fill(AppTheme.Colors.primary.opacity(0.7))
.frame(width: radius * 0.8, height: radius * 0.8)
.offset(knobOffset)
}
.gesture(
DragGesture()
.onChanged { value in
let vector = CGSize(width: value.translation.width, height: value.translation.height)
let distance = sqrt(vector.width * vector.width + vector.height * vector.height)
if distance <= radius {
knobOffset = vector
} else {
knobOffset = CGSize(
width: vector.width / distance * radius,
height: vector.height / distance * radius
)
}
direction = CGVector(dx: knobOffset.width / radius, dy: -knobOffset.height / radius)
}
.onEnded { _ in
knobOffset = .zero
direction = .zero
}
)
}
}
@AppStorage("hasCompletedTutorial") private var tutorialDone = false
// Show tutorial overlay on first launch
if !tutorialDone {
TutorialOverlay(onComplete: { tutorialDone = true })
}
Pattern: highlight target area, show instruction text, require player action to advance.
.scaleMode = .resizeFill — scene adapts to any iPad sizetesting
Use for 3D games: racing, 3D sports, board games, marble maze, tower defense, bowling. SceneKit + SceneView architecture, 3D scene hierarchy, physics, game loop, primitives, materials, cameras, particles, audio.
tools
Download free game sprites/textures/3D models and generate procedural assets. Covers nw_download_asset tool, texture factories, sprite atlas organization, 3D model loading, and programmatic asset creation.
testing
Use for 2D games: arcade, puzzle, sports, ping pong, platformer, shooter, 2D racing. SpriteKit + SpriteView architecture, scene hierarchy, physics, game loop, audio, particles, game feel.
tools
Share extension with NSExtensionActivationRule filtering, SLComposeServiceViewController handling, App Group data sharing, and shared content processing for URLs, text, and images. Use when adding share sheet integration to an iOS app.