SpriteKit is a powerful framework provided by Apple for creating 2D games and interactive animations. It's a versatile tool for game developers, allowing them to bring their creative ideas to life. In this article, we'll explore how to create the classic "Snake" game using SpriteKit. "Snake" is a timeless arcade game that involves controlling a snake to eat food and grow longer while avoiding collisions with the walls. Prerequisites Before we dive into the development process, make sure you have the following set up: Xcode: Download and install Xcode, Apple's integrated development environment (IDE), from the Mac App Store. Basic knowledge of Swift: Familiarity with the Swift programming language is essential for understanding the code in this article. Setting Up Your Project Let's get started by creating a new SpriteKit project in Xcode: Open Xcode and choose "Create a new Xcode project." Select the "Game" template under the "iOS" category. Fill in the project details, such as the product name and organization identifier. Ensure that the language is set to Swift. Choose "SpriteKit" as the game technology. Click "Next" and specify the project location, then click "Create." Designing the Game Collision Detection To begin with, we will determine the components of our game, and we will make the masks for collisions for them enum CollisionMask: UInt32 { case snakeHead = 1 case snakeBody = 2 case apple = 4 case edgeBody = 8 } In the method our game scene, you'll need to implement collision detection to check if the snake has collided with the food or edge. When the snake eats the food, it should grow longer, and a new food item should appear. update(_:) Snake and Food To create the snake and food objects, we'll use . SKShapeNode final class Apple: SKShapeNode { convenience init(position: CGPoint) { self.init() self.path = CGPath(ellipseIn: CGRect(x: 0, y: 0, width: 10, height: 10), transform: nil) self.fillColor = .systemRed self.strokeColor = .systemOrange self.lineWidth = 2 self.position = position let pBody = SKPhysicsBody(circleOfRadius: 10, center: CGPoint(x: 5, y: 5)) pBody.categoryBitMask = CollisionMask.apple.rawValue self.physicsBody = pBody } } Our snake have base items which build body: class SnakeBodyElement: SKShapeNode { var setChildSuperParent: (SKNode) -> Void = { _ in } var diameter: CGFloat { 10 } var runDuration: TimeInterval { 0.2 } private var isNeedAddCild: Bool = false private var childElement: SnakeBodyElement? init(atPoint point: CGPoint) { super.init() self.path = CGPath( ellipseIn: CGRect(x: 0, y: 0, width: diameter, height: diameter), transform: nil ) let pBody = SKPhysicsBody( circleOfRadius: diameter / 2, center: CGPoint(x: diameter / 2, y: diameter / 2) ) pBody.isDynamic = true pBody.categoryBitMask = CollisionMask.snakeBody.rawValue pBody.contactTestBitMask = CollisionMask.edgeBody.rawValue | CollisionMask.apple.rawValue self.physicsBody = pBody self.fillColor = .systemTeal self.strokeColor = .systemGreen self.lineWidth = 1 self.position = point } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func removeFromParent() { super.removeFromParent() childElement?.removeFromParent() } func moveTo(position: CGPoint) { let currentPosition = self.position self.run(SKAction.move(to: position, duration: runDuration)) if let child = childElement { child.moveTo(position: currentPosition) } else if isNeedAddCild { let newElement = SnakeBodyElement(atPoint: currentPosition) newElement.setChildSuperParent = setChildSuperParent childElement = newElement setChildSuperParent(newElement) isNeedAddCild = false } } func eat() { if let child = childElement { child.eat() } else { isNeedAddCild = true } } } And a head: final class SnakeHead: SnakeBodyElement { override var diameter: CGFloat { 20 } override var runDuration: TimeInterval { 1 } private let moveSpeed: Double = 100 private var angle: CGFloat = 0 override init(atPoint point: CGPoint) { super.init(atPoint: point) self.name = "SnakeHead" self.physicsBody?.categoryBitMask = CollisionMask.snakeHead.rawValue self.physicsBody?.contactTestBitMask = CollisionMask.edgeBody.rawValue | CollisionMask.apple.rawValue | CollisionMask.snakeBody.rawValue } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func moveTo(position: CGPoint) { let dx = CGFloat(moveSpeed) * sin(angle) let dy = CGFloat(moveSpeed) * cos(angle) let nextPosition = CGPoint(x: self.position.x + dx, y: self.position.y + dy) super.moveTo(position: nextPosition) } func moveClockwise() { angle += CGFloat.pi / 2 } func moveCounterClockwise() { angle -= CGFloat.pi / 2 } } Game Loop SpriteKit provides a built-in game loop through the method in our scene. This is where you'll update the game's logic, such as moving the snake and checking for collisions. update(_:) override func update(_ currentTime: TimeInterval) { // Add game logic here } User Interaction To control the snake, you'll want to capture user input. One common way to do this is by handling touch events: override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // Handle touch events to change the snake's direction } Implementing Game Logic Now that you have the basic structure of game, it's time to implement the game logic. Here are some key steps: Move the snake in the direction determined by user input. Check for collisions with the walls. If the snake collides, the game should end. Check if the snake's head collides with the food. If so, make the snake longer and spawn a new food item. Keep track of the snake's length and update the score. Implement game over logic when the snake collides with the walls. Game Scene In SpriteKit, games are built around scenes. We'll begin by designing our game scene, including the snake and food elements. Open the file in your Xcode project. GameScene.swift Change the existing code: final class GameScene: SKScene { private var snake: SnakeHead? private var apple: Apple? let infoSender = CurrentValueSubject<Int, Never>(0) override init(size: CGSize) { super.init(size: size) scaleMode = .resizeFill } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func didMove(to view: SKView) { view.showsPhysics = true guard let viewScene = view.scene else { return } viewScene.physicsWorld.contactDelegate = self viewScene.physicsBody?.allowsRotation = false viewScene.backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 0.8, alpha: 1) viewScene.physicsBody = SKPhysicsBody(edgeLoopFrom: frame) viewScene.physicsBody?.categoryBitMask = CollisionMask.edgeBody.rawValue viewScene.physicsBody?.collisionBitMask = CollisionMask.snakeBody.rawValue | CollisionMask.snakeHead.rawValue viewScene.physicsWorld.gravity = CGVector(dx: 0, dy: 0) setup(skScene: viewScene) } override func update(_ currentTime: TimeInterval) { if !isPaused { snake?.moveTo(position: apple!.position) } } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { buttonAction(touches, color: .green) { [weak self] tNode in if tNode.name == "CounterClockwiseBtn" && !(self?.isPaused ?? true) { self?.snake?.moveCounterClockwise() } else if tNode.name == "ClockwiseBtn" && !(self?.isPaused ?? true) { self?.snake?.moveClockwise() } else if tNode.name == "Pause" { self?.isPaused.toggle() } } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { buttonAction(touches, color: .gray) } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { isPaused = true } private func setup(skScene: SKScene) { var position = CGPoint(x: skScene.frame.minX + 30, y: skScene.frame.minY + 30) self.addChild(createBtn(name: "CounterClockwiseBtn", color: .gray, position: position)) position.x = skScene.frame.maxX - 80 self.addChild(createBtn(name: "ClockwiseBtn", color: .gray, position: position)) position.y = skScene.frame.maxY - 80 self.addChild(createBtn(name: "Pause", color: .systemBlue, position: position)) createSnake(skFrame: skScene.frame) createApple(skFrame: skScene.frame) } private func createSnake(skFrame: CGRect) { snake = SnakeHead(atPoint: CGPoint(x: skFrame.midX, y: skFrame.midY)) snake?.setChildSuperParent = self.addChild(_:) self.addChild(snake!) } private func createApple(skFrame: CGRect) { let randX = CGFloat(arc4random_uniform(UInt32(skFrame.maxX)) + 1) let randY = CGFloat(arc4random_uniform(UInt32(skFrame.maxY)) + 1) apple = Apple(position: CGPoint(x: randX, y: randY)) self.addChild(apple!) } private func createBtn(name: String?, color: UIColor, position: CGPoint) -> SKShapeNode { let btn = SKShapeNode() btn.path = CGPath(ellipseIn: CGRect(x: 0, y: 0, width: 45, height: 45), transform: nil) btn.position = position btn.fillColor = color btn.strokeColor = .darkGray btn.lineWidth = 10 btn.name = name return btn } private func buttonAction( _ touches: Set<UITouch>, color: UIColor, action: (SKShapeNode) -> Void = {_ in} ) { for touch in touches { let touchLocation = touch.location(in: self) guard let touchedNode = self.atPoint(touchLocation) as? SKShapeNode, touchedNode.name == "CounterClockwiseBtn" || touchedNode.name == "ClockwiseBtn" || touchedNode.name == "Pause" else { return } touchedNode.fillColor = color action(touchedNode) } } } extension GameScene: SKPhysicsContactDelegate { func didBegin(_ contact: SKPhysicsContact) { let collisionObject: SKPhysicsBody if contact.bodyA.node?.name == "SnakeHead" { collisionObject = contact.bodyB } else { collisionObject = contact.bodyA } switch CollisionMask(rawValue: collisionObject.categoryBitMask) { case .apple: let apple = contact.bodyA.node is Apple ? contact.bodyA.node : contact.bodyB.node snake?.eat() apple?.removeFromParent() infoSender.send(infoSender.value + 1) createApple(skFrame: view?.scene?.frame ?? .zero) case .edgeBody: snake?.removeFromParent() infoSender.send(0) createSnake(skFrame: view?.scene?.frame ?? .zero) default: break } } } And finally, we'll update our application to show the score: @main struct SnakeApp: App { private let scene: GameScene //BloxScene @State private var info = "0" var body: some Scene { WindowGroup { ZStack { Color.gray .edgesIgnoringSafeArea(.all) VStack(spacing: 0) { Text("Score: \(info)") .font(.body) .foregroundColor(.white) SpriteView( scene: scene, debugOptions: [.showsFPS, .showsNodeCount, .showsDrawCount], shouldRender: { timeInterval in true } ) .frame(idealWidth: .infinity, idealHeight: .infinity) } } .onReceive(scene.infoSender.eraseToAnyPublisher()) { info = "\($0)" } } } init() { self.scene = GameScene(size: CGSize(width: 10, height: 10)) } } Conclusion Creating the classic "Snake" game using SpriteKit can be a fun and educational project for both beginners and experienced game developers. This article has provided a high-level overview of the steps involved in setting up the project and designing the game scene. The real challenge lies in implementing the game logic, handling user input, and creating an enjoyable gaming experience. Remember that game development is an iterative process, so don't be discouraged by initial setbacks. With persistence and creativity, you can create a polished "Snake" game that is both engaging and entertaining. Good luck with your SpriteKit game development journey!