Files
Claude 9e501cc4e8 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
2025-12-19 16:47:49 +00:00

198 lines
6.2 KiB
Swift

//
// CollisionManager.swift
// RollkofferSimulator
//
// Created by Ingo K.
//
import SpriteKit
/// Protocol for collision event handling
protocol CollisionManagerDelegate: AnyObject {
func didCollectGoodDog(points: Int)
func didCollectGreenHuman(points: Int)
func didHitHarmfulEntity()
}
/// Manages collision detection and response
class CollisionManager: NSObject, SKPhysicsContactDelegate {
// MARK: - Properties
weak var delegate: CollisionManagerDelegate?
// MARK: - SKPhysicsContactDelegate
func didBegin(_ contact: SKPhysicsContact) {
let collision = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
// Check if suitcase is involved
guard collision & Constants.PhysicsCategory.suitcase != 0 else { return }
let otherBody: SKPhysicsBody
if contact.bodyA.categoryBitMask == Constants.PhysicsCategory.suitcase {
otherBody = contact.bodyB
} else {
otherBody = contact.bodyA
}
handleCollision(with: otherBody)
}
// MARK: - Private Methods
private func handleCollision(with body: SKPhysicsBody) {
guard let node = body.node else { return }
switch body.categoryBitMask {
case Constants.PhysicsCategory.goodDog:
handleGoodDogCollision(node: node)
case Constants.PhysicsCategory.badDog:
handleBadDogCollision(node: node)
case Constants.PhysicsCategory.greenHuman:
handleGreenHumanCollision(node: node)
case Constants.PhysicsCategory.grayHuman:
handleGrayHumanCollision(node: node)
default:
break
}
}
private func handleGoodDogCollision(node: SKNode) {
guard let dogNode = node as? DogNode else { return }
let points = dogNode.dogType.points
showCollectEffect(at: node.position, color: .green, text: "+\(points)")
node.removeFromParent()
delegate?.didCollectGoodDog(points: points)
}
private func handleBadDogCollision(node: SKNode) {
showDamageEffect(at: node.position)
node.removeFromParent()
delegate?.didHitHarmfulEntity()
}
private func handleGreenHumanCollision(node: SKNode) {
guard let humanNode = node as? HumanNode else { return }
let points = humanNode.humanType.points
showCollectEffect(at: node.position, color: .green, text: "+\(points)")
node.removeFromParent()
delegate?.didCollectGreenHuman(points: points)
}
private func handleGrayHumanCollision(node: SKNode) {
showDamageEffect(at: node.position)
node.removeFromParent()
delegate?.didHitHarmfulEntity()
}
// MARK: - Visual Effects
private func showCollectEffect(at position: CGPoint, color: SKColor, text: String) {
guard let scene = getScene() else { return }
// Particle burst
let emitter = SKEmitterNode()
emitter.particleTexture = nil
emitter.particleBirthRate = 50
emitter.numParticlesToEmit = 20
emitter.particleLifetime = 0.5
emitter.particleSpeed = 100
emitter.particleSpeedRange = 50
emitter.emissionAngleRange = .pi * 2
emitter.particleScale = 0.3
emitter.particleScaleRange = 0.2
emitter.particleColor = color
emitter.particleColorBlendFactor = 1.0
emitter.position = position
emitter.zPosition = Constants.ZPosition.ui - 1
// Create a simple circle shape for particles
let shape = SKShapeNode(circleOfRadius: 5)
shape.fillColor = color
shape.strokeColor = .clear
if let texture = scene.view?.texture(from: shape) {
emitter.particleTexture = texture
}
scene.addChild(emitter)
let waitAction = SKAction.wait(forDuration: 1.0)
let removeAction = SKAction.removeFromParent()
emitter.run(SKAction.sequence([waitAction, removeAction]))
// Floating text
let label = SKLabelNode(text: text)
label.fontName = "AvenirNext-Bold"
label.fontSize = 24
label.fontColor = color
label.position = position
label.zPosition = Constants.ZPosition.ui
scene.addChild(label)
let moveUp = SKAction.moveBy(x: 0, y: 50, duration: 0.5)
let fadeOut = SKAction.fadeOut(withDuration: 0.5)
let group = SKAction.group([moveUp, fadeOut])
let remove = SKAction.removeFromParent()
label.run(SKAction.sequence([group, remove]))
}
private func showDamageEffect(at position: CGPoint) {
guard let scene = getScene() else { return }
// Red flash
let flash = SKShapeNode(circleOfRadius: 30)
flash.fillColor = .red
flash.strokeColor = .clear
flash.alpha = 0.7
flash.position = position
flash.zPosition = Constants.ZPosition.ui - 1
scene.addChild(flash)
let scaleUp = SKAction.scale(to: 2.0, duration: 0.2)
let fadeOut = SKAction.fadeOut(withDuration: 0.2)
let group = SKAction.group([scaleUp, fadeOut])
let remove = SKAction.removeFromParent()
flash.run(SKAction.sequence([group, remove]))
// Floating text
let label = SKLabelNode(text: "-1 ❤️")
label.fontName = "AvenirNext-Bold"
label.fontSize = 24
label.fontColor = .red
label.position = position
label.zPosition = Constants.ZPosition.ui
scene.addChild(label)
let moveUp = SKAction.moveBy(x: 0, y: 50, duration: 0.5)
let labelFadeOut = SKAction.fadeOut(withDuration: 0.5)
let labelGroup = SKAction.group([moveUp, labelFadeOut])
let labelRemove = SKAction.removeFromParent()
label.run(SKAction.sequence([labelGroup, labelRemove]))
}
private func getScene() -> SKScene? {
// This would typically be set via dependency injection
// For simplicity, we'll use the notification pattern
return nil
}
}
// MARK: - Scene Reference Extension
extension CollisionManager {
private static var sceneReference: SKScene?
func setScene(_ scene: SKScene) {
CollisionManager.sceneReference = scene
}
private func getSceneFromReference() -> SKScene? {
return CollisionManager.sceneReference
}
}