Add RollkofferSimulator iOS SpriteKit arcade game

Complete implementation of a 2D top-down arcade collector game where players
control a rolling suitcase through an airport, collecting good dogs and green
people while avoiding bad dogs and gray people.

Features:
- Touch & drag controls for suitcase movement
- Automatic scrolling airport floor with tile pattern
- 4 entity types: good dogs (small/big), bad dogs, green/gray humans
- Spawn system with configurable distribution rates
- Collision detection with visual feedback effects
- Score tracking with high score persistence
- 90-second time limit with 10 dogs + 5 humans goal
- 3 lives system with invincibility frames
- Menu, Game, GameOver, and Victory scenes
- German UI text (Created by Ingo K.)

Technical:
- iOS 15+ with SpriteKit framework
- Modular architecture with Nodes, Managers, Scenes
- Physics-based collision detection
- UserDefaults for score persistence
This commit is contained in:
Claude
2025-12-19 16:47:49 +00:00
parent 8d4afcf0ad
commit 9e501cc4e8
22 changed files with 3228 additions and 0 deletions
+265
View File
@@ -0,0 +1,265 @@
//
// MenuScene.swift
// RollkofferSimulator
//
// Created by Ingo K.
//
import SpriteKit
class MenuScene: SKScene {
// MARK: - Properties
private var startButton: SKShapeNode!
private var titleLabel: SKLabelNode!
private var subtitleLabel: SKLabelNode!
private var highScoreLabel: SKLabelNode!
private var creditsLabel: SKLabelNode!
// MARK: - Decorative elements
private var decorativeSuitcase: PlayerNode!
private var decorativeDogs: [DogNode] = []
private var decorativeHumans: [HumanNode] = []
// MARK: - Scene Lifecycle
override func didMove(to view: SKView) {
setupBackground()
setupTitle()
setupStartButton()
setupHighScore()
setupDecorations()
setupCredits()
startAnimations()
}
// MARK: - Setup
private func setupBackground() {
backgroundColor = Constants.Colors.backgroundColor
// Create floor pattern
let floorHeight = frame.height * 0.6
let floor = SKShapeNode(rect: CGRect(x: 0, y: 0,
width: frame.width, height: floorHeight))
floor.fillColor = Constants.Colors.floorColor
floor.strokeColor = .clear
floor.zPosition = Constants.ZPosition.floor
addChild(floor)
// Add floor tiles pattern
let tileSize: CGFloat = 80
for x in stride(from: CGFloat(0), to: frame.width, by: tileSize) {
for y in stride(from: CGFloat(0), to: floorHeight, by: tileSize) {
let tile = SKShapeNode(rect: CGRect(x: x, y: y,
width: tileSize - 2, height: tileSize - 2))
tile.fillColor = (Int((x + y) / tileSize) % 2 == 0) ?
SKColor(white: 0.78, alpha: 1.0) : SKColor(white: 0.72, alpha: 1.0)
tile.strokeColor = SKColor(white: 0.65, alpha: 0.5)
tile.lineWidth = 1
tile.zPosition = Constants.ZPosition.floor + 0.1
addChild(tile)
}
}
}
private func setupTitle() {
// Main title
titleLabel = SKLabelNode(text: "🧳 Rollkoffer Simulator 🧳")
titleLabel.fontName = "AvenirNext-Heavy"
titleLabel.fontSize = 36
titleLabel.fontColor = SKColor(red: 0.3, green: 0.2, blue: 0.5, alpha: 1.0)
titleLabel.position = CGPoint(x: frame.midX, y: frame.height * 0.85)
titleLabel.zPosition = Constants.ZPosition.ui
addChild(titleLabel)
// Subtitle
subtitleLabel = SKLabelNode(text: "Sammle Hunde & Menschen am Flughafen!")
subtitleLabel.fontName = "AvenirNext-Medium"
subtitleLabel.fontSize = 18
subtitleLabel.fontColor = SKColor.darkGray
subtitleLabel.position = CGPoint(x: frame.midX, y: frame.height * 0.78)
subtitleLabel.zPosition = Constants.ZPosition.ui
addChild(subtitleLabel)
// Goal info
let goalLabel = SKLabelNode(text: "Ziel: 🐕 10 Hunde + 👤 5 Grüne Menschen")
goalLabel.fontName = "AvenirNext-Medium"
goalLabel.fontSize = 16
goalLabel.fontColor = SKColor.gray
goalLabel.position = CGPoint(x: frame.midX, y: frame.height * 0.72)
goalLabel.zPosition = Constants.ZPosition.ui
addChild(goalLabel)
}
private func setupStartButton() {
// Button background
let buttonWidth: CGFloat = 200
let buttonHeight: CGFloat = 60
startButton = SKShapeNode(rect: CGRect(x: -buttonWidth / 2, y: -buttonHeight / 2,
width: buttonWidth, height: buttonHeight),
cornerRadius: 15)
startButton.fillColor = SKColor(red: 0.2, green: 0.7, blue: 0.3, alpha: 1.0)
startButton.strokeColor = SKColor(red: 0.15, green: 0.5, blue: 0.2, alpha: 1.0)
startButton.lineWidth = 3
startButton.position = CGPoint(x: frame.midX, y: frame.height * 0.5)
startButton.zPosition = Constants.ZPosition.ui
startButton.name = "startButton"
addChild(startButton)
// Button label
let buttonLabel = SKLabelNode(text: "▶ START")
buttonLabel.fontName = "AvenirNext-Bold"
buttonLabel.fontSize = 28
buttonLabel.fontColor = .white
buttonLabel.verticalAlignmentMode = .center
buttonLabel.position = .zero
buttonLabel.zPosition = 1
startButton.addChild(buttonLabel)
// Button pulse animation
let scaleUp = SKAction.scale(to: 1.05, duration: 0.8)
let scaleDown = SKAction.scale(to: 1.0, duration: 0.8)
let pulse = SKAction.sequence([scaleUp, scaleDown])
startButton.run(SKAction.repeatForever(pulse))
}
private func setupHighScore() {
let highScore = ScoreManager.shared.highScore
highScoreLabel = SKLabelNode(text: "🏆 High Score: \(highScore)")
highScoreLabel.fontName = "AvenirNext-DemiBold"
highScoreLabel.fontSize = 20
highScoreLabel.fontColor = SKColor(red: 0.8, green: 0.6, blue: 0.1, alpha: 1.0)
highScoreLabel.position = CGPoint(x: frame.midX, y: frame.height * 0.38)
highScoreLabel.zPosition = Constants.ZPosition.ui
addChild(highScoreLabel)
// Statistics label
let victories = ScoreManager.shared.victories
let gamesPlayed = ScoreManager.shared.gamesPlayed
let statsText = "Siege: \(victories) | Spiele: \(gamesPlayed)"
let statsLabel = SKLabelNode(text: statsText)
statsLabel.fontName = "AvenirNext-Regular"
statsLabel.fontSize = 14
statsLabel.fontColor = SKColor.gray
statsLabel.position = CGPoint(x: frame.midX, y: frame.height * 0.33)
statsLabel.zPosition = Constants.ZPosition.ui
addChild(statsLabel)
}
private func setupDecorations() {
// Decorative suitcase
decorativeSuitcase = PlayerNode()
decorativeSuitcase.position = CGPoint(x: frame.midX, y: frame.height * 0.2)
decorativeSuitcase.zPosition = Constants.ZPosition.entities
addChild(decorativeSuitcase)
// Add some decorative dogs
let dogTypes: [DogType] = [.smallGood, .bigGood, .bad]
let dogPositions: [CGPoint] = [
CGPoint(x: frame.width * 0.15, y: frame.height * 0.25),
CGPoint(x: frame.width * 0.85, y: frame.height * 0.22),
CGPoint(x: frame.width * 0.25, y: frame.height * 0.12)
]
for (index, type) in dogTypes.enumerated() {
let dog = DogNode(type: type)
dog.position = dogPositions[index]
dog.zPosition = Constants.ZPosition.entities
addChild(dog)
decorativeDogs.append(dog)
}
// Add decorative humans
let humanTypes: [HumanType] = [.green, .gray]
let humanPositions: [CGPoint] = [
CGPoint(x: frame.width * 0.75, y: frame.height * 0.15),
CGPoint(x: frame.width * 0.6, y: frame.height * 0.1)
]
for (index, type) in humanTypes.enumerated() {
let human = HumanNode(type: type)
human.position = humanPositions[index]
human.zPosition = Constants.ZPosition.entities
addChild(human)
decorativeHumans.append(human)
}
}
private func setupCredits() {
creditsLabel = SKLabelNode(text: "Created by Ingo K.")
creditsLabel.fontName = "AvenirNext-Italic"
creditsLabel.fontSize = 14
creditsLabel.fontColor = SKColor.gray
creditsLabel.position = CGPoint(x: frame.midX, y: 30)
creditsLabel.zPosition = Constants.ZPosition.ui
addChild(creditsLabel)
}
private func startAnimations() {
// Animate decorative suitcase
let moveLeft = SKAction.moveBy(x: -20, y: 0, duration: 1.5)
let moveRight = SKAction.moveBy(x: 40, y: 0, duration: 3.0)
let moveBack = SKAction.moveBy(x: -20, y: 0, duration: 1.5)
let suitcaseSequence = SKAction.sequence([moveLeft, moveRight, moveBack])
decorativeSuitcase.run(SKAction.repeatForever(suitcaseSequence))
// Animate decorative entities
for (index, dog) in decorativeDogs.enumerated() {
let delay = Double(index) * 0.3
let bounce = SKAction.sequence([
SKAction.wait(forDuration: delay),
SKAction.moveBy(x: 0, y: 10, duration: 0.4),
SKAction.moveBy(x: 0, y: -10, duration: 0.4)
])
dog.run(SKAction.repeatForever(bounce))
}
for (index, human) in decorativeHumans.enumerated() {
let delay = Double(index) * 0.4 + 0.2
let sway = SKAction.sequence([
SKAction.wait(forDuration: delay),
SKAction.rotate(byAngle: 0.05, duration: 0.5),
SKAction.rotate(byAngle: -0.1, duration: 1.0),
SKAction.rotate(byAngle: 0.05, duration: 0.5)
])
human.run(SKAction.repeatForever(sway))
}
// Title animation
let titleScale = SKAction.sequence([
SKAction.scale(to: 1.02, duration: 2.0),
SKAction.scale(to: 1.0, duration: 2.0)
])
titleLabel.run(SKAction.repeatForever(titleScale))
}
// MARK: - Touch Handling
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self)
if startButton.contains(location) {
startGame()
}
}
private func startGame() {
// Button press effect
let pressDown = SKAction.scale(to: 0.9, duration: 0.1)
let pressUp = SKAction.scale(to: 1.0, duration: 0.1)
startButton.run(SKAction.sequence([pressDown, pressUp])) { [weak self] in
self?.transitionToGame()
}
}
private func transitionToGame() {
let gameScene = GameScene(size: size)
gameScene.scaleMode = scaleMode
let transition = SKTransition.fade(withDuration: 0.5)
view?.presentScene(gameScene, transition: transition)
}
}