SpriteKit Project — AbsolutelyBrickingIt — Part 6: Add the about scene
In this article, we’ll add the about scene that the user sees when they tap the about button.
Add new assets and scene
Add the text.about
images for the about scene to Assets:
Add a new file, called AboutScene
to the project and add the background, logo, and text nodes as children of the scene:
import SpriteKit
class AboutScene: SKScene {
override func didMove(to view: SKView) {
let backgroundNode = SKSpriteNode(imageNamed: "background.menu")
backgroundNode.position = CGPoint(x: 0.5 * size.width, y: 0.5 * size.height)
addChild(backgroundNode)
let logoNode = SKSpriteNode(imageNamed: "logo")
logoNode.position = CGPoint(x: 0.5 * size.width, y: 0.8 * size.height)
addChild(logoNode)
let textNode = SKSpriteNode(imageNamed: "text.about")
textNode.position = CGPoint(x: 0.5 * size.width, y: 0.4 * size.height)
addChild(textNode)
}
}
Handle about button taps
Update MenuScene
to keep a reference to the about node and to call a coordinator method when about gets touched:
protocol MenuSceneCoordinator: AnyObject {
func menuScenePlayTapped(_ scene: MenuScene)
func menuSceneAboutTapped(_ scene: MenuScene)
}
class MenuScene: SKScene {
weak var coordinator: MenuSceneCoordinator?
private var aboutNode: SKSpriteNode?
private var playNode: SKSpriteNode?
override func didMove(to view: SKView) {
...
addChild(aboutNode)
self.aboutNode = aboutNode
...
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let point = touch.location(in: self)
if playNode?.contains(point) == true {
coordinator?.menuScenePlayTapped(self)
} else if aboutNode?.contains(point) == true {
coordinator?.menuSceneAboutTapped(self)
}
}
}
}
GameViewController
needs to be updated to include this new coordinator method, since it already conforms to MenuSceneCoordinator
:
extension GameViewController: MenuSceneCoordinator {
func menuSceneAboutTapped(_ scene: MenuScene) {
guard let view = self.view as? SKView else { return }
let scene = AboutScene(size: CGSize(width: 375.0, height: 667.0))
scene.scaleMode = .resizeFill
view.presentScene(scene)
}
func menuScenePlayTapped(_ scene: MenuScene) {
...
}
}
Running the app, we can now tap About
to open the AboutScene
:
Returning to the menu from the about scene
We’ll follow a similar approach to return to the menu. Let’s add a coordinator to AboutScene
with a method that gets called whenever a touch completes anywhere on the about screen:
protocol AboutSceneCoordinator: AnyObject {
func aboutSceneScreenTapped(_ scene: AboutScene)
}
class AboutScene: SKScene {
weak var coordinator: AboutSceneCoordinator?
...
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
coordinator?.aboutSceneScreenTapped(self)
}
}
Let’s set the GameViewController
as the coordinator for the AboutScene
and open MenuScene
whenever the coordinator method gets called:
extension GameViewController: MenuSceneCoordinator {
func menuSceneAboutTapped(_ scene: MenuScene) {
...
let scene = AboutScene(size: CGSize(width: 375.0, height: 667.0))
scene.coordinator = self
...
}
}
extension GameViewController: AboutSceneCoordinator {
func aboutSceneScreenTapped(_ scene: AboutScene) {
guard let view = self.view as? SKView else { return }
let scene = MenuScene(size: CGSize(width: 375.0, height: 667.0))
scene.coordinator = self
scene.scaleMode = .resizeFill
view.presentScene(scene)
}
}
Now when the app runs, the user can tap About
to open the AboutScene
and tap anywhere on the about screen to open the MenuScene
again.
Returning to the menu from the game scene
Following the exact same approach, we can navigate back and forth between GameScene
and MenuScene
. This is just a temporary measure, since we'll have to update it once the game is in place, but it's useful for early development. Update GameScene to:
import SpriteKit
protocol GameSceneCoordinator: AnyObject {
func gameSceneScreenTapped(_ scene: GameScene)
}
class GameScene: SKScene {
weak var coordinator: GameSceneCoordinator?
override func didMove(to view: SKView) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
coordinator?.gameSceneScreenTapped(self)
}
}
(Note GameplayKit was also removed, since we won’t be using that for now.)
In GameViewController
set the coordinator for the GameScene
and implement the GameSceneCoordinator
interface:
extension GameViewController: MenuSceneCoordinator {
func menuScenePlayTapped(_ scene: MenuScene) {
...
let scene = GameScene(size: CGSize(width: 375.0, height: 667.0))
scene.coordinator = self
...
}
}
extension GameViewController: GameSceneCoordinator {
func gameSceneScreenTapped(_ scene: GameScene) {
guard let view = self.view as? SKView else { return }
let scene = MenuScene(size: CGSize(width: 375.0, height: 667.0))
scene.coordinator = self
scene.scaleMode = .resizeFill
view.presentScene(scene)
}
}
Now when the app runs, the user can tap Play
to open the GameScene
and tap anywhere on the game screen to open the MenuScene
again.
Cleaning up GameViewController a bit
You’ve probably noticed there’s a lot of duplicated/nearly identical code in GameViewController
. Let's refactor this to reduce duplication and hopefully make it easier to understand. First, we can define a constant for the screen size:
private let sceneSize = CGSize(width: 375.0, height: 667.0)
This can be used everywhere a scene is created, e.g.:
let scene = MenuScene(size: sceneSize)
Next, let’s update viewDidLoad()
as follows:
override func viewDidLoad() {
super.viewDidLoad()
setupView()
present(scene: createMenuScene())
}
func setupView() {
guard let view = self.view as? SKView else { return }
view.ignoresSiblingOrder = false
view.showsFPS = true
view.showsNodeCount = true
}
func present(scene: SKScene) {
guard let view = self.view as? SKView else { return }
view.presentScene(scene)
}
func createMenuScene() -> MenuScene {
let scene = MenuScene(size: sceneSize)
scene.coordinator = self
scene.scaleMode = .resizeFill
return scene
}
It’s much easier to see that viewDidLoad()
does 2 things now: sets up the view and presents the menu scene.
It makes sense to implement createMenuScene()
as a computed var instead in this case, which helps make the code a little cleaner still:
override func viewDidLoad() {
super.viewDidLoad()
setupView()
present(scene: menuScene)
}
var menuScene: MenuScene {
let scene = MenuScene(size: sceneSize)
scene.coordinator = self
scene.scaleMode = .resizeFill
return scene
}
We can also define computed vars for the about scene and the game scene in this way:
var aboutScene: AboutScene {
let scene = AboutScene(size: sceneSize)
scene.coordinator = self
scene.scaleMode = .resizeFill
return scene
}
var gameScene: GameScene {
let scene = GameScene(size: sceneSize)
scene.coordinator = self
scene.scaleMode = .resizeFill
return scene
}
Now the various coordinator implementations can be simplified right down:
extension GameViewController: MenuSceneCoordinator {
func menuSceneAboutTapped(_ scene: MenuScene) {
present(scene: aboutScene)
}
func menuScenePlayTapped(_ scene: MenuScene) {
present(scene: gameScene)
}
}
extension GameViewController: AboutSceneCoordinator {
func aboutSceneScreenTapped(_ scene: AboutScene) {
present(scene: createMenuScene())
}
}
extension GameViewController: GameSceneCoordinator {
func gameSceneScreenTapped(_ scene: GameScene) {
present(scene: createMenuScene())
}
}
Running the app will give the exact same results as before, but now the code is a lot cleaner and easier to follow.
At this point, we have a menu scene, an about scene, and an empty game scene. We can navigate back and forth between them by tapping the buttons on the menu and tapping the other screens to return to the menu!
Code
This article corresponds to the merge commit Add about scene (#6)
in the GitHub repository AbsolutelyBrickingIt