Last active
July 14, 2023 01:28
-
-
Save runys/cbf40cadab4a74959ed8bb331267a0bc to your computer and use it in GitHub Desktop.
Example code of a simple integration between SwiftUI and SpriteKit using the delegate pattern.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import SpriteKit | |
protocol SquareGameLogicDelegate { | |
var totalScore: Int { get } | |
mutating func addPoint() -> Void | |
} | |
// 1. Conform the ContentView to the SquareLogicDelegate protocol | |
struct ContentView: View, SquareGameLogicDelegate { | |
// 2. Implement the totalScore variable and add the @State property wrapper | |
// so once it changes value the user interface updates too | |
@State var totalScore: Int = 0 | |
mutating func addPoint() { | |
self.totalScore += 1 | |
} | |
var screenWidth: CGFloat { UIScreen.main.bounds.width } | |
var screenHeight: CGFloat { UIScreen.main.bounds.height } | |
var gameSceneWidth: CGFloat { screenWidth } | |
var gameSceneHeight: CGFloat { screenHeight - 100 } | |
var squareGameGameScene: SquareGameGameScene { | |
let scene = SquareGameGameScene() | |
scene.size = CGSize(width: gameSceneWidth, height: gameSceneHeight) | |
scene.scaleMode = .fill | |
// 3. Remember to assign your view as the gameLogicDelegate of your game scene | |
scene.gameLogicDelegate = self | |
return scene | |
} | |
var body: some View { | |
ZStack(alignment: .top) { | |
SpriteView(scene: self.squareGameGameScene) | |
.frame(width: gameSceneWidth, height: gameSceneHeight) | |
// 4. An use the totalScore property to show the updated score | |
Text("Score: \(self.totalScore)") | |
.font(.headline).fontWeight(.bold) | |
.padding().background(Color.white).cornerRadius(10) | |
.overlay(RoundedRectangle(cornerRadius: 10).stroke(lineWidth: 4.0)) | |
} | |
} | |
} | |
struct GameSizes { | |
static let squareSize: CGFloat = 40.0 | |
} | |
class SquareGameGameScene: SKScene { | |
var gameLogicDelegate: SquareGameLogicDelegate? = nil | |
private var lastTimeSpawnedSquare: TimeInterval = 0.0 | |
override func didMove(to view: SKView) { | |
self.setUpGame() | |
} | |
override func update(_ currentTime: TimeInterval) { | |
if self.lastTimeSpawnedSquare == 0.0 { self.lastTimeSpawnedSquare = currentTime } | |
let timePassed = currentTime - self.lastTimeSpawnedSquare | |
if timePassed >= 2 { | |
self.spawnNewSquare() | |
self.lastTimeSpawnedSquare = currentTime | |
} | |
} | |
} | |
// MARK: - Setting up the game | |
extension SquareGameGameScene { | |
private func setUpGame() { | |
backgroundColor = SKColor.white | |
} | |
} | |
// MARK: - Handling Interaction | |
extension SquareGameGameScene { | |
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { | |
guard let touch = touches.first else { return } | |
let touchPosition = touch.location(in: self) | |
if let selectedNode = nodes(at: touchPosition).first as? SKShapeNode { | |
self.destroy(selectedNode) | |
self.addPoint() | |
} | |
} | |
} | |
// MARK: - Square Creation Dynamics | |
extension SquareGameGameScene { | |
private func spawnNewSquare() { | |
let square = self.getNewSquare() | |
square.position = self.getRandomPosition() | |
addChild(square) | |
} | |
private func getRandomPosition() -> CGPoint { | |
let screeWidth = self.frame.width | |
let screeHeight = self.frame.height | |
let minX = 0 + (GameSizes.squareSize * 2) | |
let maxX = screeWidth - (GameSizes.squareSize * 2) | |
let minY = 0 + (GameSizes.squareSize * 2) | |
let maxY = screeHeight - (GameSizes.squareSize * 2) | |
let randomX = CGFloat.random(in: minX...maxX) | |
let randomY = CGFloat.random(in: minY...maxY) | |
return CGPoint(x: randomX, y: randomY) | |
} | |
private func getNewSquare() -> SKShapeNode { | |
let newSquare = SKShapeNode(rectOf: CGSize(width: GameSizes.squareSize, height: GameSizes.squareSize)) | |
newSquare.fillColor = SKColor.red | |
newSquare.strokeColor = SKColor.black | |
newSquare.lineWidth = 4.0 | |
return newSquare | |
} | |
} | |
// MARK: - Square Destruction Dynamics | |
extension SquareGameGameScene { | |
private func destroy(_ square: SKShapeNode) { | |
square.removeFromParent() | |
} | |
} | |
// MARK: - Score Dynamics | |
extension SquareGameGameScene { | |
private func addPoint() { | |
if var gameLogicDelegate = self.gameLogicDelegate { | |
gameLogicDelegate.addPoint() | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
// Import SpriteKit to have access to the SpriteView(_:) View | |
import SpriteKit | |
struct ContentView: View { | |
// Getting the dimensions of the screen | |
var screenWidth: CGFloat { UIScreen.main.bounds.width } | |
var screenHeight: CGFloat { UIScreen.main.bounds.height } | |
// Set the dimensions of the game scene | |
var gameSceneWidth: CGFloat { screenWidth } | |
var gameSceneHeight: CGFloat { screenHeight - 100 } | |
// Initialize the Game Scene | |
var squareGameGameScene: SquareGameGameScene { | |
let scene = SquareGameGameScene() | |
scene.size = CGSize(width: gameSceneWidth, height: gameSceneHeight) | |
scene.scaleMode = .fill | |
return scene | |
} | |
var body: some View { | |
// You can also use a VStack to organize your user interface | |
ZStack(alignment: .top) { | |
// Use the SpriteView from SpriteKit frameworks to present | |
// your game scene | |
SpriteView(scene: self.squareGameGameScene) | |
.frame(width: gameSceneWidth, height: gameSceneHeight) | |
// Presents the score of the player | |
Text("Score: \(0)") | |
.font(.headline).fontWeight(.bold) | |
.padding().background(Color.white).cornerRadius(10) | |
.overlay(RoundedRectangle(cornerRadius: 10).stroke(lineWidth: 4.0)) | |
} | |
} | |
} | |
import SpriteKit | |
// A struct to store values that repeat often in the code | |
struct GameSizes { | |
static let squareSize: CGFloat = 40.0 | |
} | |
class SquareGameGameScene: SKScene { | |
// 1. A variable to keep track of how much time since the last update cycle | |
private var lastTimeSpawnedSquare: TimeInterval = 0.0 | |
// 2. Set up the game once the Game Scene is presented | |
override func didMove(to view: SKView) { | |
self.setUpGame() | |
} | |
override func update(_ currentTime: TimeInterval) { | |
// 3. Every 2 seconds a new square is created and placed on the screen | |
if self.lastTimeSpawnedSquare == 0.0 { self.lastTimeSpawnedSquare = currentTime } | |
let timePassed = currentTime - self.lastTimeSpawnedSquare | |
if timePassed >= 2 { | |
self.spawnNewSquare() | |
// Reseting the timer | |
self.lastTimeSpawnedSquare = currentTime | |
} | |
} | |
} | |
// MARK: - Setting up the game | |
extension SquareGameGameScene { | |
private func setUpGame() { | |
backgroundColor = SKColor.white | |
} | |
} | |
// MARK: - Handling Interaction | |
extension SquareGameGameScene { | |
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { | |
// Gets the first touch object in the set of touches | |
guard let touch = touches.first else { return } | |
// Gets the position of the touch in the game scene | |
let touchPosition = touch.location(in: self) | |
// 1. Checks if there is a SKShapeNode where the player touched. | |
if let selectedNode = nodes(at: touchPosition).first as? SKShapeNode { | |
// 2. If so, calls a function to destroy the node | |
self.destroy(selectedNode) | |
} | |
} | |
} | |
// MARK: - Square Creation Dynamics | |
extension SquareGameGameScene { | |
// Called every two seconds in the update(:_) function. | |
private func spawnNewSquare() { | |
let square = self.getNewSquare() | |
square.position = self.getRandomPosition() | |
addChild(square) | |
} | |
// Returns a random position based on the dimensions of the game scene | |
// and the dimensions of the squares. | |
private func getRandomPosition() -> CGPoint { | |
let screeWidth = self.frame.width | |
let screeHeight = self.frame.height | |
let minX = 0 + (GameSizes.squareSize * 2) | |
let maxX = screeWidth - (GameSizes.squareSize * 2) | |
let minY = 0 + (GameSizes.squareSize * 2) | |
let maxY = screeHeight - (GameSizes.squareSize * 2) | |
let randomX = CGFloat.random(in: minX...maxX) | |
let randomY = CGFloat.random(in: minY...maxY) | |
return CGPoint(x: randomX, y: randomY) | |
} | |
// Creates a SKShapeNode to be used in the game. | |
private func getNewSquare() -> SKShapeNode { | |
let newSquare = SKShapeNode(rectOf: CGSize(width: GameSizes.squareSize, height: GameSizes.squareSize)) | |
newSquare.fillColor = SKColor.red | |
newSquare.strokeColor = SKColor.black | |
newSquare.lineWidth = 4.0 | |
return newSquare | |
} | |
} | |
// MARK: - Square Destruction Dynamics | |
extension SquareGameGameScene { | |
private func destroy(_ square: SKShapeNode) { | |
square.removeFromParent() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment