Last week on WWDC Apple showed a short demo about a Swift Playground for iOS, which was part of iOS 10 on iPads. They focused on using playgrounds to teach kids and students coding. That seems a little bit unspectacular and is a great understatement. Swift Playground support a lot of advanced iOS libraries like SpriteKit, SceneKit, UIKit.
I was wondering if this can really replace a Mac with XCode to develop for my favorite platform. In fact I’m dreaming about only carrying my iPad with me and develop directly on an iPad.
So here we are: My first game SWIFT Playground completely written on an iPad: Pong (updated for iOS 10 Beta 5)
1. Open the Playground App on your iPad:
2. Create a new SWIFT Playground:
3. Here is the SourceCode. Just copy it in your playground and hit start. I’ll not introduce the basics of SpriteKit. You can check my other posts to get an introduction.
import SpriteKit import PlaygroundSupport // Declare some global constants let width = 800 as CGFloat let height = 1200 as CGFloat let racketHeight = 150 as CGFloat let ballRadius = 20 as CGFloat // Three types of collision objects possible enum CollisionTypes: UInt32 { case Ball = 1 case Wall = 2 case Racket = 4 } // Racket direction enum Direction: Int{ case None = 0 case Up = 1 case Down = 2 } // SpriteKit scene class gameScene: SKScene, SKPhysicsContactDelegate { let racketSpeed = 500.0 var direction = Direction.None var score = 0 var gameRunning = false // Screen elements var racket: SKShapeNode? var ball: SKShapeNode? let scoreLabel = SKLabelNode() // Initialize objects during first start override func sceneDidLoad() { super.sceneDidLoad() var resr = SKShapeNode() scoreLabel.fontSize = 40 scoreLabel.position = CGPoint(x: width/2, y: height - 100) self.addChild(scoreLabel) createWalls() createBall(position: CGPoint(x: width / 2, y: height / 2)) createRacket() startNewGame() self.physicsWorld.contactDelegate = self } // Create the ball sprite func createBall(position: CGPoint) { let physicsBody = SKPhysicsBody(circleOfRadius: ballRadius) ball = SKShapeNode(circleOfRadius: ballRadius) physicsBody.categoryBitMask = CollisionTypes.Ball.rawValue physicsBody.collisionBitMask = CollisionTypes.Wall.rawValue | CollisionTypes.Ball.rawValue | CollisionTypes.Racket.rawValue physicsBody.affectedByGravity = false physicsBody.restitution = 1 physicsBody.linearDamping = 0 physicsBody.velocity = CGVector(dx: -500, dy: 500) ball!.physicsBody = physicsBody ball!.position = position ball!.fillColor = SKColor.white } // Create the walls func createWalls() { createWall(rect: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: ballRadius, height: height))) createWall(rect: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: width, height: ballRadius))) createWall(rect: CGRect(origin: CGPoint(x: 0, y: height - ballRadius), size: CGSize(width: width, height: ballRadius))) } func createWall(rect: CGRect) { let node = SKShapeNode(rect: rect) node.fillColor = SKColor.white node.physicsBody = getWallPhysicsbody(rect: rect) self.addChild(node) } // Create the physics objetcs to handle wall collisions func getWallPhysicsbody(rect: CGRect) -> SKPhysicsBody { let physicsBody = SKPhysicsBody(rectangleOf: rect.size, center: CGPoint(x: rect.midX, y: rect.midY)) physicsBody.affectedByGravity = false physicsBody.isDynamic = false physicsBody.collisionBitMask = CollisionTypes.Ball.rawValue physicsBody.categoryBitMask = CollisionTypes.Wall.rawValue return physicsBody } // Create the racket sprite func createRacket() { racket = SKShapeNode(rect: CGRect(origin: CGPoint.zero, size: CGSize(width: ballRadius, height: racketHeight))) self.addChild(racket!) racket!.fillColor = SKColor.white let physicsBody = SKPhysicsBody(rectangleOf: racket!.frame.size, center: CGPoint(x: racket!.frame.midX, y: racket!.frame.midY)) physicsBody.affectedByGravity = false physicsBody.isDynamic = false physicsBody.collisionBitMask = CollisionTypes.Ball.rawValue physicsBody.categoryBitMask = CollisionTypes.Racket.rawValue physicsBody.contactTestBitMask = CollisionTypes.Ball.rawValue racket!.physicsBody = physicsBody } // Start a new game func startNewGame() { score = 0 scoreLabel.text = "0" racket!.position = CGPoint(x: width - ballRadius * 2, y: height / 2) var counter = 3 let startLabel = SKLabelNode(text: "Game Over") startLabel.position = CGPoint(x: width / 2, y: height / 2) startLabel.fontSize = 160 self.addChild(startLabel) // Animated countdown let fadeIn = SKAction.fadeIn(withDuration: 0.5) let fadeOut = SKAction.fadeOut(withDuration: 0.5) startLabel.text = "3" startLabel.run(SKAction.sequence([fadeIn, fadeOut]), completion: { startLabel.text = "2" startLabel.run(SKAction.sequence([fadeIn, fadeOut]), completion: { startLabel.text = "1" startLabel.run(SKAction.sequence([fadeIn, fadeOut]), completion: { startLabel.text = "0" startLabel.run(SKAction.sequence([fadeIn, fadeOut]), completion: { startLabel.removeFromParent() self.gameRunning = true self.ball!.position = CGPoint(x: 30, y: height / 2) self.addChild(self.ball!) }) }) }) }) } // Handle touch events to move the racket override func touchesBegan(_ touches: Set, with event: UIEvent?) { for touch in touches { var location = touch.location(in: self) if location.y > height / 2 { direction = Direction.Up } else if location.y < height / 2{ direction = Direction.Down } } } // Stop racket movement override func touchesEnded(_ touches: Set, with event: UIEvent?) { direction = Direction.None } // Game loop: // - Game over check: Ball still on screen // - Trigger racket movement var dt = TimeInterval(0) override func update(_ currentTime: TimeInterval) { if gameRunning { super.update(currentTime) checkGameOver() if dt > 0 { moveRacket(dt: currentTime - dt) } dt = currentTime } } // Move the racket up or down func moveRacket(dt: TimeInterval) { if direction == Direction.Up && racket!.position.y < height - racketHeight { racket!.position.y = racket!.position.y + CGFloat(racketSpeed * dt) } else if direction == Direction.Down && racket!.position.y > 0 { racket!.position.y = racket!.position.y - CGFloat(racketSpeed * dt) } } // Check if the ball is still on screen // Game Over animation func checkGameOver() { if ball!.position.x > CGFloat(width) { gameRunning = false ball!.removeFromParent() let gameOverLabel = SKLabelNode(text: "Game Over") gameOverLabel.position = CGPoint(x: width / 2, y: height / 2) gameOverLabel.fontSize = 80 self.addChild(gameOverLabel) // Game Over animation let rotateAction = SKAction.rotate(byAngle: CGFloat(M_PI), duration: 1) let fadeInAction = SKAction.fadeIn(withDuration: 2) gameOverLabel.run(SKAction.repeat(rotateAction, count: 2)) gameOverLabel.run(SKAction.scale(to: 0, duration: 2.5), completion: { gameOverLabel.removeFromParent() self.startNewGame() }) } } // Detect collisions between ball and racket to increase the score func didBegin(_ contact: SKPhysicsContact) { if contact.bodyA.categoryBitMask == CollisionTypes.Racket.rawValue || contact.bodyB.categoryBitMask == CollisionTypes.Racket.rawValue { score += 1 scoreLabel.text = String(score) } } } //Initialize the playground and start the scene: let skView = SKView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: width, height: height))) let scene = gameScene(size: skView.frame.size) skView.presentScene(scene) PlaygroundPage.current.liveView = skView
You can also download the sample directly from my GitHub repository.
My personal conclusion: You can really write advanced Playground Apps completely on your iPad. The editor offers some intelligent solutions to choose the next needed code snippet without typing, but for bigger projects an external keypad improves the coding experience a lot. A possibility to upload the playgrounds as an App to the AppStore is missing. So a kind of Swift Playground Store would be nice for the future. Maybe this will come with the final release of iOS 10.
That’s all for today.
If you want to support me, please download my Apps from the Apple AppStore.
Cheers,
Stefan