Skip to content

Instantly share code, notes, and snippets.

@topherPedersen
Created July 17, 2018 00:51
Show Gist options
  • Save topherPedersen/9d44e2831e879c1ea97c0fcfe103bb5f to your computer and use it in GitHub Desktop.
Save topherPedersen/9d44e2831e879c1ea97c0fcfe103bb5f to your computer and use it in GitHub Desktop.
Vertical Scene Scrolling in SpriteKit (using multiple images)
// This gist is comprised of two swift files which demonstrate how to do
// vertical scene scrolling with SpriteKit using two images. This code
// is based on the example app from 'Beginning Swift Games Development for iOS'
// by James Goodwill and Wesley Matlock. In Goodwill's example app from the
// book, SuperSpaceMan, the app uses a single background image for scene
// scrolling, and scrolling is limited to the size of the single background.
// I was interested in achieving the same effect using multiple images to
// allow for further scrolling, so I've gone ahead and modified Goodwill's
// app and posted it here for my own personal reference. Specifically, this
// code is being posted for use in my app team class at theCoderSchool in
// Flower Mound, TX where I teach children to code.
//
// GameScene.swift
// SuperSpaceMan
//
// Created by Christopher Pedersen on 6/28/18.
// Copyright © 2018 Christopher Pedersen. All rights reserved.
//
// import the SpriteKit 2D game development framework
import SpriteKit
// import the CoreMotion framework so we can access the devices accelerometer
import CoreMotion
class GameScene: SKScene {
// Declare & Instantiate Background and Player Sprite Objects
let backgroundNode = SKSpriteNode(imageNamed: "Background")
let nextBackgroundNode = SKSpriteNode(imageNamed: "NextBackground")
let foregroundNode = SKSpriteNode()
let playerNode = SKSpriteNode(imageNamed: "Player")
// declare game-state variables
var impulseCount = 4
// create an instance of CMMotionManager
// This will allow us to access the devices accelerometer
let coreMotionManager = CMMotionManager()
// Add Bit Masks
let CollisionCategoryPlayer: UInt32 = 0x1 << 1
let CollisionCategoryPowerUpOrbs: UInt32 = 0x1 << 2
func addOrbsToForeground() {
var orbNodePosition = CGPoint(x: playerNode.position.x, y: playerNode.position.y + 100)
var orbXShift: CGFloat = -1.0
for _ in 1...50 {
let orbNode = SKSpriteNode(imageNamed: "PowerUp")
if orbNodePosition.x - (orbNode.size.width * 2) <= 0 {
orbXShift = 1.0
}
if orbNodePosition.x + orbNode.size.width >= size.width {
orbXShift = -1.0
}
orbNodePosition.x += 40.0 * orbXShift
orbNodePosition.y += 120
orbNode.position = orbNodePosition
orbNode.physicsBody = SKPhysicsBody(circleOfRadius: orbNode.size.width / 2)
orbNode.physicsBody?.isDynamic = false
orbNode.physicsBody?.categoryBitMask = CollisionCategoryPowerUpOrbs
orbNode.physicsBody?.collisionBitMask = 0
orbNode.name = "POWER_UP_ORB"
foregroundNode.addChild(orbNode)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(size: CGSize) {
super.init(size: size)
// Make GameScene the delegate of the scene's physicsWorld.contactDelegate
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0.0, dy: -5.0)
backgroundColor = SKColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
// Enable User Input (enabling allows user to control the game with touch input)
isUserInteractionEnabled = true
// adding the background
backgroundNode.anchorPoint = CGPoint(x: 0.5, y: 0.0)
backgroundNode.position = CGPoint(x: size.width / 2.0, y: 0.0)
addChild(backgroundNode)
// add another background image (this background is placed above the first background on the y-axis)
nextBackgroundNode.anchorPoint = CGPoint(x: 0.5, y: 0.0)
nextBackgroundNode.position = CGPoint(x: size.width / 2.0, y: 1000.0)
// print("backgroundNode.position.y: \(backgroundNode.position.y)")
// print("backgroundNode.size.height: \(backgroundNode.size.height)")
addChild(nextBackgroundNode)
// add the foreground (holds sprites allowing background to scroll past)
addChild(foregroundNode)
// add the player
playerNode.physicsBody = SKPhysicsBody(circleOfRadius: playerNode.size.width / 2)
playerNode.physicsBody?.isDynamic = false
playerNode.position = CGPoint(x: size.width / 2.0, y: 180.0)
playerNode.physicsBody?.linearDamping = 1.0
playerNode.physicsBody?.allowsRotation = false
playerNode.physicsBody?.categoryBitMask = CollisionCategoryPlayer
playerNode.physicsBody?.contactTestBitMask = CollisionCategoryPowerUpOrbs
playerNode.physicsBody?.collisionBitMask = 0
foregroundNode.addChild(playerNode)
// add orbs
addOrbsToForeground()
}
// define what happens when a user taps
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if !playerNode.physicsBody!.isDynamic {
playerNode.physicsBody?.isDynamic = true
coreMotionManager.accelerometerUpdateInterval = 0.3 // set coreMotionManager update rate in seconds
coreMotionManager.startAccelerometerUpdates() // set event listener (accelerometer)
}
if impulseCount > 0 {
playerNode.physicsBody!.applyImpulse(CGVector(dx: 0.0, dy: 40.0))
// impulseCount decrement below disabled for experimentation purposes
// impulseCount -= 1
}
}
// This method is automatically called by default whenever a collision is detected.
// However, for this method to be called automatically we must extend the current
// class, GameScene, using SKPhysicsContactDelegate (see 'extension GameScene' below)
func didBegin(_ contact: SKPhysicsContact) {
let nodeB = contact.bodyB.node!
if nodeB.name == "POWER_UP_ORB" {
// give player additional "power ups" by incrementing the impulseCount
impulseCount += 1
// remove orb sprite upon collision detection
nodeB.removeFromParent()
}
}
// override the didSimulatePhysics method so we can add our own custom code.
// Specifically, we will be adding code that responds to accelerometer events.
// The game will thus respond to accelerometer events when appropriate every
// time the didSimulatePhysics method is called.
override func didSimulatePhysics() {
if let accelerometerData = coreMotionManager.accelerometerData {
playerNode.physicsBody!.velocity = CGVector(dx: CGFloat(accelerometerData.acceleration.x * 380.0), dy: playerNode.physicsBody!.velocity.dy)
}
if playerNode.position.x < -(playerNode.size.width / 2) {
playerNode.position = CGPoint(x: size.width - playerNode.size.width / 2, y: playerNode.position.y)
} else if playerNode.position.x > self.size.width {
playerNode.position = CGPoint(x: playerNode.size.width / 2, y: playerNode.position.y)
}
}
// override the update method so we can add our own code
// the update method serves as a sort of main game loop which
// is called repeatedly while the game is running
override func update(_ currentTime: TimeInterval) {
/*
if playerNode.position.y >= 180.0 {
backgroundNode.position = CGPoint(x: backgroundNode.position.x, y: -((playerNode.position.y - 180.0)/8))
print("position: \(backgroundNode.position)")
foregroundNode.position = CGPoint(x: foregroundNode.position.x, y: -(playerNode.position.y - 180.0))
nextBackgroundNode.position = CGPoint(x: nextBackgroundNode.position.x, y: nextBackgroundNode.position.y - 180.0)
}
*/
if playerNode.position.y >= 180.0 {
backgroundNode.position = CGPoint(x: backgroundNode.position.x, y: -((playerNode.position.y - 180.0)/8))
print("position: \(backgroundNode.position)")
foregroundNode.position = CGPoint(x: foregroundNode.position.x, y: -(playerNode.position.y - 180.0))
nextBackgroundNode.position = CGPoint(x: nextBackgroundNode.position.x, y: -((playerNode.position.y - 180.0)/8) + 1000.0)
}
}
// turn off accelerometer updates when the GameScene is no longer used
deinit {
coreMotionManager.stopAccelerometerUpdates()
}
}
extension GameScene: SKPhysicsContactDelegate {
}
//
// GameViewController.swift
// SuperSpaceMan
//
// Created by Christopher Pedersen on 6/28/18.
// Copyright © 2018 Christopher Pedersen. All rights reserved.
//
import SpriteKit
class GameViewController: UIViewController {
var scene: GameScene!
override var prefersStatusBarHidden: Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
// 1. Configure the main view
let skView = view as! SKView
skView.showsFPS = true
// 2. Create and configure our game scene
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .aspectFill
// 3. Show the scene.
skView.presentScene(scene)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment