So erstellen Sie ein SpriteKit-Spiel in Swift 3 (Teil 3)
Veröffentlicht: 2022-03-10Haben Sie sich jemals gefragt, was es braucht, um ein SpriteKit-Spiel zu erstellen? Erscheinen Schaltflächen eine größere Aufgabe, als sie sein sollten? Haben Sie sich jemals gefragt, wie Sie Einstellungen in einem Spiel beibehalten können? Seit der Einführung von SpriteKit war das Erstellen von Spielen unter iOS noch nie so einfach. In Teil drei dieser dreiteiligen Serie werden wir unser RainCat-Spiel beenden und unsere Einführung in SpriteKit vervollständigen.
Wenn Sie die vorherige Lektion verpasst haben, können Sie sie nachholen, indem Sie den Code auf GitHub abrufen. Denken Sie daran, dass dieses Tutorial Xcode 8 und Swift 3 erfordert.
Weiterführende Literatur zu SmashingMag: Link
- Gamification und UX: Wo Benutzer gewinnen oder verlieren
- Spielerisches UX-Design: Ein besseres Spiel entwickeln
- Kombination von UX-Design und Psychologie zur Änderung des Benutzerverhaltens
Dies ist die dritte Lektion unserer RainCat-Reise. In der vorherigen Lektion hatten wir einen langen Tag mit einigen einfachen Animationen, Katzenverhalten, schnellen Soundeffekten und Hintergrundmusik.
Heute werden wir uns auf Folgendes konzentrieren:
- Heads-up-Display (HUD) für die Wertung;
- Hauptmenü — mit Tasten;
- Optionen zum Stummschalten von Tönen;
- Option zum Beenden des Spiels.
Noch mehr Vermögen
Die Assets für die letzte Lektion sind auf GitHub verfügbar. Ziehen Sie die Bilder erneut in Assets.xcassets
, genau wie in den vorherigen Lektionen.
Kopf hoch!
Wir brauchen einen Weg, um Punkte zu halten. Dazu können wir ein Heads-up-Display (HUD) erstellen. Das wird ziemlich einfach sein; Es wird ein SKNode
, der die Punktzahl und eine Schaltfläche zum Beenden des Spiels enthält. Im Moment konzentrieren wir uns nur auf die Punktzahl. Die Schriftart, die wir verwenden werden, ist Pixel Digivolve, die Sie bei Dafont.com erhalten können. Wie bei der Verwendung von Bildern oder Tönen, die nicht Ihnen gehören, lesen Sie die Lizenz der Schriftart, bevor Sie sie verwenden. Diese gibt an, dass sie für den persönlichen Gebrauch kostenlos ist, aber wenn Ihnen die Schriftart wirklich gefällt, können Sie von der Seite an den Autor spenden. Man kann nicht immer alles selbst machen, also ist es schön, denen etwas zurückzugeben, die einem auf diesem Weg geholfen haben.
Als nächstes müssen wir die benutzerdefinierte Schriftart zum Projekt hinzufügen. Dieser Vorgang kann beim ersten Mal schwierig sein.
Laden Sie die Schriftart herunter und verschieben Sie sie in den Projektordner unter einem Ordner „Schriftarten“. Wir haben dies in den vorherigen Lektionen einige Male gemacht, also gehen wir diesen Vorgang etwas schneller durch. Fügen Sie dem Projekt eine Gruppe mit dem Namen Fonts
hinzu, und fügen Sie die Datei Pixel digivolve.otf
.
Jetzt kommt der heikle Teil. Wenn Sie diesen Teil verpassen, können Sie die Schriftart wahrscheinlich nicht verwenden. Wir müssen es zu unserer Info.plist
-Datei hinzufügen. Diese Datei befindet sich im linken Bereich von Xcode. Klicken Sie darauf und Sie sehen die Eigenschaftsliste (oder plist
). Klicken Sie mit der rechten Maustaste auf die Liste und klicken Sie auf „Zeile hinzufügen“.
Wenn die neue Zeile erscheint, geben Sie Folgendes ein:
Fonts provided by application
Dann müssen wir unter Item 0
den Namen unserer Schriftart hinzufügen. Die plist
sollte wie folgt aussehen:
Die Schriftart sollte einsatzbereit sein! Wir sollten einen kurzen Test durchführen, um sicherzustellen, dass es wie beabsichtigt funktioniert. Wechseln Sie zu GameScene.swift
und fügen Sie in sceneDidLoad
den folgenden Code oben in der Funktion hinzu:
let label = SKLabelNode(fontNamed: "PixelDigivolve") label.text = "Hello World!" label.position = CGPoint(x: size.width / 2, y: size.height / 2) label.zPosition = 1000 addChild(label)
Funktioniert es?
Wenn es funktioniert, dann haben Sie alles richtig gemacht. Wenn nicht, dann stimmt etwas nicht. Code With Chris hat eine ausführlichere Anleitung zur Fehlerbehebung, aber beachten Sie, dass es sich um eine ältere Version von Swift handelt, sodass Sie kleinere Änderungen vornehmen müssen, um es auf Swift 3 zu bringen.
Jetzt, da wir benutzerdefinierte Schriftarten laden können, können wir mit unserem HUD beginnen. Löschen Sie das Label „Hello World“, da wir es nur verwendet haben, um sicherzustellen, dass unsere Schriftart geladen wird. Das HUD wird ein SKNode
, der wie ein Container für unsere HUD-Elemente fungiert. Dies ist derselbe Prozess, den wir beim Erstellen des Hintergrundknotens in Lektion eins befolgt haben.
Erstellen Sie die Datei HudNode.swift
mit den üblichen Methoden und geben Sie den folgenden Code ein:
import SpriteKit class HudNode : SKNode { private let scoreKey = "RAINCAT_HIGHSCORE" private let scoreNode = SKLabelNode(fontNamed: "PixelDigivolve") private(set) var score : Int = 0 private var highScore : Int = 0 private var showingHighScore = false /// Set up HUD here. public func setup(size: CGSize) { let defaults = UserDefaults.standard highScore = defaults.integer(forKey: scoreKey) scoreNode.text = "\(score)" scoreNode.fontSize = 70 scoreNode.position = CGPoint(x: size.width / 2, y: size.height - 100) scoreNode.zPosition = 1 addChild(scoreNode) } /// Add point. /// - Increments the score. /// - Saves to user defaults. /// - If a high score is achieved, then enlarge the scoreNode and update the color. public func addPoint() { score += 1 updateScoreboard() if score > highScore { let defaults = UserDefaults.standard defaults.set(score, forKey: scoreKey) if !showingHighScore { showingHighScore = true scoreNode.run(SKAction.scale(to: 1.5, duration: 0.25)) scoreNode.fontColor = SKColor(red:0.99, green:0.92, blue:0.55, alpha:1.0) } } } /// Reset points. /// - Sets score to zero. /// - Updates score label. /// - Resets color and size to default values. public func resetPoints() { score = 0 updateScoreboard() if showingHighScore { showingHighScore = false scoreNode.run(SKAction.scale(to: 1.0, duration: 0.25)) scoreNode.fontColor = SKColor.white } } /// Updates the score label to show the current score. private func updateScoreboard() { scoreNode.text = "\(score)" } }
Bevor wir irgendetwas anderes tun, öffnen Sie Constants.swift
und fügen Sie die folgende Zeile am Ende der Datei hinzu – wir werden sie verwenden, um den Highscore abzurufen und zu speichern:
let ScoreKey = "RAINCAT_HIGHSCORE"
Im Code haben wir fünf Variablen, die sich auf das Scoreboard beziehen. Die erste Variable ist der eigentliche SKLabelNode
, den wir verwenden, um das Label darzustellen. Als nächstes ist unsere Variable, um die aktuelle Punktzahl zu halten; dann die Variable mit dem besten Ergebnis. Die letzte Variable ist ein boolescher Wert, der uns mitteilt, ob wir gerade den Highscore präsentieren (wir verwenden dies, um festzustellen, ob wir eine SKAction
, um die Skala der Anzeigetafel zu vergrößern und sie auf das Gelb des Bodens zu färben).
Die erste Funktion, setup(size:)
, ist nur dazu da, alles einzurichten. Wir richten den SKLabelNode
auf die gleiche Weise wie zuvor ein. Die SKNode
-Klasse hat standardmäßig keine Größeneigenschaften, daher müssen wir eine Möglichkeit schaffen, eine Größe festzulegen, um unser scoreNode
Label zu positionieren. Wir rufen auch den aktuellen Highscore von UserDefaults
ab. Dies ist eine schnelle und einfache Möglichkeit, kleine Datenmengen zu speichern, aber es ist nicht sicher. Da wir uns in diesem Beispiel keine Sorgen um die Sicherheit machen, ist UserDefaults
vollkommen in Ordnung.
In unserem addPoint()
wir die aktuelle score
-Variable und prüfen, ob der Benutzer einen Highscore erzielt hat. Wenn sie eine hohe Punktzahl haben, speichern wir diese Punktzahl in UserDefaults
und prüfen, ob wir derzeit die beste Punktzahl anzeigen. Wenn der Benutzer eine hohe Punktzahl erreicht hat, können wir die Größe und Farbe von scoreNode
.
In der Funktion resetPoints()
setzen wir den aktuellen Punktestand auf 0
. Wir müssen dann überprüfen, ob wir den Highscore angezeigt haben, und die Größe und Farbe bei Bedarf auf die Standardwerte zurücksetzen.
Schließlich haben wir eine kleine Funktion namens updateScoreboard
. Dies ist eine interne Funktion, um die Punktzahl auf den Text von scoreNode
zu setzen. Dies wird sowohl in addPoint()
als auch in resetPoints() resetPoints()
.
Anschließen des HUD
Wir müssen testen, ob unser HUD korrekt funktioniert. Wechseln Sie zu GameScene.swift
und fügen Sie die folgende Zeile unter der Variablen foodNode
am Anfang der Datei hinzu:
private let hudNode = HudNode()
Fügen Sie die folgenden zwei Zeilen in der Funktion sceneDidLoad()
oben hinzu:
hudNode.setup(size: size) addChild(hudNode)
Setzen Sie dann in der Funktion spawnCat()
die Punkte zurück, falls die Katze vom Bildschirm gefallen ist. Fügen Sie die folgende Zeile hinzu, nachdem Sie der Szene das Katzen-Sprite hinzugefügt haben:
hudNode.resetPoints()
Als Nächstes müssen wir in der Funktion handleCatCollision(contact:)
die Punktzahl erneut zurücksetzen, wenn die Katze von Regen getroffen wird. Fügen Sie in der switch
Anweisung am Ende der Funktion – wenn der andere Körper eine RainDropCategory
ist – die folgende Zeile hinzu:
hudNode.resetPoints()
Schließlich müssen wir dem Scoreboard mitteilen, wann der Benutzer Punkte verdient hat. Am Ende der Datei in handleFoodHit(contact:)
finden Sie die folgenden Zeilen bis hierher:
//TODO increment points print("fed cat")
Und ersetzen Sie sie durch diese:
hudNode.addPoint()
Voila!
Sie sollten das HUD in Aktion sehen. Lauf herum und sammle etwas zu essen. Wenn Sie zum ersten Mal Nahrung sammeln, sollten Sie sehen, dass die Punktzahl gelb wird und an Größe zunimmt. Wenn Sie das sehen, lassen Sie die Katze schlagen. Wenn die Punktzahl zurückgesetzt wird, wissen Sie, dass Sie auf dem richtigen Weg sind!
Die nächste Szene
Das ist richtig, wir bewegen uns in eine andere Szene! Tatsächlich wird dies nach Abschluss der erste Bildschirm unserer App sein. Bevor Sie irgendetwas anderes tun, öffnen Sie Constants.swift
und fügen Sie die folgende Zeile am Ende der Datei hinzu – wir werden sie verwenden, um den Highscore abzurufen und zu speichern:
let ScoreKey = "RAINCAT_HIGHSCORE"
Erstellen Sie die neue Szene, platzieren Sie sie im Ordner „Scenes“ und nennen Sie sie MenuScene.swift
. Geben Sie den folgenden Code in die Datei MenuScene.swift
:
import SpriteKit class MenuScene : SKScene { let startButtonTexture = SKTexture(imageNamed: "button_start") let startButtonPressedTexture = SKTexture(imageNamed: "button_start_pressed") let soundButtonTexture = SKTexture(imageNamed: "speaker_on") let soundButtonTextureOff = SKTexture(imageNamed: "speaker_off") let logoSprite = SKSpriteNode(imageNamed: "logo") var startButton : SKSpriteNode! = nil var soundButton : SKSpriteNode! = nil let highScoreNode = SKLabelNode(fontNamed: "PixelDigivolve") var selectedButton : SKSpriteNode? override func sceneDidLoad() { backgroundColor = SKColor(red:0.30, green:0.81, blue:0.89, alpha:1.0) //Set up logo - sprite initialized earlier logoSprite.position = CGPoint(x: size.width / 2, y: size.height / 2 + 100) addChild(logoSprite) //Set up start button startButton = SKSpriteNode(texture: startButtonTexture) startButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - startButton.size.height / 2) addChild(startButton) let edgeMargin : CGFloat = 25 //Set up sound button soundButton = SKSpriteNode(texture: soundButtonTexture) soundButton.position = CGPoint(x: size.width - soundButton.size.width / 2 - edgeMargin, y: soundButton.size.height / 2 + edgeMargin) addChild(soundButton) //Set up high-score node let defaults = UserDefaults.standard let highScore = defaults.integer(forKey: ScoreKey) highScoreNode.text = "\(highScore)" highScoreNode.fontSize = 90 highScoreNode.verticalAlignmentMode = .top highScoreNode.position = CGPoint(x: size.width / 2, y: startButton.position.y - startButton.size.height / 2 - 50) highScoreNode.zPosition = 1 addChild(highScoreNode) } }
Da diese Szene relativ einfach ist, werden wir keine speziellen Klassen erstellen. Unsere Szene besteht aus zwei Schaltflächen. Diese könnten (und verdienen es möglicherweise) ihre eigene Klasse von SKSpriteNodes
, aber da sie unterschiedlich genug sind, müssen wir keine neuen Klassen für sie erstellen. Dies ist ein wichtiger Tipp, wenn Sie Ihr eigenes Spiel erstellen: Sie müssen in der Lage sein, zu bestimmen, wo Sie aufhören müssen, und den Code umzugestalten, wenn die Dinge komplex werden. Sobald Sie einem Spiel mehr als drei oder vier Schaltflächen hinzugefügt haben, ist es möglicherweise an der Zeit, den Code der Menüschaltfläche anzuhalten und in eine eigene Klasse umzugestalten.
Der obige Code macht nichts Besonderes; es setzt die Positionen von vier Sprites. Wir stellen auch die Hintergrundfarbe der Szene ein, damit der gesamte Hintergrund den richtigen Wert hat. Ein nettes Tool zum Generieren von Farbcodes aus HEX-Strings für Xcode ist UI Color. Der obige Code legt auch die Texturen für unsere Schaltflächenzustände fest. Die Taste zum Starten des Spiels hat einen normalen Zustand und einen gedrückten Zustand, während die Tontaste ein Umschalter ist. Um die Dinge für den Umschalter zu vereinfachen, ändern wir den Alpha-Wert der Sound-Taste beim Drücken des Benutzers. Wir ziehen und setzen auch den Highscore SKLabelNode
.
Unsere MenuScene
sieht ziemlich gut aus. Jetzt müssen wir die Szene zeigen, wenn die App geladen wird. Wechseln Sie zu GameViewController.swift
und suchen Sie die folgende Zeile:
let sceneNode = GameScene(size: view.frame.size)
Ersetzen Sie es durch dieses:
let sceneNode = MenuScene(size: view.frame.size)
Diese kleine Änderung lädt standardmäßig MenuScene
anstelle von GameScene
.
Schaltflächenzustände
Schaltflächen können in SpriteKit schwierig sein. Es sind viele Optionen von Drittanbietern verfügbar (ich habe sogar selbst eine erstellt), aber theoretisch müssen Sie nur die drei Berührungsmethoden kennen:
-
touchesBegan(_ touches: with event:)
-
touchesMoved(_ touches: with event:)
-
touchesEnded(_ touches: with event:)
Wir haben dies beim Aktualisieren des Umbrellas kurz behandelt, aber jetzt müssen wir Folgendes wissen: welche Schaltfläche berührt wurde, ob der Benutzer losgelassen oder auf diese Schaltfläche geklickt hat und ob der Benutzer sie immer noch berührt. Hier kommt unsere selectedButton
-Variable ins Spiel. Wenn eine Berührung beginnt, können wir die Schaltfläche erfassen, auf die der Benutzer mit dieser Variablen geklickt hat. Wenn sie über die Schaltfläche hinaus gezogen werden, können wir damit umgehen und ihr die entsprechende Textur geben. Wenn sie die Berührung loslassen, können wir dann sehen, ob sie immer noch in der Taste berühren. Wenn dies der Fall ist, können wir die zugehörige Aktion darauf anwenden. Fügen Sie die folgenden Zeilen am Ende von MenuScene.swift
:
override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton != nil { handleStartButtonHover(isHovering: false) handleSoundButtonHover(isHovering: false) } // Check which button was clicked (if any) if startButton.contains(touch.location(in: self)) { selectedButton = startButton handleStartButtonHover(isHovering: true) } else if soundButton.contains(touch.location(in: self)) { selectedButton = soundButton handleSoundButtonHover(isHovering: true) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { // Check which button was clicked (if any) if selectedButton == startButton { handleStartButtonHover(isHovering: (startButton.contains(touch.location(in: self)))) } else if selectedButton == soundButton { handleSoundButtonHover(isHovering: (soundButton.contains(touch.location(in: self)))) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton == startButton { // Start button clicked handleStartButtonHover(isHovering: false) if (startButton.contains(touch.location(in: self))) { handleStartButtonClick() } } else if selectedButton == soundButton { // Sound button clicked handleSoundButtonHover(isHovering: false) if (soundButton.contains(touch.location(in: self))) { handleSoundButtonClick() } } } selectedButton = nil } /// Handles start button hover behavior func handleStartButtonHover(isHovering : Bool) { if isHovering { startButton.texture = startButtonPressedTexture } else { startButton.texture = startButtonTexture } } /// Handles sound button hover behavior func handleSoundButtonHover(isHovering : Bool) { if isHovering { soundButton.alpha = 0.5 } else { soundButton.alpha = 1.0 } } /// Stubbed out start button on click method func handleStartButtonClick() { print("start clicked") } /// Stubbed out sound button on click method func handleSoundButtonClick() { print("sound clicked") }
Dies ist eine einfache Tastenbehandlung für unsere beiden Tasten. In touchesBegan(_ touches: with events:)
beginnen wir damit, zu prüfen, ob wir aktuell ausgewählte Schaltflächen haben. Wenn wir dies tun, müssen wir den Zustand der Schaltfläche auf „nicht gedrückt“ zurücksetzen. Dann müssen wir prüfen, ob eine Taste gedrückt ist. Wenn einer gedrückt wird, wird der hervorgehobene Zustand für die Schaltfläche angezeigt. Dann setzen wir selectedButton
auf die Schaltfläche zur Verwendung in den anderen beiden Methoden.
In touchesMoved(_ touches: with events:)
prüfen wir, welcher Button ursprünglich berührt wurde. Dann prüfen wir, ob die aktuelle Berührung noch innerhalb der Grenzen von selectedButton
liegt, und aktualisieren von dort aus den hervorgehobenen Zustand. Der hervorgehobene Zustand von startButton
ändert die Textur in die Textur des gedrückten Zustands, wobei im hervorgehobenen Zustand von soundButton
der Alphawert des Sprites auf 50 % gesetzt ist.
Schließlich prüfen wir in touchesEnded(_ touches: with event:)
noch einmal, welcher Button ggf. ausgewählt ist und ob sich die Berührung noch innerhalb der Grenzen des Buttons befindet. Wenn alle Fälle erfüllt sind, rufen wir handleStartButtonClick()
oder handleSoundButtonClick()
für die richtige Schaltfläche auf.
Eine Zeit zum Handeln
Jetzt, da wir das grundlegende Verhalten der Schaltflächen unten haben, brauchen wir ein Ereignis, das ausgelöst wird, wenn sie angeklickt werden. Die einfacher zu implementierende Schaltfläche ist startButton
. Auf Klick brauchen wir nur noch die GameScene
zu präsentieren. Aktualisieren handleStartButtonClick()
in der MenuScene.swift
Funktion auf den folgenden Code:
func handleStartButtonClick() { let transition = SKTransition.reveal(with: .down, duration: 0.75) let gameScene = GameScene(size: size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene, transition: transition) }
Wenn Sie die App jetzt ausführen und die Taste drücken, wird das Spiel gestartet!
Jetzt müssen wir den Mute-Schalter implementieren. Wir haben bereits einen Soundmanager, aber wir müssen ihm sagen können, ob die Stummschaltung ein- oder ausgeschaltet ist. In Constants.swift
müssen wir einen Schlüssel hinzufügen, der bestehen bleibt, wenn die Stummschaltung aktiviert ist. Fügen Sie die folgende Zeile hinzu:
let MuteKey = "RAINCAT_MUTED"
Wir werden dies verwenden, um einen booleschen Wert in UserDefaults
zu speichern. Nachdem dies eingerichtet ist, können wir zu SoundManager.swift
. Hier prüfen und setzen wir UserDefaults
, um zu sehen, ob die Stummschaltung ein- oder ausgeschaltet ist. Fügen Sie oben in der Datei unter der Variablen trackPosition
die folgende Zeile hinzu:
private(set) var isMuted = false
Dies ist die Variable, die das Hauptmenü (und alles andere, was Ton abspielt) überprüft, um festzustellen, ob Ton erlaubt ist. Wir initialisieren es als false
, aber jetzt müssen wir UserDefaults
überprüfen, um zu sehen, was der Benutzer will. Ersetzen Sie die Funktion init()
durch Folgendes:
private override init() { //This is private, so you can only have one Sound Manager ever. trackPosition = Int(arc4random_uniform(UInt32(SoundManager.tracks.count))) let defaults = UserDefaults.standard isMuted = defaults.bool(forKey: MuteKey) }
Da wir nun einen Standardwert für isMuted
haben, müssen wir ihn ändern können. Fügen Sie den folgenden Code am Ende von SoundManager.swift
:
func toggleMute() -> Bool { isMuted = !isMuted let defaults = UserDefaults.standard defaults.set(isMuted, forKey: MuteKey) defaults.synchronize() if isMuted { audioPlayer?.stop() } else { startPlaying() } return isMuted }
Diese Methode schaltet unsere stummgeschaltete Variable um und aktualisiert UserDefaults
. Wenn der neue Wert nicht stummgeschaltet ist, beginnt die Wiedergabe der Musik; wenn der neue Wert stummgeschaltet ist, beginnt die Wiedergabe nicht. Andernfalls stoppen wir die Wiedergabe des aktuellen Titels. Danach müssen wir die if
-Anweisung in startPlaying()
bearbeiten.
Suchen Sie die folgende Zeile:
if audioPlayer == nil || audioPlayer?.isPlaying == false {
Und ersetzen Sie es durch dieses:
if !isMuted && (audioPlayer == nil || audioPlayer?.isPlaying == false) {
Wenn jetzt die Stummschaltung ausgeschaltet ist und entweder der Audioplayer nicht eingestellt ist oder der aktuelle Audioplayer nicht mehr spielt, spielen wir den nächsten Titel.
Von hier aus können wir zurück zu MenuScene.swift
, um unsere Stummschalttaste zu beenden. Ersetzen Sie handleSoundbuttonClick()
durch den folgenden Code:
func handleSoundButtonClick() { if SoundManager.sharedInstance.toggleMute() { //Is muted soundButton.texture = soundButtonTextureOff } else { //Is not muted soundButton.texture = soundButtonTexture } }
Dies schaltet den Ton im SoundManager
, prüft das Ergebnis und stellt dann die Textur entsprechend ein, um dem Benutzer anzuzeigen, ob der Ton stummgeschaltet ist oder nicht. Wir sind fast fertig! Wir müssen nur die anfängliche Textur der Schaltfläche beim Start festlegen. Suchen Sie in sceneDidLoad()
die folgende Zeile:
soundButton = SKSpriteNode(texture: soundButtonTexture)
Und ersetzen Sie es durch dieses:
soundButton = SKSpriteNode(texture: SoundManager.sharedInstance.isMuted ? soundButtonTextureOff : soundButtonTexture)
Das obige Beispiel verwendet einen ternären Operator, um die richtige Textur festzulegen.
Jetzt, da die Musik angeschlossen ist, können wir zu CatSprite.swift
, um das Miauen der Katze zu deaktivieren, wenn die Stummschaltung aktiviert ist. In hitByRain()
können wir nach dem Entfernen der Walking-Aktion die folgende if
-Anweisung hinzufügen:
if SoundManager.sharedInstance.isMuted { return }
Diese Anweisung gibt zurück, ob der Benutzer die App stummgeschaltet hat. Aus diesem Grund werden wir unsere currentRainHits
, maxRainHits
und miauenden Soundeffekte vollständig ignorieren.
Nach all dem ist es jetzt an der Zeit, unsere Stummschalttaste auszuprobieren. Führen Sie die App aus und überprüfen Sie, ob sie Sounds richtig abspielt und stumm schaltet. Schalten Sie den Ton stumm, schließen Sie die App und öffnen Sie sie erneut. Stellen Sie sicher, dass die Stummschaltung bestehen bleibt. Beachten Sie, dass Sie UserDefaults
möglicherweise nicht genügend Zeit zum Speichern gegeben haben, wenn Sie die App einfach stummschalten und von Xcode aus erneut ausführen. Spielen Sie das Spiel und stellen Sie sicher, dass die Katze nie miaut, wenn Sie stummgeschaltet sind.
Verlassen des Spiels
Jetzt, da wir die erste Art von Schaltflächen für das Hauptmenü haben, können wir uns an ein kniffliges Geschäft machen, indem wir unserer Spielszene die Schaltfläche „Beenden“ hinzufügen. Einige interessante Interaktionen können mit unserem Spielstil einhergehen; Derzeit bewegt sich der Regenschirm dorthin, wo der Benutzer ihn berührt oder bewegt. Offensichtlich ist die Bewegung des Regenschirms zur Beenden-Schaltfläche, wenn der Benutzer versucht, das Spiel zu beenden, eine ziemlich schlechte Benutzererfahrung, also werden wir versuchen, dies zu verhindern.
Die Schaltfläche zum Beenden, die wir implementieren, ahmt die Schaltfläche zum Starten des Spiels nach, die wir zuvor hinzugefügt haben, wobei ein Großteil des Prozesses gleich bleibt. Die Veränderung wird darin bestehen, wie wir mit Berührungen umgehen. Holen Sie sich Ihre quit_button
und quit_button_pressed
Assets in die Assets.xcassets
-Datei und fügen Sie den folgenden Code zur HudNode.swift
-Datei hinzu:
private var quitButton : SKSpriteNode! private let quitButtonTexture = SKTexture(imageNamed: "quit_button") private let quitButtonPressedTexture = SKTexture(imageNamed: "quit_button_pressed")
Dies behandelt unsere quitButton
Referenz zusammen mit den Texturen, die wir für die Schaltflächenzustände festlegen werden. Um sicherzustellen, dass wir den Regenschirm nicht versehentlich aktualisieren, während wir versuchen, das Spiel zu beenden, brauchen wir eine Variable, die dem HUD (und der Spielszene) mitteilt, dass wir mit der Schaltfläche „Beenden“ und nicht mit dem Regenschirm interagieren. Fügen Sie den folgenden Code unter der booleschen Variable showingHighScore
:
private(set) var quitButtonPressed = false
Auch dies ist eine Variable, die nur der HudNode
setzen kann, aber die andere Klassen überprüfen können. Nachdem unsere Variablen eingerichtet sind, können wir die Schaltfläche zum HUD hinzufügen. Fügen Sie der Funktion setup(size:)
den folgenden Code hinzu:
quitButton = SKSpriteNode(texture: quitButtonTexture) let margin : CGFloat = 15 quitButton.position = CGPoint(x: size.width - quitButton.size.width - margin, y: size.height - quitButton.size.height - margin) quitButton.zPosition = 1000 addChild(quitButton)
Der obige Code setzt die Schaltfläche „Beenden“ mit der Textur unseres nicht gedrückten Zustands. Wir setzen auch die Position auf die obere rechte Ecke und setzen die zPosition
auf eine hohe Zahl, um zu erzwingen, dass immer oben gezeichnet wird. Wenn Sie das Spiel jetzt ausführen, wird es in GameScene
, kann aber noch nicht angeklickt werden.
Nachdem die Schaltfläche nun positioniert wurde, müssen wir in der Lage sein, damit zu interagieren. Im Moment ist der einzige Ort, an dem wir in GameScene
interagieren, der, wenn wir mit umbrellaSprite
interagieren. In unserem Beispiel hat das HUD Vorrang vor dem Regenschirm, sodass Benutzer den Regenschirm nicht aus dem Weg räumen müssen, um den Bildschirm zu verlassen. Wir können dieselben Funktionen in HudNode.swift
, um die Touch-Funktionalität in GameScene.swift
. Fügen Sie HudNode.swift
den folgenden Code hinzu:
func touchBeganAtPoint(point: CGPoint) { let containsPoint = quitButton.contains(point) if quitButtonPressed && !containsPoint { //Cancel the last click quitButtonPressed = false quitButton.texture = quitButtonTexture } else if containsPoint { quitButton.texture = quitButtonPressedTexture quitButtonPressed = true } } func touchMovedToPoint(point: CGPoint) { if quitButtonPressed { if quitButton.contains(point) { quitButton.texture = quitButtonPressedTexture } else { quitButton.texture = quitButtonTexture } } } func touchEndedAtPoint(point: CGPoint) { if quitButton.contains(point) { //TODO tell the gamescene to quit the game } quitButton.texture = quitButtonTexture }
Der obige Code ist dem Code sehr ähnlich, den wir für MenuScene
erstellt haben. Der Unterschied besteht darin, dass es nur einen Knopf gibt, den wir verfolgen müssen, sodass wir alles innerhalb dieser Berührungsmethoden handhaben können. Da wir außerdem den Ort der Berührung in GameScene
, können wir einfach prüfen, ob unsere Schaltfläche den Berührungspunkt enthält.
Wechseln Sie zu GameScene.swift
und ersetzen Sie die touchesBegan(_ touches with event:)
und touchesMoved(_ touches: with event:)
durch den folgenden Code:
override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchBeganAtPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchMovedToPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchEndedAtPoint(point: point) } }
Hier behandelt jede Methode alles ziemlich gleich. Wir teilen dem HUD mit, dass der Benutzer mit der Szene interagiert hat. Dann prüfen wir, ob der Beenden-Button gerade die Berührungen erfasst. Ist dies nicht der Fall, bewegen wir den Regenschirm. Wir haben auch die touchesEnded(_ touches: with event:)
hinzugefügt, um das Ende des Klicks für die Schaltfläche „Beenden“ zu handhaben, aber wir verwenden sie immer noch nicht für umbrellaSprite
.
Jetzt, wo wir eine Schaltfläche haben, brauchen wir eine Möglichkeit, damit sie sich auf GameScene
. Fügen Sie die folgende Zeile oben in HudeNode.swift
:
var quitButtonAction : (() -> ())?
Dies ist ein generischer Abschluss, der keinen Eingang und keinen Ausgang hat. Wir werden dies mit Code in der Datei GameScene.swift
und aufrufen, wenn wir auf die Schaltfläche in HudNode.swift
. Dann können wir das TODO
in dem Code, den wir zuvor in der Funktion touchEndedAtPoint(point:)
erstellt haben, durch Folgendes ersetzen:
if quitButton.contains(point) && quitButtonAction != nil { quitButtonAction!() }
Wenn wir jetzt die Schließung quitButtonAction
setzen, wird sie von diesem Punkt aus aufgerufen.
Um die Schließung von quitButtonAction
, müssen wir zu GameScene.swift
. In sceneDidLoad()
können wir unser HUD-Setup durch den folgenden Code ersetzen:
hudNode.setup(size: size) hudNode.quitButtonAction = { let transition = SKTransition.reveal(with: .up, duration: 0.75) let gameScene = MenuScene(size: self.size) gameScene.scaleMode = self.scaleMode self.view?.presentScene(gameScene, transition: transition) self.hudNode.quitButtonAction = nil } addChild(hudNode)
Führen Sie die App aus, drücken Sie auf „Play“ und dann auf „Beenden“. Wenn Sie wieder im Hauptmenü sind, funktioniert Ihre Beenden-Taste wie vorgesehen. In der von uns erstellten Closure haben wir einen Übergang zur MenuScene
initialisiert. Und wir setzen diese Schließung für den HUD
-Knoten so, dass sie ausgeführt wird, wenn auf die Schaltfläche „Beenden“ geklickt wird. Eine weitere wichtige Zeile hier ist, wenn wir quitButtonAction
auf nil
setzen. Der Grund dafür ist, dass ein Retain-Zyklus auftritt. Die Szene enthält einen Verweis auf das HUD, wobei das HUD einen Verweis auf die Szene enthält. Da es einen Verweis auf beide Objekte gibt, wird keines von beiden entsorgt, wenn es Zeit für die Garbage Collection ist. In diesem Fall wird jedes Mal, wenn wir GameScene
betreten und verlassen, eine weitere Instanz davon erstellt und nie veröffentlicht. Dies ist schlecht für die Leistung und der App wird schließlich der Arbeitsspeicher ausgehen. Es gibt eine Reihe von Möglichkeiten, dies zu vermeiden, aber in unserem Fall können wir einfach den Verweis auf GameScene
aus dem HUD entfernen, und die Szene und das HUD werden beendet, sobald wir zu MenuScene
. Krakendev hat eine tiefere Erklärung der Referenztypen und wie man diese Zyklen vermeidet.
Wechseln Sie nun zu GameViewController.swift
und entfernen oder kommentieren Sie die folgenden drei Codezeilen aus:
view.showsPhysics = true view.showsFPS = true view.showsNodeCount = true
Mit den Debugging-Daten sieht das Spiel wirklich gut aus! Herzlichen Glückwunsch: Wir befinden uns derzeit in der Beta! Sehen Sie sich den endgültigen Code von heute auf GitHub an.
Abschließende Gedanken
Dies ist die letzte Lektion eines dreiteiligen Tutorials, und wenn Sie es bis hierher geschafft haben, haben Sie gerade eine Menge Arbeit an Ihrem Spiel geleistet. In diesem Tutorial sind Sie von einer absolut leeren Szene zu einem fertigen Spiel übergegangen. Herzlichen Glückwunsch! In Lektion eins haben wir den Boden, die Regentropfen, den Hintergrund und die Regenschirm-Sprites hinzugefügt. Wir haben auch mit der Physik herumgespielt und dafür gesorgt, dass sich unsere Regentropfen nicht häufen. Wir begannen mit der Kollisionserkennung und arbeiteten am Culling von Knoten, damit uns der Speicher nicht ausgeht. Wir haben auch etwas Benutzerinteraktion hinzugefügt, indem wir dem Regenschirm erlaubt haben, sich dorthin zu bewegen, wo der Benutzer den Bildschirm berührt.
In Lektion zwei haben wir die Katze und das Futter hinzugefügt, zusammen mit benutzerdefinierten Laichmethoden für jede von ihnen. Wir haben unsere Kollisionserkennung aktualisiert, um die Katze und die Essensgeister zu berücksichtigen. Wir haben auch an der Bewegung der Katze gearbeitet. Die Katze hat ein Ziel erreicht: Fressen Sie jedes verfügbare Stückchen Nahrung. Wir haben eine einfache Animation für die Katze hinzugefügt und benutzerdefinierte Interaktionen zwischen der Katze und dem Regen hinzugefügt. Schließlich haben wir Soundeffekte und Musik hinzugefügt, damit es sich wie ein vollständiges Spiel anfühlt.
In dieser letzten Lektion haben wir ein Heads-up-Display erstellt, um unser Score-Label sowie unseren Beenden-Button zu halten. Wir haben Aktionen knotenübergreifend gehandhabt und dem Benutzer ermöglicht, mit einem Rückruf vom HUD-Knoten zu beenden. Wir haben auch eine weitere Szene hinzugefügt, in die der Benutzer starten und zu der er zurückkehren kann, nachdem er auf die Schaltfläche „Beenden“ geklickt hat. Wir haben den Prozess zum Starten des Spiels und zum Steuern des Sounds im Spiel gehandhabt.
Wohin von hier aus
Wir haben viel Zeit investiert, um so weit zu kommen, aber es gibt noch viel Arbeit, die in dieses Spiel gesteckt werden kann. RainCat entwickelt sich weiter und ist im App Store erhältlich. Nachfolgend finden Sie eine Liste der Wünsche und Bedürfnisse, die hinzugefügt werden müssen. Einige der Artikel wurden hinzugefügt, während andere noch ausstehen:
- Fügen Sie Symbole und einen Begrüßungsbildschirm hinzu.
- Finalisieren Sie das Hauptmenü (vereinfacht für das Tutorial).
- Beheben Sie Fehler, einschließlich betrügerischer Regentropfen und das Spawnen mehrerer Lebensmittel.
- Refaktorisieren und optimieren Sie den Code.
- Ändern Sie die Farbpalette des Spiels basierend auf der Punktzahl.
- Aktualisieren Sie die Schwierigkeit basierend auf der Punktzahl.
- Animiere die Katze, wenn Futter direkt darüber ist.
- Gamecenter integrieren.
- Geben Sie Anerkennung (einschließlich angemessener Anerkennung für Musiktitel).
Behalten Sie auf GitHub den Überblick, da diese Änderungen in Zukunft vorgenommen werden. Wenn Sie Fragen zum Code haben, können Sie uns gerne eine E-Mail an [email protected] schreiben, und wir können darüber sprechen. Wenn bestimmte Themen genug Aufmerksamkeit bekommen, können wir vielleicht einen weiteren Artikel schreiben, der das Thema behandelt.
Danke!
Ich möchte allen Leuten danken, die bei der Erstellung des Spiels und der Entwicklung der dazugehörigen Artikel geholfen haben.
- Cathryn Rowe Für die anfängliche Grafik, das Design und die Bearbeitung und für die Veröffentlichung der Artikel in unserer Garage.
- Morgan Wheaton Für das endgültige Menüdesign und die Farbpaletten (die großartig aussehen werden, sobald ich diese Funktionen tatsächlich implementiert habe – bleiben Sie dran).
- Nikki Clark Für die großartigen Überschriften und Unterteilungen in den Artikeln und für die Hilfe beim Bearbeiten der Artikel.
- Laura Levisay Für all die tollen GIFs in den Artikeln und dafür, dass sie mir süße Katzen-GIFs zur moralischen Unterstützung geschickt haben.
- Tom Hudson Für die Hilfe bei der Bearbeitung der Artikel und ohne die diese Serie überhaupt nicht entstanden wäre.
- Lani DeGuire Für die Hilfe beim Bearbeiten der Artikel, was eine Menge Arbeit war.
- Jeff Moon Für Hilfe bei der Bearbeitung von Lektion drei und Ping-Pong. Viel Tischtennis.
- Tom Nelson dafür, dass er geholfen hat sicherzustellen, dass das Tutorial so funktioniert, wie es sollte.
Im Ernst, es hat eine Menge Leute gebraucht, um alles für diesen Artikel vorzubereiten und ihn in den Laden zu bringen.
Vielen Dank auch an alle, die diesen Satz lesen.