From 8cf2c1b4f57c0ee0f224d9d122738fdefb8cc6ac Mon Sep 17 00:00:00 2001 From: David Schilling Date: Wed, 25 Feb 2026 21:18:33 +0100 Subject: [PATCH 1/3] WIP: align iOS menu and level selection UI to Android --- .../LevelMode/LevelButtonPositioner.swift | 4 +- SOPA/view/LevelMode/LevelChoiceScene.swift | 434 ++++++++++++------ SOPA/view/LevelMode/LevelSelectButton.swift | 77 +++- 3 files changed, 359 insertions(+), 156 deletions(-) diff --git a/SOPA/view/LevelMode/LevelButtonPositioner.swift b/SOPA/view/LevelMode/LevelButtonPositioner.swift index 35d34c0..167fee2 100644 --- a/SOPA/view/LevelMode/LevelButtonPositioner.swift +++ b/SOPA/view/LevelMode/LevelButtonPositioner.swift @@ -16,7 +16,7 @@ class LevelButtonPositioner { init(size: CGSize) { self.size = size - self.buttonSize = CGSize(width: size.width * 0.3, height: size.width * 0.3) + self.buttonSize = CGSize(width: size.width * 0.24, height: size.width * 0.24) self.drawingHeight = size.height } @@ -26,7 +26,7 @@ class LevelButtonPositioner { let column = idOnPage % 3 let row = idOnPage / 3 let xPos: CGFloat = size.width / 4.0 * CGFloat(1 + column) + (size.width * CGFloat(page)) - let yPos: CGFloat = size.height - (drawingHeight / 5.0 * CGFloat(row + 1)) + let yPos: CGFloat = size.height - (drawingHeight / 5.0 * CGFloat(row + 1)) + size.height * 0.03 let position = CGPoint(x: xPos, y: yPos) return position diff --git a/SOPA/view/LevelMode/LevelChoiceScene.swift b/SOPA/view/LevelMode/LevelChoiceScene.swift index f0f8af7..61ce7b3 100644 --- a/SOPA/view/LevelMode/LevelChoiceScene.swift +++ b/SOPA/view/LevelMode/LevelChoiceScene.swift @@ -38,31 +38,98 @@ class LevelChoiceScene: SKScene { private let levelInfos: [LevelInfo] private var levelButtonArea: LevelButtonArea? private var menuButton: SpriteButton? + private var leftArrowButton: SpriteButton? + private var rightArrowButton: SpriteButton? init(size: CGSize, levelService: LevelService) { levelInfos = levelService.getLevelInfos() super.init(size: size) levelButtonArea = LevelButtonArea(size: size, levelInfos: levelInfos, update: update) addChild(levelButtonArea!) + backgroundColor = .black addButtons() - self.backgroundColor = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255, alpha: 1.0) + addPageArrows() + update() } private func addButtons() { - let side = size.height * 0.08 - menuButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side), onClick: backToStartMenu) - menuButton?.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) + let side = min(size.width * 0.095, size.height * 0.07) + menuButton = SpriteButton(texture: makeMenuBackTexture(side: side), onClick: backToStartMenu) + menuButton?.position = CGPoint(x: size.width * 0.085, y: size.height * 0.90) + menuButton?.zPosition = 20 addChild(menuButton!) } + + private func makeMenuBackTexture(side: CGFloat) -> SKTexture { + let textureSize = CGSize(width: side, height: side) + let renderer = UIGraphicsImageRenderer(size: textureSize) + let image = renderer.image { _ in + let config = UIImage.SymbolConfiguration(pointSize: side * 0.72, weight: .semibold) + if let symbol = UIImage(systemName: "chevron.left", withConfiguration: config)? + .withTintColor(UIColor(red: 160.0 / 255.0, green: 164.0 / 255.0, blue: 170.0 / 255.0, alpha: 0.95), renderingMode: .alwaysOriginal) { + let symbolRect = CGRect( + x: (side - symbol.size.width) / 2.0, + y: (side - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + } + return SKTexture(image: image) + } + + private func addPageArrows() { + let arrowSide = min(size.width * 0.16, size.height * 0.13) + leftArrowButton = SpriteButton(texture: makePageArrowTexture(imageNamed: "ArrowLeft", side: arrowSide), onClick: goToPreviousPage) + leftArrowButton?.position = CGPoint(x: size.width * 0.08, y: size.height * 0.21) + leftArrowButton?.zPosition = 20 + addChild(leftArrowButton!) + + rightArrowButton = SpriteButton(texture: makePageArrowTexture(imageNamed: "ArrowRight", side: arrowSide), onClick: goToNextPage) + rightArrowButton?.position = CGPoint(x: size.width * 0.92, y: size.height * 0.21) + rightArrowButton?.zPosition = 20 + addChild(rightArrowButton!) + } + + private func makePageArrowTexture(imageNamed: String, side: CGFloat) -> SKTexture { + let texture = SKTexture(imageNamed: imageNamed) + let imageSize = texture.size() + let ratio = imageSize.width / max(1, imageSize.height) + let targetSize = CGSize(width: side * ratio, height: side) + let renderer = UIGraphicsImageRenderer(size: targetSize) + let image = renderer.image { _ in + let rect = CGRect(origin: .zero, size: targetSize) + UIImage(named: imageNamed)?.draw(in: rect) + } + return SKTexture(image: image) + } + + private func goToPreviousPage() { + levelButtonArea?.swipeLeft() + } + + private func goToNextPage() { + levelButtonArea?.swipeRight() + } private func backToStartMenu() { ResourcesManager.getInstance().storyService?.loadStartMenuScene() } func update() { - // Intentionally empty: level paging is swipe-only. + guard let area = levelButtonArea else { + leftArrowButton?.isHidden = true + rightArrowButton?.isHidden = true + return + } + + let isFirstPage = area.currentLevelPage == 0 + let isLastPage = area.currentLevelPage >= area.pageCount - 1 + leftArrowButton?.isHidden = isFirstPage + rightArrowButton?.isHidden = isLastPage || area.pageCount <= 1 } @@ -74,153 +141,188 @@ class LevelChoiceScene: SKScene { } class StartMenuScene: SKScene { - private let background = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255, alpha: 1.0) - private let textColor = UIColor(red: 240.0 / 255.0, green: 239.0 / 255.0, blue: 238.0 / 255.0, alpha: 1.0) - + private let background = UIColor.black + private let titleColor = UIColor(red: 214.0 / 255.0, green: 214.0 / 255.0, blue: 214.0 / 255.0, alpha: 1.0) + private let neonColor = UIColor(red: 111.0 / 255.0, green: 227.0 / 255.0, blue: 1.0, alpha: 1.0) + private let neonGlowColor = UIColor(red: 23.0 / 255.0, green: 183.0 / 255.0, blue: 1.0, alpha: 1.0) + private let twitterUrl = "https://twitter.com/sopagame" + private let shareText = "I played SOPA: https://play.google.com/store/apps/details?id=com.sopaapp" + override init(size: CGSize) { super.init(size: size) - self.backgroundColor = background + backgroundColor = background addTitle() addButtons() + addSocialButtons() } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + private func addTitle() { - let title = SKLabelNode(fontNamed: "Optima-Bold") + let title = SKLabelNode(fontNamed: "Impact") title.text = "SOPA" - title.fontSize = size.height * 0.12 - title.fontColor = textColor - title.position = CGPoint(x: size.width / 2, y: size.height * 0.78) + title.fontSize = size.height * 0.14 + title.fontColor = titleColor + title.position = CGPoint(x: size.width / 2, y: size.height * 0.77) addChild(title) - - let subtitle = SKLabelNode(fontNamed: "Optima-Bold") - subtitle.text = "Choose a mode" - subtitle.fontSize = size.height * 0.04 - subtitle.fontColor = textColor - subtitle.position = CGPoint(x: size.width / 2, y: size.height * 0.70) - addChild(subtitle) } - + private func addButtons() { - let horizontalPadding = size.width * 0.10 - let interButtonGap = size.width * 0.06 - let maxButtonWidthFromScreen = (size.width - (horizontalPadding * 2.0) - interButtonGap) / 2.0 - let buttonSize = min(size.height * 0.20, maxButtonWidthFromScreen) - let iconTextureSize = CGSize(width: buttonSize, height: buttonSize) - let totalGroupWidth = buttonSize * 2.0 + interButtonGap - let leftCenterX = (size.width - totalGroupWidth) / 2.0 + buttonSize / 2.0 - let rightCenterX = leftCenterX + buttonSize + interButtonGap - let buttonCenterY = size.height * 0.42 - - let levelModeButton = SpriteButton(texture: makeModeTexture(symbolName: "square.grid.2x2", textureSize: iconTextureSize)) { - ResourcesManager.getInstance().storyService?.loadLevelCoiceScene() - } - levelModeButton.position = CGPoint(x: leftCenterX, y: buttonCenterY) - addChild(levelModeButton) - - let levelModeLabel = SKLabelNode(fontNamed: "Optima-Bold") - levelModeLabel.text = "Level Mode" - levelModeLabel.fontSize = size.height * 0.035 - levelModeLabel.fontColor = textColor - levelModeLabel.position = CGPoint(x: 0, y: -buttonSize * 0.70) - levelModeButton.addChild(levelModeLabel) - - let justPlayButton = SpriteButton(texture: makeModeTexture(symbolName: "bolt.fill", textureSize: iconTextureSize)) { - ResourcesManager.getInstance().storyService?.loadJustPlaySceneFromMenuScene() - } - justPlayButton.position = CGPoint(x: rightCenterX, y: buttonCenterY) - addChild(justPlayButton) - - let justPlayLabel = SKLabelNode(fontNamed: "Optima-Bold") - justPlayLabel.text = "Just Play" - justPlayLabel.fontSize = size.height * 0.035 - justPlayLabel.fontColor = textColor - justPlayLabel.position = CGPoint(x: 0, y: -buttonSize * 0.70) - justPlayButton.addChild(justPlayLabel) - - let utilityButtonY = size.height * 0.20 - let utilityButtonSize = CGSize(width: size.width * 0.30, height: size.height * 0.075) - let utilityGap = size.width * 0.04 - let totalUtilityWidth = utilityButtonSize.width * 2.0 + utilityGap - let firstUtilityX = (size.width - totalUtilityWidth) / 2.0 + utilityButtonSize.width / 2.0 - - let tutorialButton = SpriteButton(texture: makeTextButtonTexture(title: "Tutorial", size: utilityButtonSize)) { - ResourcesManager.getInstance().storyService?.loadTutorialSceneFromMenuScene() - } - tutorialButton.position = CGPoint(x: firstUtilityX, y: utilityButtonY) - addChild(tutorialButton) - - let creditsButton = SpriteButton(texture: makeTextButtonTexture(title: "Credits", size: utilityButtonSize)) { - ResourcesManager.getInstance().storyService?.loadCreditsSceneFromMenuScene() - } - creditsButton.position = CGPoint(x: firstUtilityX + utilityButtonSize.width + utilityGap, y: utilityButtonY) - addChild(creditsButton) + let menuItems: [(title: String, action: () -> Void)] = [ + ("LEVEL MODE", { ResourcesManager.getInstance().storyService?.loadLevelCoiceScene() }), + ("JUST PLAY", { ResourcesManager.getInstance().storyService?.loadJustPlaySceneFromMenuScene() }), + // Keep this wired until a dedicated Settings scene exists on iOS. + ("SETTINGS", { ResourcesManager.getInstance().storyService?.loadTutorialSceneFromMenuScene() }), + ("CREDITS", { ResourcesManager.getInstance().storyService?.loadCreditsSceneFromMenuScene() }) + ] + + let buttonSize = CGSize(width: size.width * 0.76, height: size.height * 0.095) + let firstY = size.height * 0.62 + let verticalGap = size.height * 0.11 + + for (index, item) in menuItems.enumerated() { + let button = SpriteButton(texture: makeNeonTextTexture(title: item.title, size: buttonSize), onClick: item.action) + button.position = CGPoint(x: size.width / 2.0, y: firstY - CGFloat(index) * verticalGap) + addChild(button) + } } - - private func makeModeTexture(symbolName: String, textureSize: CGSize) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: textureSize) - let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: textureSize).insetBy(dx: textureSize.width * 0.05, dy: textureSize.height * 0.05) - let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: textureSize.width * 0.18) - UIColor(white: 1.0, alpha: 0.10).setFill() - backgroundPath.fill() - UIColor(white: 1.0, alpha: 0.22).setStroke() - backgroundPath.lineWidth = textureSize.width * 0.03 - backgroundPath.stroke() - let config = UIImage.SymbolConfiguration(pointSize: textureSize.width * 0.44, weight: .semibold) - if let symbol = UIImage(systemName: symbolName, withConfiguration: config)? - .withTintColor(textColor, renderingMode: .alwaysOriginal) { - let symbolRect = CGRect( - x: (textureSize.width - symbol.size.width) / 2.0, - y: (textureSize.height - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - } - return SKTexture(image: image) + private func addSocialButtons() { + let buttonSide = min(size.width * 0.18, size.height * 0.10) + + let shareButton = SpriteButton( + texture: makeSocialButtonTexture(symbolName: "point.3.connected.trianglepath.dotted", size: CGSize(width: buttonSide, height: buttonSide), fillBackground: true), + onClick: shareApp + ) + shareButton.position = CGPoint(x: size.width * 0.20, y: size.height * 0.14) + addChild(shareButton) + + let twitterButton = SpriteButton( + texture: makeSocialButtonTexture(symbolName: "paperplane.fill", size: CGSize(width: buttonSide * 1.15, height: buttonSide * 1.15), fillBackground: false), + onClick: openTwitter + ) + twitterButton.position = CGPoint(x: size.width * 0.74, y: size.height * 0.14) + addChild(twitterButton) } - private func makeTextButtonTexture(title: String, size: CGSize) -> SKTexture { + private func makeNeonTextTexture(title: String, size: CGSize) -> SKTexture { let renderer = UIGraphicsImageRenderer(size: size) let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: size).insetBy(dx: size.width * 0.01, dy: size.height * 0.04) - let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: size.height * 0.25) - UIColor(white: 1.0, alpha: 0.10).setFill() - backgroundPath.fill() - UIColor(white: 1.0, alpha: 0.22).setStroke() - backgroundPath.lineWidth = size.height * 0.07 - backgroundPath.stroke() - let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont(name: "Optima-Bold", size: size.height * 0.40) ?? UIFont.systemFont(ofSize: size.height * 0.40, weight: .semibold), - .foregroundColor: textColor, + + let glowShadow = NSShadow() + glowShadow.shadowColor = neonGlowColor + glowShadow.shadowBlurRadius = size.height * 0.22 + glowShadow.shadowOffset = .zero + + let font = UIFont(name: "HelveticaNeue-Light", size: size.height * 0.60) + ?? UIFont.systemFont(ofSize: size.height * 0.60, weight: .light) + let textRect = CGRect(x: 0, y: size.height * 0.22, width: size.width, height: size.height * 0.60) + + let glowAttributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: neonColor, + .strokeColor: neonColor, + .strokeWidth: -1.2, + .paragraphStyle: paragraphStyle, + .shadow: glowShadow + ] + title.draw(in: textRect, withAttributes: glowAttributes) + + let coreAttributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: UIColor(red: 183.0 / 255.0, green: 245.0 / 255.0, blue: 1.0, alpha: 1.0), .paragraphStyle: paragraphStyle ] - let textRect = CGRect(x: 0.0, y: size.height * 0.28, width: size.width, height: size.height * 0.5) - title.draw(in: textRect, withAttributes: attributes) + title.draw(in: textRect, withAttributes: coreAttributes) + } + return SKTexture(image: image) + } + + private func makeSocialButtonTexture(symbolName: String, size: CGSize, fillBackground: Bool) -> SKTexture { + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { _ in + let blueColor = UIColor(red: 99.0 / 255.0, green: 177.0 / 255.0, blue: 230.0 / 255.0, alpha: 1.0) + if fillBackground { + let backgroundPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: size.width * 0.18) + blueColor.setFill() + backgroundPath.fill() + } + + let pointSize = fillBackground ? size.width * 0.50 : size.width * 0.75 + let config = UIImage.SymbolConfiguration(pointSize: pointSize, weight: .semibold) + guard let baseSymbol = UIImage(systemName: symbolName, withConfiguration: config) else { + return + } + + let iconColor = fillBackground ? UIColor.white : blueColor + let symbol = baseSymbol.withTintColor(iconColor, renderingMode: .alwaysOriginal) + let symbolRect = CGRect( + x: (size.width - symbol.size.width) / 2.0, + y: (size.height - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) } return SKTexture(image: image) } + + private func openTwitter() { + guard let url = URL(string: twitterUrl) else { + return + } + UIApplication.shared.open(url) + } + + private func shareApp() { + guard let controller = topViewController() else { + return + } + let shareController = UIActivityViewController(activityItems: [shareText], applicationActivities: nil) + shareController.popoverPresentationController?.sourceView = controller.view + controller.present(shareController, animated: true) + } + + private func topViewController(base: UIViewController? = nil) -> UIViewController? { + let initialController: UIViewController? + if let base = base { + initialController = base + } else { + initialController = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow }?.rootViewController + } + + if let navController = initialController as? UINavigationController { + return topViewController(base: navController.visibleViewController) + } + if let tabController = initialController as? UITabBarController { + return topViewController(base: tabController.selectedViewController) + } + if let presented = initialController?.presentedViewController { + return topViewController(base: presented) + } + return initialController + } } class CreditsScene: SKScene { - private let background = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255, alpha: 1.0) - private let textColor = UIColor(red: 240.0 / 255.0, green: 239.0 / 255.0, blue: 238.0 / 255.0, alpha: 1.0) + private let background = UIColor.black + private let titleColor = UIColor(red: 214.0 / 255.0, green: 214.0 / 255.0, blue: 214.0 / 255.0, alpha: 1.0) + private let sectionColor = UIColor(red: 111.0 / 255.0, green: 227.0 / 255.0, blue: 1.0, alpha: 1.0) + private let textColor = UIColor(red: 230.0 / 255.0, green: 236.0 / 255.0, blue: 242.0 / 255.0, alpha: 1.0) override init(size: CGSize) { super.init(size: size) - self.backgroundColor = background + backgroundColor = background addBackButton() addTitle() - addLines() + addCreditsContent() } required init?(coder aDecoder: NSCoder) { @@ -229,41 +331,103 @@ class CreditsScene: SKScene { private func addBackButton() { let side = size.height * 0.08 - let backButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side)) { + let backButton = SpriteButton(texture: makeBackButtonTexture(side: side)) { ResourcesManager.getInstance().storyService?.loadStartMenuScene() } backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) addChild(backButton) } + private func makeBackButtonTexture(side: CGFloat) -> SKTexture { + let textureSize = CGSize(width: side, height: side) + let renderer = UIGraphicsImageRenderer(size: textureSize) + let image = renderer.image { _ in + let frame = CGRect(origin: .zero, size: textureSize) + let path = UIBezierPath(roundedRect: frame, cornerRadius: side * 0.25) + UIColor(white: 1.0, alpha: 0.10).setFill() + path.fill() + UIColor(white: 1.0, alpha: 0.20).setStroke() + path.lineWidth = side * 0.05 + path.stroke() + + let config = UIImage.SymbolConfiguration(pointSize: side * 0.44, weight: .bold) + if let symbol = UIImage(systemName: "chevron.left", withConfiguration: config)? + .withTintColor(sectionColor, renderingMode: .alwaysOriginal) { + let symbolRect = CGRect( + x: (side - symbol.size.width) / 2.0, + y: (side - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + } + return SKTexture(image: image) + } + private func addTitle() { - let title = SKLabelNode(fontNamed: "Optima-Bold") - title.text = "Credits" - title.fontSize = size.height * 0.10 - title.fontColor = textColor + let title = SKLabelNode(fontNamed: "Impact") + title.text = "CREDITS" + title.fontSize = size.height * 0.09 + title.fontColor = titleColor title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) addChild(title) } - private func addLines() { - addLine(text: "Design and Development", y: size.height * 0.62, sizeFactor: 0.038) - addLine(text: "David Schilling", y: size.height * 0.55, sizeFactor: 0.048) - addLine(text: "Raphael Schilling", y: size.height * 0.49, sizeFactor: 0.048) - addLine(text: "Gameplay and Product Iteration", y: size.height * 0.39, sizeFactor: 0.038) - addLine(text: "SOPA Team", y: size.height * 0.33, sizeFactor: 0.048) + private func addCreditsContent() { + let content: [(section: String, entries: [String])] = [ + ("DEVELOPMENT", ["David Schilling - @schillda710", "Raphael Schilling - @ubuntius"]), + ("DESIGN", ["Raphael Schilling - @ubuntius"]), + ("MUSIC", ["Menu - axtoncrolley on opengameart.org"]) + ] + + var currentY = size.height * 0.70 + for block in content { + addSectionLine(text: block.section, y: currentY) + currentY -= size.height * 0.045 + + for entry in block.entries { + addBodyLine(text: entry, y: currentY) + currentY -= size.height * 0.038 + } + currentY -= size.height * 0.028 + } + } + + private func addSectionLine(text: String, y: CGFloat) { + let line = SKLabelNode(fontNamed: "HelveticaNeue-Bold") + line.horizontalAlignmentMode = .left + line.verticalAlignmentMode = .center + line.text = text + line.fontSize = fittedFontSize( + for: text, + fontName: "HelveticaNeue-Bold", + preferredSize: size.height * 0.032, + maxWidth: size.width * 0.88 + ) + line.fontColor = sectionColor + line.position = CGPoint(x: size.width * 0.06, y: y) + addChild(line) } - private func addLine(text: String, y: CGFloat, sizeFactor: CGFloat) { - let line = SKLabelNode(fontNamed: "Optima-Bold") + private func addBodyLine(text: String, y: CGFloat) { + let line = SKLabelNode(fontNamed: "HelveticaNeue") + line.horizontalAlignmentMode = .left + line.verticalAlignmentMode = .center line.text = text - line.fontSize = fittedFontSize(for: text, preferredSize: size.height * sizeFactor, maxWidth: size.width * 0.86) + line.fontSize = fittedFontSize( + for: text, + fontName: "HelveticaNeue", + preferredSize: size.height * 0.026, + maxWidth: size.width * 0.90 + ) line.fontColor = textColor - line.position = CGPoint(x: size.width * 0.5, y: y) + line.position = CGPoint(x: size.width * 0.09, y: y) addChild(line) } - private func fittedFontSize(for text: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - let label = SKLabelNode(fontNamed: "Optima-Bold") + private func fittedFontSize(for text: String, fontName: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { + let label = SKLabelNode(fontNamed: fontName) label.text = text var currentSize = preferredSize label.fontSize = currentSize diff --git a/SOPA/view/LevelMode/LevelSelectButton.swift b/SOPA/view/LevelMode/LevelSelectButton.swift index ee5716f..6af97be 100644 --- a/SOPA/view/LevelMode/LevelSelectButton.swift +++ b/SOPA/view/LevelMode/LevelSelectButton.swift @@ -10,24 +10,21 @@ import Foundation import SpriteKit class LevelSelectButton: SKSpriteNode { - let green = UIColor(red: 169.0 / 255.0, green: 162.0 / 255.0, blue: 121.0 / 255.0, alpha: 1.0) - let grey = UIColor(red: 0.5 , green: 0.5, blue: 0.5, alpha: 1.0) + let numberActive = UIColor(red: 223.0 / 255.0, green: 106.0 / 255.0, blue: 37.0 / 255.0, alpha: 1.0) + let numberUnlocked = UIColor(red: 98.0 / 255.0, green: 100.0 / 255.0, blue: 106.0 / 255.0, alpha: 1.0) + let numberLocked = UIColor(red: 110.0 / 255.0, green: 112.0 / 255.0, blue: 116.0 / 255.0, alpha: 1.0) let levelInfo: LevelInfo + init(levelInfo: LevelInfo, levelButtonPositioner: LevelButtonPositioner) { self.levelInfo = levelInfo + super.init(texture: nil, color: .clear, size: levelButtonPositioner.getLevelSize()) + position = levelButtonPositioner.getLevelPosition(id: levelInfo.levelId) + + let isCurrentTarget = !levelInfo.locked && levelInfo.fewestMoves < 0 + addFrame(locked: levelInfo.locked, highlighted: isCurrentTarget) + addLable(id: levelInfo.levelId, color: labelColor(locked: levelInfo.locked, highlighted: isCurrentTarget)) if !levelInfo.locked { - let texture = SKTexture(imageNamed: "Level") - super.init(texture: texture, color: UIColor.clear, size: levelButtonPositioner.getLevelSize()) - position = levelButtonPositioner.getLevelPosition(id: levelInfo.levelId) addStars(stars: levelInfo.stars) - addLable(id: levelInfo.levelId, color: green) - - } else { - let texture = SKTexture(imageNamed: "LevelSW") - super.init(texture: texture, color: UIColor.clear, size: levelButtonPositioner.getLevelSize()) - position = levelButtonPositioner.getLevelPosition(id: levelInfo.levelId) - addLable(id: levelInfo.levelId, color: grey) - } } @@ -36,14 +33,56 @@ class LevelSelectButton: SKSpriteNode { fatalError("init(coder:) has not been implemented") } + private func addFrame(locked: Bool, highlighted: Bool) { + let inset = size.width * 0.04 + let frameRect = CGRect( + x: -size.width / 2.0 + inset, + y: -size.height / 2.0 + inset, + width: size.width - 2.0 * inset, + height: size.height - 2.0 * inset + ) + let cornerRadius = size.width * 0.20 + + let glowNode = SKShapeNode(rect: frameRect, cornerRadius: cornerRadius) + glowNode.fillColor = .clear + glowNode.lineWidth = size.width * 0.040 + glowNode.strokeColor = highlighted + ? UIColor(red: 245.0 / 255.0, green: 102.0 / 255.0, blue: 34.0 / 255.0, alpha: 0.70) + : UIColor(white: 1.0, alpha: locked ? 0.12 : 0.30) + glowNode.glowWidth = size.width * 0.10 + glowNode.blendMode = .add + glowNode.zPosition = zPosition + addChild(glowNode) + + let frameNode = SKShapeNode(rect: frameRect, cornerRadius: cornerRadius) + frameNode.fillColor = .clear + frameNode.lineWidth = size.width * 0.030 + frameNode.strokeColor = highlighted + ? UIColor(red: 250.0 / 255.0, green: 192.0 / 255.0, blue: 107.0 / 255.0, alpha: 1.0) + : UIColor(white: locked ? 0.55 : 0.90, alpha: 0.95) + frameNode.lineJoin = .round + frameNode.zPosition = zPosition + 1 + addChild(frameNode) + } + + private func labelColor(locked: Bool, highlighted: Bool) -> UIColor { + if highlighted { + return numberActive + } + if locked { + return numberLocked + } + return numberUnlocked + } + func addStars(stars: Int) { let star1: SKSpriteNode = generateStar(achieved: stars >= 1) let star2: SKSpriteNode = generateStar(achieved: stars >= 2) let star3: SKSpriteNode = generateStar(achieved: stars >= 3) - star1.position = CGPoint(x: -size.width * 0.25, y: -size.height * 0.2) - star2.position = CGPoint(x: 0, y: -size.height * 0.27 ) - star3.position = CGPoint(x: size.width * 0.25, y: -size.height * 0.2) + star1.position = CGPoint(x: -size.width * 0.25, y: -size.height * 0.50) + star2.position = CGPoint(x: 0, y: -size.height * 0.56) + star3.position = CGPoint(x: size.width * 0.25, y: -size.height * 0.50) addChild(star1) addChild(star2) @@ -52,11 +91,11 @@ class LevelSelectButton: SKSpriteNode { } func addLable(id: Int, color: UIColor) { - let idLable = SKLabelNode(fontNamed: "Optima-Bold") + let idLable = SKLabelNode(fontNamed: "Impact") idLable.text = String(id) idLable.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.center idLable.verticalAlignmentMode = SKLabelVerticalAlignmentMode.center - idLable.fontSize = size.height * 0.34 + idLable.fontSize = size.height * 0.32 idLable.fontColor = color idLable.zPosition = zPosition + 1 addChild(idLable) @@ -68,7 +107,7 @@ class LevelSelectButton: SKSpriteNode { if achieved { star = SKSpriteNode(imageNamed: "star") } - star.size = CGSize(width: size.width/3, height: size.height/3) + star.size = CGSize(width: size.width/3.4, height: size.height/3.4) star.zPosition = zPosition + 1 return star } From 471cc8600cdbe315e8c8efa6690b847dadeb21a1 Mon Sep 17 00:00:00 2001 From: David Schilling Date: Wed, 25 Feb 2026 21:31:18 +0100 Subject: [PATCH 2/3] WIP: gameplay neon assets and level mode HUD/back navigation --- .../border/borders.imageset/Contents.json | 4 +- .../border/borders.imageset/borders.png | Bin 0 -> 1482 bytes .../bordersFinish.imageset/Contents.json | 4 +- .../bordersFinish.imageset/bordersFinish.png | Bin 0 -> 10621 bytes .../Contents.json | 4 +- .../bordersFinish_filled.png | Bin 0 -> 7048 bytes .../bordersStart.imageset/Contents.json | 4 +- .../bordersStart.imageset/bordersStart.png | Bin 0 -> 10792 bytes .../Contents.json | 4 +- .../bordersStart_filled.png | Bin 0 -> 7671 bytes .../tiles/a.imageset/Contents.json | 2 +- SOPA/Assets.xcassets/tiles/a.imageset/a.png | Bin 1827 -> 2825 bytes .../tiles/a_filled.imageset/Contents.json | 4 +- .../tiles/a_filled.imageset/a_filled.png | Bin 0 -> 1403 bytes .../tiles/c.imageset/Contents.json | 4 +- SOPA/Assets.xcassets/tiles/c.imageset/c.png | Bin 0 -> 6417 bytes .../tiles/c_filled.imageset/Contents.json | 4 +- .../tiles/c_filled.imageset/c_filled.png | Bin 0 -> 3638 bytes .../tiles/e.imageset/Contents.json | 4 +- SOPA/Assets.xcassets/tiles/e.imageset/e.png | Bin 0 -> 6305 bytes .../tiles/e_filled.imageset/Contents.json | 4 +- .../tiles/e_filled.imageset/e_filled.png | Bin 0 -> 3536 bytes .../tiles/g.imageset/Contents.json | 2 +- SOPA/Assets.xcassets/tiles/g.imageset/g.png | Bin 2285 -> 6185 bytes .../tiles/g_filled.imageset/Contents.json | 4 +- .../tiles/g_filled.imageset/g_filled.png | Bin 0 -> 3410 bytes .../tiles/i.imageset/Contents.json | 2 +- SOPA/Assets.xcassets/tiles/i.imageset/i.png | Bin 6664 -> 3278 bytes .../tiles/i_filled.imageset/Contents.json | 4 +- .../tiles/i_filled.imageset/i_filled.png | Bin 0 -> 1643 bytes .../tiles/o.imageset/Contents.json | 2 +- SOPA/Assets.xcassets/tiles/o.imageset/o.png | Bin 1810 -> 311 bytes .../tiles/u.imageset/Contents.json | 4 +- SOPA/Assets.xcassets/tiles/u.imageset/u.png | Bin 0 -> 6001 bytes .../tiles/u_filled.imageset/Contents.json | 4 +- .../tiles/u_filled.imageset/u_filled.png | Bin 0 -> 3228 bytes SOPA/view/LevelMode/LevelModeGameScene.swift | 129 ++++++++++++++++-- 37 files changed, 150 insertions(+), 43 deletions(-) create mode 100644 SOPA/Assets.xcassets/border/borders.imageset/borders.png create mode 100644 SOPA/Assets.xcassets/border/bordersFinish.imageset/bordersFinish.png create mode 100644 SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/bordersFinish_filled.png create mode 100644 SOPA/Assets.xcassets/border/bordersStart.imageset/bordersStart.png create mode 100644 SOPA/Assets.xcassets/border/bordersStart_filled.imageset/bordersStart_filled.png create mode 100644 SOPA/Assets.xcassets/tiles/a_filled.imageset/a_filled.png create mode 100644 SOPA/Assets.xcassets/tiles/c.imageset/c.png create mode 100644 SOPA/Assets.xcassets/tiles/c_filled.imageset/c_filled.png create mode 100644 SOPA/Assets.xcassets/tiles/e.imageset/e.png create mode 100644 SOPA/Assets.xcassets/tiles/e_filled.imageset/e_filled.png create mode 100644 SOPA/Assets.xcassets/tiles/g_filled.imageset/g_filled.png create mode 100644 SOPA/Assets.xcassets/tiles/i_filled.imageset/i_filled.png create mode 100644 SOPA/Assets.xcassets/tiles/u.imageset/u.png create mode 100644 SOPA/Assets.xcassets/tiles/u_filled.imageset/u_filled.png diff --git a/SOPA/Assets.xcassets/border/borders.imageset/Contents.json b/SOPA/Assets.xcassets/border/borders.imageset/Contents.json index 82d3b31..3cb9267 100644 --- a/SOPA/Assets.xcassets/border/borders.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/borders.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "empty.png" + "filename" : "borders.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/borders.imageset/borders.png b/SOPA/Assets.xcassets/border/borders.imageset/borders.png new file mode 100644 index 0000000000000000000000000000000000000000..23272816f317f30061dd1e0cb61d649ca3cd9dc0 GIT binary patch literal 1482 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yaTa()7Bet#3xhBt!>lF!Z*Mu~OCOg#_OQHcy7uj&r4oh$OSb=-;M`TWu5MY~It9Bg6Ox&ky`*pU zsp_AcD_5qmJ~Z;h)GwbIn7u?YnGgN>Gk;>tvz^y0PnP+Wd2Tbkd3{M*WNKx@AzsznZQ2HEVv=-qSW$ z&-|{s&O0YdWU0PpvS+#FlIokwYs{DJkqX{cZ1iSQkLNv~mH&5~YM-C~X?^b5+mmX1 zj8ZKt-%h%cbfAFA_lke>BL7XEM%SL?1Q&h0zWnFs`|;0i#vhv!J1OzD+mWkwU%&d+ z)tWyqo%CO&a`uzjNw-yQi=F)Q@&%iFWtZ2q*GDfrs+jboa#Hv0S>o&SgVuek482`j zW6{%6wq}>#r|L=fC;hQLxm!hklh0&lg=HMa>&{xR6is^)Imv#jV!*OluV2SLoAt%? zUv!yEm}9xniH8-YTc4l16_}l+tFm^{Ej_)hzMZqhPW~1W2?FE+lbQJ_r4^WI;J%iJZa>~UBBFS((P@} zx37NwUhTxK#I85*j&6$e6=*DY(?s56P(*LKvYTz=0u@Q1M{rrvs1EU$@U+^hS7$;}`xlhvSt5`YM~geE;?%*5K<#hg-ATuijFToc!k2 z_3xhGw6Ny;w`FS&&#O0?@Kt*LmaVz*v!fJ373LgZk(_K3`}T98=&5waDc{aHNBU2) z@h+P82Ov(wHK8Vwvy6!z*7NjVP+L zT(W!j+5B6LuT$rLdVIO&|7YJ<+n(h{E<1MR&<)j33 literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/border/bordersFinish.imageset/Contents.json b/SOPA/Assets.xcassets/border/bordersFinish.imageset/Contents.json index 3e8a31a..642f573 100644 --- a/SOPA/Assets.xcassets/border/bordersFinish.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/bordersFinish.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "border.png" + "filename" : "bordersFinish.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/bordersFinish.imageset/bordersFinish.png b/SOPA/Assets.xcassets/border/bordersFinish.imageset/bordersFinish.png new file mode 100644 index 0000000000000000000000000000000000000000..9291faf3ec10ffcf43d58721ab84cbf55b664155 GIT binary patch literal 10621 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 literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/Contents.json b/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/Contents.json index 3e8a31a..f25dac8 100644 --- a/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "border.png" + "filename" : "bordersFinish_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/bordersFinish_filled.png b/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/bordersFinish_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..155afa9c342218c0fcec932ce47fed9ccdeb11f7 GIT binary patch literal 7048 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 literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/Contents.json b/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/Contents.json index 3e8a31a..7d11783 100644 --- a/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "border.png" + "filename" : "bordersStart_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/bordersStart_filled.png b/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/bordersStart_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..0e0b7fa6c103c6f3587907228d4763411f1bafe2 GIT binary patch literal 7671 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 literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/tiles/a.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/a.imageset/Contents.json index a39878b..adf2f50 100644 --- a/SOPA/Assets.xcassets/tiles/a.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/a.imageset/Contents.json @@ -9,4 +9,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/a.imageset/a.png b/SOPA/Assets.xcassets/tiles/a.imageset/a.png index 6dfbddb6f9f41f8383a178d974318b854551b762..d1e26b2fab8bc9b62628de8dd9c31257f3760a23 100644 GIT binary patch literal 2825 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 literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/tiles/c_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/c_filled.imageset/Contents.json index f71c70d..67739fc 100644 --- a/SOPA/Assets.xcassets/tiles/c_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/c_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "c_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/c_filled.imageset/c_filled.png b/SOPA/Assets.xcassets/tiles/c_filled.imageset/c_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..a4d24d419e6c311535e176971609e5a8df90986b GIT binary patch literal 3638 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 YDIb#)$TIcCM;ji1fVVMgF!81Q58Yv9bpQYW literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/tiles/e.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/e.imageset/Contents.json index f71c70d..3004157 100644 --- a/SOPA/Assets.xcassets/tiles/e.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/e.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "e.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/e.imageset/e.png b/SOPA/Assets.xcassets/tiles/e.imageset/e.png new file mode 100644 index 0000000000000000000000000000000000000000..608b4b7e46ce2ae443d889796404cc6bab1df684 GIT binary patch literal 6305 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 literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/tiles/e_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/e_filled.imageset/Contents.json index f71c70d..0d614b3 100644 --- a/SOPA/Assets.xcassets/tiles/e_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/e_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "e_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/e_filled.imageset/e_filled.png b/SOPA/Assets.xcassets/tiles/e_filled.imageset/e_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..23b5e47598f85176c57d845f422c72083a1987cc GIT binary patch literal 3536 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 literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/tiles/g.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/g.imageset/Contents.json index f71c70d..9831764 100644 --- a/SOPA/Assets.xcassets/tiles/g.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/g.imageset/Contents.json @@ -9,4 +9,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/g.imageset/g.png b/SOPA/Assets.xcassets/tiles/g.imageset/g.png index 78f7814f766a5fa0393c238aa9002a6a66a05c37..804a12b8379beb66a0105f62981d078257f1fd9f 100644 GIT binary patch literal 6185 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 gzX3bkzy1N6{og^AVl<6!WqyE;<|Bn1CXj z1s;*b3=De8Ak0{?)V>TT$X?><>&pI!lY>Wte>RK23!so}W=KSdbAE1aYF-JD%fR4V zl$uzQnxasiS(2gP?&%wlqL<3fz`zmc>EaktaqI2f{eHoW62~6)7X@kEh`Qq8wTiR8 z$+MY6>V37qOIKScd(ivY`@3tpr zGuBW2uK3UWx%lLzw_5oazBepj1|o(gJ_Zge289&jfRZhsIR%_e8gpK`C9(*VDC|wT z#-{MX;TQMbuO~P_j0NX)B^X&`J>*-;t8b0zk!SkDq>b1KP7`wb=GvV7gku|Lac|NcR=P{O`)Mirav4 zM$-ItQa~F{to!oQ5~w};Yl`lotEMbK=FP9i#Fc?5@TLBj^w%JBjHGjGyE8<<+O((N zlLR@ec5c3$i-@i>P~FP)pQp-#G|Y_U?N{oO1c^n4-d(!l*Lq&YCcYFiDLGdkUr8pQ zF6qx}*9UH|2AQ-;Yd+tin7;`eK+k+F{r7Em{MKfW5%2zW%y=mQRD9yqtFwt$`oqCy znBKDKcy;sw$ai7a*W0=OEV>V3Kbz5i-CmOEpTT)GmUm||J|9R0hDl1<*Jl^fpMe5m z`fmF_rq)0|h5jrl_`KBc-+39J!I@jWetq^jrj}U-DC2)Mw(H`D6U|}3@L992&E5RG zarsoBOMdp4@h^(G*{PA%1N6ia?w{ATYfQiADx#YV@@a_wwMSKty+h7lQjYTk9T^W&%AE5x=ho)zDjaZZwzMfS(?q`MkQT{l-m_b-|k z-LdO#_o~k}ul)W$d(Qq@@UpM7qfv1Ba`mq&`fu!HqPZE~-S~MlW&8Hy%b5koTI3L! zQ#sVAh>^pj1HzYda%hYC`uwin@A_Te|G&?D-S_KxUf1<{?)!PJ>v^v0PCMe}q$sZ? zFCihJ2;eZpE#&{>V41CW->3Ta7RUs-IAJ7y|6?U>6^t!L?mW&nNsUB)hJD-_QWud0LVG?2!XFZ-h(vyv7Na;m!gr-xy@d<6krdc2Y1SElO z)bgEF(oXaL-VKHFQ`o-vhrNeK+;Az-K!0XYl^tbSfe7VqDm)0uIwinSQDAaJ>5cbR z`{bzM9%0+DI@6afK{1*j6vTScJA-}+y65sXv;-tr_(+>(M&n!~bXgXZ9cT(yrdUfvnwQVdUITKSN^%6fqD9(6&oF^{`8o_WtP{W&@y2 z@)Uf>W&cSu1!+Ej7QEg&34tXkasy8w8yJNIa z);&>@C}ia942u2)Tq?6|0!C8(=%?h6GYD4A#L%nuL^_43$srwKI7=eBbZA?Un(ym% zZ2|~=)#y4R=G&tc9hfP3IBm#-FGXEygU-4IGRs_YLQ_=0mTg_r;V3G%C(ol4I2ues zOl(81`O$gyZ6=-UAy%-j-I_RA#qBDB|Aa4{hM>A;co_o6c`@0?FN3Mz0e@X)vDZT^s)6{O2=SI`?7D1W9%V6#GV z%X-BY{mABB03>)oI*WCS-mTapZouZOs%r0?Be2DGCsEWrYfJB9W#Qqn(v<~-8hK)F zFp&|2%`P2-I~N{bV(-GrnDPxI$86;qOwqg{Zh&+PV{*6`YnJ>+lx~;$3@|`DYugxy zRrikS7&zm_fQ@|W1|nzF-HS@?RiAZo-&gn)@N3<(SMc>8u0|s&)#n9#5)1cr8n^to z7w>;gal1Qx=WfUJ7DwhaCL0f_6iw2rjEZlPdxpv?1=C$P*=t#)Qica2M|-Td19XhI zoud{9oW!~>B3?Q;55aJv;EM9B`#g=-#fDJp7|O<>!TOzQc;!>gPJ@389Fl^IBC9%9 z4ow_zekaD#t}(k~Hv9eheVcZlgu%(8^XK+QIxFqdZp}L*{#jY>UfL=H6oPdf)Mo}V zS2}a88M)TyDrF1C=Q?h&Uv>PtyY{L^2g|(%8~I7lbz&#V0Vd6ljJhk_2Dgp_u;&j% zM(&;^V@wWcmP74aSLQ`CYow+yx!1M_^SG?A)FJ=+`x+kt^@k36gNdM2_O8AnUMtk@ ze7xU7+Mc6^2TqQBKuPBt$1{yA-xL<=INQFFN7~P}8z=A9F)}`cX-GTw_9Np`{O#Ft zH`c-CRi6kC{#O%bf)>J9$?TD(I=a2U=$ApC-K#mq2Cn(95|ecE!y+QAwhn&sM1*Ve zJ6hCte~<7$?^TYKf&27^ZVfTjt~;d#^7Mjq{0;<#m{^#(ueky+bGh41Jt5RgpIA7~ z&1SYcYkWKz9r65uv}o~U*E|g9eEJK5yejLkE&-N|+uMSI?1~eCz>*jh&^8q0N2$?~ zxUlsvtzgLnQfgV(KJerx2di!rouDe_`1%h@ksw!CZ1na;(FM@uO{>RAX4J|}mLx`T z>$R%Cj?atO-BV}7KE0(rp5YVbHxgTWvwG|6dC-xH)xDZ@QQrO3;?aZ~mg!Kwgme~r zsS$R^0kWv#YmF6&3`4+@HW>%<&Dr4Wc!o5iX*ZfGc2bp(1!?ZcpgYlWdo7>q%VnE) z-*r~ixA@4B@mbk-i0wF18^Nw`(^KhDW<$&KZpLDKkaff zFJ5%|NTcOT*S=_*>?=D?P7uu;YfqSmDVtKmb%GHLQ`cCPoYUEXGXAUv;*@=g(d&I9 zT6n)ys8pwGA2nJhIK)@Swz{=b`KkI!~tFjJv;0z1>h6S*CI!kl=|wJxgW{IbSJ z^m~{fl+<@PzUYF0OFvcgckZ!b^|0YpFmp+O7&DW>SKqD>bM~32z8|3xzs?$9sqJ^P1{Y+=fC~PDTC{r##cZR34q^u*e9q z!qOLu6(h5c^mV+JT-#4J!LJ!G?3`!KQ#OFAr59mV3asJ;?f3=zp(NG=MsQ1;WQ5XYRhwjz0fvv-&-Uf1&k*kZ!{L7Y8kT#Y&# z#1&K>$lplbk_7y6F!=qJQ{CUTBjQi(K+skK#y9aPLUtJ{<17%Vf9^;E18BsMoKV2(Q_dGKwLYVi@Sq+v|?^w|4(OPvX zDx{))6Qx-p9wi{(ri$py9h@Gg3q>_zrA7#nO8 z_`cdWI8w6{?UcX3#nKFbKJtZ;nlGNWOdP81U~r@#{Mh*2j_#`Nb~Ge!;LX`KrL6LHRMQEObM|n<@%+D^mppgnt(5!HKgRxPbJ0^}-&x@Pzh>tB zUJCnO2@rsD!2Ojv&iB8xt{6RfOPOi7m%cjHJn&KBF9OgMzPcZXtSW<8z%=;Fd zqDL+{?J;0-Lq~B8H3tiCBJ*`At~9b|P*{4sMy15^>FuMPlAv(_s1n{OJ5!Tkj3`gJXew5eOOTr8_A+1IIrHKD_@4K<=id9=-#O27KiqTA@0WGb-FYYU z02BlQ?Zlu_p0dpS%OTrjk#V;Ejx25ScXdXAHvY1^Z8a1bBKI>I9}5D>@A=C&fl7bf zCj+;~VcZ z2EFcqk82B0zzVOcAHaueRn;$}C|YZi*57(-k1G?M6}8&*lsgQRe7Eu)i{J=lgnmT$ zN371Agk~7Te|0*BMt?@v;y)AZJ>}gRfzy3(UQ%N^ra-h&OMUNP$F67{(T*LqQ9~ac zz3vgWzV7%=NN8M}U$Of1CkUBunAMUzQ|B448YoYx;Q5yi@r9SheY(D%hqyUsJ>B3F zKg`6M;K!vbSMmDr*G_e=_=ZF;+LO91179f_hp9sDth^#G_E7yiee~C zL}N&zj~va1#Twh7ND|!_7u4JnGXOQ`mZ{jzCPHTdIZhannd*5)Ukj5rW=mMf1tIjj z&31%D7D35|6JBIdfpxq+sivg!+Y{|P_!1*D)9;KSgYqc8Hh#H9r4;w>$k5{=r`u*| zxb@o#`YSr?po+emFXuToOsg&TTuq2c+#Mdp+rPop=sAGwqTnYRJ2-ZeiU@-1MmJHP4yAMr?a1c&dNqRO|gK?7lDT zR|G-6sjx|Jto>B5J!w+u>!dkZpjcPj&7}0<`ogAK-1`^;`K{mNH~Mmc zba?|xTysok9ese%$A6{#+02`73zb75NtIRJqrCSAZZ}ZqgoBQ&FN?#@P|oYK&ve(D zAp1~Cyh`WM5*}`_{=>`S5+O-)>1x8yCnl#0m0ocj8vnlN_+HEUIq z;X94gj-yDrwMAH++GaOKa$X?8H_hVI7Nom5*_42Y??{4E1(1sB+uzVa4L2QUBXsOt zph3Z9?E+s=+!8rvdH2|%r={c@iU!~WPU-Vj)#>W-jO*!Lqpu74F{6)1T3CTf$QaBA za25^4rVHr(al^CUO%itSqi11TPQgSs;%QIz!zU+O=NIM3&bM09M$j0uB&IUJbymhp z8|UAj7>$T3a-k^&AWQcnCq6Bt`poHU?K)Lia2W*!Thex9evld_Tq)^ghC~EcaEpM1 z`IEklxtum;yp^aCM6_{rGfseyd3{aKPxM2+R7ob~_e`A;UoYPT6FuE*6RdPegD7I$ zxv7-0b`&10$+eBTDcFmFL6EcBU6eTTv3mpN#1ojlKOnnJEY0-J*c~p;kBza1Sv-Ee zq{*SiqmAK#V>bt5QxSo(+1!clGFN@gmiRPr0`{j>2W$l=Jle6HU{TioOLV>IcBt`9 zApYt{=iT6Cty`2y5@e!R*B0PnG~Zwk`ehc6mDqh>pN@*%L`;tR^Lcoo9^NL$m=nPN zXklhgde^U^X}Oriaz)qzi9b0kKsKilD_u=fQ-+uYOV@`%8eE-s5(RvSw0-x?I!!f= zxVBkQL{aR%LideTypf*|bR=O&TacsxkBjxRAQBh}f`LKE3sI$PBesCO3xI%#pufXE zioapm?H|S8PyP!`1pUu{`oA~e)?~ZcG^zB~3~mB%vWHzOC?Oty%IniF!!W8njWl~S zh^xNZ{4?pmb|Q%6pHhG7efyW`u<_rg@}nQvgDq>(vEqUHV>$UA;AOg1t1#AQc#nGA z?<VzNVFhr+ln3>zHa)f#M}f2U zbo2MP%0psW1OXbIawa`GU^(kjTOiK?BI|N2v-)g-@(b0+Y4)T_$c2JGM=coALfKaN z58lxju$nhfS0YYNYkBk`9XLnSpycjxp>ZgJI8vzPT4!LhA-4(n{;oS~E;Y|bUx31L zE1gYT*arIjlQ*=270T0_dq1LJ>tGMfHzTx>h&RY2WOml;FVpl_aC!Y3?SEl zF4Dw%v_F|=9XI;;mqGqz!ul;%^O>+e1_$ z8=P&Osl7Qq!pZ^4a~%zHh9Bt$iu{ks7?!X5NwyPnv%g7=nZg zYJ!G78^qAJXrRYjFpi`NJn6^@cxy_e7b{&;9pE-*H(1JN&Tb^5^d2d`fFo{~Ib6(s zisi9Cfq9)=9l397HwtxgF5HKnwlUAh;JF;EPB61VNGcT|F%zYM(!4i!_zYvQhVJ=o zxMFcdylHJpt155YB-F4{ax-9~KyDytt5E94f*6MvZd88C6U_acfj+Nm()L zF+S*c?1eokpxUbum+Y4V#uW2jr?29$OB_#iv2?*Zr_O2N)j7*#@BAkZ3T1>l@gKs) zVJ#G9?0!^?q(m*a-Om7e&4{L!_XqZVK&E4mTPg6F9hoVLRadK*sD1`;vuFlPL|@?cGus#BpXAQiIFgGd5P7U3+((-7hdpgqV+Z_8tTO6n zAu?6=LTmh|G>2VaOH3eb`e*fnRx+tJx!6{_(zT{;Dg4wB)tS)Wu!U$twM6U;X^5;g zIi3NW?!Kzp7Z-AKFHCglR3K|L&FIt7-~2&0>ChyQ-G40x;iq%NRy> NFplo1dWQ?de*=e_+rj_< literal 6664 zcmeAS@N?(olHy`uVBq!ia0y~yVEh8Y9Be?5)7S2I0V&4fAa^H*b?0PW0y&%o9+AZi z40_5S%viD1z6_`!GBYHiB*NFnDmgz_FEJ%QDOIl`w*aV`fx)K23dqb&ElE_U$j!+s zwyLmI0;{kBvO&W7N(x{lCE2!05xxNm&iO^D3Z{A{dIm~%TnY*bHbp6ERzWUqQ0+jT ztx`rwNr9EVetCJhUb(Seeo?xG?WUP)qwZeFo6#1NP{E~&-I zMVSR9nfZANAafIw@=Hr>m6Sjh!2!gbC7EdmoAQdG-U511A0(r1sAr%LHyfzc1|(|b zUzC{&v>9Z+ouLg_C5jl-9vgj-HE^m)Q^ITam)BptkI0txSRcFUz?-yZb2+s`PsH52vxfWTlgIP#@Bdf( z!L{MQY^M8lb@iX0*Zq)a+;3lJ`|RjFuLo>Q^Yb<=7UdLBaA;s?WM*`a?x9fk(0dk*%u)vpRDP3UK`EYam`ckRm-T>mwYFJ>d9t<=`!66~a;llFs$(BG5 z2q;`&VcBarQwiuZMyAV5O?I_%ry-`QDZDs)QUv5nff-@~UtWHW=mVQ-!swXqw+w9A zfh`RS{O2Dp0-L&klcTEG$Q5kaGFFz_UpCW}!KV5;T$nvs6Kt7*uELAU&o|0|P0e6x z8uh|xEPzt&XhIsz3!^2{XrVe)evc0-)<>Y4^d*mC>zkeQHDZamRzw`S?J$0RvS^l)w z39ac@1rH{Nb8-Bddg~r!nAuNA;LGHMS{?AP!Hyt@3+u0Z(*qAQ7ffncFrVdhGI-4X zz*MHDx~1F7A>-PUSy=w6M96!C>y&jO0$;SZ?(GJTY|l|qc%gpqR|Ld_r3dOKZWIWd Tf42MyXnMxe)z4*}Q$iB}58?S0 diff --git a/SOPA/Assets.xcassets/tiles/i_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/i_filled.imageset/Contents.json index 98c4b4e..f8e2ec5 100644 --- a/SOPA/Assets.xcassets/tiles/i_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/i_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "i.png" + "filename" : "i_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/i_filled.imageset/i_filled.png b/SOPA/Assets.xcassets/tiles/i_filled.imageset/i_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..3754665888be721a93828437c3a9a398d287bc09 GIT binary patch literal 1643 zcmai#dr;C@6vycordDp6l|78GqIN6S(b9Zn*y<>dk1Z+Dl%~vADnx0KLXjC-sVHT> z!pzF`^~-!4E2}+3QhU&-h@xqxrHCa#@(`Bx*{px;-1*)!^O^5G=iZrf=BD_1dl)TT zy$}L{7@<7f_UU5VR~anO?G^qYOBWU#!Faeqv|rb)reda!7!o~kCn1nUre8G=lAFI$ z2lY>(yxjE%p^HqGZg_^Cq(C5gGblG#zca!~VGv;rch#k#u&g`c20Ze@N+bSTU{|)UfBMb!aYq{h!3Ifa9!Yj@Ea$O1fXzI}vwzoF@ST`OYCX?ArY1BVz)f2It0K_?; zX|K#F`o!eNPxMzZY6D0=Zu_tsr5q|9I(kV9b_z7p+ygHnTsj}8|7;miU}d9} zgk`wtD#Vqn4xv{HPh+n%E#^xGCj+X$!s)DSBx&{puByD|owF$Rn7sa2Ej=CW~dqVDX8k0%L;$bnG zCYKM2_wTQD^`*bz>TTzcPg^QZQ5&N>i={Q6;yNC!XJA@SY|f+#B!?1+vGJEdeetMq z8@VoERcD4D&Obu%UKO=9R)@e0c3txQKTa?3VqZhdAp~q zHVAdEN-T>FizO~FX(JakG~s&U;%mY70@rR^VQ5#&*x^6~qrJZ|^(T0MfShu=A+RR| z=#5dJodB{p<(znVK@(k`D=<|wYfIA;G{8`c^nRuj;dq7NV#)LDVf<@7o9n*gkCMdZ z9Z439YA$i=9bcnas~T3`d@#9+9eoYSvdgNYY*y{?9%Cn(x9)%ye-9;%R!@o8a=(E{ zzADIY?5Q>2#u_E)XD(V?&;qieCzzz!}vrK`IXJoXD=UhaMsABC3** ztj}r-BJDhKM$w;Au?etld17H~>SYdC4?VG96c9H?KVse|I~;_=jg1Wqpilsqr!zxz zLVk-Nf8hT?e;xkTH3$7u;$PQXH0&|A-keOpyg6uYCSVTw=9Pb6;XkM@01;i9xTdvR zoeL#p*k8rMoeK;%FQsLNuQy3H9Cgs8oPN!bI^J1yG0d^$SM^ro7JqjriI>uS6$dxp zP2U^KCWS|>H)&&fS2pRoi{MaV%60zKtT&x6|0r^!X5Yk@BJUZaQwM$#Y&xSAT`iGt z3i)VVN0^iP-s=nQ$rHtKt#x7IIxOHYku?#Ah%ke_47WZ0-6)Xbwo)W#+R<7j!SFpM zZEr>Ar9%h7@4V~mUR_vc%zH3qWei)!pIjR+5bhl=cY$Hp>n^2OF%12Vngfa)mXn0i zZ{ES)SerX9u>3>oOFW>b?aC%H7)y+;jNC?~BFhUjP6A literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/tiles/o.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/o.imageset/Contents.json index e4460a0..15d73a9 100644 --- a/SOPA/Assets.xcassets/tiles/o.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/o.imageset/Contents.json @@ -9,4 +9,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/o.imageset/o.png b/SOPA/Assets.xcassets/tiles/o.imageset/o.png index af9eb82cc84a7bdea58a04e78ea78e6dc0e097fa..6f082fbcb53023d922fd27073e03c0a7effbd143 100644 GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yaTa()7Bet#3xhBt!>lS|xv6<249-QVi6yBi3gww484B*6z5(HleBwYw x8$DedLn>~)J;=xiTWe3_&=IP=XQgQ3;bwjQL3IeW<%F~*@?{{A}`Bv3IyPZw0 z0!eAF*4BN#u{i$uT!se!(`pP1j_eF9vJ3(x3=S6qKhD7*k61Ro*<<-?T#`3NP{)&jJ00Bl9}8z?N0`Ly#Zakfx9go(2i zB@zZcQcxm+rU{U^;28vwg6z;ikY)!#qztk&I|w2Lr8GyR*?i5-?S?(940hn^0aQ0& dsV=5fFgTQOd)zob@f)aW@^tlcS?83{1OTicx%&VB diff --git a/SOPA/Assets.xcassets/tiles/u.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/u.imageset/Contents.json index f71c70d..7754ae7 100644 --- a/SOPA/Assets.xcassets/tiles/u.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/u.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "u.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/u.imageset/u.png b/SOPA/Assets.xcassets/tiles/u.imageset/u.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7529f1f488ec21070849c73cf20209386e9a13 GIT binary patch literal 6001 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 ZH}Y;MI)B+iliTG0LtPV{CL}uMe*p9p5!nC$ literal 0 HcmV?d00001 diff --git a/SOPA/Assets.xcassets/tiles/u_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/u_filled.imageset/Contents.json index f71c70d..64e76c4 100644 --- a/SOPA/Assets.xcassets/tiles/u_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/u_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "u_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/u_filled.imageset/u_filled.png b/SOPA/Assets.xcassets/tiles/u_filled.imageset/u_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..7280c4662725d9b00c0aeb7fa7f94cf63dee3992 GIT binary patch literal 3228 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 literal 0 HcmV?d00001 diff --git a/SOPA/view/LevelMode/LevelModeGameScene.swift b/SOPA/view/LevelMode/LevelModeGameScene.swift index 059ed47..9245d6b 100644 --- a/SOPA/view/LevelMode/LevelModeGameScene.swift +++ b/SOPA/view/LevelMode/LevelModeGameScene.swift @@ -12,8 +12,10 @@ class LevelModeGameScene: GameScene { var restartButton: SpriteButton? var levelChoiceButton: SpriteButton? var start: NSDate? + override init(size: CGSize, proportionSet: ProportionSet, level: Level) { super.init(size: size, proportionSet: proportionSet, level: level) + backgroundColor = .black startCounter() } @@ -31,27 +33,132 @@ class LevelModeGameScene: GameScene { required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + override func addButtons() { - let restartSide = proportionSet.buttonSize() - restartButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "arrow.counterclockwise", side: restartSide), onClick: restartLevel) - restartButton!.position = proportionSet.restartButtonPos() - addChild(restartButton!) - - let side = proportionSet.levelChoiceSize() - levelChoiceButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side), onClick: loadLevelChoiceScene) - levelChoiceButton!.position = proportionSet.levelChoicePos() + let backSide = min(size.width, size.height) * 0.09 + levelChoiceButton = SpriteButton(texture: makeMinimalBackTexture(side: backSide), onClick: loadLevelChoiceScene) + levelChoiceButton!.position = CGPoint(x: size.width * 0.08, y: size.height * 0.95) addChild(levelChoiceButton!) + let restartSide = min(size.width, size.height) * 0.18 + restartButton = SpriteButton(texture: makeGameplayCircleButtonTexture(symbolName: "arrow.counterclockwise", side: restartSide), onClick: restartLevel) + restartButton!.position = CGPoint(x: size.width * 0.86, y: size.height * 0.12) + addChild(restartButton!) + } + + override func addStaticLabels() { + addChild(movesLabels) + + let headingColor = UIColor(white: 0.90, alpha: 1.0) + let valueColor = UIColor(white: 0.96, alpha: 1.0) + let titleFont = "Impact" + + let minTitle = SKLabelNode(fontNamed: titleFont) + minTitle.text = "Min. Moves" + minTitle.horizontalAlignmentMode = .center + minTitle.verticalAlignmentMode = .center + minTitle.fontSize = min(size.width, size.height) * 0.055 + minTitle.fontColor = headingColor + minTitle.position = CGPoint(x: size.width * 0.14, y: size.height * 0.90) + movesLabels.addChild(minTitle) + + let minValue = SKLabelNode(fontNamed: titleFont) + minValue.text = String(gameService.getLevel().minimumMovesToSolve ?? 0) + minValue.horizontalAlignmentMode = .center + minValue.verticalAlignmentMode = .center + minValue.fontSize = min(size.width, size.height) * 0.15 + minValue.fontColor = valueColor + minValue.position = CGPoint(x: size.width * 0.04, y: size.height * 0.84) + movesLabels.addChild(minValue) + + let currentTitle = SKLabelNode(fontNamed: titleFont) + currentTitle.text = "Current Moves" + currentTitle.horizontalAlignmentMode = .center + currentTitle.verticalAlignmentMode = .center + currentTitle.fontSize = min(size.width, size.height) * 0.055 + currentTitle.fontColor = headingColor + currentTitle.position = CGPoint(x: size.width * 0.84, y: size.height * 0.90) + movesLabels.addChild(currentTitle) + + let levelNumber = SKLabelNode(fontNamed: titleFont) + levelNumber.horizontalAlignmentMode = .left + levelNumber.verticalAlignmentMode = .center + levelNumber.position = CGPoint(x: size.width * 0.04, y: size.height * 0.11) + levelNumber.fontSize = min(size.width, size.height) * 0.15 + levelNumber.text = String(gameService.getLevel().id!) + levelNumber.fontColor = valueColor + addChild(levelNumber) + + let levelLabel = SKLabelNode(fontNamed: titleFont) + levelLabel.text = "Level" + levelLabel.horizontalAlignmentMode = .left + levelLabel.verticalAlignmentMode = .center + levelLabel.fontSize = min(size.width, size.height) * 0.06 + levelLabel.fontColor = headingColor + levelLabel.position = CGPoint(x: size.width * 0.04, y: size.height * 0.045) + addChild(levelLabel) + } + + override func addDynamicLabels() { + currentMovesNode.text = String(gameService.getLevel().movesCounter) + currentMovesNode.horizontalAlignmentMode = .center + currentMovesNode.verticalAlignmentMode = .center + currentMovesNode.fontSize = min(size.width, size.height) * 0.15 + currentMovesNode.position = CGPoint(x: size.width * 0.80, y: size.height * 0.84) + currentMovesNode.fontColor = UIColor(white: 0.96, alpha: 1.0) + movesLabels.addChild(currentMovesNode) + } + + private func makeGameplayCircleButtonTexture(symbolName: String, side: CGFloat) -> SKTexture { + let textureSize = CGSize(width: side, height: side) + let renderer = UIGraphicsImageRenderer(size: textureSize) + let image = renderer.image { _ in + UIColor(white: 0.96, alpha: 1.0).setFill() + UIBezierPath(ovalIn: CGRect(origin: .zero, size: textureSize)).fill() + + let config = UIImage.SymbolConfiguration(pointSize: side * 0.52, weight: .bold) + if let symbol = UIImage(systemName: symbolName, withConfiguration: config)? + .withTintColor(UIColor(white: 0.05, alpha: 1.0), renderingMode: .alwaysOriginal) { + let symbolRect = CGRect( + x: (side - symbol.size.width) / 2.0, + y: (side - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + } + return SKTexture(image: image) + + } + + private func makeMinimalBackTexture(side: CGFloat) -> SKTexture { + let textureSize = CGSize(width: side, height: side) + let renderer = UIGraphicsImageRenderer(size: textureSize) + let image = renderer.image { _ in + let config = UIImage.SymbolConfiguration(pointSize: side * 0.72, weight: .semibold) + if let symbol = UIImage(systemName: "chevron.left", withConfiguration: config)? + .withTintColor(UIColor(white: 0.86, alpha: 0.92), renderingMode: .alwaysOriginal) { + let symbolRect = CGRect( + x: (side - symbol.size.width) / 2.0, + y: (side - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + } + return SKTexture(image: image) } func restartLevel() { ResourcesManager.getInstance().storyService?.reloadLevelModeGameScene(levelId: gameService.getLevel().id!) } - - func loadLevelChoiceScene() { + + private func loadLevelChoiceScene() { ResourcesManager.getInstance().storyService?.loadLevelCoiceSceneFromLevelModeScene() } - + override func onSolvedGame() { let time = stopCounter() let level = gameService.getLevel() From 6ff50cd04008af8083cabdda390c95df5df8715a Mon Sep 17 00:00:00 2001 From: David Schilling Date: Wed, 25 Feb 2026 21:41:32 +0100 Subject: [PATCH 3/3] Refactor LevelMode scenes with shared UI helpers --- SOPA.xcodeproj/project.pbxproj | 42 ++ .../view/LevelMode/ButtonTextureFactory.swift | 169 +++++ SOPA/view/LevelMode/CreditsScene.swift | 89 +++ SOPA/view/LevelMode/LabelSizing.swift | 47 ++ SOPA/view/LevelMode/LevelChoiceScene.swift | 705 +----------------- SOPA/view/LevelMode/LevelModeGameScene.swift | 56 +- SOPA/view/LevelMode/SopaTheme.swift | 17 + SOPA/view/LevelMode/StartMenuScene.swift | 106 +++ SOPA/view/LevelMode/TutorialGameScene.swift | 103 +++ SOPA/view/LevelMode/TutorialScene.swift | 139 ++++ 10 files changed, 730 insertions(+), 743 deletions(-) create mode 100644 SOPA/view/LevelMode/ButtonTextureFactory.swift create mode 100644 SOPA/view/LevelMode/CreditsScene.swift create mode 100644 SOPA/view/LevelMode/LabelSizing.swift create mode 100644 SOPA/view/LevelMode/SopaTheme.swift create mode 100644 SOPA/view/LevelMode/StartMenuScene.swift create mode 100644 SOPA/view/LevelMode/TutorialGameScene.swift create mode 100644 SOPA/view/LevelMode/TutorialScene.swift diff --git a/SOPA.xcodeproj/project.pbxproj b/SOPA.xcodeproj/project.pbxproj index 575c50f..90a4f0f 100644 --- a/SOPA.xcodeproj/project.pbxproj +++ b/SOPA.xcodeproj/project.pbxproj @@ -112,6 +112,20 @@ 64EFCB3E240E732F00714E7C /* LevelDestroyer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EFCB3C240E732F00714E7C /* LevelDestroyer.swift */; }; 64EFCB40240E763A00714E7C /* LevelSolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EFCB3F240E763A00714E7C /* LevelSolver.swift */; }; 64EFCB41240E763A00714E7C /* LevelSolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EFCB3F240E763A00714E7C /* LevelSolver.swift */; }; + A11E00012602000100AAB001 /* StartMenuScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA001 /* StartMenuScene.swift */; }; + A11E00012602000100AAB002 /* StartMenuScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA001 /* StartMenuScene.swift */; }; + A11E00012602000100AAB003 /* CreditsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA002 /* CreditsScene.swift */; }; + A11E00012602000100AAB004 /* CreditsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA002 /* CreditsScene.swift */; }; + A11E00012602000100AAB005 /* TutorialScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA003 /* TutorialScene.swift */; }; + A11E00012602000100AAB006 /* TutorialScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA003 /* TutorialScene.swift */; }; + A11E00012602000100AAB007 /* TutorialGameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA004 /* TutorialGameScene.swift */; }; + A11E00012602000100AAB008 /* TutorialGameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA004 /* TutorialGameScene.swift */; }; + A11E00012602000100AAB009 /* SopaTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA005 /* SopaTheme.swift */; }; + A11E00012602000100AAB00A /* SopaTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA005 /* SopaTheme.swift */; }; + A11E00012602000100AAB00B /* LabelSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA006 /* LabelSizing.swift */; }; + A11E00012602000100AAB00C /* LabelSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA006 /* LabelSizing.swift */; }; + A11E00012602000100AAB00D /* ButtonTextureFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA007 /* ButtonTextureFactory.swift */; }; + A11E00012602000100AAB00E /* ButtonTextureFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA007 /* ButtonTextureFactory.swift */; }; A1B2C3D424FA000100000001 /* JustPlayServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D424FA000100000011 /* JustPlayServiceTest.swift */; }; A1B2C3D424FA000100000002 /* helper/LevelServiceImplUnlockingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D424FA000100000012 /* helper/LevelServiceImplUnlockingTest.swift */; }; A1B2C3D424FA000100000003 /* database/JustPlayHighscorePersistenceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D424FA000100000013 /* database/JustPlayHighscorePersistenceTest.swift */; }; @@ -191,6 +205,13 @@ 64D66B1A21426ADC00595BD3 /* progbot.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = progbot.ttf; sourceTree = ""; }; 64EFCB3C240E732F00714E7C /* LevelDestroyer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelDestroyer.swift; sourceTree = ""; }; 64EFCB3F240E763A00714E7C /* LevelSolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelSolver.swift; sourceTree = ""; }; + A11E00012602000100AAA001 /* StartMenuScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartMenuScene.swift; sourceTree = ""; }; + A11E00012602000100AAA002 /* CreditsScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsScene.swift; sourceTree = ""; }; + A11E00012602000100AAA003 /* TutorialScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialScene.swift; sourceTree = ""; }; + A11E00012602000100AAA004 /* TutorialGameScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialGameScene.swift; sourceTree = ""; }; + A11E00012602000100AAA005 /* SopaTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SopaTheme.swift; sourceTree = ""; }; + A11E00012602000100AAA006 /* LabelSizing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelSizing.swift; sourceTree = ""; }; + A11E00012602000100AAA007 /* ButtonTextureFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTextureFactory.swift; sourceTree = ""; }; A1B2C3D424FA000100000011 /* JustPlayServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustPlayServiceTest.swift; sourceTree = ""; }; A1B2C3D424FA000100000012 /* helper/LevelServiceImplUnlockingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = helper/LevelServiceImplUnlockingTest.swift; sourceTree = ""; }; A1B2C3D424FA000100000013 /* database/JustPlayHighscorePersistenceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = database/JustPlayHighscorePersistenceTest.swift; sourceTree = ""; }; @@ -377,10 +398,17 @@ 64182B18208DCE0A00CF639A /* LevelModeGameScene.swift */, 64D5B8CE20ADE2680007D982 /* LevelModeScoreScene.swift */, 64D5B8D020B46BD70007D982 /* LevelChoiceScene.swift */, + A11E00012602000100AAA001 /* StartMenuScene.swift */, + A11E00012602000100AAA002 /* CreditsScene.swift */, + A11E00012602000100AAA003 /* TutorialScene.swift */, + A11E00012602000100AAA004 /* TutorialGameScene.swift */, 642E2B3E213B0B1E002669E5 /* LevelSelectButton.swift */, 642E2B41213D257C002669E5 /* LevelButtonPositioner.swift */, 646E6AE8213E80F7001D195B /* LevelButtonArea.swift */, 648558C221412BBD00B61C31 /* EffectSpriteButton.swift */, + A11E00012602000100AAA005 /* SopaTheme.swift */, + A11E00012602000100AAA006 /* LabelSizing.swift */, + A11E00012602000100AAA007 /* ButtonTextureFactory.swift */, ); path = LevelMode; sourceTree = ""; @@ -563,6 +591,10 @@ 64EFCB40240E763A00714E7C /* LevelSolver.swift in Sources */, 6493FB652073D6510044B4E0 /* SOPA.xcdatamodeld in Sources */, 64D5B8CF20ADE2680007D982 /* LevelModeScoreScene.swift in Sources */, + A11E00012602000100AAB003 /* CreditsScene.swift in Sources */, + A11E00012602000100AAB001 /* StartMenuScene.swift in Sources */, + A11E00012602000100AAB005 /* TutorialScene.swift in Sources */, + A11E00012602000100AAB007 /* TutorialGameScene.swift in Sources */, 63F661231F9B346E0043ABCF /* AppDelegate.swift in Sources */, E1AA11122602000100ABCDEF /* SceneDelegate.swift in Sources */, 6446C3031FAC80DC00806A10 /* LevelTranslator.swift in Sources */, @@ -574,6 +606,9 @@ 6446C2EF1FAC802900806A10 /* GameServiceImpl.swift in Sources */, 642E2B3F213B0B1E002669E5 /* LevelSelectButton.swift in Sources */, 64206E6C240EBB71001151B4 /* JustPlayGameScene.swift in Sources */, + A11E00012602000100AAB009 /* SopaTheme.swift in Sources */, + A11E00012602000100AAB00B /* LabelSizing.swift in Sources */, + A11E00012602000100AAB00D /* ButtonTextureFactory.swift in Sources */, 6446C3011FAC80DC00806A10 /* LevelFileService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -632,9 +667,16 @@ 646902C6207F6B7600283F71 /* GameServiceImpl.swift in Sources */, 64847A0B215A382B0012B732 /* LevelModeScoreScene.swift in Sources */, 64206E6D240EBB71001151B4 /* JustPlayGameScene.swift in Sources */, + A11E00012602000100AAB004 /* CreditsScene.swift in Sources */, + A11E00012602000100AAB002 /* StartMenuScene.swift in Sources */, + A11E00012602000100AAB006 /* TutorialScene.swift in Sources */, + A11E00012602000100AAB008 /* TutorialGameScene.swift in Sources */, 646902CE207F6B7D00283F71 /* LevelFileService.swift in Sources */, 646902DA207F6BD500283F71 /* GameScene.swift in Sources */, 642E2B40213B0B1E002669E5 /* LevelSelectButton.swift in Sources */, + A11E00012602000100AAB00A /* SopaTheme.swift in Sources */, + A11E00012602000100AAB00C /* LabelSizing.swift in Sources */, + A11E00012602000100AAB00E /* ButtonTextureFactory.swift in Sources */, 646902C7207F6B7600283F71 /* Level.swift in Sources */, 642EE5BD22423AAF00680612 /* ProportionSet.swift in Sources */, ); diff --git a/SOPA/view/LevelMode/ButtonTextureFactory.swift b/SOPA/view/LevelMode/ButtonTextureFactory.swift new file mode 100644 index 0000000..6f96a9d --- /dev/null +++ b/SOPA/view/LevelMode/ButtonTextureFactory.swift @@ -0,0 +1,169 @@ +import SpriteKit +import UIKit + +enum ButtonTextureFactory { + static func makeCircleButtonTexture( + symbolName: String, + side: CGFloat, + circleColor: UIColor = SopaTheme.circleButtonFill, + iconColor: UIColor = SopaTheme.circleButtonIcon, + symbolScale: CGFloat = 0.42, + symbolWeight: UIImage.SymbolWeight = .semibold + ) -> SKTexture { + let textureSize = CGSize(width: side, height: side) + let renderer = UIGraphicsImageRenderer(size: textureSize) + let image = renderer.image { _ in + circleColor.setFill() + UIBezierPath(ovalIn: CGRect(origin: .zero, size: textureSize)).fill() + + let config = UIImage.SymbolConfiguration(pointSize: side * symbolScale, weight: symbolWeight) + if let symbol = UIImage(systemName: symbolName, withConfiguration: config)? + .withTintColor(iconColor, renderingMode: .alwaysOriginal) { + let symbolRect = CGRect( + x: (side - symbol.size.width) / 2.0, + y: (side - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + } + return SKTexture(image: image) + } + + static func makeMinimalBackTexture(side: CGFloat) -> SKTexture { + let textureSize = CGSize(width: side, height: side) + let renderer = UIGraphicsImageRenderer(size: textureSize) + let image = renderer.image { _ in + let config = UIImage.SymbolConfiguration(pointSize: side * 0.72, weight: .semibold) + if let symbol = UIImage(systemName: "chevron.left", withConfiguration: config)? + .withTintColor(UIColor(red: 160.0 / 255.0, green: 164.0 / 255.0, blue: 170.0 / 255.0, alpha: 0.95), renderingMode: .alwaysOriginal) { + let symbolRect = CGRect( + x: (side - symbol.size.width) / 2.0, + y: (side - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + } + return SKTexture(image: image) + } + + static func makePageArrowTexture(imageNamed: String, side: CGFloat) -> SKTexture { + let texture = SKTexture(imageNamed: imageNamed) + let imageSize = texture.size() + let ratio = imageSize.width / max(1, imageSize.height) + let targetSize = CGSize(width: side * ratio, height: side) + let renderer = UIGraphicsImageRenderer(size: targetSize) + let image = renderer.image { _ in + UIImage(named: imageNamed)?.draw(in: CGRect(origin: .zero, size: targetSize)) + } + return SKTexture(image: image) + } + + static func makeNeonTextTexture(title: String, size: CGSize) -> SKTexture { + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { _ in + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + + let glowShadow = NSShadow() + glowShadow.shadowColor = SopaTheme.neonGlowBlue + glowShadow.shadowBlurRadius = size.height * 0.22 + glowShadow.shadowOffset = .zero + + let font = UIFont(name: "HelveticaNeue-Light", size: size.height * 0.60) + ?? UIFont.systemFont(ofSize: size.height * 0.60, weight: .light) + let textRect = CGRect(x: 0, y: size.height * 0.22, width: size.width, height: size.height * 0.60) + + let glowAttributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: SopaTheme.neonBlue, + .strokeColor: SopaTheme.neonBlue, + .strokeWidth: -1.2, + .paragraphStyle: paragraphStyle, + .shadow: glowShadow + ] + title.draw(in: textRect, withAttributes: glowAttributes) + + let coreAttributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: SopaTheme.neonCoreBlue, + .paragraphStyle: paragraphStyle + ] + title.draw(in: textRect, withAttributes: coreAttributes) + } + return SKTexture(image: image) + } + + static func makeSocialButtonTexture(symbolName: String, size: CGSize, fillBackground: Bool) -> SKTexture { + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { _ in + let blueColor = UIColor(red: 99.0 / 255.0, green: 177.0 / 255.0, blue: 230.0 / 255.0, alpha: 1.0) + if fillBackground { + let backgroundPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: size.width * 0.18) + blueColor.setFill() + backgroundPath.fill() + } + + let pointSize = fillBackground ? size.width * 0.50 : size.width * 0.75 + let config = UIImage.SymbolConfiguration(pointSize: pointSize, weight: .semibold) + guard let baseSymbol = UIImage(systemName: symbolName, withConfiguration: config) else { + return + } + + let iconColor = fillBackground ? UIColor.white : blueColor + let symbol = baseSymbol.withTintColor(iconColor, renderingMode: .alwaysOriginal) + let symbolRect = CGRect( + x: (size.width - symbol.size.width) / 2.0, + y: (size.height - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + return SKTexture(image: image) + } + + static func makeTextButtonTexture( + title: String, + size: CGSize, + fontName: String, + textColor: UIColor, + weight: UIFont.Weight = .semibold + ) -> SKTexture { + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { _ in + let frame = CGRect(origin: .zero, size: size).insetBy(dx: size.width * 0.01, dy: size.height * 0.06) + let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: size.height * 0.23) + UIColor(white: 1.0, alpha: 0.10).setFill() + backgroundPath.fill() + UIColor(white: 1.0, alpha: 0.22).setStroke() + backgroundPath.lineWidth = size.height * 0.08 + backgroundPath.stroke() + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + let buttonFontSize = LabelSizing.fittedTextFontSize( + text: title, + fontName: fontName, + preferredSize: size.height * 0.38, + weight: weight, + maxWidth: size.width * 0.90 + ) + let attributes: [NSAttributedString.Key: Any] = [ + .font: UIFont(name: fontName, size: buttonFontSize) ?? UIFont.systemFont(ofSize: buttonFontSize, weight: weight), + .foregroundColor: textColor, + .paragraphStyle: paragraphStyle + ] + let textRect = CGRect(x: 0.0, y: size.height * 0.30, width: size.width, height: size.height * 0.5) + title.draw(in: textRect, withAttributes: attributes) + } + return SKTexture(image: image) + } +} + +func makeCircleButtonTexture(symbolName: String, side: CGFloat) -> SKTexture { + ButtonTextureFactory.makeCircleButtonTexture(symbolName: symbolName, side: side) +} diff --git a/SOPA/view/LevelMode/CreditsScene.swift b/SOPA/view/LevelMode/CreditsScene.swift new file mode 100644 index 0000000..63a9359 --- /dev/null +++ b/SOPA/view/LevelMode/CreditsScene.swift @@ -0,0 +1,89 @@ +import Foundation +import SpriteKit +import UIKit + +class CreditsScene: SKScene { + private let sectionColor = SopaTheme.neonBlue + + override init(size: CGSize) { + super.init(size: size) + backgroundColor = SopaTheme.blackBackground + addBackButton() + addTitle() + addCreditsContent() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addBackButton() { + let side = size.height * 0.08 + let backButton = SpriteButton(texture: ButtonTextureFactory.makeCircleButtonTexture(symbolName: "chevron.left", side: side)) { + ResourcesManager.getInstance().storyService?.loadStartMenuScene() + } + backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) + addChild(backButton) + } + + private func addTitle() { + let title = SKLabelNode(fontNamed: "Impact") + title.text = "CREDITS" + title.fontSize = size.height * 0.09 + title.fontColor = SopaTheme.titleGray + title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) + addChild(title) + } + + private func addCreditsContent() { + let content: [(section: String, entries: [String])] = [ + ("DEVELOPMENT", ["David Schilling - @schillda710", "Raphael Schilling - @ubuntius"]), + ("DESIGN", ["Raphael Schilling - @ubuntius"]), + ("MUSIC", ["Menu - axtoncrolley on opengameart.org"]) + ] + + var currentY = size.height * 0.70 + for block in content { + addSectionLine(text: block.section, y: currentY) + currentY -= size.height * 0.045 + + for entry in block.entries { + addBodyLine(text: entry, y: currentY) + currentY -= size.height * 0.038 + } + currentY -= size.height * 0.028 + } + } + + private func addSectionLine(text: String, y: CGFloat) { + let line = SKLabelNode(fontNamed: "HelveticaNeue-Bold") + line.horizontalAlignmentMode = .left + line.verticalAlignmentMode = .center + line.text = text + line.fontSize = LabelSizing.fittedLabelFontSize( + text: text, + fontName: "HelveticaNeue-Bold", + preferredSize: size.height * 0.032, + maxWidth: size.width * 0.88 + ) + line.fontColor = sectionColor + line.position = CGPoint(x: size.width * 0.06, y: y) + addChild(line) + } + + private func addBodyLine(text: String, y: CGFloat) { + let line = SKLabelNode(fontNamed: "HelveticaNeue") + line.horizontalAlignmentMode = .left + line.verticalAlignmentMode = .center + line.text = text + line.fontSize = LabelSizing.fittedLabelFontSize( + text: text, + fontName: "HelveticaNeue", + preferredSize: size.height * 0.026, + maxWidth: size.width * 0.90 + ) + line.fontColor = SopaTheme.bodyText + line.position = CGPoint(x: size.width * 0.09, y: y) + addChild(line) + } +} diff --git a/SOPA/view/LevelMode/LabelSizing.swift b/SOPA/view/LevelMode/LabelSizing.swift new file mode 100644 index 0000000..fd4e7e6 --- /dev/null +++ b/SOPA/view/LevelMode/LabelSizing.swift @@ -0,0 +1,47 @@ +import SpriteKit +import UIKit + +enum LabelSizing { + static func fittedLabelFontSize( + text: String, + fontName: String, + preferredSize: CGFloat, + maxWidth: CGFloat, + minSize: CGFloat = 10.0 + ) -> CGFloat { + guard !text.isEmpty else { + return preferredSize + } + + let label = SKLabelNode(fontNamed: fontName) + label.text = text + var currentSize = preferredSize + label.fontSize = currentSize + + while label.frame.width > maxWidth && currentSize > minSize { + currentSize -= 1.0 + label.fontSize = currentSize + } + return currentSize + } + + static func fittedTextFontSize( + text: String, + fontName: String, + preferredSize: CGFloat, + weight: UIFont.Weight, + maxWidth: CGFloat, + minSize: CGFloat = 10.0 + ) -> CGFloat { + var currentSize = preferredSize + while currentSize > minSize { + let font = UIFont(name: fontName, size: currentSize) ?? UIFont.systemFont(ofSize: currentSize, weight: weight) + let measuredWidth = (text as NSString).size(withAttributes: [.font: font]).width + if measuredWidth <= maxWidth { + return currentSize + } + currentSize -= 1.0 + } + return minSize + } +} diff --git a/SOPA/view/LevelMode/LevelChoiceScene.swift b/SOPA/view/LevelMode/LevelChoiceScene.swift index 61ce7b3..f422720 100644 --- a/SOPA/view/LevelMode/LevelChoiceScene.swift +++ b/SOPA/view/LevelMode/LevelChoiceScene.swift @@ -10,30 +10,6 @@ import Foundation import SpriteKit import UIKit -func makeCircleButtonTexture(symbolName: String, side: CGFloat) -> SKTexture { - let textureSize = CGSize(width: side, height: side) - let circleColor = UIColor(white: 0.94, alpha: 1.0) - let iconColor = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255.0, alpha: 1.0) - let renderer = UIGraphicsImageRenderer(size: textureSize) - let image = renderer.image { _ in - circleColor.setFill() - UIBezierPath(ovalIn: CGRect(origin: .zero, size: textureSize)).fill() - - let config = UIImage.SymbolConfiguration(pointSize: side * 0.42, weight: .semibold) - if let symbol = UIImage(systemName: symbolName, withConfiguration: config)? - .withTintColor(iconColor, renderingMode: .alwaysOriginal) { - let symbolRect = CGRect( - x: (side - symbol.size.width) / 2.0, - y: (side - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - } - return SKTexture(image: image) -} - class LevelChoiceScene: SKScene { private let levelInfos: [LevelInfo] private var levelButtonArea: LevelButtonArea? @@ -50,63 +26,29 @@ class LevelChoiceScene: SKScene { addButtons() addPageArrows() update() - - } - + private func addButtons() { let side = min(size.width * 0.095, size.height * 0.07) - menuButton = SpriteButton(texture: makeMenuBackTexture(side: side), onClick: backToStartMenu) + menuButton = SpriteButton(texture: ButtonTextureFactory.makeMinimalBackTexture(side: side), onClick: backToStartMenu) menuButton?.position = CGPoint(x: size.width * 0.085, y: size.height * 0.90) menuButton?.zPosition = 20 addChild(menuButton!) } - private func makeMenuBackTexture(side: CGFloat) -> SKTexture { - let textureSize = CGSize(width: side, height: side) - let renderer = UIGraphicsImageRenderer(size: textureSize) - let image = renderer.image { _ in - let config = UIImage.SymbolConfiguration(pointSize: side * 0.72, weight: .semibold) - if let symbol = UIImage(systemName: "chevron.left", withConfiguration: config)? - .withTintColor(UIColor(red: 160.0 / 255.0, green: 164.0 / 255.0, blue: 170.0 / 255.0, alpha: 0.95), renderingMode: .alwaysOriginal) { - let symbolRect = CGRect( - x: (side - symbol.size.width) / 2.0, - y: (side - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - } - return SKTexture(image: image) - } - private func addPageArrows() { let arrowSide = min(size.width * 0.16, size.height * 0.13) - leftArrowButton = SpriteButton(texture: makePageArrowTexture(imageNamed: "ArrowLeft", side: arrowSide), onClick: goToPreviousPage) + leftArrowButton = SpriteButton(texture: ButtonTextureFactory.makePageArrowTexture(imageNamed: "ArrowLeft", side: arrowSide), onClick: goToPreviousPage) leftArrowButton?.position = CGPoint(x: size.width * 0.08, y: size.height * 0.21) leftArrowButton?.zPosition = 20 addChild(leftArrowButton!) - rightArrowButton = SpriteButton(texture: makePageArrowTexture(imageNamed: "ArrowRight", side: arrowSide), onClick: goToNextPage) + rightArrowButton = SpriteButton(texture: ButtonTextureFactory.makePageArrowTexture(imageNamed: "ArrowRight", side: arrowSide), onClick: goToNextPage) rightArrowButton?.position = CGPoint(x: size.width * 0.92, y: size.height * 0.21) rightArrowButton?.zPosition = 20 addChild(rightArrowButton!) } - private func makePageArrowTexture(imageNamed: String, side: CGFloat) -> SKTexture { - let texture = SKTexture(imageNamed: imageNamed) - let imageSize = texture.size() - let ratio = imageSize.width / max(1, imageSize.height) - let targetSize = CGSize(width: side * ratio, height: side) - let renderer = UIGraphicsImageRenderer(size: targetSize) - let image = renderer.image { _ in - let rect = CGRect(origin: .zero, size: targetSize) - UIImage(named: imageNamed)?.draw(in: rect) - } - return SKTexture(image: image) - } - private func goToPreviousPage() { levelButtonArea?.swipeLeft() } @@ -114,11 +56,11 @@ class LevelChoiceScene: SKScene { private func goToNextPage() { levelButtonArea?.swipeRight() } - + private func backToStartMenu() { ResourcesManager.getInstance().storyService?.loadStartMenuScene() } - + func update() { guard let area = levelButtonArea else { leftArrowButton?.isHidden = true @@ -131,643 +73,8 @@ class LevelChoiceScene: SKScene { leftArrowButton?.isHidden = isFirstPage rightArrowButton?.isHidden = isLastPage || area.pageCount <= 1 } - - - - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class StartMenuScene: SKScene { - private let background = UIColor.black - private let titleColor = UIColor(red: 214.0 / 255.0, green: 214.0 / 255.0, blue: 214.0 / 255.0, alpha: 1.0) - private let neonColor = UIColor(red: 111.0 / 255.0, green: 227.0 / 255.0, blue: 1.0, alpha: 1.0) - private let neonGlowColor = UIColor(red: 23.0 / 255.0, green: 183.0 / 255.0, blue: 1.0, alpha: 1.0) - private let twitterUrl = "https://twitter.com/sopagame" - private let shareText = "I played SOPA: https://play.google.com/store/apps/details?id=com.sopaapp" - - override init(size: CGSize) { - super.init(size: size) - backgroundColor = background - addTitle() - addButtons() - addSocialButtons() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func addTitle() { - let title = SKLabelNode(fontNamed: "Impact") - title.text = "SOPA" - title.fontSize = size.height * 0.14 - title.fontColor = titleColor - title.position = CGPoint(x: size.width / 2, y: size.height * 0.77) - addChild(title) - } - - private func addButtons() { - let menuItems: [(title: String, action: () -> Void)] = [ - ("LEVEL MODE", { ResourcesManager.getInstance().storyService?.loadLevelCoiceScene() }), - ("JUST PLAY", { ResourcesManager.getInstance().storyService?.loadJustPlaySceneFromMenuScene() }), - // Keep this wired until a dedicated Settings scene exists on iOS. - ("SETTINGS", { ResourcesManager.getInstance().storyService?.loadTutorialSceneFromMenuScene() }), - ("CREDITS", { ResourcesManager.getInstance().storyService?.loadCreditsSceneFromMenuScene() }) - ] - - let buttonSize = CGSize(width: size.width * 0.76, height: size.height * 0.095) - let firstY = size.height * 0.62 - let verticalGap = size.height * 0.11 - - for (index, item) in menuItems.enumerated() { - let button = SpriteButton(texture: makeNeonTextTexture(title: item.title, size: buttonSize), onClick: item.action) - button.position = CGPoint(x: size.width / 2.0, y: firstY - CGFloat(index) * verticalGap) - addChild(button) - } - } - - private func addSocialButtons() { - let buttonSide = min(size.width * 0.18, size.height * 0.10) - - let shareButton = SpriteButton( - texture: makeSocialButtonTexture(symbolName: "point.3.connected.trianglepath.dotted", size: CGSize(width: buttonSide, height: buttonSide), fillBackground: true), - onClick: shareApp - ) - shareButton.position = CGPoint(x: size.width * 0.20, y: size.height * 0.14) - addChild(shareButton) - - let twitterButton = SpriteButton( - texture: makeSocialButtonTexture(symbolName: "paperplane.fill", size: CGSize(width: buttonSide * 1.15, height: buttonSide * 1.15), fillBackground: false), - onClick: openTwitter - ) - twitterButton.position = CGPoint(x: size.width * 0.74, y: size.height * 0.14) - addChild(twitterButton) - } - - private func makeNeonTextTexture(title: String, size: CGSize) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: size) - let image = renderer.image { _ in - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - - let glowShadow = NSShadow() - glowShadow.shadowColor = neonGlowColor - glowShadow.shadowBlurRadius = size.height * 0.22 - glowShadow.shadowOffset = .zero - - let font = UIFont(name: "HelveticaNeue-Light", size: size.height * 0.60) - ?? UIFont.systemFont(ofSize: size.height * 0.60, weight: .light) - let textRect = CGRect(x: 0, y: size.height * 0.22, width: size.width, height: size.height * 0.60) - - let glowAttributes: [NSAttributedString.Key: Any] = [ - .font: font, - .foregroundColor: neonColor, - .strokeColor: neonColor, - .strokeWidth: -1.2, - .paragraphStyle: paragraphStyle, - .shadow: glowShadow - ] - title.draw(in: textRect, withAttributes: glowAttributes) - - let coreAttributes: [NSAttributedString.Key: Any] = [ - .font: font, - .foregroundColor: UIColor(red: 183.0 / 255.0, green: 245.0 / 255.0, blue: 1.0, alpha: 1.0), - .paragraphStyle: paragraphStyle - ] - title.draw(in: textRect, withAttributes: coreAttributes) - } - return SKTexture(image: image) - } - - private func makeSocialButtonTexture(symbolName: String, size: CGSize, fillBackground: Bool) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: size) - let image = renderer.image { _ in - let blueColor = UIColor(red: 99.0 / 255.0, green: 177.0 / 255.0, blue: 230.0 / 255.0, alpha: 1.0) - if fillBackground { - let backgroundPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: size.width * 0.18) - blueColor.setFill() - backgroundPath.fill() - } - - let pointSize = fillBackground ? size.width * 0.50 : size.width * 0.75 - let config = UIImage.SymbolConfiguration(pointSize: pointSize, weight: .semibold) - guard let baseSymbol = UIImage(systemName: symbolName, withConfiguration: config) else { - return - } - - let iconColor = fillBackground ? UIColor.white : blueColor - let symbol = baseSymbol.withTintColor(iconColor, renderingMode: .alwaysOriginal) - let symbolRect = CGRect( - x: (size.width - symbol.size.width) / 2.0, - y: (size.height - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - return SKTexture(image: image) - } - - private func openTwitter() { - guard let url = URL(string: twitterUrl) else { - return - } - UIApplication.shared.open(url) - } - - private func shareApp() { - guard let controller = topViewController() else { - return - } - let shareController = UIActivityViewController(activityItems: [shareText], applicationActivities: nil) - shareController.popoverPresentationController?.sourceView = controller.view - controller.present(shareController, animated: true) - } - - private func topViewController(base: UIViewController? = nil) -> UIViewController? { - let initialController: UIViewController? - if let base = base { - initialController = base - } else { - initialController = UIApplication.shared.connectedScenes - .compactMap { $0 as? UIWindowScene } - .flatMap { $0.windows } - .first { $0.isKeyWindow }?.rootViewController - } - - if let navController = initialController as? UINavigationController { - return topViewController(base: navController.visibleViewController) - } - if let tabController = initialController as? UITabBarController { - return topViewController(base: tabController.selectedViewController) - } - if let presented = initialController?.presentedViewController { - return topViewController(base: presented) - } - return initialController - } -} - -class CreditsScene: SKScene { - private let background = UIColor.black - private let titleColor = UIColor(red: 214.0 / 255.0, green: 214.0 / 255.0, blue: 214.0 / 255.0, alpha: 1.0) - private let sectionColor = UIColor(red: 111.0 / 255.0, green: 227.0 / 255.0, blue: 1.0, alpha: 1.0) - private let textColor = UIColor(red: 230.0 / 255.0, green: 236.0 / 255.0, blue: 242.0 / 255.0, alpha: 1.0) - - override init(size: CGSize) { - super.init(size: size) - backgroundColor = background - addBackButton() - addTitle() - addCreditsContent() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func addBackButton() { - let side = size.height * 0.08 - let backButton = SpriteButton(texture: makeBackButtonTexture(side: side)) { - ResourcesManager.getInstance().storyService?.loadStartMenuScene() - } - backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) - addChild(backButton) - } - - private func makeBackButtonTexture(side: CGFloat) -> SKTexture { - let textureSize = CGSize(width: side, height: side) - let renderer = UIGraphicsImageRenderer(size: textureSize) - let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: textureSize) - let path = UIBezierPath(roundedRect: frame, cornerRadius: side * 0.25) - UIColor(white: 1.0, alpha: 0.10).setFill() - path.fill() - UIColor(white: 1.0, alpha: 0.20).setStroke() - path.lineWidth = side * 0.05 - path.stroke() - - let config = UIImage.SymbolConfiguration(pointSize: side * 0.44, weight: .bold) - if let symbol = UIImage(systemName: "chevron.left", withConfiguration: config)? - .withTintColor(sectionColor, renderingMode: .alwaysOriginal) { - let symbolRect = CGRect( - x: (side - symbol.size.width) / 2.0, - y: (side - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - } - return SKTexture(image: image) - } - - private func addTitle() { - let title = SKLabelNode(fontNamed: "Impact") - title.text = "CREDITS" - title.fontSize = size.height * 0.09 - title.fontColor = titleColor - title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) - addChild(title) - } - - private func addCreditsContent() { - let content: [(section: String, entries: [String])] = [ - ("DEVELOPMENT", ["David Schilling - @schillda710", "Raphael Schilling - @ubuntius"]), - ("DESIGN", ["Raphael Schilling - @ubuntius"]), - ("MUSIC", ["Menu - axtoncrolley on opengameart.org"]) - ] - - var currentY = size.height * 0.70 - for block in content { - addSectionLine(text: block.section, y: currentY) - currentY -= size.height * 0.045 - - for entry in block.entries { - addBodyLine(text: entry, y: currentY) - currentY -= size.height * 0.038 - } - currentY -= size.height * 0.028 - } - } - - private func addSectionLine(text: String, y: CGFloat) { - let line = SKLabelNode(fontNamed: "HelveticaNeue-Bold") - line.horizontalAlignmentMode = .left - line.verticalAlignmentMode = .center - line.text = text - line.fontSize = fittedFontSize( - for: text, - fontName: "HelveticaNeue-Bold", - preferredSize: size.height * 0.032, - maxWidth: size.width * 0.88 - ) - line.fontColor = sectionColor - line.position = CGPoint(x: size.width * 0.06, y: y) - addChild(line) - } - - private func addBodyLine(text: String, y: CGFloat) { - let line = SKLabelNode(fontNamed: "HelveticaNeue") - line.horizontalAlignmentMode = .left - line.verticalAlignmentMode = .center - line.text = text - line.fontSize = fittedFontSize( - for: text, - fontName: "HelveticaNeue", - preferredSize: size.height * 0.026, - maxWidth: size.width * 0.90 - ) - line.fontColor = textColor - line.position = CGPoint(x: size.width * 0.09, y: y) - addChild(line) - } - - private func fittedFontSize(for text: String, fontName: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - let label = SKLabelNode(fontNamed: fontName) - label.text = text - var currentSize = preferredSize - label.fontSize = currentSize - - while label.frame.width > maxWidth && currentSize > 10.0 { - currentSize -= 1.0 - label.fontSize = currentSize - } - return currentSize - } -} - -class TutorialScene: SKScene { - private let background = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255, alpha: 1.0) - private let textColor = UIColor(red: 240.0 / 255.0, green: 239.0 / 255.0, blue: 238.0 / 255.0, alpha: 1.0) - private var currentPage = 0 - private let headline = SKLabelNode(fontNamed: "Optima-Bold") - private let textLine1 = SKLabelNode(fontNamed: "Optima-Bold") - private let textLine2 = SKLabelNode(fontNamed: "Optima-Bold") - private let textLine3 = SKLabelNode(fontNamed: "Optima-Bold") - private let tapHint = SKLabelNode(fontNamed: "Optima-Bold") - private var letsGoButton: SpriteButton? - - override init(size: CGSize) { - super.init(size: size) - self.backgroundColor = background - addBackButton() - addTitle() - addContentNodes() - updatePage() - } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - private func addBackButton() { - let side = size.height * 0.08 - let backButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side)) { - ResourcesManager.getInstance().storyService?.loadStartMenuScene() - } - backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) - addChild(backButton) - } - - private func addTitle() { - let title = SKLabelNode(fontNamed: "Optima-Bold") - title.text = "Tutorial" - title.fontSize = size.height * 0.10 - title.fontColor = textColor - title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) - addChild(title) - } - - private func addContentNodes() { - headline.fontColor = textColor - headline.fontSize = size.height * 0.048 - headline.position = CGPoint(x: size.width * 0.5, y: size.height * 0.62) - addChild(headline) - - textLine1.fontColor = textColor - textLine1.fontSize = size.height * 0.034 - textLine1.position = CGPoint(x: size.width * 0.5, y: size.height * 0.54) - addChild(textLine1) - - textLine2.fontColor = textColor - textLine2.fontSize = size.height * 0.034 - textLine2.position = CGPoint(x: size.width * 0.5, y: size.height * 0.48) - addChild(textLine2) - - textLine3.fontColor = textColor - textLine3.fontSize = size.height * 0.034 - textLine3.position = CGPoint(x: size.width * 0.5, y: size.height * 0.42) - addChild(textLine3) - - tapHint.fontColor = textColor - tapHint.fontSize = size.height * 0.030 - tapHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.26) - addChild(tapHint) - } - - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - if currentPage < 2 { - currentPage += 1 - updatePage() - } - } - - private func updatePage() { - switch currentPage { - case 0: - setPage( - headlineText: "Goal", - line1: "Connect the start and finish pipes.", - line2: "Swipe rows or columns to move tiles.", - line3: "Only matching pipe segments will connect.", - hint: "Tap anywhere to continue" - ) - letsGoButton?.isHidden = true - case 1: - setPage( - headlineText: "How It Works", - line1: "Horizontal swipe moves a full row.", - line2: "Vertical swipe moves a full column.", - line3: "Try to solve it in as few moves as possible.", - hint: "Tap anywhere to continue" - ) - letsGoButton?.isHidden = true - default: - setPage( - headlineText: "Try It Now", - line1: "Next you play a simple interactive level.", - line2: "Use restart if you want to try again.", - line3: "", - hint: "" - ) - ensureLetsGoButton() - letsGoButton?.isHidden = false - } - } - - private func setPage(headlineText: String, line1: String, line2: String, line3: String, hint: String) { - headline.text = headlineText - headline.fontSize = fittedFontSize(for: headlineText, preferredSize: size.height * 0.048, maxWidth: size.width * 0.90) - - textLine1.text = line1 - textLine1.fontSize = fittedFontSize(for: line1, preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) - textLine2.text = line2 - textLine2.fontSize = fittedFontSize(for: line2, preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) - textLine3.text = line3 - textLine3.fontSize = fittedFontSize(for: line3, preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) - tapHint.text = hint - tapHint.fontSize = fittedFontSize(for: hint, preferredSize: size.height * 0.030, maxWidth: size.width * 0.92) - } - - private func ensureLetsGoButton() { - if letsGoButton != nil { - return - } - - let buttonSize = CGSize(width: size.width * 0.34, height: size.height * 0.10) - let button = SpriteButton(texture: makeTextButtonTexture(title: "Let's Go", size: buttonSize)) { - ResourcesManager.getInstance().storyService?.loadTutorialGameSceneFromTutorialScene() - } - button.position = CGPoint(x: size.width * 0.5, y: size.height * 0.25) - addChild(button) - letsGoButton = button - } - - private func makeTextButtonTexture(title: String, size: CGSize) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: size) - let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: size).insetBy(dx: size.width * 0.01, dy: size.height * 0.06) - let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: size.height * 0.23) - UIColor(white: 1.0, alpha: 0.10).setFill() - backgroundPath.fill() - UIColor(white: 1.0, alpha: 0.22).setStroke() - backgroundPath.lineWidth = size.height * 0.08 - backgroundPath.stroke() - - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont(name: "Optima-Bold", size: size.height * 0.42) ?? UIFont.systemFont(ofSize: size.height * 0.42, weight: .semibold), - .foregroundColor: textColor, - .paragraphStyle: paragraphStyle - ] - let textRect = CGRect(x: 0.0, y: size.height * 0.28, width: size.width, height: size.height * 0.5) - title.draw(in: textRect, withAttributes: attributes) - } - return SKTexture(image: image) - } - - private func fittedFontSize(for text: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - if text.isEmpty { - return preferredSize - } - let label = SKLabelNode(fontNamed: "Optima-Bold") - label.text = text - var currentSize = preferredSize - label.fontSize = currentSize - - while label.frame.width > maxWidth && currentSize > 10.0 { - currentSize -= 1.0 - label.fontSize = currentSize - } - return currentSize - } -} - -class TutorialGameScene: GameScene { - private var restartButton: SpriteButton? - private var menuButton: SpriteButton? - private var finishButton: SpriteButton? - private let tutorialHint = SKLabelNode(fontNamed: "Optima-Bold") - private let tutorialSubHint = SKLabelNode(fontNamed: "Optima-Bold") - - override init(size: CGSize, proportionSet: ProportionSet, level: Level) { - super.init(size: size, proportionSet: proportionSet, level: level) - addTutorialHints() - updateHintText() - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func addStaticLabels() { - // Tutorial scene keeps UI minimal and avoids overlap with end-of-tutorial CTA. - } - - override func addDynamicLabels() { - // No move counters in tutorial. - } - - override func addButtons() { - let side = proportionSet.levelChoiceSize() - restartButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "arrow.counterclockwise", side: side), onClick: restartTutorial) - restartButton!.position = CGPoint(x: size.width - size.height * 0.057, y: size.height * 0.91) - addChild(restartButton!) - - menuButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side), onClick: backToStartMenu) - menuButton!.position = proportionSet.levelChoicePos() - addChild(menuButton!) - } - - override func moveLine(horizontal: Bool, rowOrColumn: Int, steps: Int) { - super.moveLine(horizontal: horizontal, rowOrColumn: rowOrColumn, steps: steps) - if !levelSolved { - updateHintText() - } - } - - override func onSolvedGame() { - tutorialHint.text = "Solved" - tutorialHint.fontSize = size.height * 0.06 - tutorialHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.30) - tutorialSubHint.text = "" - showFinishButton() - } - - private func addTutorialHints() { - tutorialHint.fontColor = fontColor - tutorialHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.24) - tutorialHint.zPosition = 10 - addChild(tutorialHint) - - tutorialSubHint.fontColor = fontColor - tutorialSubHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.19) - tutorialSubHint.zPosition = 10 - addChild(tutorialSubHint) - } - - private func updateHintText() { - if gameService.getLevel().movesCounter == 0 { - tutorialHint.text = "Swipe any row or column" - tutorialHint.fontSize = fittedFontSize(for: tutorialHint.text ?? "", preferredSize: size.height * 0.040, maxWidth: size.width * 0.92) - tutorialSubHint.text = "Connect start and finish in this level." - tutorialSubHint.fontSize = fittedFontSize(for: tutorialSubHint.text ?? "", preferredSize: size.height * 0.032, maxWidth: size.width * 0.92) - } else { - tutorialHint.text = "Good" - tutorialHint.fontSize = size.height * 0.044 - tutorialSubHint.text = "Keep going until the path is connected." - tutorialSubHint.fontSize = fittedFontSize(for: tutorialSubHint.text ?? "", preferredSize: size.height * 0.032, maxWidth: size.width * 0.92) - } - } - - private func showFinishButton() { - if finishButton != nil { - finishButton?.isHidden = false - return - } - let buttonSize = CGSize(width: size.width * 0.62, height: size.height * 0.095) - finishButton = SpriteButton(texture: makeTextButtonTexture(title: "Back to Menu", size: buttonSize)) { - ResourcesManager.getInstance().storyService?.loadStartMenuScene() - } - finishButton?.position = CGPoint(x: size.width * 0.5, y: size.height * 0.15) - finishButton?.zPosition = 10 - addChild(finishButton!) - } - - private func restartTutorial() { - ResourcesManager.getInstance().storyService?.loadTutorialGameSceneFromTutorialScene() - } - - private func backToStartMenu() { - ResourcesManager.getInstance().storyService?.loadStartMenuScene() - } - - private func makeTextButtonTexture(title: String, size: CGSize) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: size) - let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: size).insetBy(dx: size.width * 0.01, dy: size.height * 0.06) - let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: size.height * 0.23) - UIColor(white: 1.0, alpha: 0.10).setFill() - backgroundPath.fill() - UIColor(white: 1.0, alpha: 0.22).setStroke() - backgroundPath.lineWidth = size.height * 0.08 - backgroundPath.stroke() - - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - let buttonFontSize = fittedButtonFontSize(for: title, preferredSize: size.height * 0.38, maxWidth: size.width * 0.90) - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont(name: "Optima-Bold", size: buttonFontSize) ?? UIFont.systemFont(ofSize: buttonFontSize, weight: .semibold), - .foregroundColor: fontColor, - .paragraphStyle: paragraphStyle - ] - let textRect = CGRect(x: 0.0, y: size.height * 0.30, width: size.width, height: size.height * 0.5) - title.draw(in: textRect, withAttributes: attributes) - } - return SKTexture(image: image) - } - - private func fittedButtonFontSize(for text: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - var currentSize = preferredSize - while currentSize > 10.0 { - let font = UIFont(name: "Optima-Bold", size: currentSize) ?? UIFont.systemFont(ofSize: currentSize, weight: .semibold) - let measuredWidth = (text as NSString).size(withAttributes: [.font: font]).width - if measuredWidth <= maxWidth { - return currentSize - } - currentSize -= 1.0 - } - return 10.0 - } - - private func fittedFontSize(for text: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - if text.isEmpty { - return preferredSize - } - let label = SKLabelNode(fontNamed: "Optima-Bold") - label.text = text - var currentSize = preferredSize - label.fontSize = currentSize - - while label.frame.width > maxWidth && currentSize > 10.0 { - currentSize -= 1.0 - label.fontSize = currentSize - } - return currentSize - } } diff --git a/SOPA/view/LevelMode/LevelModeGameScene.swift b/SOPA/view/LevelMode/LevelModeGameScene.swift index 9245d6b..44bb5ee 100644 --- a/SOPA/view/LevelMode/LevelModeGameScene.swift +++ b/SOPA/view/LevelMode/LevelModeGameScene.swift @@ -36,12 +36,22 @@ class LevelModeGameScene: GameScene { override func addButtons() { let backSide = min(size.width, size.height) * 0.09 - levelChoiceButton = SpriteButton(texture: makeMinimalBackTexture(side: backSide), onClick: loadLevelChoiceScene) + levelChoiceButton = SpriteButton(texture: ButtonTextureFactory.makeMinimalBackTexture(side: backSide), onClick: loadLevelChoiceScene) levelChoiceButton!.position = CGPoint(x: size.width * 0.08, y: size.height * 0.95) addChild(levelChoiceButton!) let restartSide = min(size.width, size.height) * 0.18 - restartButton = SpriteButton(texture: makeGameplayCircleButtonTexture(symbolName: "arrow.counterclockwise", side: restartSide), onClick: restartLevel) + restartButton = SpriteButton( + texture: ButtonTextureFactory.makeCircleButtonTexture( + symbolName: "arrow.counterclockwise", + side: restartSide, + circleColor: UIColor(white: 0.96, alpha: 1.0), + iconColor: UIColor(white: 0.05, alpha: 1.0), + symbolScale: 0.52, + symbolWeight: .bold + ), + onClick: restartLevel + ) restartButton!.position = CGPoint(x: size.width * 0.86, y: size.height * 0.12) addChild(restartButton!) } @@ -109,48 +119,6 @@ class LevelModeGameScene: GameScene { movesLabels.addChild(currentMovesNode) } - private func makeGameplayCircleButtonTexture(symbolName: String, side: CGFloat) -> SKTexture { - let textureSize = CGSize(width: side, height: side) - let renderer = UIGraphicsImageRenderer(size: textureSize) - let image = renderer.image { _ in - UIColor(white: 0.96, alpha: 1.0).setFill() - UIBezierPath(ovalIn: CGRect(origin: .zero, size: textureSize)).fill() - - let config = UIImage.SymbolConfiguration(pointSize: side * 0.52, weight: .bold) - if let symbol = UIImage(systemName: symbolName, withConfiguration: config)? - .withTintColor(UIColor(white: 0.05, alpha: 1.0), renderingMode: .alwaysOriginal) { - let symbolRect = CGRect( - x: (side - symbol.size.width) / 2.0, - y: (side - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - } - return SKTexture(image: image) - - } - - private func makeMinimalBackTexture(side: CGFloat) -> SKTexture { - let textureSize = CGSize(width: side, height: side) - let renderer = UIGraphicsImageRenderer(size: textureSize) - let image = renderer.image { _ in - let config = UIImage.SymbolConfiguration(pointSize: side * 0.72, weight: .semibold) - if let symbol = UIImage(systemName: "chevron.left", withConfiguration: config)? - .withTintColor(UIColor(white: 0.86, alpha: 0.92), renderingMode: .alwaysOriginal) { - let symbolRect = CGRect( - x: (side - symbol.size.width) / 2.0, - y: (side - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - } - return SKTexture(image: image) - } - func restartLevel() { ResourcesManager.getInstance().storyService?.reloadLevelModeGameScene(levelId: gameService.getLevel().id!) } diff --git a/SOPA/view/LevelMode/SopaTheme.swift b/SOPA/view/LevelMode/SopaTheme.swift new file mode 100644 index 0000000..ee57c31 --- /dev/null +++ b/SOPA/view/LevelMode/SopaTheme.swift @@ -0,0 +1,17 @@ +import UIKit + +enum SopaTheme { + static let blackBackground = UIColor.black + static let warmBackground = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255.0, alpha: 1.0) + + static let titleGray = UIColor(red: 214.0 / 255.0, green: 214.0 / 255.0, blue: 214.0 / 255.0, alpha: 1.0) + static let neonBlue = UIColor(red: 111.0 / 255.0, green: 227.0 / 255.0, blue: 1.0, alpha: 1.0) + static let neonGlowBlue = UIColor(red: 23.0 / 255.0, green: 183.0 / 255.0, blue: 1.0, alpha: 1.0) + static let neonCoreBlue = UIColor(red: 183.0 / 255.0, green: 245.0 / 255.0, blue: 1.0, alpha: 1.0) + + static let bodyText = UIColor(red: 230.0 / 255.0, green: 236.0 / 255.0, blue: 242.0 / 255.0, alpha: 1.0) + static let gameText = UIColor(red: 240.0 / 255.0, green: 239.0 / 255.0, blue: 238.0 / 255.0, alpha: 1.0) + + static let circleButtonFill = UIColor(white: 0.94, alpha: 1.0) + static let circleButtonIcon = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255.0, alpha: 1.0) +} diff --git a/SOPA/view/LevelMode/StartMenuScene.swift b/SOPA/view/LevelMode/StartMenuScene.swift new file mode 100644 index 0000000..89782b8 --- /dev/null +++ b/SOPA/view/LevelMode/StartMenuScene.swift @@ -0,0 +1,106 @@ +import Foundation +import SpriteKit +import UIKit + +class StartMenuScene: SKScene { + private let twitterUrl = "https://twitter.com/sopagame" + private let shareText = "I played SOPA: https://play.google.com/store/apps/details?id=com.sopaapp" + + override init(size: CGSize) { + super.init(size: size) + backgroundColor = SopaTheme.blackBackground + addTitle() + addButtons() + addSocialButtons() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addTitle() { + let title = SKLabelNode(fontNamed: "Impact") + title.text = "SOPA" + title.fontSize = size.height * 0.14 + title.fontColor = SopaTheme.titleGray + title.position = CGPoint(x: size.width / 2, y: size.height * 0.77) + addChild(title) + } + + private func addButtons() { + let menuItems: [(title: String, action: () -> Void)] = [ + ("LEVEL MODE", { ResourcesManager.getInstance().storyService?.loadLevelCoiceScene() }), + ("JUST PLAY", { ResourcesManager.getInstance().storyService?.loadJustPlaySceneFromMenuScene() }), + // Keep this wired until a dedicated Settings scene exists on iOS. + ("SETTINGS", { ResourcesManager.getInstance().storyService?.loadTutorialSceneFromMenuScene() }), + ("CREDITS", { ResourcesManager.getInstance().storyService?.loadCreditsSceneFromMenuScene() }) + ] + + let buttonSize = CGSize(width: size.width * 0.76, height: size.height * 0.095) + let firstY = size.height * 0.62 + let verticalGap = size.height * 0.11 + + for (index, item) in menuItems.enumerated() { + let button = SpriteButton(texture: ButtonTextureFactory.makeNeonTextTexture(title: item.title, size: buttonSize), onClick: item.action) + button.position = CGPoint(x: size.width / 2.0, y: firstY - CGFloat(index) * verticalGap) + addChild(button) + } + } + + private func addSocialButtons() { + let buttonSide = min(size.width * 0.18, size.height * 0.10) + + let shareButton = SpriteButton( + texture: ButtonTextureFactory.makeSocialButtonTexture(symbolName: "point.3.connected.trianglepath.dotted", size: CGSize(width: buttonSide, height: buttonSide), fillBackground: true), + onClick: shareApp + ) + shareButton.position = CGPoint(x: size.width * 0.20, y: size.height * 0.14) + addChild(shareButton) + + let twitterButton = SpriteButton( + texture: ButtonTextureFactory.makeSocialButtonTexture(symbolName: "paperplane.fill", size: CGSize(width: buttonSide * 1.15, height: buttonSide * 1.15), fillBackground: false), + onClick: openTwitter + ) + twitterButton.position = CGPoint(x: size.width * 0.74, y: size.height * 0.14) + addChild(twitterButton) + } + + private func openTwitter() { + guard let url = URL(string: twitterUrl) else { + return + } + UIApplication.shared.open(url) + } + + private func shareApp() { + guard let controller = topViewController() else { + return + } + let shareController = UIActivityViewController(activityItems: [shareText], applicationActivities: nil) + shareController.popoverPresentationController?.sourceView = controller.view + controller.present(shareController, animated: true) + } + + private func topViewController(base: UIViewController? = nil) -> UIViewController? { + let initialController: UIViewController? + if let base = base { + initialController = base + } else { + initialController = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow }?.rootViewController + } + + if let navController = initialController as? UINavigationController { + return topViewController(base: navController.visibleViewController) + } + if let tabController = initialController as? UITabBarController { + return topViewController(base: tabController.selectedViewController) + } + if let presented = initialController?.presentedViewController { + return topViewController(base: presented) + } + return initialController + } +} diff --git a/SOPA/view/LevelMode/TutorialGameScene.swift b/SOPA/view/LevelMode/TutorialGameScene.swift new file mode 100644 index 0000000..889b057 --- /dev/null +++ b/SOPA/view/LevelMode/TutorialGameScene.swift @@ -0,0 +1,103 @@ +import Foundation +import SpriteKit +import UIKit + +class TutorialGameScene: GameScene { + private var restartButton: SpriteButton? + private var menuButton: SpriteButton? + private var finishButton: SpriteButton? + private let tutorialHint = SKLabelNode(fontNamed: "Optima-Bold") + private let tutorialSubHint = SKLabelNode(fontNamed: "Optima-Bold") + + override init(size: CGSize, proportionSet: ProportionSet, level: Level) { + super.init(size: size, proportionSet: proportionSet, level: level) + addTutorialHints() + updateHintText() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func addStaticLabels() { + // Tutorial scene keeps UI minimal and avoids overlap with end-of-tutorial CTA. + } + + override func addDynamicLabels() { + // No move counters in tutorial. + } + + override func addButtons() { + let side = proportionSet.levelChoiceSize() + restartButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "arrow.counterclockwise", side: side), onClick: restartTutorial) + restartButton!.position = CGPoint(x: size.width - size.height * 0.057, y: size.height * 0.91) + addChild(restartButton!) + + menuButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side), onClick: backToStartMenu) + menuButton!.position = proportionSet.levelChoicePos() + addChild(menuButton!) + } + + override func moveLine(horizontal: Bool, rowOrColumn: Int, steps: Int) { + super.moveLine(horizontal: horizontal, rowOrColumn: rowOrColumn, steps: steps) + if !levelSolved { + updateHintText() + } + } + + override func onSolvedGame() { + tutorialHint.text = "Solved" + tutorialHint.fontSize = size.height * 0.06 + tutorialHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.30) + tutorialSubHint.text = "" + showFinishButton() + } + + private func addTutorialHints() { + tutorialHint.fontColor = fontColor + tutorialHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.24) + tutorialHint.zPosition = 10 + addChild(tutorialHint) + + tutorialSubHint.fontColor = fontColor + tutorialSubHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.19) + tutorialSubHint.zPosition = 10 + addChild(tutorialSubHint) + } + + private func updateHintText() { + if gameService.getLevel().movesCounter == 0 { + tutorialHint.text = "Swipe any row or column" + tutorialHint.fontSize = LabelSizing.fittedLabelFontSize(text: tutorialHint.text ?? "", fontName: "Optima-Bold", preferredSize: size.height * 0.040, maxWidth: size.width * 0.92) + tutorialSubHint.text = "Connect start and finish in this level." + tutorialSubHint.fontSize = LabelSizing.fittedLabelFontSize(text: tutorialSubHint.text ?? "", fontName: "Optima-Bold", preferredSize: size.height * 0.032, maxWidth: size.width * 0.92) + } else { + tutorialHint.text = "Good" + tutorialHint.fontSize = size.height * 0.044 + tutorialSubHint.text = "Keep going until the path is connected." + tutorialSubHint.fontSize = LabelSizing.fittedLabelFontSize(text: tutorialSubHint.text ?? "", fontName: "Optima-Bold", preferredSize: size.height * 0.032, maxWidth: size.width * 0.92) + } + } + + private func showFinishButton() { + if finishButton != nil { + finishButton?.isHidden = false + return + } + let buttonSize = CGSize(width: size.width * 0.62, height: size.height * 0.095) + finishButton = SpriteButton(texture: ButtonTextureFactory.makeTextButtonTexture(title: "Back to Menu", size: buttonSize, fontName: "Optima-Bold", textColor: fontColor)) { + ResourcesManager.getInstance().storyService?.loadStartMenuScene() + } + finishButton?.position = CGPoint(x: size.width * 0.5, y: size.height * 0.15) + finishButton?.zPosition = 10 + addChild(finishButton!) + } + + private func restartTutorial() { + ResourcesManager.getInstance().storyService?.loadTutorialGameSceneFromTutorialScene() + } + + private func backToStartMenu() { + ResourcesManager.getInstance().storyService?.loadStartMenuScene() + } +} diff --git a/SOPA/view/LevelMode/TutorialScene.swift b/SOPA/view/LevelMode/TutorialScene.swift new file mode 100644 index 0000000..7c7331b --- /dev/null +++ b/SOPA/view/LevelMode/TutorialScene.swift @@ -0,0 +1,139 @@ +import Foundation +import SpriteKit +import UIKit + +class TutorialScene: SKScene { + private var currentPage = 0 + private let headline = SKLabelNode(fontNamed: "Optima-Bold") + private let textLine1 = SKLabelNode(fontNamed: "Optima-Bold") + private let textLine2 = SKLabelNode(fontNamed: "Optima-Bold") + private let textLine3 = SKLabelNode(fontNamed: "Optima-Bold") + private let tapHint = SKLabelNode(fontNamed: "Optima-Bold") + private var letsGoButton: SpriteButton? + + override init(size: CGSize) { + super.init(size: size) + self.backgroundColor = SopaTheme.warmBackground + addBackButton() + addTitle() + addContentNodes() + updatePage() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addBackButton() { + let side = size.height * 0.08 + let backButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side)) { + ResourcesManager.getInstance().storyService?.loadStartMenuScene() + } + backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) + addChild(backButton) + } + + private func addTitle() { + let title = SKLabelNode(fontNamed: "Optima-Bold") + title.text = "Tutorial" + title.fontSize = size.height * 0.10 + title.fontColor = SopaTheme.gameText + title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) + addChild(title) + } + + private func addContentNodes() { + headline.fontColor = SopaTheme.gameText + headline.fontSize = size.height * 0.048 + headline.position = CGPoint(x: size.width * 0.5, y: size.height * 0.62) + addChild(headline) + + textLine1.fontColor = SopaTheme.gameText + textLine1.fontSize = size.height * 0.034 + textLine1.position = CGPoint(x: size.width * 0.5, y: size.height * 0.54) + addChild(textLine1) + + textLine2.fontColor = SopaTheme.gameText + textLine2.fontSize = size.height * 0.034 + textLine2.position = CGPoint(x: size.width * 0.5, y: size.height * 0.48) + addChild(textLine2) + + textLine3.fontColor = SopaTheme.gameText + textLine3.fontSize = size.height * 0.034 + textLine3.position = CGPoint(x: size.width * 0.5, y: size.height * 0.42) + addChild(textLine3) + + tapHint.fontColor = SopaTheme.gameText + tapHint.fontSize = size.height * 0.030 + tapHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.26) + addChild(tapHint) + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + if currentPage < 2 { + currentPage += 1 + updatePage() + } + } + + private func updatePage() { + switch currentPage { + case 0: + setPage( + headlineText: "Goal", + line1: "Connect the start and finish pipes.", + line2: "Swipe rows or columns to move tiles.", + line3: "Only matching pipe segments will connect.", + hint: "Tap anywhere to continue" + ) + letsGoButton?.isHidden = true + case 1: + setPage( + headlineText: "How It Works", + line1: "Horizontal swipe moves a full row.", + line2: "Vertical swipe moves a full column.", + line3: "Try to solve it in as few moves as possible.", + hint: "Tap anywhere to continue" + ) + letsGoButton?.isHidden = true + default: + setPage( + headlineText: "Try It Now", + line1: "Next you play a simple interactive level.", + line2: "Use restart if you want to try again.", + line3: "", + hint: "" + ) + ensureLetsGoButton() + letsGoButton?.isHidden = false + } + } + + private func setPage(headlineText: String, line1: String, line2: String, line3: String, hint: String) { + headline.text = headlineText + headline.fontSize = LabelSizing.fittedLabelFontSize(text: headlineText, fontName: "Optima-Bold", preferredSize: size.height * 0.048, maxWidth: size.width * 0.90) + + textLine1.text = line1 + textLine1.fontSize = LabelSizing.fittedLabelFontSize(text: line1, fontName: "Optima-Bold", preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) + textLine2.text = line2 + textLine2.fontSize = LabelSizing.fittedLabelFontSize(text: line2, fontName: "Optima-Bold", preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) + textLine3.text = line3 + textLine3.fontSize = LabelSizing.fittedLabelFontSize(text: line3, fontName: "Optima-Bold", preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) + tapHint.text = hint + tapHint.fontSize = LabelSizing.fittedLabelFontSize(text: hint, fontName: "Optima-Bold", preferredSize: size.height * 0.030, maxWidth: size.width * 0.92) + } + + private func ensureLetsGoButton() { + if letsGoButton != nil { + return + } + + let buttonSize = CGSize(width: size.width * 0.34, height: size.height * 0.10) + let button = SpriteButton(texture: ButtonTextureFactory.makeTextButtonTexture(title: "Let's Go", size: buttonSize, fontName: "Optima-Bold", textColor: SopaTheme.gameText)) { + ResourcesManager.getInstance().storyService?.loadTutorialGameSceneFromTutorialScene() + } + button.position = CGPoint(x: size.width * 0.5, y: size.height * 0.25) + addChild(button) + letsGoButton = button + } +}