Skip to content

Instantly share code, notes, and snippets.

@runys
Last active July 14, 2023 01:28
Show Gist options
  • Save runys/cbf40cadab4a74959ed8bb331267a0bc to your computer and use it in GitHub Desktop.
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.
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()
}
}
}
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