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
+162
View File
@@ -0,0 +1,162 @@
//
// DogNode.swift
// RollkofferSimulator
//
// Created by Ingo K.
//
import SpriteKit
/// A dog entity node
class DogNode: SKNode {
// MARK: - Properties
let dogType: DogType
private let bodyNode: SKShapeNode
// MARK: - Initialization
init(type: DogType) {
self.dogType = type
let size: CGSize
let color: SKColor
let hasRedOutline: Bool
switch type {
case .smallGood:
size = Constants.smallDogSize
color = Constants.Colors.goodDogColor
hasRedOutline = false
case .bigGood:
size = Constants.bigDogSize
color = Constants.Colors.goodDogColor
hasRedOutline = false
case .bad:
size = Constants.badDogSize
color = Constants.Colors.goodDogColor
hasRedOutline = true
}
// Create dog body (simple oval shape)
let bodyPath = CGMutablePath()
bodyPath.addEllipse(in: CGRect(x: -size.width / 2, y: -size.height / 2,
width: size.width, height: size.height * 0.7))
bodyNode = SKShapeNode(path: bodyPath)
bodyNode.fillColor = color
bodyNode.strokeColor = hasRedOutline ? Constants.Colors.badDogColor : SKColor(white: 0.3, alpha: 1.0)
bodyNode.lineWidth = hasRedOutline ? 4 : 2
super.init()
addChild(bodyNode)
// Add head
let headSize = size.width * 0.5
let head = SKShapeNode(circleOfRadius: headSize / 2)
head.position = CGPoint(x: 0, y: size.height * 0.25)
head.fillColor = color
head.strokeColor = hasRedOutline ? Constants.Colors.badDogColor : SKColor(white: 0.3, alpha: 1.0)
head.lineWidth = hasRedOutline ? 3 : 1.5
addChild(head)
// Add ears
let earSize = headSize * 0.4
for xOffset in [-headSize * 0.4, headSize * 0.4] {
let ear = SKShapeNode(ellipseOf: CGSize(width: earSize, height: earSize * 1.5))
ear.position = CGPoint(x: xOffset, y: size.height * 0.25 + headSize * 0.35)
ear.fillColor = color.withAlphaComponent(0.8)
ear.strokeColor = hasRedOutline ? Constants.Colors.badDogColor : SKColor(white: 0.3, alpha: 1.0)
ear.lineWidth = hasRedOutline ? 2 : 1
addChild(ear)
}
// Add eyes
let eyeSize: CGFloat = headSize * 0.15
for xOffset in [-headSize * 0.15, headSize * 0.15] {
let eye = SKShapeNode(circleOfRadius: eyeSize)
eye.position = CGPoint(x: xOffset, y: size.height * 0.28)
eye.fillColor = hasRedOutline ? .red : .black
eye.strokeColor = .clear
addChild(eye)
}
// Add nose
let nose = SKShapeNode(circleOfRadius: headSize * 0.1)
nose.position = CGPoint(x: 0, y: size.height * 0.18)
nose.fillColor = .black
nose.strokeColor = .clear
addChild(nose)
// Add tail
let tailPath = CGMutablePath()
tailPath.move(to: CGPoint(x: 0, y: -size.height * 0.25))
tailPath.addQuadCurve(to: CGPoint(x: size.width * 0.3, y: -size.height * 0.1),
control: CGPoint(x: size.width * 0.4, y: -size.height * 0.3))
let tail = SKShapeNode(path: tailPath)
tail.strokeColor = color
tail.lineWidth = size.width * 0.1
tail.lineCap = .round
addChild(tail)
// Add legs
let legWidth = size.width * 0.12
let legHeight = size.height * 0.25
let legPositions: [CGFloat] = [-size.width * 0.25, -size.width * 0.1,
size.width * 0.1, size.width * 0.25]
for xPos in legPositions {
let leg = SKShapeNode(rect: CGRect(x: xPos - legWidth / 2,
y: -size.height * 0.35 - legHeight,
width: legWidth, height: legHeight),
cornerRadius: legWidth / 2)
leg.fillColor = color
leg.strokeColor = hasRedOutline ? Constants.Colors.badDogColor : SKColor(white: 0.3, alpha: 1.0)
leg.lineWidth = hasRedOutline ? 2 : 1
addChild(leg)
}
// Add angry expression for bad dogs
if hasRedOutline {
let browPath = CGMutablePath()
browPath.move(to: CGPoint(x: -headSize * 0.3, y: size.height * 0.35))
browPath.addLine(to: CGPoint(x: -headSize * 0.05, y: size.height * 0.32))
let leftBrow = SKShapeNode(path: browPath)
leftBrow.strokeColor = .black
leftBrow.lineWidth = 2
addChild(leftBrow)
let browPath2 = CGMutablePath()
browPath2.move(to: CGPoint(x: headSize * 0.3, y: size.height * 0.35))
browPath2.addLine(to: CGPoint(x: headSize * 0.05, y: size.height * 0.32))
let rightBrow = SKShapeNode(path: browPath2)
rightBrow.strokeColor = .black
rightBrow.lineWidth = 2
addChild(rightBrow)
}
setupPhysics(size: size)
self.zPosition = Constants.ZPosition.entities
self.name = type.isHarmful ? "badDog" : "goodDog"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Physics Setup
private func setupPhysics(size: CGSize) {
let radius = min(size.width, size.height) / 2
physicsBody = SKPhysicsBody(circleOfRadius: radius)
physicsBody?.isDynamic = true
physicsBody?.affectedByGravity = false
physicsBody?.allowsRotation = false
if dogType.isHarmful {
physicsBody?.categoryBitMask = Constants.PhysicsCategory.badDog
} else {
physicsBody?.categoryBitMask = Constants.PhysicsCategory.goodDog
}
physicsBody?.contactTestBitMask = Constants.PhysicsCategory.suitcase
physicsBody?.collisionBitMask = Constants.PhysicsCategory.none
}
}