9363a2dd99
- Enable Mac Catalyst in Xcode project (SUPPORTS_MACCATALYST=YES) - Set macOS deployment target to 13.0 (Ventura+) - Add keyboard support for all scenes (Escape, Space, Enter) - Add macOS menu bar with game commands (Cmd+P pause, Cmd+R restart) - Configure window size restrictions for macOS - Update Info.plist with macOS minimum version
285 lines
10 KiB
Swift
285 lines
10 KiB
Swift
//
|
|
// 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()
|
|
}
|
|
}
|
|
|
|
// MARK: - Keyboard Handling (macOS)
|
|
#if targetEnvironment(macCatalyst)
|
|
override var canBecomeFirstResponder: Bool { true }
|
|
|
|
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
|
|
guard let key = presses.first?.key else {
|
|
super.pressesBegan(presses, with: event)
|
|
return
|
|
}
|
|
|
|
switch key.keyCode {
|
|
case .keyboardSpacebar, .keyboardReturnOrEnter:
|
|
startGame()
|
|
default:
|
|
super.pressesBegan(presses, with: event)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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)
|
|
}
|
|
}
|