├── Examples ├── Effects │ ├── Demo │ │ ├── en.lproj │ │ │ └── InfoPlist.strings │ │ ├── AppDelegate.swift │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Icon-58.png │ │ │ │ ├── Icon-80.png │ │ │ │ ├── Icon-120.png │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage │ │ │ │ └── Contents.json │ │ ├── ViewController.swift │ │ ├── Demo-Info.plist │ │ ├── Base.lproj │ │ │ └── Main.storyboard │ │ ├── Launch Screen.storyboard │ │ └── MyScene.swift │ ├── Demo.gif │ ├── Sprites │ │ ├── Ball.png │ │ └── Ball@2x.png │ ├── Graphics │ │ ├── Ball.psd │ │ └── Icon-1024.png │ ├── README.txt │ └── Demo.xcodeproj │ │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── Demo.xccheckout │ │ └── project.pbxproj ├── Tests │ ├── SKTUtils │ │ ├── ViewController.swift │ │ ├── AppDelegate.swift │ │ ├── Images.xcassets │ │ │ ├── LaunchImage.launchimage │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── Base.lproj │ │ │ └── Main.storyboard │ ├── SKTUtils.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── SKTUtils.xccheckout │ │ └── project.pbxproj │ └── SKTUtilsTests │ │ ├── Info.plist │ │ ├── IntTests.swift │ │ ├── FloatTests.swift │ │ ├── Vector3Tests.swift │ │ ├── CGVectorTests.swift │ │ └── CGPointTests.swift └── Playground │ ├── SKTUtils.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── SKTUtils.xccheckout │ ├── MyPlayground.playground │ ├── contents.xcplayground │ └── Contents.swift │ ├── SKTUtils.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── SKTUtils.xccheckout │ └── project.pbxproj │ ├── SKTUtils.h │ └── Info.plist ├── .gitignore ├── LICENSE.txt ├── SKTUtils ├── SKColor+Extensions.swift ├── SKAction+Extensions.swift ├── Int+Extensions.swift ├── SKTAudio.swift ├── CGFloat+Extensions.swift ├── SKNode+Extensions.swift ├── SKAction+SpecialEffects.swift ├── SKTEffects.swift ├── CGVector+Extensions.swift ├── Vector3.swift ├── CGPoint+Extensions.swift └── SKTTimingFunctions.swift └── README.md /Examples/Effects/Demo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Examples/Effects/Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodecocodes/SKTUtils/HEAD/Examples/Effects/Demo.gif -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ViewController: UIViewController { 4 | } 5 | -------------------------------------------------------------------------------- /Examples/Effects/Sprites/Ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodecocodes/SKTUtils/HEAD/Examples/Effects/Sprites/Ball.png -------------------------------------------------------------------------------- /Examples/Effects/Graphics/Ball.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodecocodes/SKTUtils/HEAD/Examples/Effects/Graphics/Ball.psd -------------------------------------------------------------------------------- /Examples/Effects/Sprites/Ball@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodecocodes/SKTUtils/HEAD/Examples/Effects/Sprites/Ball@2x.png -------------------------------------------------------------------------------- /Examples/Effects/Graphics/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodecocodes/SKTUtils/HEAD/Examples/Effects/Graphics/Icon-1024.png -------------------------------------------------------------------------------- /Examples/Effects/README.txt: -------------------------------------------------------------------------------- 1 | This demo app shows how to use the SKTEffects classes to add cool animations to 2 | your Sprite Kit nodes in just a few lines of code. 3 | -------------------------------------------------------------------------------- /Examples/Effects/Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | var window: UIWindow? 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Effects/Demo/Images.xcassets/AppIcon.appiconset/Icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodecocodes/SKTUtils/HEAD/Examples/Effects/Demo/Images.xcassets/AppIcon.appiconset/Icon-58.png -------------------------------------------------------------------------------- /Examples/Effects/Demo/Images.xcassets/AppIcon.appiconset/Icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodecocodes/SKTUtils/HEAD/Examples/Effects/Demo/Images.xcassets/AppIcon.appiconset/Icon-80.png -------------------------------------------------------------------------------- /Examples/Effects/Demo/Images.xcassets/AppIcon.appiconset/Icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kodecocodes/SKTUtils/HEAD/Examples/Effects/Demo/Images.xcassets/AppIcon.appiconset/Icon-120.png -------------------------------------------------------------------------------- /Examples/Effects/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Playground/SKTUtils.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Playground/MyPlayground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Examples/Playground/SKTUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | var window: UIWindow? 6 | 7 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 8 | return true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | !default.xcworkspace 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | 16 | # OS X 17 | .DS_Store 18 | Icon? 19 | 20 | # Thumbnails 21 | ._* 22 | 23 | # Files that might appear on external disk 24 | .Spotlight-V100 25 | .Trashes 26 | -------------------------------------------------------------------------------- /Examples/Playground/MyPlayground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | // To run this playground, first build the project (Command-B). 2 | // To see the output, open the Assistant Editor (Option-Command-Enter). 3 | 4 | import SKTUtils 5 | 6 | let pt1 = CGPoint(x: 10, y: 20) 7 | let pt2 = CGPoint(x: -5, y: 0) 8 | let pt3 = pt1 + pt2 9 | let pt4 = pt3 * CGFloat(100) 10 | 11 | print("Point has length \(pt4.length())") 12 | 13 | let pt5 = pt4.normalized() 14 | let dist = pt1.distanceTo(pt2) 15 | -------------------------------------------------------------------------------- /Examples/Effects/Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | 2 | import SpriteKit 3 | 4 | class ViewController: UIViewController { 5 | @IBOutlet weak var skView: SKView! 6 | 7 | override func viewDidLoad() { 8 | super.viewDidLoad() 9 | 10 | skView.showsFPS = true 11 | skView.showsNodeCount = true 12 | skView.showsDrawCount = true 13 | } 14 | 15 | override func viewWillLayoutSubviews() { 16 | super.viewWillLayoutSubviews() 17 | 18 | if skView.scene == nil { 19 | let scene = MyScene(size: skView.bounds.size) 20 | skView.presentScene(scene) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Examples/Playground/SKTUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // SKTUtils.h 3 | // SKTUtils 4 | // 5 | // Created by Corinne Krych on 29/08/14. 6 | // Copyright (c) 2014 raywenderlich. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SKTUtils. 12 | FOUNDATION_EXPORT double SKTUtilsVersionNumber; 13 | 14 | //! Project version string for SKTUtils. 15 | FOUNDATION_EXPORT const unsigned char SKTUtilsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | #import 21 | #import -------------------------------------------------------------------------------- /Examples/Tests/SKTUtilsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/Effects/Demo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-58.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "idiom" : "iphone", 11 | "size" : "29x29", 12 | "scale" : "3x" 13 | }, 14 | { 15 | "size" : "40x40", 16 | "idiom" : "iphone", 17 | "filename" : "Icon-80.png", 18 | "scale" : "2x" 19 | }, 20 | { 21 | "idiom" : "iphone", 22 | "size" : "40x40", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "60x60", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-120.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "idiom" : "iphone", 33 | "size" : "60x60", 34 | "scale" : "3x" 35 | } 36 | ], 37 | "info" : { 38 | "version" : 1, 39 | "author" : "xcode" 40 | } 41 | } -------------------------------------------------------------------------------- /Examples/Playground/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 Razeware LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "ipad", 6 | "minimum-system-version" : "7.0", 7 | "extent" : "full-screen", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "landscape", 12 | "idiom" : "ipad", 13 | "minimum-system-version" : "7.0", 14 | "extent" : "full-screen", 15 | "scale" : "1x" 16 | }, 17 | { 18 | "orientation" : "landscape", 19 | "idiom" : "ipad", 20 | "minimum-system-version" : "7.0", 21 | "extent" : "full-screen", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "orientation" : "portrait", 26 | "idiom" : "iphone", 27 | "minimum-system-version" : "7.0", 28 | "scale" : "2x" 29 | }, 30 | { 31 | "orientation" : "portrait", 32 | "idiom" : "iphone", 33 | "minimum-system-version" : "7.0", 34 | "subtype" : "retina4", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "orientation" : "portrait", 39 | "idiom" : "ipad", 40 | "minimum-system-version" : "7.0", 41 | "extent" : "full-screen", 42 | "scale" : "1x" 43 | } 44 | ], 45 | "info" : { 46 | "version" : 1, 47 | "author" : "xcode" 48 | } 49 | } -------------------------------------------------------------------------------- /Examples/Effects/Demo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "8.0", 8 | "subtype" : "736h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "ipad", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "orientation" : "landscape", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "2x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "to-status-bar", 29 | "scale" : "1x" 30 | }, 31 | { 32 | "orientation" : "landscape", 33 | "idiom" : "ipad", 34 | "extent" : "full-screen", 35 | "scale" : "1x" 36 | }, 37 | { 38 | "orientation" : "landscape", 39 | "idiom" : "ipad", 40 | "extent" : "to-status-bar", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "orientation" : "landscape", 45 | "idiom" : "ipad", 46 | "extent" : "full-screen", 47 | "scale" : "2x" 48 | } 49 | ], 50 | "info" : { 51 | "version" : 1, 52 | "author" : "xcode" 53 | } 54 | } -------------------------------------------------------------------------------- /Examples/Effects/Demo/Demo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | Launch Screen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UIStatusBarHidden 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SKTUtils/SKColor+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import SpriteKit 24 | 25 | public func SKColorWithRGB(_ r: Int, g: Int, b: Int) -> SKColor { 26 | return SKColor(red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: 1.0) 27 | } 28 | 29 | public func SKColorWithRGBA(_ r: Int, g: Int, b: Int, a: Int) -> SKColor { 30 | return SKColor(red: CGFloat(r)/255.0, green: CGFloat(g)/255.0, blue: CGFloat(b)/255.0, alpha: CGFloat(a)/255.0) 31 | } 32 | -------------------------------------------------------------------------------- /Examples/Effects/Demo.xcodeproj/project.xcworkspace/xcshareddata/Demo.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 76608467-F857-4DE4-8A83-EFA61D4F4905 9 | IDESourceControlProjectName 10 | Demo 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 14 | https://github.com/raywenderlich/SKTUtils.git 15 | 16 | IDESourceControlProjectPath 17 | Examples/Effects/Demo.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 21 | ../../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/raywenderlich/SKTUtils.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 36 | IDESourceControlWCCName 37 | SKTUtils 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/Playground/SKTUtils.xcworkspace/xcshareddata/SKTUtils.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 124A0098-1CFD-417E-A29E-BCDE94A518EA 9 | IDESourceControlProjectName 10 | SKTUtils 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 14 | https://github.com/raywenderlich/SKTUtils.git 15 | 16 | IDESourceControlProjectPath 17 | Examples/Playground/SKTUtils.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/raywenderlich/SKTUtils.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 36 | IDESourceControlWCCName 37 | SKTUtils 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils.xcodeproj/project.xcworkspace/xcshareddata/SKTUtils.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | B4032A04-1ACC-4F69-83AC-3484DCF3D41F 9 | IDESourceControlProjectName 10 | SKTUtils 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 14 | https://github.com/raywenderlich/SKTUtils.git 15 | 16 | IDESourceControlProjectPath 17 | Examples/Tests/SKTUtils.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 21 | ../../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/raywenderlich/SKTUtils.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 36 | IDESourceControlWCCName 37 | SKTUtils 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/Playground/SKTUtils.xcodeproj/project.xcworkspace/xcshareddata/SKTUtils.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 06514DEB-F873-4FDF-8A93-066478BBB8CE 9 | IDESourceControlProjectName 10 | SKTUtils 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 14 | https://github.com/raywenderlich/SKTUtils.git 15 | 16 | IDESourceControlProjectPath 17 | Examples/Playground/SKTUtils.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 21 | ../../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/raywenderlich/SKTUtils.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C89EDD4DBF1210609F3A458B8547E87C3C4AEA5F 36 | IDESourceControlWCCName 37 | SKTUtils 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/Effects/Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /SKTUtils/SKAction+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import SpriteKit 24 | 25 | public extension SKAction { 26 | /** 27 | * Performs an action after the specified delay. 28 | */ 29 | public class func afterDelay(_ delay: TimeInterval, performAction action: SKAction) -> SKAction { 30 | return SKAction.sequence([SKAction.wait(forDuration: delay), action]) 31 | } 32 | 33 | /** 34 | * Performs a block after the specified delay. 35 | */ 36 | public class func afterDelay(_ delay: TimeInterval, runBlock block: @escaping () -> Void) -> SKAction { 37 | return SKAction.afterDelay(delay, performAction: SKAction.run(block)) 38 | } 39 | 40 | /** 41 | * Removes the node from its parent after the specified delay. 42 | */ 43 | public class func removeFromParentAfterDelay(_ delay: TimeInterval) -> SKAction { 44 | return SKAction.afterDelay(delay, performAction: SKAction.removeFromParent()) 45 | } 46 | 47 | /** 48 | * Creates an action to perform a parabolic jump. 49 | */ 50 | public class func jumpToHeight(_ height: CGFloat, duration: TimeInterval, originalPosition: CGPoint) -> SKAction { 51 | return SKAction.customAction(withDuration: duration) {(node, elapsedTime) in 52 | let fraction = elapsedTime / CGFloat(duration) 53 | let yOffset = height * 4 * fraction * (1 - fraction) 54 | node.position = CGPoint(x: originalPosition.x, y: originalPosition.y + yOffset) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtilsTests/IntTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | 4 | class IntTests: XCTestCase { 5 | 6 | func testClampedHalfOpenRange() { 7 | XCTAssertEqual(10.clamped(-5 ..< 7), 6) 8 | XCTAssertEqual(7.clamped(-5 ..< 7), 6) 9 | XCTAssertEqual(6.clamped(-5 ..< 7), 6) 10 | XCTAssertEqual(5.clamped(-5 ..< 7), 5) 11 | XCTAssertEqual(1.clamped(-5 ..< 7), 1) 12 | XCTAssertEqual(0.clamped(-5 ..< 7), 0) 13 | XCTAssertEqual((-4).clamped(-5 ..< 7), -4) 14 | XCTAssertEqual((-5).clamped(-5 ..< 7), -5) 15 | XCTAssertEqual((-6).clamped(-5 ..< 7), -5) 16 | XCTAssertEqual((-10).clamped(-5 ..< 7), -5) 17 | } 18 | 19 | func testClampedEmptyHalfOpenRange() { 20 | XCTAssertEqual(10.clamped(7 ..< 7), 6) // weird, huh! 21 | XCTAssertEqual((-10).clamped(7 ..< 7), 7) 22 | } 23 | 24 | func testClampedOpenRange() { 25 | XCTAssertEqual(10.clamped(-5 ... 7), 7) 26 | XCTAssertEqual(8.clamped(-5 ... 7), 7) 27 | XCTAssertEqual(7.clamped(-5 ... 7), 7) 28 | XCTAssertEqual(6.clamped(-5 ... 7), 6) 29 | XCTAssertEqual(1.clamped(-5 ... 7), 1) 30 | XCTAssertEqual(0.clamped(-5 ... 7), 0) 31 | XCTAssertEqual((-4).clamped(-5 ... 7), -4) 32 | XCTAssertEqual((-5).clamped(-5 ... 7), -5) 33 | XCTAssertEqual((-6).clamped(-5 ... 7), -5) 34 | XCTAssertEqual((-10).clamped(-5 ... 7), -5) 35 | } 36 | 37 | func testClampedEmptyOpenRange() { 38 | XCTAssertEqual(10.clamped(7 ... 7), 7) 39 | XCTAssertEqual((-10).clamped(7 ... 7), 7) 40 | } 41 | 42 | func testClamped() { 43 | XCTAssertEqual(10.clamped(-5, 6), 6) 44 | XCTAssertEqual(7.clamped(-5, 6), 6) 45 | XCTAssertEqual(6.clamped(-5, 6), 6) 46 | XCTAssertEqual(5.clamped(-5, 6), 5) 47 | XCTAssertEqual(1.clamped(-5, 6), 1) 48 | XCTAssertEqual(0.clamped(-5, 6), 0) 49 | XCTAssertEqual((-4).clamped(-5, 6), -4) 50 | XCTAssertEqual((-5).clamped(-5, 6), -5) 51 | XCTAssertEqual((-6).clamped(-5, 6), -5) 52 | XCTAssertEqual((-10).clamped(-5, 6), -5) 53 | } 54 | 55 | func testClampedReverseOrder() { 56 | XCTAssertEqual(10.clamped(6, -5), 6) 57 | XCTAssertEqual(7.clamped(6, -5), 6) 58 | XCTAssertEqual(6.clamped(6, -5), 6) 59 | XCTAssertEqual(5.clamped(6, -5), 5) 60 | XCTAssertEqual(1.clamped(6, -5), 1) 61 | XCTAssertEqual(0.clamped(6, -5), 0) 62 | XCTAssertEqual((-4).clamped(6, -5), -4) 63 | XCTAssertEqual((-5).clamped(6, -5), -5) 64 | XCTAssertEqual((-6).clamped(6, -5), -5) 65 | XCTAssertEqual((-10).clamped(6, -5), -5) 66 | } 67 | 68 | func testThatClampedDoesNotChangeOriginalValue() { 69 | let original = 50 70 | _ = original.clamped(100 ... 200) 71 | XCTAssertEqual(original, 50) 72 | } 73 | 74 | func testThatClampReturnsNewValue() { 75 | var original = 50 76 | original.clamp(100 ... 200) 77 | XCTAssertEqual(original, 100) 78 | } 79 | 80 | func testThatRandomStaysInHalfOpenRange() { 81 | for _ in 0..<1000 { 82 | let v = Int.random(-10 ..< 10) 83 | XCTAssert(v >= -10 && v < 10) 84 | 85 | let w = Int.random(10) 86 | XCTAssert(w >= 0 && w < 10) 87 | } 88 | } 89 | 90 | func testThatRandomStaysInOpenRange() { 91 | for _ in 0..<1000 { 92 | let v = Int.random(-10 ... 10) 93 | XCTAssert(v >= -10 && v <= 10) 94 | 95 | let w = Int.random(min: -10, max: 10) 96 | XCTAssert(w >= -10 && w <= 10) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /SKTUtils/Int+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreGraphics 24 | 25 | public extension Int { 26 | /** 27 | * Ensures that the integer value stays with the specified range. 28 | */ 29 | public func clamped(_ range: Range) -> Int { 30 | return (self < range.lowerBound) ? range.lowerBound : ((self >= range.upperBound) ? range.upperBound - 1: self) 31 | } 32 | 33 | public func clamped(_ range: ClosedRange) -> Int { 34 | return (self < range.lowerBound) ? range.lowerBound : ((self > range.upperBound) ? range.upperBound: self) 35 | } 36 | 37 | /** 38 | * Ensures that the integer value stays with the specified range. 39 | */ 40 | public mutating func clamp(_ range: Range) -> Int { 41 | self = clamped(range) 42 | return self 43 | } 44 | 45 | public mutating func clamp(_ range: ClosedRange) -> Int { 46 | self = clamped(range) 47 | return self 48 | } 49 | 50 | /** 51 | * Ensures that the integer value stays between the given values, inclusive. 52 | */ 53 | public func clamped(_ v1: Int, _ v2: Int) -> Int { 54 | let min = v1 < v2 ? v1 : v2 55 | let max = v1 > v2 ? v1 : v2 56 | return self < min ? min : (self > max ? max : self) 57 | } 58 | 59 | /** 60 | * Ensures that the integer value stays between the given values, inclusive. 61 | */ 62 | public mutating func clamp(_ v1: Int, _ v2: Int) -> Int { 63 | self = clamped(v1, v2) 64 | return self 65 | } 66 | 67 | /** 68 | * Returns a random integer in the specified range. 69 | */ 70 | public static func random(_ range: Range) -> Int { 71 | return Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound - 1))) + range.lowerBound 72 | } 73 | 74 | public static func random(_ range: ClosedRange) -> Int { 75 | return Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound))) + range.lowerBound 76 | } 77 | 78 | /** 79 | * Returns a random integer between 0 and n-1. 80 | */ 81 | public static func random(_ n: Int) -> Int { 82 | return Int(arc4random_uniform(UInt32(n))) 83 | } 84 | 85 | /** 86 | * Returns a random integer in the range min...max, inclusive. 87 | */ 88 | public static func random(min: Int, max: Int) -> Int { 89 | assert(min < max) 90 | return Int(arc4random_uniform(UInt32(max - min + 1))) + min 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /SKTUtils/SKTAudio.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import AVFoundation 24 | 25 | /** 26 | * Audio player that uses AVFoundation to play looping background music and 27 | * short sound effects. For when using SKActions just isn't good enough. 28 | */ 29 | public class SKTAudio { 30 | public var backgroundMusicPlayer: AVAudioPlayer? 31 | public var soundEffectPlayer: AVAudioPlayer? 32 | 33 | public class func sharedInstance() -> SKTAudio { 34 | return SKTAudioInstance 35 | } 36 | 37 | public func playBackgroundMusic(_ filename: String) { 38 | let url = Bundle.main.url(forResource: filename, withExtension: nil) 39 | if (url == nil) { 40 | print("Could not find file: \(filename)") 41 | return 42 | } 43 | 44 | var error: NSError? = nil 45 | do { 46 | backgroundMusicPlayer = try AVAudioPlayer(contentsOf: url!) 47 | } catch let error1 as NSError { 48 | error = error1 49 | backgroundMusicPlayer = nil 50 | } 51 | if let player = backgroundMusicPlayer { 52 | player.numberOfLoops = -1 53 | player.prepareToPlay() 54 | player.play() 55 | } else { 56 | print("Could not create audio player: \(error!)") 57 | } 58 | } 59 | 60 | public func pauseBackgroundMusic() { 61 | if let player = backgroundMusicPlayer { 62 | if player.isPlaying { 63 | player.pause() 64 | } 65 | } 66 | } 67 | 68 | public func resumeBackgroundMusic() { 69 | if let player = backgroundMusicPlayer { 70 | if !player.isPlaying { 71 | player.play() 72 | } 73 | } 74 | } 75 | 76 | public func playSoundEffect(_ filename: String) { 77 | let url = Bundle.main.url(forResource: filename, withExtension: nil) 78 | if (url == nil) { 79 | print("Could not find file: \(filename)") 80 | return 81 | } 82 | 83 | var error: NSError? = nil 84 | do { 85 | soundEffectPlayer = try AVAudioPlayer(contentsOf: url!) 86 | } catch let error1 as NSError { 87 | error = error1 88 | soundEffectPlayer = nil 89 | } 90 | if let player = soundEffectPlayer { 91 | player.numberOfLoops = 0 92 | player.prepareToPlay() 93 | player.play() 94 | } else { 95 | print("Could not create audio player: \(error!)") 96 | } 97 | } 98 | } 99 | 100 | private let SKTAudioInstance = SKTAudio() 101 | -------------------------------------------------------------------------------- /SKTUtils/CGFloat+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreGraphics 24 | 25 | /** The value of π as a CGFloat */ 26 | let π = CGFloat(M_PI) 27 | 28 | public extension CGFloat { 29 | /** 30 | * Converts an angle in degrees to radians. 31 | */ 32 | public func degreesToRadians() -> CGFloat { 33 | return π * self / 180.0 34 | } 35 | 36 | /** 37 | * Converts an angle in radians to degrees. 38 | */ 39 | public func radiansToDegrees() -> CGFloat { 40 | return self * 180.0 / π 41 | } 42 | 43 | /** 44 | * Ensures that the float value stays between the given values, inclusive. 45 | */ 46 | public func clamped(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 47 | let min = v1 < v2 ? v1 : v2 48 | let max = v1 > v2 ? v1 : v2 49 | return self < min ? min : (self > max ? max : self) 50 | } 51 | 52 | /** 53 | * Ensures that the float value stays between the given values, inclusive. 54 | */ 55 | public mutating func clamp(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 56 | self = clamped(v1, v2) 57 | return self 58 | } 59 | 60 | /** 61 | * Returns 1.0 if a floating point value is positive; -1.0 if it is negative. 62 | */ 63 | public func sign() -> CGFloat { 64 | return (self >= 0.0) ? 1.0 : -1.0 65 | } 66 | 67 | /** 68 | * Returns a random floating point number between 0.0 and 1.0, inclusive. 69 | */ 70 | public static func random() -> CGFloat { 71 | return CGFloat(Float(arc4random()) / 0xFFFFFFFF) 72 | } 73 | 74 | /** 75 | * Returns a random floating point number in the range min...max, inclusive. 76 | */ 77 | public static func random(min: CGFloat, max: CGFloat) -> CGFloat { 78 | assert(min < max) 79 | return CGFloat.random() * (max - min) + min 80 | } 81 | 82 | /** 83 | * Randomly returns either 1.0 or -1.0. 84 | */ 85 | public static func randomSign() -> CGFloat { 86 | return (arc4random_uniform(2) == 0) ? 1.0 : -1.0 87 | } 88 | } 89 | 90 | /** 91 | * Returns the shortest angle between two angles. The result is always between 92 | * -π and π. 93 | */ 94 | public func shortestAngleBetween(_ angle1: CGFloat, angle2: CGFloat) -> CGFloat { 95 | let twoπ = π * 2.0 96 | var angle = (angle2 - angle1).truncatingRemainder(dividingBy: twoπ) 97 | if (angle >= π) { 98 | angle = angle - twoπ 99 | } 100 | if (angle <= -π) { 101 | angle = angle + twoπ 102 | } 103 | return angle 104 | } 105 | -------------------------------------------------------------------------------- /SKTUtils/SKNode+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import SpriteKit 24 | 25 | public extension SKNode { 26 | 27 | /** Lets you treat the node's scale as a CGPoint value. */ 28 | public var scaleAsPoint: CGPoint { 29 | get { 30 | return CGPoint(x: xScale, y: yScale) 31 | } 32 | set { 33 | xScale = newValue.x 34 | yScale = newValue.y 35 | } 36 | } 37 | 38 | /** 39 | * Runs an action on the node that performs a closure or function after 40 | * a given time. 41 | */ 42 | public func afterDelay(_ delay: TimeInterval, runBlock block: @escaping () -> Void) { 43 | run(SKAction.sequence([SKAction.wait(forDuration: delay), SKAction.run(block)])) 44 | } 45 | 46 | /** 47 | * Makes this node the frontmost node in its parent. 48 | */ 49 | public func bringToFront() { 50 | if let parent = self.parent{ 51 | removeFromParent() 52 | parent.addChild(self) 53 | } 54 | } 55 | 56 | /** 57 | * Orients the node in the direction that it is moving by tweening its 58 | * rotation angle. This assumes that at 0 degrees the node is facing up. 59 | * 60 | * @param velocity The current speed and direction of the node. You can get 61 | * this from node.physicsBody.velocity. 62 | * @param rate How fast the node rotates. Must have a value between 0.0 and 63 | * 1.0, where smaller means slower; 1.0 is instantaneous. 64 | */ 65 | public func rotateToVelocity(_ velocity: CGVector, rate: CGFloat) { 66 | // Determine what the rotation angle of the node ought to be based on the 67 | // current velocity of its physics body. This assumes that at 0 degrees the 68 | // node is pointed up, not to the right, so to compensate we subtract π/4 69 | // (90 degrees) from the calculated angle. 70 | let newAngle = atan2(velocity.dy, velocity.dx) - π/2 71 | 72 | // This always makes the node rotate over the shortest possible distance. 73 | // Because the range of atan2() is -180 to 180 degrees, a rotation from, 74 | // -170 to -190 would otherwise be from -170 to 170, which makes the node 75 | // rotate the wrong way (and the long way) around. We adjust the angle to 76 | // go from 190 to 170 instead, which is equivalent to -170 to -190. 77 | if newAngle - zRotation > π { 78 | zRotation += π * 2.0 79 | } else if zRotation - newAngle > π { 80 | zRotation -= π * 2.0 81 | } 82 | 83 | // Use the "standard exponential slide" to slowly tween zRotation to the 84 | // new angle. The greater the value of rate, the faster this goes. 85 | zRotation += (newAngle - zRotation) * rate 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /SKTUtils/SKAction+SpecialEffects.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import SpriteKit 24 | 25 | public extension SKAction { 26 | /** 27 | * Creates a screen shake animation. 28 | * 29 | * @param node The node to shake. You cannot apply this effect to an SKScene. 30 | * @param amount The vector by which the node is displaced. 31 | * @param oscillations The number of oscillations; 10 is a good value. 32 | * @param duration How long the effect lasts. Shorter is better. 33 | */ 34 | public class func screenShakeWithNode(_ node: SKNode, amount: CGPoint, oscillations: Int, duration: TimeInterval) -> SKAction { 35 | let oldPosition = node.position 36 | let newPosition = oldPosition + amount 37 | 38 | let effect = SKTMoveEffect(node: node, duration: duration, startPosition: newPosition, endPosition: oldPosition) 39 | effect.timingFunction = SKTCreateShakeFunction(oscillations) 40 | 41 | return SKAction.actionWithEffect(effect) 42 | } 43 | 44 | /** 45 | * Creates a screen rotation animation. 46 | * 47 | * @param node You usually want to apply this effect to a pivot node that is 48 | * centered in the scene. You cannot apply the effect to an SKScene. 49 | * @param angle The angle in radians. 50 | * @param oscillations The number of oscillations; 10 is a good value. 51 | * @param duration How long the effect lasts. Shorter is better. 52 | */ 53 | public class func screenRotateWithNode(_ node: SKNode, angle: CGFloat, oscillations: Int, duration: TimeInterval) -> SKAction { 54 | let oldAngle = node.zRotation 55 | let newAngle = oldAngle + angle 56 | 57 | let effect = SKTRotateEffect(node: node, duration: duration, startAngle: newAngle, endAngle: oldAngle) 58 | effect.timingFunction = SKTCreateShakeFunction(oscillations) 59 | 60 | return SKAction.actionWithEffect(effect) 61 | } 62 | 63 | /** 64 | * Creates a screen zoom animation. 65 | * 66 | * @param node You usually want to apply this effect to a pivot node that is 67 | * centered in the scene. You cannot apply the effect to an SKScene. 68 | * @param amount How much to scale the node in the X and Y directions. 69 | * @param oscillations The number of oscillations; 10 is a good value. 70 | * @param duration How long the effect lasts. Shorter is better. 71 | */ 72 | public class func screenZoomWithNode(_ node: SKNode, amount: CGPoint, oscillations: Int, duration: TimeInterval) -> SKAction { 73 | let oldScale = CGPoint(x: node.xScale, y: node.yScale) 74 | let newScale = oldScale * amount 75 | 76 | let effect = SKTScaleEffect(node: node, duration: duration, startScale: newScale, endScale: oldScale) 77 | effect.timingFunction = SKTCreateShakeFunction(oscillations) 78 | 79 | return SKAction.actionWithEffect(effect) 80 | } 81 | 82 | /** 83 | * Causes the scene background to flash for duration seconds. 84 | */ 85 | public class func colorGlitchWithScene(_ scene: SKScene, originalColor: SKColor, duration: TimeInterval) -> SKAction { 86 | return SKAction.customAction(withDuration: duration) {(node, elapsedTime) in 87 | if elapsedTime < CGFloat(duration) { 88 | scene.backgroundColor = SKColorWithRGB(Int.random(0...255), g: Int.random(0...255), b: Int.random(0...255)) 89 | } else { 90 | scene.backgroundColor = originalColor 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Examples/Effects/Demo/Launch Screen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /SKTUtils/SKTEffects.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import SpriteKit 24 | 25 | /** 26 | * Allows you to perform actions with custom timing functions. 27 | * 28 | * Unfortunately, SKAction does not have a concept of a timing function, so 29 | * we need to replicate the actions using SKTEffect subclasses. 30 | */ 31 | public class SKTEffect { 32 | unowned var node: SKNode 33 | var duration: TimeInterval 34 | public var timingFunction: ((CGFloat) -> CGFloat)? 35 | 36 | public init(node: SKNode, duration: TimeInterval) { 37 | self.node = node 38 | self.duration = duration 39 | timingFunction = SKTTimingFunctionLinear 40 | } 41 | 42 | public func update(_ t: CGFloat) { 43 | // subclasses implement this 44 | } 45 | } 46 | 47 | /** 48 | * Moves a node from its current position to a new position. 49 | */ 50 | public class SKTMoveEffect: SKTEffect { 51 | var startPosition: CGPoint 52 | var delta: CGPoint 53 | var previousPosition: CGPoint 54 | 55 | public init(node: SKNode, duration: TimeInterval, startPosition: CGPoint, endPosition: CGPoint) { 56 | previousPosition = node.position 57 | self.startPosition = startPosition 58 | delta = endPosition - startPosition 59 | super.init(node: node, duration: duration) 60 | } 61 | 62 | public override func update(_ t: CGFloat) { 63 | // This allows multiple SKTMoveEffect objects to modify the same node 64 | // at the same time. 65 | let newPosition = startPosition + delta*t 66 | let diff = newPosition - previousPosition 67 | previousPosition = newPosition 68 | node.position += diff 69 | } 70 | } 71 | 72 | /** 73 | * Scales a node to a certain scale factor. 74 | */ 75 | public class SKTScaleEffect: SKTEffect { 76 | var startScale: CGPoint 77 | var delta: CGPoint 78 | var previousScale: CGPoint 79 | 80 | public init(node: SKNode, duration: TimeInterval, startScale: CGPoint, endScale: CGPoint) { 81 | previousScale = CGPoint(x: node.xScale, y: node.yScale) 82 | self.startScale = startScale 83 | delta = endScale - startScale 84 | super.init(node: node, duration: duration) 85 | } 86 | 87 | public override func update(_ t: CGFloat) { 88 | let newScale = startScale + delta*t 89 | let diff = newScale / previousScale 90 | previousScale = newScale 91 | node.xScale *= diff.x 92 | node.yScale *= diff.y 93 | } 94 | } 95 | 96 | /** 97 | * Rotates a node to a certain angle. 98 | */ 99 | public class SKTRotateEffect: SKTEffect { 100 | var startAngle: CGFloat 101 | var delta: CGFloat 102 | var previousAngle: CGFloat 103 | 104 | public init(node: SKNode, duration: TimeInterval, startAngle: CGFloat, endAngle: CGFloat) { 105 | previousAngle = node.zRotation 106 | self.startAngle = startAngle 107 | delta = endAngle - startAngle 108 | super.init(node: node, duration: duration) 109 | } 110 | 111 | public override func update(_ t: CGFloat) { 112 | let newAngle = startAngle + delta*t 113 | let diff = newAngle - previousAngle 114 | previousAngle = newAngle 115 | node.zRotation += diff 116 | } 117 | } 118 | 119 | /** 120 | * Wrapper that allows you to use SKTEffect objects as regular SKActions. 121 | */ 122 | public extension SKAction { 123 | public class func actionWithEffect(_ effect: SKTEffect) -> SKAction { 124 | return SKAction.customAction(withDuration: effect.duration) { node, elapsedTime in 125 | var t = elapsedTime / CGFloat(effect.duration) 126 | 127 | if let timingFunction = effect.timingFunction { 128 | t = timingFunction(t) // the magic happens here 129 | } 130 | 131 | effect.update(t) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtilsTests/FloatTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import CoreGraphics 4 | 5 | class FloatTests: XCTestCase { 6 | 7 | func testClamped() { 8 | XCTAssertEqual(CGFloat(10).clamped(-5, 6), CGFloat(6)) 9 | XCTAssertEqual(CGFloat(7).clamped(-5, 6), CGFloat(6)) 10 | XCTAssertEqual(CGFloat(6).clamped(-5, 6), CGFloat(6)) 11 | XCTAssertEqual(CGFloat(5).clamped(-5, 6), CGFloat(5)) 12 | XCTAssertEqual(CGFloat(1).clamped(-5, 6), CGFloat(1)) 13 | XCTAssertEqual(CGFloat(0).clamped(-5, 6), CGFloat(0)) 14 | XCTAssertEqual(CGFloat(-4).clamped(-5, 6), CGFloat(-4)) 15 | XCTAssertEqual(CGFloat(-5).clamped(-5, 6), CGFloat(-5)) 16 | XCTAssertEqual(CGFloat(-6).clamped(-5, 6), CGFloat(-5)) 17 | XCTAssertEqual(CGFloat(-10).clamped(-5, 6), CGFloat(-5)) 18 | } 19 | 20 | func testClampedReverseOrder() { 21 | XCTAssertEqual(CGFloat(10).clamped(6, -5), CGFloat(6)) 22 | XCTAssertEqual(CGFloat(7).clamped(6, -5), CGFloat(6)) 23 | XCTAssertEqual(CGFloat(6).clamped(6, -5), CGFloat(6)) 24 | XCTAssertEqual(CGFloat(5).clamped(6, -5), CGFloat(5)) 25 | XCTAssertEqual(CGFloat(1).clamped(6, -5), CGFloat(1)) 26 | XCTAssertEqual(CGFloat(0).clamped(6, -5), CGFloat(0)) 27 | XCTAssertEqual(CGFloat(-4).clamped(6, -5), CGFloat(-4)) 28 | XCTAssertEqual(CGFloat(-5).clamped(6, -5), CGFloat(-5)) 29 | XCTAssertEqual(CGFloat(-6).clamped(6, -5), CGFloat(-5)) 30 | XCTAssertEqual(CGFloat(-10).clamped(6, -5), CGFloat(-5)) 31 | } 32 | 33 | func testThatClampedDoesNotChangeOriginalValue() { 34 | let original: CGFloat = 50 35 | _ = original.clamped(100, 200) 36 | XCTAssertEqual(original, CGFloat(50)) 37 | } 38 | 39 | func testThatClampReturnsNewValue() { 40 | var original: CGFloat = 50 41 | original.clamp(100, 200) 42 | XCTAssertEqual(original, CGFloat(100)) 43 | } 44 | 45 | func testThatRandomStaysBetweenOneAndZero() { 46 | for _ in 0..<1000 { 47 | let value = CGFloat.random() 48 | XCTAssert(value >= 0 && value <= 1) 49 | } 50 | } 51 | 52 | func testThatRandomStaysInRange() { 53 | for _ in 0..<1000 { 54 | let value = CGFloat.random(min: -10, max: 10) 55 | XCTAssert(value >= -10 && value <= 10) 56 | } 57 | } 58 | 59 | func testThatRandomSignIsMinusOrPlusOne() { 60 | for _ in 0..<1000 { 61 | let value = CGFloat.randomSign() 62 | XCTAssert(value == -1.0 || value == 1.0) 63 | } 64 | } 65 | 66 | func testSign() { 67 | XCTAssertEqual(CGFloat(-100.0).sign(), -1.0) 68 | XCTAssertEqual(CGFloat(100.0).sign(), CGFloat(1.0)) 69 | XCTAssertEqual(CGFloat(0.0).sign(), CGFloat(1.0)) 70 | } 71 | 72 | func testDegreesToRadians() { 73 | XCTAssertEqualWithAccuracy(CGFloat(0).degreesToRadians(), CGFloat(0), accuracy: CGFloat(FLT_EPSILON)) 74 | XCTAssertEqualWithAccuracy(CGFloat(45).degreesToRadians(), π/4, accuracy: CGFloat(FLT_EPSILON)) 75 | XCTAssertEqualWithAccuracy(CGFloat(90).degreesToRadians(), π/2, accuracy: CGFloat(FLT_EPSILON)) 76 | XCTAssertEqualWithAccuracy(CGFloat(135).degreesToRadians(), 3*π/4, accuracy: CGFloat(FLT_EPSILON)) 77 | XCTAssertEqualWithAccuracy(CGFloat(180).degreesToRadians(), π, accuracy: CGFloat(FLT_EPSILON)) 78 | XCTAssertEqualWithAccuracy(CGFloat(-135).degreesToRadians(), -3*π/4, accuracy: CGFloat(FLT_EPSILON)) 79 | XCTAssertEqualWithAccuracy(CGFloat(-90).degreesToRadians(), -π/2, accuracy: CGFloat(FLT_EPSILON)) 80 | XCTAssertEqualWithAccuracy(CGFloat(-45).degreesToRadians(), -π/4, accuracy: CGFloat(FLT_EPSILON)) 81 | } 82 | 83 | func testRadiansToDegrees() { 84 | XCTAssertEqualWithAccuracy(CGFloat(0).radiansToDegrees(), CGFloat(0), accuracy: CGFloat(FLT_EPSILON)) 85 | XCTAssertEqualWithAccuracy((π/4).radiansToDegrees(), CGFloat(45), accuracy: CGFloat(FLT_EPSILON)) 86 | XCTAssertEqualWithAccuracy((π/2).radiansToDegrees(), CGFloat(90), accuracy: CGFloat(FLT_EPSILON)) 87 | XCTAssertEqualWithAccuracy((3*π/4).radiansToDegrees(), CGFloat(135), accuracy: CGFloat(FLT_EPSILON)) 88 | XCTAssertEqualWithAccuracy(π.radiansToDegrees(), CGFloat(180), accuracy: CGFloat(FLT_EPSILON)) 89 | XCTAssertEqualWithAccuracy((-3*π/4).radiansToDegrees(), CGFloat(-135), accuracy: CGFloat(FLT_EPSILON)) 90 | XCTAssertEqualWithAccuracy((-π/2).radiansToDegrees(), CGFloat(-90), accuracy: CGFloat(FLT_EPSILON)) 91 | XCTAssertEqualWithAccuracy((-π/4).radiansToDegrees(), CGFloat(-45), accuracy: CGFloat(FLT_EPSILON)) 92 | } 93 | 94 | func testAllAngles() { 95 | let startAngle = CGFloat(-360) 96 | let endAngle = CGFloat(360) 97 | for angle in stride(from: startAngle, through: endAngle, by: 0.5) { 98 | let radians = angle.degreesToRadians() 99 | XCTAssertEqualWithAccuracy(radians.radiansToDegrees(), angle, accuracy: 1.0e6) 100 | } 101 | } 102 | 103 | func testShortestAngleBetween() { 104 | // TODO! 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /SKTUtils/CGVector+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreGraphics 24 | import SpriteKit 25 | 26 | public extension CGVector { 27 | /** 28 | * Creates a new CGVector given a CGPoint. 29 | */ 30 | public init(point: CGPoint) { 31 | self.init(dx: point.x, dy: point.y) 32 | } 33 | 34 | /** 35 | * Given an angle in radians, creates a vector of length 1.0 and returns the 36 | * result as a new CGVector. An angle of 0 is assumed to point to the right. 37 | */ 38 | public init(angle: CGFloat) { 39 | self.init(dx: cos(angle), dy: sin(angle)) 40 | } 41 | 42 | /** 43 | * Adds (dx, dy) to the vector. 44 | */ 45 | public mutating func offset(dx: CGFloat, dy: CGFloat) -> CGVector { 46 | self.dx += dx 47 | self.dy += dy 48 | return self 49 | } 50 | 51 | /** 52 | * Returns the length (magnitude) of the vector described by the CGVector. 53 | */ 54 | public func length() -> CGFloat { 55 | return sqrt(dx*dx + dy*dy) 56 | } 57 | 58 | /** 59 | * Returns the squared length of the vector described by the CGVector. 60 | */ 61 | public func lengthSquared() -> CGFloat { 62 | return dx*dx + dy*dy 63 | } 64 | 65 | /** 66 | * Normalizes the vector described by the CGVector to length 1.0 and returns 67 | * the result as a new CGVector. 68 | public */ 69 | func normalized() -> CGVector { 70 | let len = length() 71 | return len>0 ? self / len : CGVector.zero 72 | } 73 | 74 | /** 75 | * Normalizes the vector described by the CGVector to length 1.0. 76 | */ 77 | public mutating func normalize() -> CGVector { 78 | self = normalized() 79 | return self 80 | } 81 | 82 | /** 83 | * Calculates the distance between two CGVectors. Pythagoras! 84 | */ 85 | public func distanceTo(_ vector: CGVector) -> CGFloat { 86 | return (self - vector).length() 87 | } 88 | 89 | /** 90 | * Returns the angle in radians of the vector described by the CGVector. 91 | * The range of the angle is -π to π; an angle of 0 points to the right. 92 | */ 93 | public var angle: CGFloat { 94 | return atan2(dy, dx) 95 | } 96 | } 97 | 98 | /** 99 | * Adds two CGVector values and returns the result as a new CGVector. 100 | */ 101 | public func + (left: CGVector, right: CGVector) -> CGVector { 102 | return CGVector(dx: left.dx + right.dx, dy: left.dy + right.dy) 103 | } 104 | 105 | /** 106 | * Increments a CGVector with the value of another. 107 | */ 108 | public func += (left: inout CGVector, right: CGVector) { 109 | left = left + right 110 | } 111 | 112 | /** 113 | * Subtracts two CGVector values and returns the result as a new CGVector. 114 | */ 115 | public func - (left: CGVector, right: CGVector) -> CGVector { 116 | return CGVector(dx: left.dx - right.dx, dy: left.dy - right.dy) 117 | } 118 | 119 | /** 120 | * Decrements a CGVector with the value of another. 121 | */ 122 | public func -= (left: inout CGVector, right: CGVector) { 123 | left = left - right 124 | } 125 | 126 | /** 127 | * Multiplies two CGVector values and returns the result as a new CGVector. 128 | */ 129 | public func * (left: CGVector, right: CGVector) -> CGVector { 130 | return CGVector(dx: left.dx * right.dx, dy: left.dy * right.dy) 131 | } 132 | 133 | /** 134 | * Multiplies a CGVector with another. 135 | */ 136 | public func *= (left: inout CGVector, right: CGVector) { 137 | left = left * right 138 | } 139 | 140 | /** 141 | * Multiplies the x and y fields of a CGVector with the same scalar value and 142 | * returns the result as a new CGVector. 143 | */ 144 | public func * (vector: CGVector, scalar: CGFloat) -> CGVector { 145 | return CGVector(dx: vector.dx * scalar, dy: vector.dy * scalar) 146 | } 147 | 148 | /** 149 | * Multiplies the x and y fields of a CGVector with the same scalar value. 150 | */ 151 | public func *= (vector: inout CGVector, scalar: CGFloat) { 152 | vector = vector * scalar 153 | } 154 | 155 | /** 156 | * Divides two CGVector values and returns the result as a new CGVector. 157 | */ 158 | public func / (left: CGVector, right: CGVector) -> CGVector { 159 | return CGVector(dx: left.dx / right.dx, dy: left.dy / right.dy) 160 | } 161 | 162 | /** 163 | * Divides a CGVector by another. 164 | */ 165 | public func /= (left: inout CGVector, right: CGVector) { 166 | left = left / right 167 | } 168 | 169 | /** 170 | * Divides the dx and dy fields of a CGVector by the same scalar value and 171 | * returns the result as a new CGVector. 172 | */ 173 | public func / (vector: CGVector, scalar: CGFloat) -> CGVector { 174 | return CGVector(dx: vector.dx / scalar, dy: vector.dy / scalar) 175 | } 176 | 177 | /** 178 | * Divides the dx and dy fields of a CGVector by the same scalar value. 179 | */ 180 | public func /= (vector: inout CGVector, scalar: CGFloat) { 181 | vector = vector / scalar 182 | } 183 | 184 | /** 185 | * Performs a linear interpolation between two CGVector values. 186 | */ 187 | public func lerp(start: CGVector, end: CGVector, t: CGFloat) -> CGVector { 188 | return start + (end - start) * t 189 | } 190 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtilsTests/Vector3Tests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import SpriteKit 4 | 5 | class Vector3Tests: XCTestCase { 6 | var v1 = Vector3(x: 100, y: 50, z: 25) 7 | let v2 = Vector3(x: 10, y: 5, z: 5) 8 | 9 | func testAddingTwoVectors() { 10 | XCTAssertEqual(v1 + v2, Vector3(x: 110, y: 55, z: 30)) 11 | } 12 | 13 | func testAddingVectorToVector() { 14 | v1 += v2 15 | XCTAssertEqual(v1, Vector3(x: 110, y: 55, z: 30)) 16 | } 17 | 18 | func testSubtractingTwoVectors() { 19 | XCTAssertEqual(v1 - v2, Vector3(x: 90, y: 45, z: 20)) 20 | } 21 | 22 | func testSubtractingVectorFromVector() { 23 | v1 -= v2 24 | XCTAssertEqual(v1, Vector3(x: 90, y: 45, z: 20)) 25 | } 26 | 27 | func testMultiplyingTwoVectors() { 28 | XCTAssertEqual(v1 * v2, Vector3(x: 1000, y: 250, z: 125)) 29 | } 30 | 31 | func testMultiplyingVectorByVector() { 32 | v1 *= v2 33 | XCTAssertEqual(v1, Vector3(x: 1000, y: 250, z: 125)) 34 | } 35 | 36 | func testMultiplyingVectorAndFloat() { 37 | XCTAssertEqual(v1 * 2.5, Vector3(x: 250, y: 125, z: 62.5)) 38 | } 39 | 40 | func testMultiplyingVectorByFloat() { 41 | v1 *= 2.5 42 | XCTAssertEqual(v1, Vector3(x: 250, y: 125, z: 62.5)) 43 | } 44 | 45 | func testDividingTwoVectors() { 46 | XCTAssertEqual(v1 / v2, Vector3(x: 10, y: 10, z: 5)) 47 | } 48 | 49 | func testDividingVectorByVector() { 50 | v1 /= v2 51 | XCTAssertEqual(v1, Vector3(x: 10, y: 10, z: 5)) 52 | } 53 | 54 | func testDividingVectorAndFloat() { 55 | XCTAssertEqual(v1 / 2.5, Vector3(x: 40, y: 20, z: 10)) 56 | } 57 | 58 | func testDividingVectorByFloat() { 59 | v1 /= 2.5 60 | XCTAssertEqual(v1, Vector3(x: 40, y: 20, z: 10)) 61 | } 62 | 63 | func testEquality() { 64 | let v3 = v1 65 | XCTAssert(v1 == v3) 66 | XCTAssertFalse(v1 == v2) 67 | } 68 | 69 | func testEqualityScalar() { 70 | let v3 = Vector3(x: 3.0, y: 3.0, z: 3.0) 71 | XCTAssertFalse(v1 == 3.0) 72 | XCTAssert(v3 == 3.0) 73 | } 74 | 75 | func testInit() { 76 | let v = Vector3(x: -10, y: -20, z: -30) 77 | XCTAssertEqual(v.x, CGFloat(-10)) 78 | XCTAssertEqual(v.y, CGFloat(-20)) 79 | XCTAssertEqual(v.z, CGFloat(-30)) 80 | } 81 | 82 | func testLengthXUnitVector() { 83 | let v = Vector3(x: 1.0, y: 0.0, z: 0.0) 84 | XCTAssertEqual(v.length(), CGFloat(1.0)) 85 | } 86 | 87 | func testLengthYUnitVector() { 88 | let v = Vector3(x: 0.0, y: 1.0, z: 0.0) 89 | XCTAssertEqual(v.length(), CGFloat(1.0)) 90 | } 91 | 92 | func testLengthZUnitVector() { 93 | let v = Vector3(x: 0.0, y: 0.0, z: 1.0) 94 | XCTAssertEqual(v.length(), CGFloat(1.0)) 95 | } 96 | 97 | func testLength() { 98 | let v = Vector3(x: 1.0, y: 1.0, z: 1.0) 99 | XCTAssertEqual(v.length(), sqrt(3.0)) 100 | } 101 | 102 | func testLengthIsPositive() { 103 | let v = Vector3(x: -1.0, y: -1.0, z: -1.0) 104 | XCTAssertEqual(v.length(), sqrt(3.0)) 105 | } 106 | 107 | func testNormalized() { 108 | let normalized = v1.normalized() 109 | XCTAssertEqualWithAccuracy(normalized.x, 4.0/sqrt(21.0), accuracy: CGFloat(FLT_EPSILON)) 110 | XCTAssertEqualWithAccuracy(normalized.y, 2.0/sqrt(21.0), accuracy: CGFloat(FLT_EPSILON)) 111 | XCTAssertEqualWithAccuracy(normalized.z, 1.0/sqrt(21.0), accuracy: CGFloat(FLT_EPSILON)) 112 | } 113 | 114 | func testThatNormalizedDoesNotChangeOriginalValue() { 115 | let old = v1 116 | _ = v1.normalized() 117 | XCTAssertEqual(v1.x, old.x) 118 | XCTAssertEqual(v1.y, old.y) 119 | XCTAssertEqual(v1.z, old.z) 120 | } 121 | 122 | func testThatNormalizeReturnsNewValue() { 123 | v1.normalize() 124 | XCTAssertEqualWithAccuracy(v1.x, 4.0/sqrt(21.0), accuracy: CGFloat(FLT_EPSILON)) 125 | XCTAssertEqualWithAccuracy(v1.y, 2.0/sqrt(21.0), accuracy: CGFloat(FLT_EPSILON)) 126 | XCTAssertEqualWithAccuracy(v1.z, 1.0/sqrt(21.0), accuracy: CGFloat(FLT_EPSILON)) 127 | } 128 | 129 | func testLerp() { 130 | let start = Vector3(x: -100, y: -75, z:-3) 131 | let end = Vector3(x: 100, y: 25, z: 7) 132 | 133 | let expected = [ 134 | Vector3(x: -100, y: -75, z: -3), 135 | Vector3(x: -80, y: -65, z: -2), 136 | Vector3(x: -60, y: -55, z: -1), 137 | Vector3(x: -40, y: -45, z: 0), 138 | Vector3(x: -20, y: -35, z: 1), 139 | Vector3(x: 0, y: -25, z: 2), 140 | Vector3(x: 20, y: -15, z: 3), 141 | Vector3(x: 40, y: -5, z: 4), 142 | Vector3(x: 60, y: 5, z: 5), 143 | Vector3(x: 80, y: 15, z: 6), 144 | Vector3(x: 100, y: 25, z: 7) 145 | ] 146 | 147 | var i = 0 148 | for t in stride(from: 0.0, through: 1.0, by: 0.1) { 149 | let lerped = lerp(start: start, end: end, t: CGFloat(t)) 150 | XCTAssertEqualWithAccuracy(lerped.x, expected[i].x, accuracy: 1.0e6) 151 | XCTAssertEqualWithAccuracy(lerped.y, expected[i].y, accuracy: 1.0e6) 152 | XCTAssertEqualWithAccuracy(lerped.z, expected[i].z, accuracy: 1.0e6) 153 | i += 1 154 | } 155 | } 156 | 157 | func testDotProduct() { 158 | let v1 = Vector3(x: -1.0, y: 2.0, z: -3.0) 159 | let v2 = Vector3(x: 4.0, y: -5.0, z: -6.0) 160 | 161 | // test class dot product 162 | let r1 = Vector3.dotProduct(v1, right: v2) 163 | XCTAssertEqual(r1,CGFloat(4.0)) 164 | 165 | // test member dot product 166 | let r2 = v1.dot(v2) 167 | XCTAssertEqual(r2,CGFloat(4.0)) 168 | XCTAssertEqual(v1.x,CGFloat(-1.0)) 169 | XCTAssertEqual(v1.y,CGFloat(2.0)) 170 | XCTAssertEqual(v1.z,CGFloat(-3.0)) 171 | } 172 | 173 | func testCrossProduct() { 174 | let v1 = Vector3(x: -1.0, y: 2.0, z: -3.0) 175 | let v2 = Vector3(x: 4.0, y: -5.0, z: -6.0) 176 | 177 | // test class cross product 178 | let r1 = Vector3.crossProduct(v1, right: v2) 179 | XCTAssertEqual(r1.x,CGFloat(-27.0)) 180 | XCTAssertEqual(r1.y,CGFloat(-18.0)) 181 | XCTAssertEqual(r1.z,CGFloat(-3.0)) 182 | 183 | // test member cross product 184 | let r2 = v1.cross(v2) 185 | XCTAssertEqual(r2.x,CGFloat(-27.0)) 186 | XCTAssertEqual(r2.y,CGFloat(-18.0)) 187 | XCTAssertEqual(r2.z,CGFloat(-3.0)) 188 | XCTAssertEqual(v1.x,CGFloat(-1.0)) 189 | XCTAssertEqual(v1.y,CGFloat(2.0)) 190 | XCTAssertEqual(v1.z,CGFloat(-3.0)) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtilsTests/CGVectorTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import CoreGraphics 4 | import SpriteKit 5 | 6 | class CGVectorTests: XCTestCase { 7 | var v1 = CGVector(dx: 100, dy: 50) 8 | let v2 = CGVector(dx: 10, dy: 5) 9 | 10 | func testAddingTwoVectors() { 11 | XCTAssertEqual(v1 + v2, CGVector(dx: 110, dy: 55)) 12 | } 13 | 14 | func testAddingVectorToVector() { 15 | v1 += v2 16 | XCTAssertEqual(v1, CGVector(dx: 110, dy: 55)) 17 | } 18 | 19 | func testSubtractingTwoVectors() { 20 | XCTAssertEqual(v1 - v2, CGVector(dx: 90, dy: 45)) 21 | } 22 | 23 | func testSubtractingVectorFromVector() { 24 | v1 -= v2 25 | XCTAssertEqual(v1, CGVector(dx: 90, dy: 45)) 26 | } 27 | 28 | func testMultiplyingTwoVectors() { 29 | XCTAssertEqual(v1 * v2, CGVector(dx: 1000, dy: 250)) 30 | } 31 | 32 | func testMultiplyingVectorByVector() { 33 | v1 *= v2 34 | XCTAssertEqual(v1, CGVector(dx: 1000, dy: 250)) 35 | } 36 | 37 | func testMultiplyingVectorAndFloat() { 38 | XCTAssertEqual(v1 * 2.5, CGVector(dx: 250, dy: 125)) 39 | } 40 | 41 | func testMultiplyingVectorByFloat() { 42 | v1 *= 2.5 43 | XCTAssertEqual(v1, CGVector(dx: 250, dy: 125)) 44 | } 45 | 46 | func testDividingTwoVectors() { 47 | XCTAssertEqual(v1 / v2, CGVector(dx: 10, dy: 10)) 48 | } 49 | 50 | func testDividingVectorByVector() { 51 | v1 /= v2 52 | XCTAssertEqual(v1, CGVector(dx: 10, dy: 10)) 53 | } 54 | 55 | func testDividingVectorAndFloat() { 56 | XCTAssertEqual(v1 / 2.5, CGVector(dx: 40, dy: 20)) 57 | } 58 | 59 | func testDividingVectorByFloat() { 60 | v1 /= 2.5 61 | XCTAssertEqual(v1, CGVector(dx: 40, dy: 20)) 62 | } 63 | 64 | func testOffsettingVector() { 65 | v1.offset(dx: 10, dy: 5) 66 | XCTAssertEqual(v1, CGVector(dx: 110, dy: 55)) 67 | } 68 | 69 | func testThatOffsetReturnsNewValue() { 70 | XCTAssertEqual(v1.offset(dx: 10, dy: 5), v1) 71 | } 72 | 73 | func testInitWithPoint() { 74 | let pt = CGPoint(x: -10, y: -20) 75 | let v = CGVector(point: pt) 76 | XCTAssertEqual(v.dx, pt.x) 77 | XCTAssertEqual(v.dy, pt.y) 78 | } 79 | 80 | func testInitWithZeroDegreeAngle() { 81 | let a: CGFloat = 0 82 | let v = CGVector(angle: a) 83 | XCTAssertEqual(v.dx, CGFloat(1.0)) 84 | XCTAssertEqual(v.dy, CGFloat(0.0)) 85 | } 86 | 87 | func testInitWith45DegreeAngle() { 88 | let a = π/4.0 89 | let v = CGVector(angle: a) 90 | XCTAssertEqualWithAccuracy(v.dx, 1.0/sqrt(2.0), accuracy: CGFloat(FLT_EPSILON)) 91 | XCTAssertEqualWithAccuracy(v.dy, 1.0/sqrt(2.0), accuracy: CGFloat(FLT_EPSILON)) 92 | } 93 | 94 | func testInitWith90DegreeAngle() { 95 | let a = π/2.0 96 | let v = CGVector(angle: a) 97 | XCTAssertEqualWithAccuracy(v.dx, CGFloat(0.0), accuracy: CGFloat(FLT_EPSILON)) 98 | XCTAssertEqual(v.dy, CGFloat(1.0)) 99 | } 100 | 101 | func testInitWith180DegreeAngle() { 102 | let a = π 103 | let v = CGVector(angle: a) 104 | XCTAssertEqual(v.dx, -1.0) 105 | XCTAssertEqualWithAccuracy(v.dy, CGFloat(0.0), accuracy: CGFloat(FLT_EPSILON)) 106 | } 107 | 108 | func testInitWithMinus135DegreeAngle() { 109 | let a = -3.0*π/4.0 110 | let v = CGVector(angle: a) 111 | XCTAssertEqualWithAccuracy(v.dx, -1.0/sqrt(2.0), accuracy: CGFloat(FLT_EPSILON)) 112 | XCTAssertEqualWithAccuracy(v.dy, -1.0/sqrt(2.0), accuracy: CGFloat(FLT_EPSILON)) 113 | } 114 | 115 | func testZeroDegreeAngle() { 116 | let v = CGVector(dx: 1.0, dy: 0.0) 117 | XCTAssertEqual(v.angle, CGFloat(0)) 118 | } 119 | 120 | func test45DegreeAngle() { 121 | let v = CGVector(dx: 1.0/sqrt(2.0), dy: 1.0/sqrt(2.0)) 122 | XCTAssertEqual(v.angle, π/4.0) 123 | } 124 | 125 | func test90DegreeAngle() { 126 | let v = CGVector(dx: 0.0, dy: 1.0) 127 | XCTAssertEqual(v.angle, π/2.0) 128 | } 129 | 130 | func test180DegreeAngle() { 131 | let v = CGVector(dx: -1.0, dy: 0.0) 132 | XCTAssertEqualWithAccuracy(v.angle, π, accuracy: 1.0e-6) 133 | } 134 | 135 | func testMinus135DegreeAngle() { 136 | let v = CGVector(dx: -1.0/sqrt(2.0), dy: -1.0/sqrt(2.0)) 137 | XCTAssertEqualWithAccuracy(v.angle, -3.0*π/4.0, accuracy: CGFloat(FLT_EPSILON)) 138 | } 139 | 140 | func testLengthHorizontalUnitVector() { 141 | let v = CGVector(dx: 1.0, dy: 0.0) 142 | XCTAssertEqual(v.length(), CGFloat(1.0)) 143 | } 144 | 145 | func testLengthVerticalUnitVector() { 146 | let v = CGVector(dx: 0.0, dy: 1.0) 147 | XCTAssertEqual(v.length(), CGFloat(1.0)) 148 | } 149 | 150 | func testLength() { 151 | let v = CGVector(dx: 1.0, dy: 1.0) 152 | XCTAssertEqual(v.length(), sqrt(2.0)) 153 | } 154 | 155 | func testLengthIsPositive() { 156 | let v = CGVector(dx: -1.0, dy: -1.0) 157 | XCTAssertEqual(v.length(), sqrt(2.0)) 158 | } 159 | 160 | func testLengthSquared() { 161 | let v = CGVector(dx: 1.0, dy: 1.0) 162 | XCTAssertEqual(v.lengthSquared(), CGFloat(2.0)) 163 | } 164 | 165 | func testDistance() { 166 | XCTAssertEqualWithAccuracy(v1.distanceTo(v2), CGFloat(100.6230589874), accuracy: CGFloat(FLT_EPSILON)) 167 | } 168 | 169 | func testThatLengthEqualsDistance() { 170 | XCTAssertEqualWithAccuracy(v1.distanceTo(v2), (v1 - v2).length(), accuracy: CGFloat(FLT_EPSILON)) 171 | } 172 | 173 | func testNormalized() { 174 | let normalized = v1.normalized() 175 | XCTAssertEqualWithAccuracy(normalized.dx, 2.0/sqrt(5.0), accuracy: CGFloat(FLT_EPSILON)) 176 | XCTAssertEqualWithAccuracy(normalized.dy, 1.0/sqrt(5.0), accuracy: CGFloat(FLT_EPSILON)) 177 | } 178 | 179 | func testThatNormalizedDoesNotChangeOriginalValue() { 180 | let old = v1 181 | _ = v1.normalized() 182 | XCTAssertEqual(v1.dx, old.dx) 183 | XCTAssertEqual(v1.dy, old.dy) 184 | } 185 | 186 | func testThatNormalizeReturnsNewValue() { 187 | v1.normalize() 188 | XCTAssertEqualWithAccuracy(v1.dx, 2.0/sqrt(5.0), accuracy: CGFloat(FLT_EPSILON)) 189 | XCTAssertEqualWithAccuracy(v1.dy, 1.0/sqrt(5.0), accuracy: CGFloat(FLT_EPSILON)) 190 | } 191 | 192 | func testThatNormalizingKeepsSameAngle() { 193 | let angle = v1.angle 194 | XCTAssertEqual(angle, v1.normalize().angle) 195 | } 196 | 197 | func testLerp() { 198 | let start = CGVector(dx: -100, dy: -75) 199 | let end = CGVector(dx: 100, dy: 25) 200 | 201 | let expected = [ 202 | CGVector(dx: -100, dy: -75), 203 | CGVector(dx: -80, dy: -65), 204 | CGVector(dx: -60, dy: -55), 205 | CGVector(dx: -40, dy: -45), 206 | CGVector(dx: -20, dy: -35), 207 | CGVector(dx: 0, dy: -25), 208 | CGVector(dx: 20, dy: -15), 209 | CGVector(dx: 40, dy: -5), 210 | CGVector(dx: 60, dy: 5), 211 | CGVector(dx: 80, dy: 15), 212 | CGVector(dx: 100, dy: 25) 213 | ] 214 | 215 | var i = 0 216 | for t in stride(from: 0.0, through: 1.0, by: 0.1) { 217 | let lerped = lerp(start: start, end: end, t: CGFloat(t)) 218 | XCTAssertEqualWithAccuracy(lerped.dx, expected[i].dx, accuracy: 1.0e6) 219 | XCTAssertEqualWithAccuracy(lerped.dy, expected[i].dy, accuracy: 1.0e6) 220 | i += 1 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /SKTUtils/Vector3.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreGraphics 24 | 25 | public struct Vector3: Equatable { 26 | public var x: CGFloat 27 | public var y: CGFloat 28 | public var z: CGFloat 29 | 30 | public init(x: CGFloat, y: CGFloat, z: CGFloat) { 31 | self.x = x 32 | self.y = y 33 | self.z = z 34 | } 35 | } 36 | 37 | /** 38 | * Returns true if two vectors have the same element values. 39 | */ 40 | public func == (lhs: Vector3, rhs: Vector3) -> Bool { 41 | return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z 42 | } 43 | 44 | /** 45 | * Returns true if all the vector elements are equal to the provided scalar. 46 | */ 47 | public func == (lhs: Vector3, rhs: CGFloat) -> Bool { 48 | return lhs.x == rhs && lhs.y == rhs && lhs.z == rhs 49 | } 50 | 51 | extension Vector3 { 52 | /** 53 | * A vector constant with value (0, 0, 0). 54 | */ 55 | static var zeroVector: Vector3 { 56 | return Vector3(x: 0, y: 0, z: 0) 57 | } 58 | 59 | /** 60 | * Returns true if all the vector elements are equal to the provided value. 61 | * 62 | * DEPRECATED: Use the == operator instead. 63 | */ 64 | public func equalToScalar(_ value: CGFloat) -> Bool { 65 | return x == value && y == value && z == value 66 | } 67 | 68 | /** 69 | * Returns the magnitude of the vector. 70 | */ 71 | public func length() -> CGFloat { 72 | return sqrt(x*x + y*y + z*z) 73 | } 74 | 75 | /** 76 | * Normalizes the vector and returns the result as a new vector. 77 | */ 78 | public func normalized() -> Vector3 { 79 | let scale = 1.0/length() 80 | return Vector3(x: x * scale, y: y * scale, z: z * scale) 81 | } 82 | 83 | /** 84 | * Normalizes the vector described by this Vector3 object. 85 | */ 86 | public mutating func normalize() { 87 | let scale = 1.0/length() 88 | x *= scale 89 | y *= scale 90 | z *= scale 91 | } 92 | 93 | /** 94 | * Calculates the dot product with another Vector3. 95 | */ 96 | public func dot(_ vector: Vector3) -> CGFloat { 97 | return Vector3.dotProduct(self, right: vector) 98 | } 99 | 100 | /** 101 | * Calculates the cross product with another Vector3. 102 | */ 103 | public func cross(_ vector: Vector3) -> Vector3 { 104 | return Vector3.crossProduct(self, right: vector) 105 | } 106 | 107 | /** 108 | * Calculates the dot product of two vectors. 109 | * 110 | * DEPRECATED: Use dot() instead. 111 | */ 112 | public static func dotProduct(_ left: Vector3, right: Vector3) -> CGFloat { 113 | return left.x * right.x + left.y * right.y + left.z * right.z 114 | } 115 | 116 | /** 117 | * Calculates the cross product of two vectors. 118 | * 119 | * DEPRECATED: Use cross() instead. 120 | */ 121 | public static func crossProduct(_ left: Vector3, right: Vector3) -> Vector3 { 122 | let crossProduct = Vector3(x: left.y * right.z - left.z * right.y, 123 | y: left.z * right.x - left.x * right.z, 124 | z: left.x * right.y - left.y * right.x) 125 | return crossProduct 126 | } 127 | } 128 | 129 | /** 130 | * Adds two Vector3 values and returns the result as a new Vector3. 131 | */ 132 | public func + (left: Vector3, right: Vector3) -> Vector3 { 133 | return Vector3(x: left.x + right.x, y: left.y + right.y, z: left.z + right.z) 134 | } 135 | 136 | /** 137 | * Increments a Vector3 with the value of another. 138 | */ 139 | public func += (left: inout Vector3, right: Vector3) { 140 | left = left + right 141 | } 142 | 143 | /** 144 | * Subtracts two Vector3 values and returns the result as a new Vector3. 145 | */ 146 | public func - (left: Vector3, right: Vector3) -> Vector3 { 147 | return Vector3(x: left.x - right.x, y: left.y - right.y, z: left.z - right.z) 148 | } 149 | 150 | /** 151 | * Decrements a Vector3 with the value of another. 152 | */ 153 | public func -= (left: inout Vector3, right: Vector3) { 154 | left = left - right 155 | } 156 | 157 | /** 158 | * Multiplies two Vector3 values and returns the result as a new Vector3. 159 | */ 160 | public func * (left: Vector3, right: Vector3) -> Vector3 { 161 | return Vector3(x: left.x * right.x, y: left.y * right.y, z: left.z * right.z) 162 | } 163 | 164 | /** 165 | * Multiplies a Vector3 with another. 166 | */ 167 | public func *= (left: inout Vector3, right: Vector3) { 168 | left = left * right 169 | } 170 | 171 | /** 172 | * Multiplies the x,y,z fields of a Vector3 with the same scalar value and 173 | * returns the result as a new Vector3. 174 | */ 175 | public func * (vector: Vector3, scalar: CGFloat) -> Vector3 { 176 | return Vector3(x: vector.x * scalar, y: vector.y * scalar, z: vector.z * scalar) 177 | } 178 | 179 | /** 180 | * Multiplies the x,y,z fields of a Vector3 with the same scalar value. 181 | */ 182 | public func *= (vector: inout Vector3, scalar: CGFloat) { 183 | vector = vector * scalar 184 | } 185 | 186 | /** 187 | * Divides two Vector3 values and returns the result as a new Vector3. 188 | */ 189 | public func / (left: Vector3, right: Vector3) -> Vector3 { 190 | return Vector3(x: left.x / right.x, y: left.y / right.y, z: left.z / right.z) 191 | } 192 | 193 | /** 194 | * Divides a Vector3 by another. 195 | */ 196 | public func /= (left: inout Vector3, right: Vector3) { 197 | left = left / right 198 | } 199 | 200 | /** 201 | * Divides the x,y,z fields of a Vector3 by the same scalar value and 202 | * returns the result as a new Vector3. 203 | */ 204 | public func / (vector: Vector3, scalar: CGFloat) -> Vector3 { 205 | return Vector3(x: vector.x / scalar, y: vector.y / scalar, z: vector.z / scalar) 206 | } 207 | 208 | /** 209 | * Divides the x,y,z fields of a Vector3 by the same scalar value. 210 | */ 211 | public func /= (vector: inout Vector3, scalar: CGFloat) { 212 | vector = vector / scalar 213 | } 214 | 215 | /** 216 | * Performs a linear interpolation between two Vector3 values. 217 | */ 218 | public func lerp(start: Vector3, end: Vector3, t: CGFloat) -> Vector3 { 219 | return start + (end - start) * t 220 | } 221 | -------------------------------------------------------------------------------- /SKTUtils/CGPoint+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreGraphics 24 | import SpriteKit 25 | 26 | public extension CGPoint { 27 | /** 28 | * Creates a new CGPoint given a CGVector. 29 | */ 30 | public init(vector: CGVector) { 31 | self.init(x: vector.dx, y: vector.dy) 32 | } 33 | 34 | /** 35 | * Given an angle in radians, creates a vector of length 1.0 and returns the 36 | * result as a new CGPoint. An angle of 0 is assumed to point to the right. 37 | */ 38 | public init(angle: CGFloat) { 39 | self.init(x: cos(angle), y: sin(angle)) 40 | } 41 | 42 | /** 43 | * Adds (dx, dy) to the point. 44 | */ 45 | public mutating func offset(dx: CGFloat, dy: CGFloat) -> CGPoint { 46 | x += dx 47 | y += dy 48 | return self 49 | } 50 | 51 | /** 52 | * Returns the length (magnitude) of the vector described by the CGPoint. 53 | */ 54 | public func length() -> CGFloat { 55 | return sqrt(x*x + y*y) 56 | } 57 | 58 | /** 59 | * Returns the squared length of the vector described by the CGPoint. 60 | */ 61 | public func lengthSquared() -> CGFloat { 62 | return x*x + y*y 63 | } 64 | 65 | /** 66 | * Normalizes the vector described by the CGPoint to length 1.0 and returns 67 | * the result as a new CGPoint. 68 | */ 69 | func normalized() -> CGPoint { 70 | let len = length() 71 | return len>0 ? self / len : CGPoint.zero 72 | } 73 | 74 | /** 75 | * Normalizes the vector described by the CGPoint to length 1.0. 76 | */ 77 | public mutating func normalize() -> CGPoint { 78 | self = normalized() 79 | return self 80 | } 81 | 82 | /** 83 | * Calculates the distance between two CGPoints. Pythagoras! 84 | */ 85 | public func distanceTo(_ point: CGPoint) -> CGFloat { 86 | return (self - point).length() 87 | } 88 | 89 | /** 90 | * Returns the angle in radians of the vector described by the CGPoint. 91 | * The range of the angle is -π to π; an angle of 0 points to the right. 92 | */ 93 | public var angle: CGFloat { 94 | return atan2(y, x) 95 | } 96 | } 97 | 98 | /** 99 | * Adds two CGPoint values and returns the result as a new CGPoint. 100 | */ 101 | public func + (left: CGPoint, right: CGPoint) -> CGPoint { 102 | return CGPoint(x: left.x + right.x, y: left.y + right.y) 103 | } 104 | 105 | /** 106 | * Increments a CGPoint with the value of another. 107 | */ 108 | public func += (left: inout CGPoint, right: CGPoint) { 109 | left = left + right 110 | } 111 | 112 | /** 113 | * Adds a CGVector to this CGPoint and returns the result as a new CGPoint. 114 | */ 115 | public func + (left: CGPoint, right: CGVector) -> CGPoint { 116 | return CGPoint(x: left.x + right.dx, y: left.y + right.dy) 117 | } 118 | 119 | /** 120 | * Increments a CGPoint with the value of a CGVector. 121 | */ 122 | public func += (left: inout CGPoint, right: CGVector) { 123 | left = left + right 124 | } 125 | 126 | /** 127 | * Subtracts two CGPoint values and returns the result as a new CGPoint. 128 | */ 129 | public func - (left: CGPoint, right: CGPoint) -> CGPoint { 130 | return CGPoint(x: left.x - right.x, y: left.y - right.y) 131 | } 132 | 133 | /** 134 | * Decrements a CGPoint with the value of another. 135 | */ 136 | public func -= (left: inout CGPoint, right: CGPoint) { 137 | left = left - right 138 | } 139 | 140 | /** 141 | * Subtracts a CGVector from a CGPoint and returns the result as a new CGPoint. 142 | */ 143 | public func - (left: CGPoint, right: CGVector) -> CGPoint { 144 | return CGPoint(x: left.x - right.dx, y: left.y - right.dy) 145 | } 146 | 147 | /** 148 | * Decrements a CGPoint with the value of a CGVector. 149 | */ 150 | public func -= (left: inout CGPoint, right: CGVector) { 151 | left = left - right 152 | } 153 | 154 | /** 155 | * Multiplies two CGPoint values and returns the result as a new CGPoint. 156 | */ 157 | public func * (left: CGPoint, right: CGPoint) -> CGPoint { 158 | return CGPoint(x: left.x * right.x, y: left.y * right.y) 159 | } 160 | 161 | /** 162 | * Multiplies a CGPoint with another. 163 | */ 164 | public func *= (left: inout CGPoint, right: CGPoint) { 165 | left = left * right 166 | } 167 | 168 | /** 169 | * Multiplies the x and y fields of a CGPoint with the same scalar value and 170 | * returns the result as a new CGPoint. 171 | */ 172 | public func * (point: CGPoint, scalar: CGFloat) -> CGPoint { 173 | return CGPoint(x: point.x * scalar, y: point.y * scalar) 174 | } 175 | 176 | /** 177 | * Multiplies the x and y fields of a CGPoint with the same scalar value. 178 | */ 179 | public func *= (point: inout CGPoint, scalar: CGFloat) { 180 | point = point * scalar 181 | } 182 | 183 | /** 184 | * Multiplies a CGPoint with a CGVector and returns the result as a new CGPoint. 185 | */ 186 | public func * (left: CGPoint, right: CGVector) -> CGPoint { 187 | return CGPoint(x: left.x * right.dx, y: left.y * right.dy) 188 | } 189 | 190 | /** 191 | * Multiplies a CGPoint with a CGVector. 192 | */ 193 | public func *= (left: inout CGPoint, right: CGVector) { 194 | left = left * right 195 | } 196 | 197 | /** 198 | * Divides two CGPoint values and returns the result as a new CGPoint. 199 | */ 200 | public func / (left: CGPoint, right: CGPoint) -> CGPoint { 201 | return CGPoint(x: left.x / right.x, y: left.y / right.y) 202 | } 203 | 204 | /** 205 | * Divides a CGPoint by another. 206 | */ 207 | public func /= (left: inout CGPoint, right: CGPoint) { 208 | left = left / right 209 | } 210 | 211 | /** 212 | * Divides the x and y fields of a CGPoint by the same scalar value and returns 213 | * the result as a new CGPoint. 214 | */ 215 | public func / (point: CGPoint, scalar: CGFloat) -> CGPoint { 216 | return CGPoint(x: point.x / scalar, y: point.y / scalar) 217 | } 218 | 219 | /** 220 | * Divides the x and y fields of a CGPoint by the same scalar value. 221 | */ 222 | public func /= (point: inout CGPoint, scalar: CGFloat) { 223 | point = point / scalar 224 | } 225 | 226 | /** 227 | * Divides a CGPoint by a CGVector and returns the result as a new CGPoint. 228 | */ 229 | public func / (left: CGPoint, right: CGVector) -> CGPoint { 230 | return CGPoint(x: left.x / right.dx, y: left.y / right.dy) 231 | } 232 | 233 | /** 234 | * Divides a CGPoint by a CGVector. 235 | */ 236 | public func /= (left: inout CGPoint, right: CGVector) { 237 | left = left / right 238 | } 239 | 240 | /** 241 | * Performs a linear interpolation between two CGPoint values. 242 | */ 243 | public func lerp(start: CGPoint, end: CGPoint, t: CGFloat) -> CGPoint { 244 | return start + (end - start) * t 245 | } 246 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtilsTests/CGPointTests.swift: -------------------------------------------------------------------------------- 1 | 2 | import XCTest 3 | import CoreGraphics 4 | import SpriteKit 5 | 6 | class CGPointTests: XCTestCase { 7 | var pt1 = CGPoint(x: 100, y: 50) 8 | let pt2 = CGPoint(x: 10, y: 5) 9 | let v = CGVector(dx: 2, dy: 0.5) 10 | 11 | func testAddingTwoPoints() { 12 | XCTAssertEqual(pt1 + pt2, CGPoint(x: 110, y: 55)) 13 | } 14 | 15 | func testAddingPointToPoint() { 16 | pt1 += pt2 17 | XCTAssertEqual(pt1, CGPoint(x: 110, y: 55)) 18 | } 19 | 20 | func testAddingVectorToPoint() { 21 | pt1 += v 22 | XCTAssertEqual(pt1, CGPoint(x: 102, y: 50.5)) 23 | } 24 | 25 | func testSubtractingTwoPoints() { 26 | XCTAssertEqual(pt1 - pt2, CGPoint(x: 90, y: 45)) 27 | } 28 | 29 | func testSubtractingPointFromPoint() { 30 | pt1 -= pt2 31 | XCTAssertEqual(pt1, CGPoint(x: 90, y: 45)) 32 | } 33 | 34 | func testSubtractingVectorFromPoint() { 35 | pt1 -= v 36 | XCTAssertEqual(pt1, CGPoint(x: 98, y: 49.5)) 37 | } 38 | 39 | func testMultiplyingTwoPoints() { 40 | XCTAssertEqual(pt1 * pt2, CGPoint(x: 1000, y: 250)) 41 | } 42 | 43 | func testMultiplyingPointByPoint() { 44 | pt1 *= pt2 45 | XCTAssertEqual(pt1, CGPoint(x: 1000, y: 250)) 46 | } 47 | 48 | func testMultiplyingPointAndFloat() { 49 | XCTAssertEqual(pt1 * 2.5, CGPoint(x: 250, y: 125)) 50 | } 51 | 52 | func testMultiplyingPointByFloat() { 53 | pt1 *= 2.5 54 | XCTAssertEqual(pt1, CGPoint(x: 250, y: 125)) 55 | } 56 | 57 | func testMultiplyingPointAndVector() { 58 | XCTAssertEqual(pt1 * v, CGPoint(x: 200, y: 25)) 59 | } 60 | 61 | func testMultiplyingPointByVector() { 62 | pt1 *= v 63 | XCTAssertEqual(pt1, CGPoint(x: 200, y: 25)) 64 | } 65 | 66 | func testDividingTwoPoints() { 67 | XCTAssertEqual(pt1 / pt2, CGPoint(x: 10, y: 10)) 68 | } 69 | 70 | func testDividingPointByPoint() { 71 | pt1 /= pt2 72 | XCTAssertEqual(pt1, CGPoint(x: 10, y: 10)) 73 | } 74 | 75 | func testDividingPointAndFloat() { 76 | XCTAssertEqual(pt1 / 2.5, CGPoint(x: 40, y: 20)) 77 | } 78 | 79 | func testDividingPointByFloat() { 80 | pt1 /= 2.5 81 | XCTAssertEqual(pt1, CGPoint(x: 40, y: 20)) 82 | } 83 | 84 | func testDividingPointAndVector() { 85 | XCTAssertEqual(pt1 / v, CGPoint(x: 50, y: 100)) 86 | } 87 | 88 | func testDividingPointByVector() { 89 | pt1 /= v 90 | XCTAssertEqual(pt1, CGPoint(x: 50, y: 100)) 91 | } 92 | 93 | func testOffsettingPoint() { 94 | pt1.offset(dx: 10, dy: 5) 95 | XCTAssertEqual(pt1, CGPoint(x: 110, y: 55)) 96 | } 97 | 98 | func testThatOffsetReturnsNewValue() { 99 | XCTAssertEqual(pt1.offset(dx: 10, dy: 5), pt1) 100 | } 101 | 102 | func testInitWithVector() { 103 | let v = CGVector(dx: -10, dy: -20) 104 | let pt = CGPoint(vector: v) 105 | XCTAssertEqual(v.dx, pt.x) 106 | XCTAssertEqual(v.dy, pt.y) 107 | } 108 | 109 | func testInitWithZeroDegreeAngle() { 110 | let a: CGFloat = 0 111 | let pt = CGPoint(angle: a) 112 | XCTAssertEqual(pt.x, CGFloat(1.0)) 113 | XCTAssertEqual(pt.y, CGFloat(0.0)) 114 | } 115 | 116 | func testInitWith45DegreeAngle() { 117 | let a = π/4.0 118 | let pt = CGPoint(angle: a) 119 | XCTAssertEqualWithAccuracy(pt.x, 1.0/sqrt(2.0), accuracy: CGFloat(FLT_EPSILON)) 120 | XCTAssertEqualWithAccuracy(pt.y, 1.0/sqrt(2.0), accuracy: CGFloat(FLT_EPSILON)) 121 | } 122 | 123 | func testInitWith90DegreeAngle() { 124 | let a = π/2.0 125 | let pt = CGPoint(angle: a) 126 | XCTAssertEqualWithAccuracy(pt.x, CGFloat(0.0), accuracy: CGFloat(FLT_EPSILON)) 127 | XCTAssertEqual(pt.y, CGFloat(1.0)) 128 | } 129 | 130 | func testInitWith180DegreeAngle() { 131 | let a = π 132 | let pt = CGPoint(angle: a) 133 | XCTAssertEqual(pt.x, -1.0) 134 | XCTAssertEqualWithAccuracy(pt.y, CGFloat(0.0), accuracy: CGFloat(FLT_EPSILON)) 135 | } 136 | 137 | func testInitWithMinus135DegreeAngle() { 138 | let a = -3.0*π/4.0 139 | let pt = CGPoint(angle: a) 140 | XCTAssertEqualWithAccuracy(pt.x, -1.0/sqrt(2.0), accuracy: CGFloat(FLT_EPSILON)) 141 | XCTAssertEqualWithAccuracy(pt.y, -1.0/sqrt(2.0), accuracy: CGFloat(FLT_EPSILON)) 142 | } 143 | 144 | func testZeroDegreeAngle() { 145 | let pt = CGPoint(x: 1.0, y: 0.0) 146 | XCTAssertEqual(pt.angle, CGFloat(0)) 147 | } 148 | 149 | func test45DegreeAngle() { 150 | let pt = CGPoint(x: 1.0/sqrt(2.0), y: 1.0/sqrt(2.0)) 151 | XCTAssertEqual(pt.angle, π/4.0) 152 | } 153 | 154 | func test90DegreeAngle() { 155 | let pt = CGPoint(x: 0.0, y: 1.0) 156 | XCTAssertEqual(pt.angle, π/2.0) 157 | } 158 | 159 | func test180DegreeAngle() { 160 | let pt = CGPoint(x: -1.0, y: 0.0) 161 | XCTAssertEqualWithAccuracy(pt.angle, π, accuracy: 1.0e-6) 162 | } 163 | 164 | func testMinus135DegreeAngle() { 165 | let pt = CGPoint(x: -1.0/sqrt(2.0), y: -1.0/sqrt(2.0)) 166 | XCTAssertEqualWithAccuracy(pt.angle, -3.0*π/4.0, accuracy: CGFloat(FLT_EPSILON)) 167 | } 168 | 169 | func testLengthHorizontalUnitVector() { 170 | let pt = CGPoint(x: 1.0, y: 0.0) 171 | XCTAssertEqual(pt.length(), CGFloat(1.0)) 172 | } 173 | 174 | func testLengthVerticalUnitVector() { 175 | let pt = CGPoint(x: 0.0, y: 1.0) 176 | XCTAssertEqual(pt.length(), CGFloat(1.0)) 177 | } 178 | 179 | func testLength() { 180 | let pt = CGPoint(x: 1.0, y: 1.0) 181 | XCTAssertEqual(pt.length(), sqrt(2.0)) 182 | } 183 | 184 | func testLengthIsPositive() { 185 | let pt = CGPoint(x: -1.0, y: -1.0) 186 | XCTAssertEqual(pt.length(), sqrt(2.0)) 187 | } 188 | 189 | func testLengthSquared() { 190 | let pt = CGPoint(x: 1.0, y: 1.0) 191 | XCTAssertEqual(pt.lengthSquared(), CGFloat(2.0)) 192 | } 193 | 194 | func testDistance() { 195 | XCTAssertEqualWithAccuracy(pt1.distanceTo(pt2), CGFloat(100.6230589874), accuracy: CGFloat(FLT_EPSILON)) 196 | } 197 | 198 | func testThatLengthEqualsDistance() { 199 | XCTAssertEqualWithAccuracy(pt1.distanceTo(pt2), (pt1 - pt2).length(), accuracy: CGFloat(FLT_EPSILON)) 200 | } 201 | 202 | func testNormalized() { 203 | let normalized = pt1.normalized() 204 | XCTAssertEqualWithAccuracy(normalized.x, 2.0/sqrt(5.0), accuracy: CGFloat(FLT_EPSILON)) 205 | XCTAssertEqualWithAccuracy(normalized.y, 1.0/sqrt(5.0), accuracy: CGFloat(FLT_EPSILON)) 206 | } 207 | 208 | func testThatNormalizedDoesNotChangeOriginalValue() { 209 | let old = pt1 210 | _ = pt1.normalized() 211 | XCTAssertEqual(pt1.x, old.x) 212 | XCTAssertEqual(pt1.y, old.y) 213 | } 214 | 215 | func testThatNormalizeReturnsNewValue() { 216 | pt1.normalize() 217 | XCTAssertEqualWithAccuracy(pt1.x, 2.0/sqrt(5.0), accuracy: CGFloat(FLT_EPSILON)) 218 | XCTAssertEqualWithAccuracy(pt1.y, 1.0/sqrt(5.0), accuracy: CGFloat(FLT_EPSILON)) 219 | } 220 | 221 | func testThatNormalizingKeepsSameAngle() { 222 | let angle = pt1.angle 223 | XCTAssertEqual(angle, pt1.normalize().angle) 224 | } 225 | 226 | func testLerp() { 227 | let start = CGPoint(x: -100, y: -75) 228 | let end = CGPoint(x: 100, y: 25) 229 | 230 | let expected = [ 231 | CGPoint(x: -100, y: -75), 232 | CGPoint(x: -80, y: -65), 233 | CGPoint(x: -60, y: -55), 234 | CGPoint(x: -40, y: -45), 235 | CGPoint(x: -20, y: -35), 236 | CGPoint(x: 0, y: -25), 237 | CGPoint(x: 20, y: -15), 238 | CGPoint(x: 40, y: -5), 239 | CGPoint(x: 60, y: 5), 240 | CGPoint(x: 80, y: 15), 241 | CGPoint(x: 100, y: 25) 242 | ] 243 | 244 | var i = 0 245 | for t in stride(from: 0.0, through: 1.0, by: 0.1) { 246 | let lerped = lerp(start: start, end: end, t: CGFloat(t)) 247 | print("\(i), \(t)") 248 | XCTAssertEqualWithAccuracy(lerped.x, expected[i].x, accuracy: 1.0e6) 249 | XCTAssertEqualWithAccuracy(lerped.y, expected[i].y, accuracy: 1.0e6) 250 | i += 1 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /SKTUtils/SKTTimingFunctions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Timing functions for SKTEffects. Based on Robert Penner's easing equations 3 | * http://robertpenner.com/easing/ and https://github.com/warrenm/AHEasing 4 | * 5 | * Copyright (c) 2013-2014 Razeware LLC 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | import Foundation 27 | import CoreGraphics 28 | 29 | public func SKTTimingFunctionLinear(_ t: CGFloat) -> CGFloat { 30 | return t 31 | } 32 | 33 | public func SKTTimingFunctionQuadraticEaseIn(_ t: CGFloat) -> CGFloat { 34 | return t * t 35 | } 36 | 37 | public func SKTTimingFunctionQuadraticEaseOut(_ t: CGFloat) -> CGFloat { 38 | return t * (2.0 - t) 39 | } 40 | 41 | public func SKTTimingFunctionQuadraticEaseInOut(_ t: CGFloat) -> CGFloat { 42 | if t < 0.5 { 43 | return 2.0 * t * t 44 | } else { 45 | let f = t - 1.0 46 | return 1.0 - 2.0 * f * f 47 | } 48 | } 49 | 50 | func SKTTimingFunctionCubicEaseIn(_ t: CGFloat) -> CGFloat { 51 | return t * t * t 52 | } 53 | 54 | func SKTTimingFunctionCubicEaseOut(_ t: CGFloat) -> CGFloat { 55 | let f = t - 1.0 56 | return 1.0 + f * f * f 57 | } 58 | 59 | public func SKTTimingFunctionCubicEaseInOut(_ t: CGFloat) -> CGFloat { 60 | if t < 0.5 { 61 | return 4.0 * t * t * t 62 | } else { 63 | let f = t - 1.0 64 | return 1.0 + 4.0 * f * f * f 65 | } 66 | } 67 | 68 | public func SKTTimingFunctionQuarticEaseIn(_ t: CGFloat) -> CGFloat { 69 | return t * t * t * t 70 | } 71 | 72 | public func SKTTimingFunctionQuarticEaseOut(_ t: CGFloat) -> CGFloat { 73 | let f = t - 1.0 74 | return 1.0 - f * f * f * f 75 | } 76 | 77 | public func SKTTimingFunctionQuarticEaseInOut(_ t: CGFloat) -> CGFloat { 78 | if t < 0.5 { 79 | return 8.0 * t * t * t * t 80 | } else { 81 | let f = t - 1.0 82 | return 1.0 - 8.0 * f * f * f * f 83 | } 84 | } 85 | 86 | public func SKTTimingFunctionQuinticEaseIn(_ t: CGFloat) -> CGFloat { 87 | return t * t * t * t * t 88 | } 89 | 90 | public func SKTTimingFunctionQuinticEaseOut(_ t: CGFloat) -> CGFloat { 91 | let f = t - 1.0 92 | return 1.0 + f * f * f * f * f 93 | } 94 | 95 | func SKTTimingFunctionQuinticEaseInOut(_ t: CGFloat) -> CGFloat { 96 | if t < 0.5 { 97 | return 16.0 * t * t * t * t * t 98 | } else { 99 | let f = t - 1.0 100 | return 1.0 + 16.0 * f * f * f * f * f 101 | } 102 | } 103 | 104 | public func SKTTimingFunctionSineEaseIn(_ t: CGFloat) -> CGFloat { 105 | return sin((t - 1.0) * π/2) + 1.0 106 | } 107 | 108 | public func SKTTimingFunctionSineEaseOut(_ t: CGFloat) -> CGFloat { 109 | return sin(t * π/2) 110 | } 111 | 112 | public func SKTTimingFunctionSineEaseInOut(_ t: CGFloat) -> CGFloat { 113 | return 0.5 * (1.0 - cos(t * π)) 114 | } 115 | 116 | public func SKTTimingFunctionCircularEaseIn(_ t: CGFloat) -> CGFloat { 117 | return 1.0 - sqrt(1.0 - t * t) 118 | } 119 | 120 | public func SKTTimingFunctionCircularEaseOut(_ t: CGFloat) -> CGFloat { 121 | return sqrt((2.0 - t) * t) 122 | } 123 | 124 | public func SKTTimingFunctionCircularEaseInOut(_ t: CGFloat) -> CGFloat { 125 | if t < 0.5 { 126 | return 0.5 * (1.0 - sqrt(1.0 - 4.0 * t * t)) 127 | } else { 128 | return 0.5 * sqrt(-4.0 * t * t + 8.0 * t - 3.0) + 0.5 129 | } 130 | } 131 | 132 | public func SKTTimingFunctionExponentialEaseIn(_ t: CGFloat) -> CGFloat { 133 | return (t == 0.0) ? t : pow(2.0, 10.0 * (t - 1.0)) 134 | } 135 | 136 | public func SKTTimingFunctionExponentialEaseOut(_ t: CGFloat) -> CGFloat { 137 | return (t == 1.0) ? t : 1.0 - pow(2.0, -10.0 * t) 138 | } 139 | 140 | public func SKTTimingFunctionExponentialEaseInOut(_ t: CGFloat) -> CGFloat { 141 | if t == 0.0 || t == 1.0 { 142 | return t 143 | } else if t < 0.5 { 144 | return 0.5 * pow(2.0, 20.0 * t - 10.0) 145 | } else { 146 | return 1.0 - 0.5 * pow(2.0, -20.0 * t + 10.0) 147 | } 148 | } 149 | 150 | public func SKTTimingFunctionElasticEaseIn(_ t: CGFloat) -> CGFloat { 151 | return sin(13.0 * π/2 * t) * pow(2.0, 10.0 * (t - 1.0)) 152 | } 153 | 154 | public func SKTTimingFunctionElasticEaseOut(_ t: CGFloat) -> CGFloat { 155 | return sin(-13.0 * π/2 * (t + 1.0)) * pow(2.0, -10.0 * t) + 1.0 156 | } 157 | 158 | public func SKTTimingFunctionElasticEaseInOut(_ t: CGFloat) -> CGFloat { 159 | if t < 0.5 { 160 | return 0.5 * sin(13.0 * π * t) * pow(2.0, 20.0 * t - 10.0) 161 | } else { 162 | return 0.5 * sin(-13.0 * π * t) * pow(2.0, -20.0 * t + 10.0) + 1.0 163 | } 164 | } 165 | 166 | public func SKTTimingFunctionBackEaseIn(_ t: CGFloat) -> CGFloat { 167 | let s: CGFloat = 1.70158 168 | return ((s + 1.0) * t - s) * t * t 169 | } 170 | 171 | public func SKTTimingFunctionBackEaseOut(_ t: CGFloat) -> CGFloat { 172 | let s: CGFloat = 1.70158 173 | let f = 1.0 - t 174 | return 1.0 - ((s + 1.0) * f - s) * f * f 175 | } 176 | 177 | public func SKTTimingFunctionBackEaseInOut(_ t: CGFloat) -> CGFloat { 178 | let s: CGFloat = 1.70158 179 | if t < 0.5 { 180 | let f = 2.0 * t 181 | return 0.5 * ((s + 1.0) * f - s) * f * f 182 | } else { 183 | let f = 2.0 * (1.0 - t) 184 | return 1.0 - 0.5 * ((s + 1.0) * f - s) * f * f 185 | } 186 | } 187 | 188 | public func SKTTimingFunctionExtremeBackEaseIn(_ t: CGFloat) -> CGFloat { 189 | return (t * t - sin(t * π)) * t 190 | } 191 | 192 | public func SKTTimingFunctionExtremeBackEaseOut(_ t: CGFloat) -> CGFloat { 193 | let f = 1.0 - t 194 | return 1.0 - (f * f - sin(f * π)) * f 195 | } 196 | 197 | public func SKTTimingFunctionExtremeBackEaseInOut(_ t: CGFloat) -> CGFloat { 198 | if t < 0.5 { 199 | let f = 2.0 * t 200 | return 0.5 * (f * f - sin(f * π)) * f 201 | } else { 202 | let f = 2.0 * (1.0 - t) 203 | return 1.0 - 0.5 * (f * f - sin(f * π)) * f 204 | } 205 | } 206 | 207 | public func SKTTimingFunctionBounceEaseIn(_ t: CGFloat) -> CGFloat { 208 | return 1.0 - SKTTimingFunctionBounceEaseOut(1.0 - t) 209 | } 210 | 211 | public func SKTTimingFunctionBounceEaseOut(_ t: CGFloat) -> CGFloat { 212 | if t < 1.0 / 2.75 { 213 | return 7.5625 * t * t 214 | } else if t < 2.0 / 2.75 { 215 | let f = t - 1.5 / 2.75 216 | return 7.5625 * f * f + 0.75 217 | } else if t < 2.5 / 2.75 { 218 | let f = t - 2.25 / 2.75 219 | return 7.5625 * f * f + 0.9375 220 | } else { 221 | let f = t - 2.625 / 2.75 222 | return 7.5625 * f * f + 0.984375 223 | } 224 | } 225 | 226 | public func SKTTimingFunctionBounceEaseInOut(_ t: CGFloat) -> CGFloat { 227 | if t < 0.5 { 228 | return 0.5 * SKTTimingFunctionBounceEaseIn(t * 2.0) 229 | } else { 230 | return 0.5 * SKTTimingFunctionBounceEaseOut(t * 2.0 - 1.0) + 0.5 231 | } 232 | } 233 | 234 | public func SKTTimingFunctionSmoothstep(_ t: CGFloat) -> CGFloat { 235 | return t * t * (3 - 2 * t) 236 | } 237 | 238 | public func SKTCreateShakeFunction(_ oscillations: Int) -> (CGFloat) -> CGFloat { 239 | return {t in -pow(2.0, -10.0 * t) * sin(t * π * CGFloat(oscillations) * 2.0) + 1.0} 240 | } 241 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sprite Kit Utils 2 | 3 | A collection of Sprite Kit helper classes and functions, written in Swift. 4 | 5 | This code was originally written for the book [iOS Games by Tutorials, Second Edition](http://raywenderlich.com/store/ios-games-by-tutorials), which is published through [raywenderlich.com](http://raywenderlich.com). 6 | 7 | ![iOS Games by Tutorials](http://cdn2.raywenderlich.com/wp-content/themes/raywenderlich/images/store/iGT-PDF-phones-640.png "iOS Games by Tutorials") 8 | 9 | SKTUtils requires Xcode 6.1. For the older Objective-C version of SKTUtils, see the [objective-c branch](http://github.com/raywenderlich/SKTUtils/tree/objective-c). 10 | 11 | ## What can SKTUtils do for you? 12 | 13 | It defines handy constants such as `π`. 14 | 15 | It extends `CGPoint` and `CGVector` so you can do: 16 | 17 | ```swift 18 | let pt1 = CGPoint(x: 10, y: 20) 19 | let pt2 = CGPoint(x: -5, y: 0) 20 | let pt3 = pt1 + pt2 21 | let pt4 = pt3 * 100 22 | println("Point has length \(pt4.length())") 23 | let pt5 = pt4.normalized() 24 | let dist = pt1.distanceTo(pt2) 25 | ``` 26 | 27 | It adds handy functions to `Int` and `Float`: 28 | 29 | ```swift 30 | let x = 100 31 | let y = x.clamped(10...50) 32 | let z = Int.random(20..30) 33 | 34 | let r = 180.degreesToRadians() 35 | let d = π.radiansToDegrees() 36 | ``` 37 | 38 | It extends various Sprite Kit classes with convenience methods, such as: 39 | 40 | ```swift 41 | let color = SKColor(red: 255, green: 128, blue: 64) 42 | let action = SKAction.afterDelay(2.0, runBlock: { /* your code here */ }) 43 | ``` 44 | 45 | And much more... including `SKTEffects`, which lets you make your games much more [juicy](http://bitly.com/juice-it)! 46 | 47 | ## Introducting SKTEffects 48 | 49 | Sprite Kit has a handy feature named *actions* that make it really easy to move, rotate and scale your sprites. However, a big downside is the omission of timing functions beyond the standard *ease in* and *ease out*. The `SKTEffects` classes from this package add support for many more easing functions to Sprite Kit. 50 | 51 | Note: The iOS 8 version of Sprite Kit includes an `SKAction.timingFunction` property, but unfortunately it is [pretty useless](https://openradar.appspot.com/radar?id=6464265753985024). It's a step in the right direction, but it still won't let you perform the kinds of effects that make games juicy. 52 | 53 | It lets you do things like this with just a few lines of code: 54 | 55 | ![The demo app](Examples/Effects/Demo.gif) 56 | 57 | The only reason `SKTEffects` exists is because `SKAction` does not allow arbitrary timing functions, only standard ease-in and ease-out. The `SKTEffect` subclasses are re-implementations of what `SKAction` already does but with the addition of custom timing functions. It's a bit of a roundabout way of achieving something that really should have been built into Sprite Kit. 58 | 59 | There are currently three `SKTEffect` subclasses: 60 | 61 | - `SKTMoveEffect` 62 | - `SKTRotateEffect` 63 | - `SKTScaleEffect` 64 | 65 | You use them like this: 66 | 67 | ```swift 68 | let moveEffect = SKTMoveEffect(node: node, duration: 1.0, startPosition: startPoint, endPosition: endPoint) 69 | 70 | moveEffect.timingFunction = SKTTimingFunctionBounceEaseOut 71 | 72 | node.runAction(SKAction.actionWithEffect(moveEffect)) 73 | ``` 74 | 75 | First you create the `SKTMoveEffect` object and pass it the node that it should animate, the duration of the animation in seconds, and the starting and ending position of the node. 76 | 77 | Then you (optionally) set the timing function on the effect object. You can use the supplied timing functions -- for example, elastic, bounce, and many others -- or create your own. See **SKTTimingFunctions.swift** for a complete list. 78 | 79 | Finally, you wrap the effect object inside a regular `SKAction` and run that action on the node. 80 | 81 | The process for `SKTRotateEffect` and `SKTScaleEffect` is identical, but you specify rotation angles and scale vectors, respectively. 82 | 83 | You can combine multiple effects at the same time, e.g. have more than one scale effect going at once on the same node. 84 | 85 | ### Warning about SKTScaleEffect 86 | 87 | IMPORTANT: When using `SKTScaleEffect`, the node that you're scaling must *not* have a physics body, otherwise the physics body gets scaled as well and collision detection becomes unpredictable (objects may suddenly move through other objects). 88 | 89 | To solve this, make a new `SKNode`, give it the physics body, and add the node that you're scaling as a child node. 90 | 91 | ### Caveats 92 | 93 | Currently there is no "relative" version of the effects. You always have to supply an absolute starting and ending position, rotation angle, or scale. Most of the time this is no big deal, but it does mean you cannot put them into repeating actions. 94 | 95 | For example, the demo project does the following to rotate a node every second by 45 degrees: 96 | 97 | ```swift 98 | node.runAction(SKAction.repeatActionForever(SKAction.sequence([ 99 | SKAction.waitForDuration(0.75), 100 | SKAction.runBlock { 101 | let effect = SKTRotateEffect(node: node, duration: 0.25, startAngle: node.zRotation, endAngle: node.zRotation + π/4) 102 | 103 | effect.timingFunction = SKTTimingFunctionBackEaseInOut 104 | 105 | node.runAction(SKAction.actionWithEffect(effect)) 106 | }]))) 107 | ``` 108 | 109 | If the effects had a relative version, this could have simply been written as: 110 | 111 | ```swift 112 | let effect = SKTRotateEffect(node: node, duration: 0.25, byAngle: π/4) 113 | 114 | effect.timingFunction = SKTTimingFunctionBackEaseInOut 115 | 116 | node.runAction(SKAction.repeatActionForever(SKAction.sequence([ 117 | SKAction.waitForDuration(0.75), 118 | SKAction.actionWithEffect(effect) 119 | ]))) 120 | ``` 121 | 122 | Not only is this simpler to read, it also saves you from having to create a new effect instance for every repetition. However, this doesn't work in the current version of the library. 123 | 124 | Effects keep state (unlike `SKActions`), so you should not reuse the same effect instance in multiple actions. 125 | 126 | If you use a lot of effects over a long period of time, you may run into memory fragmentation problems, because you need to allocate a new object for every effect. Currently, effects cannot be reset, so it's tricky to put them into an object pool and reuse them. 127 | 128 | Because actions keep state, you cannot put them into an action after a delay if the node also moves in the mean time. In other words, doing the following may or may not work: 129 | 130 | ```swift 131 | let effect = SKTMoveEffect() 132 | 133 | let action = SKAction.sequence([ 134 | SKAction.waitForDuration(5.0), 135 | SKAction.actionWithEffect(effect) 136 | ]) 137 | ``` 138 | 139 | If the node has moved during the delay, either through another `SKAction`, physics, or the app changing the node's `position` property, then the effect will start in the wrong place. 140 | 141 | ### Let's get SKTEffects included in Sprite Kit! 142 | 143 | If you think custom timing functions are an important feature to have built into Sprite Kit, then go to [bugreport.apple.com](http://bugreport.apple.com]) and [duplicate this feature request](https://openradar.appspot.com/radar?id=5910148803461120). The more they receive, the better! 144 | 145 | ## The demo app 146 | 147 | The **Examples/Effects** folder contains a little demo project that shows how to do animations with more interesting timing functions. This app uses physics to move the balls and detect collisions. 148 | 149 | It has the following special effects: 150 | 151 | - The objects appear with an animation when the game starts 152 | - Screen shake on collisions 153 | - Screen rotate on collisions, for extra shaky goodness! 154 | - Screen zoom on collisions 155 | - Color glitch (flashing background color) 156 | - Ball scales up on collisions 157 | - Ball smoothly rotates in the direction it is flying 158 | - "Jelly" effect on the obstacles on collisions 159 | - And more... 160 | 161 | Most of these effects are cumulative; i.e. if there are several collisions in quick succession, then the screen shake movement is the sum of these hits. 162 | 163 | All the fun happens in **MyScene.swift**. There are several `let` statements at the top that let you turn effects on or off. 164 | 165 | Tap the screen to add a random impulse to the balls. 166 | 167 | ## Playground 168 | 169 | The **Examples/Playground** folder contains an Xcode workspace with a Playground. To use this, 170 | 171 | 1. Open **SKTUtils.xcworkspace** in Xcode. 172 | 2. Press **Command+B** to build the SKTUtils module -- this is important! 173 | 3. Open **MyPlayground.playground** and start messing around. 174 | 4. Press **Option+Command+Enter** to open the Assistant Editor so you can see the output. 175 | 176 | Have fun playing with SKTUtils! 177 | 178 | ## Unit tests 179 | 180 | The **Examples/Tests** folder contains an Xcode project with unit tests for SKTUtils. Press Command+U to run the tests. 181 | -------------------------------------------------------------------------------- /Examples/Playground/SKTUtils.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7B0F9F6B19C43F1700538BC0 /* SKTUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B0F9F6919C43F1700538BC0 /* SKTUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 7B0F9F7A19C43F3200538BC0 /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F6E19C43F3200538BC0 /* CGFloat+Extensions.swift */; }; 12 | 7B0F9F7B19C43F3200538BC0 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F6F19C43F3200538BC0 /* CGPoint+Extensions.swift */; }; 13 | 7B0F9F7C19C43F3200538BC0 /* CGVector+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7019C43F3200538BC0 /* CGVector+Extensions.swift */; }; 14 | 7B0F9F7D19C43F3200538BC0 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7119C43F3200538BC0 /* Int+Extensions.swift */; }; 15 | 7B0F9F7E19C43F3200538BC0 /* SKAction+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7219C43F3200538BC0 /* SKAction+Extensions.swift */; }; 16 | 7B0F9F7F19C43F3200538BC0 /* SKAction+SpecialEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7319C43F3200538BC0 /* SKAction+SpecialEffects.swift */; }; 17 | 7B0F9F8019C43F3200538BC0 /* SKColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7419C43F3200538BC0 /* SKColor+Extensions.swift */; }; 18 | 7B0F9F8119C43F3200538BC0 /* SKNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7519C43F3200538BC0 /* SKNode+Extensions.swift */; }; 19 | 7B0F9F8219C43F3200538BC0 /* SKTAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7619C43F3200538BC0 /* SKTAudio.swift */; }; 20 | 7B0F9F8319C43F3200538BC0 /* SKTEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7719C43F3200538BC0 /* SKTEffects.swift */; }; 21 | 7B0F9F8419C43F3200538BC0 /* SKTTimingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7819C43F3200538BC0 /* SKTTimingFunctions.swift */; }; 22 | 7B0F9F8519C43F3200538BC0 /* Vector3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9F7919C43F3200538BC0 /* Vector3.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 481EEDB819B3AF5500469716 /* SKTUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SKTUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 481EEDC319B3AF5500469716 /* SKTUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SKTUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 7B0F9F6719C43F1700538BC0 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 7B0F9F6819C43F1700538BC0 /* MyPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = MyPlayground.playground; sourceTree = ""; }; 30 | 7B0F9F6919C43F1700538BC0 /* SKTUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKTUtils.h; sourceTree = ""; }; 31 | 7B0F9F6E19C43F3200538BC0 /* CGFloat+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Extensions.swift"; sourceTree = ""; }; 32 | 7B0F9F6F19C43F3200538BC0 /* CGPoint+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint+Extensions.swift"; sourceTree = ""; }; 33 | 7B0F9F7019C43F3200538BC0 /* CGVector+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGVector+Extensions.swift"; sourceTree = ""; }; 34 | 7B0F9F7119C43F3200538BC0 /* Int+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Extensions.swift"; sourceTree = ""; }; 35 | 7B0F9F7219C43F3200538BC0 /* SKAction+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKAction+Extensions.swift"; sourceTree = ""; }; 36 | 7B0F9F7319C43F3200538BC0 /* SKAction+SpecialEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKAction+SpecialEffects.swift"; sourceTree = ""; }; 37 | 7B0F9F7419C43F3200538BC0 /* SKColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKColor+Extensions.swift"; sourceTree = ""; }; 38 | 7B0F9F7519C43F3200538BC0 /* SKNode+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKNode+Extensions.swift"; sourceTree = ""; }; 39 | 7B0F9F7619C43F3200538BC0 /* SKTAudio.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTAudio.swift; sourceTree = ""; }; 40 | 7B0F9F7719C43F3200538BC0 /* SKTEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTEffects.swift; sourceTree = ""; }; 41 | 7B0F9F7819C43F3200538BC0 /* SKTTimingFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTTimingFunctions.swift; sourceTree = ""; }; 42 | 7B0F9F7919C43F3200538BC0 /* Vector3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vector3.swift; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 481EEDB419B3AF5500469716 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | 481EEDC019B3AF5500469716 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXFrameworksBuildPhase section */ 61 | 62 | /* Begin PBXGroup section */ 63 | 481EEDAE19B3AF5500469716 = { 64 | isa = PBXGroup; 65 | children = ( 66 | 7B0F9F6819C43F1700538BC0 /* MyPlayground.playground */, 67 | 7B0F9F6D19C43F3200538BC0 /* SKTUtils */, 68 | 7B0F9F6C19C43F2000538BC0 /* Supporting Files */, 69 | 481EEDB919B3AF5500469716 /* Products */, 70 | ); 71 | sourceTree = ""; 72 | }; 73 | 481EEDB919B3AF5500469716 /* Products */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 481EEDB819B3AF5500469716 /* SKTUtils.framework */, 77 | 481EEDC319B3AF5500469716 /* SKTUtilsTests.xctest */, 78 | ); 79 | name = Products; 80 | sourceTree = ""; 81 | }; 82 | 7B0F9F6C19C43F2000538BC0 /* Supporting Files */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 7B0F9F6919C43F1700538BC0 /* SKTUtils.h */, 86 | 7B0F9F6719C43F1700538BC0 /* Info.plist */, 87 | ); 88 | name = "Supporting Files"; 89 | sourceTree = ""; 90 | }; 91 | 7B0F9F6D19C43F3200538BC0 /* SKTUtils */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 7B0F9F6E19C43F3200538BC0 /* CGFloat+Extensions.swift */, 95 | 7B0F9F6F19C43F3200538BC0 /* CGPoint+Extensions.swift */, 96 | 7B0F9F7019C43F3200538BC0 /* CGVector+Extensions.swift */, 97 | 7B0F9F7119C43F3200538BC0 /* Int+Extensions.swift */, 98 | 7B0F9F7219C43F3200538BC0 /* SKAction+Extensions.swift */, 99 | 7B0F9F7319C43F3200538BC0 /* SKAction+SpecialEffects.swift */, 100 | 7B0F9F7419C43F3200538BC0 /* SKColor+Extensions.swift */, 101 | 7B0F9F7519C43F3200538BC0 /* SKNode+Extensions.swift */, 102 | 7B0F9F7619C43F3200538BC0 /* SKTAudio.swift */, 103 | 7B0F9F7719C43F3200538BC0 /* SKTEffects.swift */, 104 | 7B0F9F7819C43F3200538BC0 /* SKTTimingFunctions.swift */, 105 | 7B0F9F7919C43F3200538BC0 /* Vector3.swift */, 106 | ); 107 | name = SKTUtils; 108 | path = ../../SKTUtils; 109 | sourceTree = ""; 110 | }; 111 | /* End PBXGroup section */ 112 | 113 | /* Begin PBXHeadersBuildPhase section */ 114 | 481EEDB519B3AF5500469716 /* Headers */ = { 115 | isa = PBXHeadersBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | 7B0F9F6B19C43F1700538BC0 /* SKTUtils.h in Headers */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXHeadersBuildPhase section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | 481EEDB719B3AF5500469716 /* SKTUtils */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 481EEDCB19B3AF5500469716 /* Build configuration list for PBXNativeTarget "SKTUtils" */; 128 | buildPhases = ( 129 | 481EEDB319B3AF5500469716 /* Sources */, 130 | 481EEDB419B3AF5500469716 /* Frameworks */, 131 | 481EEDB519B3AF5500469716 /* Headers */, 132 | 481EEDB619B3AF5500469716 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = SKTUtils; 139 | productName = SKTUtils; 140 | productReference = 481EEDB819B3AF5500469716 /* SKTUtils.framework */; 141 | productType = "com.apple.product-type.framework"; 142 | }; 143 | 481EEDC219B3AF5500469716 /* SKTUtilsTests */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = 481EEDCE19B3AF5500469716 /* Build configuration list for PBXNativeTarget "SKTUtilsTests" */; 146 | buildPhases = ( 147 | 481EEDBF19B3AF5500469716 /* Sources */, 148 | 481EEDC019B3AF5500469716 /* Frameworks */, 149 | 481EEDC119B3AF5500469716 /* Resources */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = SKTUtilsTests; 156 | productName = SKTUtilsTests; 157 | productReference = 481EEDC319B3AF5500469716 /* SKTUtilsTests.xctest */; 158 | productType = "com.apple.product-type.bundle.unit-test"; 159 | }; 160 | /* End PBXNativeTarget section */ 161 | 162 | /* Begin PBXProject section */ 163 | 481EEDAF19B3AF5500469716 /* Project object */ = { 164 | isa = PBXProject; 165 | attributes = { 166 | LastSwiftUpdateCheck = 0700; 167 | LastUpgradeCheck = 0800; 168 | ORGANIZATIONNAME = raywenderlich; 169 | TargetAttributes = { 170 | 481EEDB719B3AF5500469716 = { 171 | CreatedOnToolsVersion = 6.0; 172 | DevelopmentTeam = KFCNEC27GU; 173 | DevelopmentTeamName = "Razeware LLC"; 174 | LastSwiftMigration = 0800; 175 | }; 176 | 481EEDC219B3AF5500469716 = { 177 | CreatedOnToolsVersion = 6.0; 178 | DevelopmentTeam = KFCNEC27GU; 179 | DevelopmentTeamName = "Razeware LLC"; 180 | }; 181 | }; 182 | }; 183 | buildConfigurationList = 481EEDB219B3AF5500469716 /* Build configuration list for PBXProject "SKTUtils" */; 184 | compatibilityVersion = "Xcode 3.2"; 185 | developmentRegion = English; 186 | hasScannedForEncodings = 0; 187 | knownRegions = ( 188 | en, 189 | ); 190 | mainGroup = 481EEDAE19B3AF5500469716; 191 | productRefGroup = 481EEDB919B3AF5500469716 /* Products */; 192 | projectDirPath = ""; 193 | projectRoot = ""; 194 | targets = ( 195 | 481EEDB719B3AF5500469716 /* SKTUtils */, 196 | 481EEDC219B3AF5500469716 /* SKTUtilsTests */, 197 | ); 198 | }; 199 | /* End PBXProject section */ 200 | 201 | /* Begin PBXResourcesBuildPhase section */ 202 | 481EEDB619B3AF5500469716 /* Resources */ = { 203 | isa = PBXResourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | 481EEDC119B3AF5500469716 /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXResourcesBuildPhase section */ 217 | 218 | /* Begin PBXSourcesBuildPhase section */ 219 | 481EEDB319B3AF5500469716 /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 7B0F9F7B19C43F3200538BC0 /* CGPoint+Extensions.swift in Sources */, 224 | 7B0F9F8319C43F3200538BC0 /* SKTEffects.swift in Sources */, 225 | 7B0F9F8419C43F3200538BC0 /* SKTTimingFunctions.swift in Sources */, 226 | 7B0F9F8119C43F3200538BC0 /* SKNode+Extensions.swift in Sources */, 227 | 7B0F9F7E19C43F3200538BC0 /* SKAction+Extensions.swift in Sources */, 228 | 7B0F9F8219C43F3200538BC0 /* SKTAudio.swift in Sources */, 229 | 7B0F9F8019C43F3200538BC0 /* SKColor+Extensions.swift in Sources */, 230 | 7B0F9F7F19C43F3200538BC0 /* SKAction+SpecialEffects.swift in Sources */, 231 | 7B0F9F7D19C43F3200538BC0 /* Int+Extensions.swift in Sources */, 232 | 7B0F9F7A19C43F3200538BC0 /* CGFloat+Extensions.swift in Sources */, 233 | 7B0F9F8519C43F3200538BC0 /* Vector3.swift in Sources */, 234 | 7B0F9F7C19C43F3200538BC0 /* CGVector+Extensions.swift in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | 481EEDBF19B3AF5500469716 /* Sources */ = { 239 | isa = PBXSourcesBuildPhase; 240 | buildActionMask = 2147483647; 241 | files = ( 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXSourcesBuildPhase section */ 246 | 247 | /* Begin XCBuildConfiguration section */ 248 | 481EEDC919B3AF5500469716 /* Debug */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_ENABLE_MODULES = YES; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_WARN_BOOL_CONVERSION = YES; 257 | CLANG_WARN_CONSTANT_CONVERSION = YES; 258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | CURRENT_PROJECT_VERSION = 1; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | ENABLE_TESTABILITY = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu99; 271 | GCC_DYNAMIC_NO_PIC = NO; 272 | GCC_NO_COMMON_BLOCKS = YES; 273 | GCC_OPTIMIZATION_LEVEL = 0; 274 | GCC_PREPROCESSOR_DEFINITIONS = ( 275 | "DEBUG=1", 276 | "$(inherited)", 277 | ); 278 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 286 | MTL_ENABLE_DEBUG_INFO = YES; 287 | ONLY_ACTIVE_ARCH = YES; 288 | SDKROOT = iphoneos; 289 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 290 | TARGETED_DEVICE_FAMILY = "1,2"; 291 | VERSIONING_SYSTEM = "apple-generic"; 292 | VERSION_INFO_PREFIX = ""; 293 | }; 294 | name = Debug; 295 | }; 296 | 481EEDCA19B3AF5500469716 /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_CONSTANT_CONVERSION = YES; 306 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INT_CONVERSION = YES; 310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 311 | CLANG_WARN_UNREACHABLE_CODE = YES; 312 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 313 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 314 | COPY_PHASE_STRIP = YES; 315 | CURRENT_PROJECT_VERSION = 1; 316 | ENABLE_NS_ASSERTIONS = NO; 317 | ENABLE_STRICT_OBJC_MSGSEND = YES; 318 | GCC_C_LANGUAGE_STANDARD = gnu99; 319 | GCC_NO_COMMON_BLOCKS = YES; 320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 321 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 322 | GCC_WARN_UNDECLARED_SELECTOR = YES; 323 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 324 | GCC_WARN_UNUSED_FUNCTION = YES; 325 | GCC_WARN_UNUSED_VARIABLE = YES; 326 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 327 | MTL_ENABLE_DEBUG_INFO = NO; 328 | SDKROOT = iphoneos; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | VALIDATE_PRODUCT = YES; 331 | VERSIONING_SYSTEM = "apple-generic"; 332 | VERSION_INFO_PREFIX = ""; 333 | }; 334 | name = Release; 335 | }; 336 | 481EEDCC19B3AF5500469716 /* Debug */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | CLANG_ENABLE_MODULES = YES; 340 | DEFINES_MODULE = YES; 341 | DYLIB_COMPATIBILITY_VERSION = 1; 342 | DYLIB_CURRENT_VERSION = 1; 343 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 344 | INFOPLIST_FILE = Info.plist; 345 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 346 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 347 | PRODUCT_BUNDLE_IDENTIFIER = "org.raywenderlich.$(PRODUCT_NAME:rfc1034identifier)"; 348 | PRODUCT_NAME = "$(TARGET_NAME)"; 349 | SKIP_INSTALL = YES; 350 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 351 | SWIFT_VERSION = 3.0; 352 | }; 353 | name = Debug; 354 | }; 355 | 481EEDCD19B3AF5500469716 /* Release */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | CLANG_ENABLE_MODULES = YES; 359 | DEFINES_MODULE = YES; 360 | DYLIB_COMPATIBILITY_VERSION = 1; 361 | DYLIB_CURRENT_VERSION = 1; 362 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 363 | INFOPLIST_FILE = Info.plist; 364 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 365 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 366 | PRODUCT_BUNDLE_IDENTIFIER = "org.raywenderlich.$(PRODUCT_NAME:rfc1034identifier)"; 367 | PRODUCT_NAME = "$(TARGET_NAME)"; 368 | SKIP_INSTALL = YES; 369 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 370 | SWIFT_VERSION = 3.0; 371 | }; 372 | name = Release; 373 | }; 374 | 481EEDCF19B3AF5500469716 /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | FRAMEWORK_SEARCH_PATHS = ( 378 | "$(SDKROOT)/Developer/Library/Frameworks", 379 | "$(inherited)", 380 | ); 381 | GCC_PREPROCESSOR_DEFINITIONS = ( 382 | "DEBUG=1", 383 | "$(inherited)", 384 | ); 385 | INFOPLIST_FILE = SKTUtilsTests/Info.plist; 386 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 387 | PRODUCT_NAME = "$(TARGET_NAME)"; 388 | }; 389 | name = Debug; 390 | }; 391 | 481EEDD019B3AF5500469716 /* Release */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | FRAMEWORK_SEARCH_PATHS = ( 395 | "$(SDKROOT)/Developer/Library/Frameworks", 396 | "$(inherited)", 397 | ); 398 | INFOPLIST_FILE = SKTUtilsTests/Info.plist; 399 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | }; 402 | name = Release; 403 | }; 404 | /* End XCBuildConfiguration section */ 405 | 406 | /* Begin XCConfigurationList section */ 407 | 481EEDB219B3AF5500469716 /* Build configuration list for PBXProject "SKTUtils" */ = { 408 | isa = XCConfigurationList; 409 | buildConfigurations = ( 410 | 481EEDC919B3AF5500469716 /* Debug */, 411 | 481EEDCA19B3AF5500469716 /* Release */, 412 | ); 413 | defaultConfigurationIsVisible = 0; 414 | defaultConfigurationName = Release; 415 | }; 416 | 481EEDCB19B3AF5500469716 /* Build configuration list for PBXNativeTarget "SKTUtils" */ = { 417 | isa = XCConfigurationList; 418 | buildConfigurations = ( 419 | 481EEDCC19B3AF5500469716 /* Debug */, 420 | 481EEDCD19B3AF5500469716 /* Release */, 421 | ); 422 | defaultConfigurationIsVisible = 0; 423 | defaultConfigurationName = Release; 424 | }; 425 | 481EEDCE19B3AF5500469716 /* Build configuration list for PBXNativeTarget "SKTUtilsTests" */ = { 426 | isa = XCConfigurationList; 427 | buildConfigurations = ( 428 | 481EEDCF19B3AF5500469716 /* Debug */, 429 | 481EEDD019B3AF5500469716 /* Release */, 430 | ); 431 | defaultConfigurationIsVisible = 0; 432 | defaultConfigurationName = Release; 433 | }; 434 | /* End XCConfigurationList section */ 435 | }; 436 | rootObject = 481EEDAF19B3AF5500469716 /* Project object */; 437 | } 438 | -------------------------------------------------------------------------------- /Examples/Effects/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3558F7521D21A7E200AC8ABC /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3558F7511D21A7E200AC8ABC /* Launch Screen.storyboard */; }; 11 | 7B0610101945ED26002D27B1 /* SKAction+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B06100A1945ED26002D27B1 /* SKAction+Extensions.swift */; }; 12 | 7B0610121945ED26002D27B1 /* SKAction+SpecialEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B06100B1945ED26002D27B1 /* SKAction+SpecialEffects.swift */; }; 13 | 7B0610141945ED26002D27B1 /* SKNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B06100C1945ED26002D27B1 /* SKNode+Extensions.swift */; }; 14 | 7B0610161945ED26002D27B1 /* SKTEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B06100D1945ED26002D27B1 /* SKTEffects.swift */; }; 15 | 7B0610181945ED26002D27B1 /* SKTTimingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B06100E1945ED26002D27B1 /* SKTTimingFunctions.swift */; }; 16 | 7B06101A1945ED26002D27B1 /* SKColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B06100F1945ED26002D27B1 /* SKColor+Extensions.swift */; }; 17 | 7B2A707E1945FA2E00585C2D /* MyScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2A707D1945FA2E00585C2D /* MyScene.swift */; }; 18 | 7B3D97251948634200D0AB26 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D97241948634200D0AB26 /* ViewController.swift */; }; 19 | 7B3D9727194864E600D0AB26 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D9726194864E600D0AB26 /* AppDelegate.swift */; }; 20 | 7B3D972B19486C2A00D0AB26 /* SKTAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3D972A19486C2A00D0AB26 /* SKTAudio.swift */; }; 21 | 7B77E69219544FFA00DB0E4D /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B77E68D19544FFA00DB0E4D /* CGPoint+Extensions.swift */; }; 22 | 7B77E69419544FFA00DB0E4D /* CGVector+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B77E68E19544FFA00DB0E4D /* CGVector+Extensions.swift */; }; 23 | 7B77E69619544FFA00DB0E4D /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B77E68F19544FFA00DB0E4D /* CGFloat+Extensions.swift */; }; 24 | 7B77E69819544FFA00DB0E4D /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B77E69019544FFA00DB0E4D /* Int+Extensions.swift */; }; 25 | 7B77E69A19544FFA00DB0E4D /* Vector3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B77E69119544FFA00DB0E4D /* Vector3.swift */; }; 26 | 7BAE7852182FBA95009B4DA0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BAE7851182FBA95009B4DA0 /* Foundation.framework */; }; 27 | 7BAE7854182FBA95009B4DA0 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BAE7853182FBA95009B4DA0 /* CoreGraphics.framework */; }; 28 | 7BAE7856182FBA95009B4DA0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BAE7855182FBA95009B4DA0 /* UIKit.framework */; }; 29 | 7BAE7858182FBA95009B4DA0 /* SpriteKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BAE7857182FBA95009B4DA0 /* SpriteKit.framework */; }; 30 | 7BAE785E182FBA95009B4DA0 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7BAE785C182FBA95009B4DA0 /* InfoPlist.strings */; }; 31 | 7BAE7867182FBA95009B4DA0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7BAE7865182FBA95009B4DA0 /* Main.storyboard */; }; 32 | 7BAE7871182FBA95009B4DA0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7BAE7870182FBA95009B4DA0 /* Images.xcassets */; }; 33 | 7BAE7890182FBBB6009B4DA0 /* Ball.png in Resources */ = {isa = PBXBuildFile; fileRef = 7BAE788E182FBBB6009B4DA0 /* Ball.png */; }; 34 | 7BAE7891182FBBB6009B4DA0 /* Ball@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7BAE788F182FBBB6009B4DA0 /* Ball@2x.png */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 3558F7511D21A7E200AC8ABC /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; 39 | 7B06100A1945ED26002D27B1 /* SKAction+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKAction+Extensions.swift"; sourceTree = ""; }; 40 | 7B06100B1945ED26002D27B1 /* SKAction+SpecialEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKAction+SpecialEffects.swift"; sourceTree = ""; }; 41 | 7B06100C1945ED26002D27B1 /* SKNode+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKNode+Extensions.swift"; sourceTree = ""; }; 42 | 7B06100D1945ED26002D27B1 /* SKTEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTEffects.swift; sourceTree = ""; }; 43 | 7B06100E1945ED26002D27B1 /* SKTTimingFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTTimingFunctions.swift; sourceTree = ""; }; 44 | 7B06100F1945ED26002D27B1 /* SKColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKColor+Extensions.swift"; sourceTree = ""; }; 45 | 7B2A707D1945FA2E00585C2D /* MyScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyScene.swift; sourceTree = ""; }; 46 | 7B3D97241948634200D0AB26 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 47 | 7B3D9726194864E600D0AB26 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 48 | 7B3D972A19486C2A00D0AB26 /* SKTAudio.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTAudio.swift; sourceTree = ""; }; 49 | 7B77E68D19544FFA00DB0E4D /* CGPoint+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint+Extensions.swift"; sourceTree = ""; }; 50 | 7B77E68E19544FFA00DB0E4D /* CGVector+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGVector+Extensions.swift"; sourceTree = ""; }; 51 | 7B77E68F19544FFA00DB0E4D /* CGFloat+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Extensions.swift"; sourceTree = ""; }; 52 | 7B77E69019544FFA00DB0E4D /* Int+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Extensions.swift"; sourceTree = ""; }; 53 | 7B77E69119544FFA00DB0E4D /* Vector3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vector3.swift; sourceTree = ""; }; 54 | 7BAE784E182FBA95009B4DA0 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 7BAE7851182FBA95009B4DA0 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 56 | 7BAE7853182FBA95009B4DA0 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 57 | 7BAE7855182FBA95009B4DA0 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 58 | 7BAE7857182FBA95009B4DA0 /* SpriteKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SpriteKit.framework; path = System/Library/Frameworks/SpriteKit.framework; sourceTree = SDKROOT; }; 59 | 7BAE785B182FBA95009B4DA0 /* Demo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Demo-Info.plist"; sourceTree = ""; }; 60 | 7BAE785D182FBA95009B4DA0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 61 | 7BAE7866182FBA95009B4DA0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 62 | 7BAE7870182FBA95009B4DA0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 63 | 7BAE7877182FBA95009B4DA0 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 64 | 7BAE788E182FBBB6009B4DA0 /* Ball.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Ball.png; sourceTree = ""; }; 65 | 7BAE788F182FBBB6009B4DA0 /* Ball@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Ball@2x.png"; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 7BAE784B182FBA95009B4DA0 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 7BAE7854182FBA95009B4DA0 /* CoreGraphics.framework in Frameworks */, 74 | 7BAE7856182FBA95009B4DA0 /* UIKit.framework in Frameworks */, 75 | 7BAE7858182FBA95009B4DA0 /* SpriteKit.framework in Frameworks */, 76 | 7BAE7852182FBA95009B4DA0 /* Foundation.framework in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | 7BAE7845182FBA95009B4DA0 = { 84 | isa = PBXGroup; 85 | children = ( 86 | 7BAE7859182FBA95009B4DA0 /* Demo */, 87 | 7BCC2E14183266B300E5DA2D /* SKTUtils */, 88 | 7BAE788D182FBBB6009B4DA0 /* Sprites */, 89 | 7BAE7850182FBA95009B4DA0 /* Frameworks */, 90 | 7BAE784F182FBA95009B4DA0 /* Products */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 7BAE784F182FBA95009B4DA0 /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 7BAE784E182FBA95009B4DA0 /* Demo.app */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | 7BAE7850182FBA95009B4DA0 /* Frameworks */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 7BAE7851182FBA95009B4DA0 /* Foundation.framework */, 106 | 7BAE7853182FBA95009B4DA0 /* CoreGraphics.framework */, 107 | 7BAE7855182FBA95009B4DA0 /* UIKit.framework */, 108 | 7BAE7857182FBA95009B4DA0 /* SpriteKit.framework */, 109 | 7BAE7877182FBA95009B4DA0 /* XCTest.framework */, 110 | ); 111 | name = Frameworks; 112 | sourceTree = ""; 113 | }; 114 | 7BAE7859182FBA95009B4DA0 /* Demo */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 7B3D9726194864E600D0AB26 /* AppDelegate.swift */, 118 | 7B3D97241948634200D0AB26 /* ViewController.swift */, 119 | 7B2A707D1945FA2E00585C2D /* MyScene.swift */, 120 | 7BAE7870182FBA95009B4DA0 /* Images.xcassets */, 121 | 7BAE7865182FBA95009B4DA0 /* Main.storyboard */, 122 | 7BAE785A182FBA95009B4DA0 /* Supporting Files */, 123 | 3558F7511D21A7E200AC8ABC /* Launch Screen.storyboard */, 124 | ); 125 | path = Demo; 126 | sourceTree = ""; 127 | }; 128 | 7BAE785A182FBA95009B4DA0 /* Supporting Files */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 7BAE785B182FBA95009B4DA0 /* Demo-Info.plist */, 132 | 7BAE785C182FBA95009B4DA0 /* InfoPlist.strings */, 133 | ); 134 | name = "Supporting Files"; 135 | sourceTree = ""; 136 | }; 137 | 7BAE788D182FBBB6009B4DA0 /* Sprites */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 7BAE788E182FBBB6009B4DA0 /* Ball.png */, 141 | 7BAE788F182FBBB6009B4DA0 /* Ball@2x.png */, 142 | ); 143 | path = Sprites; 144 | sourceTree = ""; 145 | }; 146 | 7BCC2E14183266B300E5DA2D /* SKTUtils */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 7B77E68F19544FFA00DB0E4D /* CGFloat+Extensions.swift */, 150 | 7B77E68D19544FFA00DB0E4D /* CGPoint+Extensions.swift */, 151 | 7B77E68E19544FFA00DB0E4D /* CGVector+Extensions.swift */, 152 | 7B77E69019544FFA00DB0E4D /* Int+Extensions.swift */, 153 | 7B06100A1945ED26002D27B1 /* SKAction+Extensions.swift */, 154 | 7B06100B1945ED26002D27B1 /* SKAction+SpecialEffects.swift */, 155 | 7B06100F1945ED26002D27B1 /* SKColor+Extensions.swift */, 156 | 7B06100C1945ED26002D27B1 /* SKNode+Extensions.swift */, 157 | 7B3D972A19486C2A00D0AB26 /* SKTAudio.swift */, 158 | 7B06100D1945ED26002D27B1 /* SKTEffects.swift */, 159 | 7B06100E1945ED26002D27B1 /* SKTTimingFunctions.swift */, 160 | 7B77E69119544FFA00DB0E4D /* Vector3.swift */, 161 | ); 162 | name = SKTUtils; 163 | path = ../../SKTUtils; 164 | sourceTree = ""; 165 | }; 166 | /* End PBXGroup section */ 167 | 168 | /* Begin PBXNativeTarget section */ 169 | 7BAE784D182FBA95009B4DA0 /* Demo */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = 7BAE7887182FBA95009B4DA0 /* Build configuration list for PBXNativeTarget "Demo" */; 172 | buildPhases = ( 173 | 7BAE784A182FBA95009B4DA0 /* Sources */, 174 | 7BAE784B182FBA95009B4DA0 /* Frameworks */, 175 | 7BAE784C182FBA95009B4DA0 /* Resources */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | ); 181 | name = Demo; 182 | productName = Demo; 183 | productReference = 7BAE784E182FBA95009B4DA0 /* Demo.app */; 184 | productType = "com.apple.product-type.application"; 185 | }; 186 | /* End PBXNativeTarget section */ 187 | 188 | /* Begin PBXProject section */ 189 | 7BAE7846182FBA95009B4DA0 /* Project object */ = { 190 | isa = PBXProject; 191 | attributes = { 192 | LastSwiftMigration = 0700; 193 | LastSwiftUpdateCheck = 0700; 194 | LastUpgradeCheck = 0810; 195 | ORGANIZATIONNAME = "Razeware LLC"; 196 | TargetAttributes = { 197 | 7BAE784D182FBA95009B4DA0 = { 198 | DevelopmentTeam = KFCNEC27GU; 199 | DevelopmentTeamName = "Razeware LLC"; 200 | LastSwiftMigration = 0800; 201 | }; 202 | }; 203 | }; 204 | buildConfigurationList = 7BAE7849182FBA95009B4DA0 /* Build configuration list for PBXProject "Demo" */; 205 | compatibilityVersion = "Xcode 3.2"; 206 | developmentRegion = English; 207 | hasScannedForEncodings = 0; 208 | knownRegions = ( 209 | en, 210 | Base, 211 | ); 212 | mainGroup = 7BAE7845182FBA95009B4DA0; 213 | productRefGroup = 7BAE784F182FBA95009B4DA0 /* Products */; 214 | projectDirPath = ""; 215 | projectRoot = ""; 216 | targets = ( 217 | 7BAE784D182FBA95009B4DA0 /* Demo */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXResourcesBuildPhase section */ 223 | 7BAE784C182FBA95009B4DA0 /* Resources */ = { 224 | isa = PBXResourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 3558F7521D21A7E200AC8ABC /* Launch Screen.storyboard in Resources */, 228 | 7BAE785E182FBA95009B4DA0 /* InfoPlist.strings in Resources */, 229 | 7BAE7871182FBA95009B4DA0 /* Images.xcassets in Resources */, 230 | 7BAE7890182FBBB6009B4DA0 /* Ball.png in Resources */, 231 | 7BAE7891182FBBB6009B4DA0 /* Ball@2x.png in Resources */, 232 | 7BAE7867182FBA95009B4DA0 /* Main.storyboard in Resources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXResourcesBuildPhase section */ 237 | 238 | /* Begin PBXSourcesBuildPhase section */ 239 | 7BAE784A182FBA95009B4DA0 /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 7B3D9727194864E600D0AB26 /* AppDelegate.swift in Sources */, 244 | 7B0610101945ED26002D27B1 /* SKAction+Extensions.swift in Sources */, 245 | 7B0610141945ED26002D27B1 /* SKNode+Extensions.swift in Sources */, 246 | 7B77E69219544FFA00DB0E4D /* CGPoint+Extensions.swift in Sources */, 247 | 7B77E69819544FFA00DB0E4D /* Int+Extensions.swift in Sources */, 248 | 7B0610121945ED26002D27B1 /* SKAction+SpecialEffects.swift in Sources */, 249 | 7B06101A1945ED26002D27B1 /* SKColor+Extensions.swift in Sources */, 250 | 7B3D97251948634200D0AB26 /* ViewController.swift in Sources */, 251 | 7B77E69A19544FFA00DB0E4D /* Vector3.swift in Sources */, 252 | 7B77E69419544FFA00DB0E4D /* CGVector+Extensions.swift in Sources */, 253 | 7B0610181945ED26002D27B1 /* SKTTimingFunctions.swift in Sources */, 254 | 7B77E69619544FFA00DB0E4D /* CGFloat+Extensions.swift in Sources */, 255 | 7B2A707E1945FA2E00585C2D /* MyScene.swift in Sources */, 256 | 7B3D972B19486C2A00D0AB26 /* SKTAudio.swift in Sources */, 257 | 7B0610161945ED26002D27B1 /* SKTEffects.swift in Sources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | /* End PBXSourcesBuildPhase section */ 262 | 263 | /* Begin PBXVariantGroup section */ 264 | 7BAE785C182FBA95009B4DA0 /* InfoPlist.strings */ = { 265 | isa = PBXVariantGroup; 266 | children = ( 267 | 7BAE785D182FBA95009B4DA0 /* en */, 268 | ); 269 | name = InfoPlist.strings; 270 | sourceTree = ""; 271 | }; 272 | 7BAE7865182FBA95009B4DA0 /* Main.storyboard */ = { 273 | isa = PBXVariantGroup; 274 | children = ( 275 | 7BAE7866182FBA95009B4DA0 /* Base */, 276 | ); 277 | name = Main.storyboard; 278 | sourceTree = ""; 279 | }; 280 | /* End PBXVariantGroup section */ 281 | 282 | /* Begin XCBuildConfiguration section */ 283 | 7BAE7885182FBA95009B4DA0 /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ALWAYS_SEARCH_USER_PATHS = NO; 287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 288 | CLANG_CXX_LIBRARY = "libc++"; 289 | CLANG_ENABLE_MODULES = YES; 290 | CLANG_ENABLE_OBJC_ARC = YES; 291 | CLANG_WARN_BOOL_CONVERSION = YES; 292 | CLANG_WARN_CONSTANT_CONVERSION = YES; 293 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 294 | CLANG_WARN_EMPTY_BODY = YES; 295 | CLANG_WARN_ENUM_CONVERSION = YES; 296 | CLANG_WARN_INFINITE_RECURSION = YES; 297 | CLANG_WARN_INT_CONVERSION = YES; 298 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 299 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 300 | CLANG_WARN_UNREACHABLE_CODE = YES; 301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 302 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 303 | COPY_PHASE_STRIP = NO; 304 | ENABLE_STRICT_OBJC_MSGSEND = YES; 305 | ENABLE_TESTABILITY = YES; 306 | GCC_C_LANGUAGE_STANDARD = gnu99; 307 | GCC_DYNAMIC_NO_PIC = NO; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_OPTIMIZATION_LEVEL = 0; 310 | GCC_PREPROCESSOR_DEFINITIONS = ( 311 | "DEBUG=1", 312 | "$(inherited)", 313 | ); 314 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 315 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 316 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 317 | GCC_WARN_UNDECLARED_SELECTOR = YES; 318 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 319 | GCC_WARN_UNUSED_FUNCTION = YES; 320 | GCC_WARN_UNUSED_VARIABLE = YES; 321 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 322 | ONLY_ACTIVE_ARCH = YES; 323 | SDKROOT = iphoneos; 324 | }; 325 | name = Debug; 326 | }; 327 | 7BAE7886182FBA95009B4DA0 /* Release */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | ALWAYS_SEARCH_USER_PATHS = NO; 331 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 332 | CLANG_CXX_LIBRARY = "libc++"; 333 | CLANG_ENABLE_MODULES = YES; 334 | CLANG_ENABLE_OBJC_ARC = YES; 335 | CLANG_WARN_BOOL_CONVERSION = YES; 336 | CLANG_WARN_CONSTANT_CONVERSION = YES; 337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 338 | CLANG_WARN_EMPTY_BODY = YES; 339 | CLANG_WARN_ENUM_CONVERSION = YES; 340 | CLANG_WARN_INFINITE_RECURSION = YES; 341 | CLANG_WARN_INT_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 347 | COPY_PHASE_STRIP = YES; 348 | ENABLE_NS_ASSERTIONS = NO; 349 | ENABLE_STRICT_OBJC_MSGSEND = YES; 350 | GCC_C_LANGUAGE_STANDARD = gnu99; 351 | GCC_NO_COMMON_BLOCKS = YES; 352 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 353 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 354 | GCC_WARN_UNDECLARED_SELECTOR = YES; 355 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 356 | GCC_WARN_UNUSED_FUNCTION = YES; 357 | GCC_WARN_UNUSED_VARIABLE = YES; 358 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 359 | SDKROOT = iphoneos; 360 | VALIDATE_PRODUCT = YES; 361 | }; 362 | name = Release; 363 | }; 364 | 7BAE7888182FBA95009B4DA0 /* Debug */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 368 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 369 | CLANG_ENABLE_MODULES = YES; 370 | CLANG_STATIC_ANALYZER_MODE = deep; 371 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 372 | GCC_PREFIX_HEADER = "Demo/Demo-Prefix.pch"; 373 | INFOPLIST_FILE = "Demo/Demo-Info.plist"; 374 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 375 | PRODUCT_BUNDLE_IDENTIFIER = "com.razeware.${PRODUCT_NAME:rfc1034identifier}"; 376 | PRODUCT_NAME = "$(TARGET_NAME)"; 377 | RUN_CLANG_STATIC_ANALYZER = YES; 378 | SPRITEKIT_TEXTURE_ATLAS_OUTPUT = YES; 379 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 380 | SWIFT_VERSION = 3.0; 381 | WARNING_CFLAGS = ( 382 | "-Wall", 383 | "-Wextra", 384 | "-Wno-unused-parameter", 385 | ); 386 | WRAPPER_EXTENSION = app; 387 | }; 388 | name = Debug; 389 | }; 390 | 7BAE7889182FBA95009B4DA0 /* Release */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 394 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 395 | CLANG_ENABLE_MODULES = YES; 396 | CLANG_STATIC_ANALYZER_MODE = deep; 397 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 398 | GCC_PREFIX_HEADER = "Demo/Demo-Prefix.pch"; 399 | INFOPLIST_FILE = "Demo/Demo-Info.plist"; 400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 401 | PRODUCT_BUNDLE_IDENTIFIER = "com.razeware.${PRODUCT_NAME:rfc1034identifier}"; 402 | PRODUCT_NAME = "$(TARGET_NAME)"; 403 | RUN_CLANG_STATIC_ANALYZER = YES; 404 | SPRITEKIT_TEXTURE_ATLAS_OUTPUT = YES; 405 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 406 | SWIFT_VERSION = 3.0; 407 | WARNING_CFLAGS = ( 408 | "-Wall", 409 | "-Wextra", 410 | "-Wno-unused-parameter", 411 | ); 412 | WRAPPER_EXTENSION = app; 413 | }; 414 | name = Release; 415 | }; 416 | /* End XCBuildConfiguration section */ 417 | 418 | /* Begin XCConfigurationList section */ 419 | 7BAE7849182FBA95009B4DA0 /* Build configuration list for PBXProject "Demo" */ = { 420 | isa = XCConfigurationList; 421 | buildConfigurations = ( 422 | 7BAE7885182FBA95009B4DA0 /* Debug */, 423 | 7BAE7886182FBA95009B4DA0 /* Release */, 424 | ); 425 | defaultConfigurationIsVisible = 0; 426 | defaultConfigurationName = Release; 427 | }; 428 | 7BAE7887182FBA95009B4DA0 /* Build configuration list for PBXNativeTarget "Demo" */ = { 429 | isa = XCConfigurationList; 430 | buildConfigurations = ( 431 | 7BAE7888182FBA95009B4DA0 /* Debug */, 432 | 7BAE7889182FBA95009B4DA0 /* Release */, 433 | ); 434 | defaultConfigurationIsVisible = 0; 435 | defaultConfigurationName = Release; 436 | }; 437 | /* End XCConfigurationList section */ 438 | }; 439 | rootObject = 7BAE7846182FBA95009B4DA0 /* Project object */; 440 | } 441 | -------------------------------------------------------------------------------- /Examples/Effects/Demo/MyScene.swift: -------------------------------------------------------------------------------- 1 | 2 | import SpriteKit 3 | 4 | // These flags enable or disable the various effects that happen when a ball 5 | // collides with a border, the barrier, or another ball. Turning them all on 6 | // at the same time might result in sea sickness... ;-) 7 | let FLASH_BALL = false 8 | let FLASH_BORDER = true 9 | let FLASH_BARRIER = true 10 | let SCALE_BALL = true 11 | let SCALE_BORDER = true 12 | let SCALE_BARRIER = true 13 | let SQUASH_BALL = false 14 | let STRETCH_BALL = false 15 | let SCREEN_SHAKE = true 16 | let SCREEN_ZOOM = true 17 | let SCREEN_TUMBLE = true 18 | let COLOR_GLITCH = false 19 | let BARRIER_JELLY = true 20 | 21 | // How fat the borders around the screen are. 22 | let BorderThickness: CGFloat = 20 23 | 24 | // Categories for physics collisions. 25 | let BallCategory: UInt32 = 1 << 0 26 | let BorderCategory: UInt32 = 1 << 1 27 | let BarrierCategory: UInt32 = 1 << 2 28 | 29 | class MyScene: SKScene, SKPhysicsContactDelegate { 30 | 31 | // The layer that contains all the nodes. Having a separate world node is 32 | // necessary for the screen shake effect because you cannot apply that to 33 | // an SKScene directly. 34 | let worldLayer = SKNode() 35 | 36 | // For screen zoom and tumble, the world layer must sit in a separate pivot 37 | // node that centers the world on the screen. 38 | let worldPivot = SKNode() 39 | 40 | let sceneBackgroundColor = SKColorWithRGB(8, g: 57, b: 71) 41 | let borderColor = SKColorWithRGB(160, g: 160, b: 160) 42 | let borderFlashColor = SKColor.white 43 | let barrierColor = SKColorWithRGB(212, g: 212, b: 212) 44 | let barrierFlashColor = SKColor.white 45 | let ballFlashColor = SKColor.red 46 | 47 | // ---- Initialization ---- 48 | 49 | required init?(coder aDecoder: NSCoder) { 50 | fatalError("init(coder) is not used in this app") 51 | } 52 | 53 | override init(size: CGSize) { 54 | super.init(size: size) 55 | 56 | // Preload the font, otherwise there is a small delay when creating the 57 | // first text label. 58 | _ = SKLabelNode(fontNamed: "HelveticaNeue-Light") 59 | 60 | backgroundColor = sceneBackgroundColor 61 | 62 | // By placing the scene's anchor point in the center of the screen and the 63 | // world layer at the scene's origin, you can make the entire scene rotate 64 | // around its center (for example for the screen tumble effect). You need 65 | // to set the anchor point before you add the world pivot node. 66 | anchorPoint = CGPoint(x: 0.5, y: 0.5) 67 | 68 | // The origin of the pivot node must be the center of the screen. 69 | addChild(worldPivot) 70 | 71 | // Create the world layer. This is the only node that is added directly 72 | // to the pivot node. If you have a HUD layer you would add that directly 73 | // to the scene and make it sit above the world layer. 74 | worldLayer.position = frame.origin 75 | worldPivot.addChild(worldLayer) 76 | 77 | physicsWorld.gravity = CGVector(dx: 0, dy: 0) 78 | physicsWorld.contactDelegate = self 79 | 80 | // Put the game objects into the world. We use delays here to make some 81 | // objects appear earlier than others, which looks cooler. 82 | addBorders() 83 | afterDelay(1.5, runBlock: addBarrier) 84 | afterDelay(2.5, runBlock: addBalls) 85 | afterDelay(6, runBlock: showLabel) 86 | 87 | // Make the barrier rotate around its center. 88 | afterDelay(4, runBlock: animateBarrier) 89 | } 90 | 91 | /** 92 | * Creates four border nodes, one for each screen edge. The nodes all have 93 | * the same shape -- a rectangle that is taller than it is wide -- but are 94 | * rotated by different angles. 95 | */ 96 | func addBorders() { 97 | let distance: CGFloat = 50 98 | 99 | let leftBorder = newBorderNodeWithLength(length: size.height, horizontal: false) 100 | leftBorder.position = CGPoint(x: BorderThickness / 2 - distance, y: size.height / 2) 101 | worldLayer.addChild(leftBorder) 102 | 103 | let rightBorder = newBorderNodeWithLength(length: size.height, horizontal: false) 104 | rightBorder.position = CGPoint(x: size.width - BorderThickness/2 + distance, y: size.height / 2) 105 | rightBorder.zRotation = π 106 | worldLayer.addChild(rightBorder) 107 | 108 | let topBorder = newBorderNodeWithLength(length: size.width, horizontal: true) 109 | topBorder.position = CGPoint(x: size.width / 2, y: size.height - BorderThickness / 2 + distance) 110 | topBorder.zRotation = -π/2 111 | worldLayer.addChild(topBorder) 112 | 113 | let bottomBorder = newBorderNodeWithLength(length: size.width, horizontal: true) 114 | bottomBorder.position = CGPoint(x: size.width / 2, y: BorderThickness / 2 - distance) 115 | bottomBorder.zRotation = π/2 116 | worldLayer.addChild(bottomBorder) 117 | 118 | // Make the borders appear with a bounce animation. 119 | 120 | addEffectToBorder(border: leftBorder, startPosition: leftBorder.position, endPosition: CGPoint(x: leftBorder.position.x + distance, y: leftBorder.position.y), delay: 0.5) 121 | 122 | addEffectToBorder(border: rightBorder, startPosition: rightBorder.position, endPosition: CGPoint(x: rightBorder.position.x - distance, y: rightBorder.position.y), delay: 0.5) 123 | 124 | addEffectToBorder(border: topBorder, startPosition: topBorder.position, endPosition: CGPoint(x: topBorder.position.x, y: topBorder.position.y - distance), delay: 1) 125 | 126 | addEffectToBorder(border: bottomBorder, startPosition: bottomBorder.position, endPosition: CGPoint(x: bottomBorder.position.x, y: bottomBorder.position.y + distance), delay: 1) 127 | } 128 | 129 | func newBorderNodeWithLength(length: CGFloat, horizontal: Bool) -> SKNode { 130 | // IMPORTANT: When using SKTScaleEffect, the node that you're scaling must 131 | // not have a physics body, otherwise the physics body gets scaled as well 132 | // and weird stuff will happen. So make a new SKNode, give it the physics 133 | // body, and add the node that you're scaling as a child node! 134 | 135 | let rect = CGRect(x: 0, y: 0, width: BorderThickness, height: length) 136 | 137 | let node = SKShapeNode() 138 | node.path = UIBezierPath(rect: rect).cgPath 139 | node.fillColor = borderColor 140 | node.strokeColor = SKColor.clear 141 | node.lineWidth = 0 142 | node.glowWidth = 0 143 | node.name = horizontal ? "horizontalBorder" : "verticalBorder" 144 | node.position = CGPoint(x: -BorderThickness/2, y: -length/2) 145 | 146 | rect.offsetBy(dx: -BorderThickness/2, dy: -length/2) 147 | 148 | let body = SKPhysicsBody(polygonFrom: UIBezierPath(rect: rect).cgPath) 149 | body.isDynamic = false 150 | body.friction = 0 151 | body.linearDamping = 0 152 | body.angularDamping = 0 153 | body.restitution = 0 154 | body.categoryBitMask = BorderCategory 155 | body.collisionBitMask = BallCategory 156 | body.contactTestBitMask = BallCategory 157 | 158 | let pivotNode = SKNode() 159 | pivotNode.addChild(node) 160 | pivotNode.physicsBody = body 161 | return pivotNode 162 | } 163 | 164 | func addEffectToBorder(border: SKNode, startPosition: CGPoint, endPosition: CGPoint, delay: TimeInterval) { 165 | let moveEffect = SKTMoveEffect(node: border, duration: 0.5, startPosition: startPosition, endPosition: endPosition) 166 | moveEffect.timingFunction = SKTTimingFunctionBounceEaseOut 167 | border.run(SKAction.afterDelay(delay, performAction: SKAction.actionWithEffect(moveEffect))) 168 | } 169 | 170 | /** 171 | * Creates a node that sits in the middle of the screen so the balls have 172 | * something to bump into. 173 | */ 174 | func addBarrier() { 175 | // SKShapeNode does not have an anchorPoint property, so create a pivot 176 | // node that acts as the anchor point, and place it in the screen center. 177 | let pivotNode = SKNode() 178 | pivotNode.name = "barrier" 179 | pivotNode.position = CGPoint(x: size.width / 2, y: size.height / 2) 180 | pivotNode.zRotation = π/2 181 | worldLayer.addChild(pivotNode) 182 | 183 | let width = BorderThickness * 2 184 | let height: CGFloat = 140 185 | let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: width, height: height)) 186 | 187 | // Create the shape node that draws the barrier on the screen. This is a 188 | // child of the pivot node, so it rotates and scales along with the pivot. 189 | let shapeNode = SKShapeNode() 190 | shapeNode.path = path.cgPath 191 | shapeNode.fillColor = barrierColor 192 | shapeNode.strokeColor = SKColor.clear 193 | shapeNode.lineWidth = 0 194 | shapeNode.glowWidth = 0 195 | shapeNode.position = CGPoint(x: -width/2, y: -height/2) 196 | 197 | // Because of SKTScaleEffect we cannot scale the pivotNode directly. It 198 | // also doesn't look good to scale the SKShapeNode because its "anchor 199 | // point" is always in its bottom-left corner. To solve this, we add 200 | // another node that sits between pivotNode and shapeNode, so that any 201 | // scaling appears to happen from the barrier shape's center. 202 | let containerNode = SKNode() 203 | pivotNode.addChild(containerNode) 204 | containerNode.addChild(shapeNode) 205 | 206 | // Create the physics body. This has the same shape as the shape node 207 | // but is attached to the pivot node. (You don't want to attach it directly 208 | // to the shape node because that causes trouble with SKTScaleEffect.) 209 | let body = SKPhysicsBody(rectangleOf: CGSize(width: width, height: height)) 210 | body.isDynamic = false 211 | body.friction = 0 212 | body.linearDamping = 0 213 | body.angularDamping = 0 214 | body.restitution = 0 215 | body.categoryBitMask = BarrierCategory 216 | body.collisionBitMask = BallCategory 217 | body.contactTestBitMask = body.collisionBitMask 218 | pivotNode.physicsBody = body 219 | 220 | // Zoom in the barrier shape. Because of SKTScaleEffect we do this on the 221 | // container node, not on the SKShapeNode directly. 222 | containerNode.xScale = 0.15 223 | containerNode.yScale = 0.15 224 | 225 | let scaleEffect = SKTScaleEffect(node: containerNode, duration: 1, startScale: CGPoint(x: containerNode.xScale, y: containerNode.yScale), endScale: CGPoint(x: 1, y: 1)) 226 | scaleEffect.timingFunction = SKTTimingFunctionBackEaseOut 227 | 228 | containerNode.run(SKAction.actionWithEffect(scaleEffect)) 229 | 230 | // Also rotate and fade in the barrier. It's OK to apply these to the 231 | // pivotNode directly. 232 | let rotateEffect = SKTRotateEffect(node: pivotNode, duration: 1, startAngle: CGFloat.random() * π/4, endAngle:pivotNode.zRotation) 233 | rotateEffect.timingFunction = SKTTimingFunctionBackEaseOut 234 | 235 | pivotNode.alpha = 0 236 | pivotNode.run(SKAction.group([ 237 | SKAction.fadeIn(withDuration: 1), 238 | SKAction.actionWithEffect(rotateEffect) 239 | ])) 240 | } 241 | 242 | func barrierNode() -> SKNode { 243 | return worldLayer.childNode(withName: "barrier")! 244 | } 245 | 246 | /** 247 | * Rotates the barrier by 45 degrees with a "back ease in-out", which makes 248 | * it look realistically mechanical. 249 | */ 250 | func animateBarrier() { 251 | let node = barrierNode() 252 | node.run(SKAction.repeatForever(SKAction.sequence([ 253 | SKAction.wait(forDuration: 0.75), 254 | SKAction.run { 255 | let effect = SKTRotateEffect(node: node, duration: 0.25, startAngle: node.zRotation, endAngle:node.zRotation + π/4) 256 | effect.timingFunction = SKTTimingFunctionBackEaseInOut 257 | node.run(SKAction.actionWithEffect(effect)) 258 | }]))) 259 | } 260 | 261 | func addBalls() { 262 | // Add a ball sprite on the left side of the screen... 263 | let ball1 = newBallNode() 264 | ball1.position = CGPoint(x: 100, y: size.height / 2) 265 | worldLayer.addChild(ball1) 266 | 267 | // ...and add a ball sprite on the right side of the screen. 268 | let ball2 = newBallNode() 269 | ball2.position = CGPoint(x: size.width - 100, y: size.height / 2) 270 | worldLayer.addChild(ball2) 271 | 272 | for ball in [ball1, ball2] { 273 | addEffectToBall(ball: ball) 274 | 275 | ball.run(SKAction.afterDelay(1, runBlock:{ 276 | // Assign a random angle to the ball's velocity. 277 | let ballSpeed: CGFloat = 200 278 | let angle = (CGFloat.random() * 360).degreesToRadians() 279 | ball.physicsBody!.velocity = CGVector(dx: cos(angle)*ballSpeed, dy: sin(angle)*ballSpeed) 280 | })) 281 | } 282 | } 283 | 284 | func newBallNode() -> SKNode { 285 | let sprite = SKSpriteNode(imageNamed: "Ball") 286 | 287 | // Create a circular physics body. It collides with the borders and 288 | // with other balls. It is slightly smaller than the sprite. 289 | let body = SKPhysicsBody(circleOfRadius:(sprite.size.width / 2) * 0.9) 290 | body.isDynamic = true 291 | body.friction = 0 292 | body.linearDamping = 0 293 | body.angularDamping = 0 294 | body.restitution = 0.9 295 | body.categoryBitMask = BallCategory 296 | body.collisionBitMask = BorderCategory | BarrierCategory | BallCategory 297 | body.contactTestBitMask = body.collisionBitMask 298 | 299 | // Create a new node to hold the sprite. This is necessary for combining 300 | // nonuniform scaling effects with rotation. Some of the effects are placed 301 | // directly on the sprite, some on this pivot node. 302 | let pivotNode = SKNode() 303 | pivotNode.name = "ball" 304 | pivotNode.physicsBody = body 305 | pivotNode.addChild(sprite) 306 | return pivotNode 307 | } 308 | 309 | func addEffectToBall(ball: SKNode) { 310 | let spriteNode = ball.children[0] as! SKSpriteNode 311 | 312 | spriteNode.xScale = 0.2 313 | spriteNode.yScale = 0.2 314 | 315 | // TODO: Instead of doing CGPointMake(spriteNode.xScale, spriteNode.yScale) 316 | // you should be able to use spriteNode.scaleAsPoint. However, in Xcode 6 317 | // beta 1, this crashes the compiler. 318 | 319 | let scaleEffect = SKTScaleEffect(node: spriteNode, duration: 0.5, startScale:CGPoint(x: spriteNode.xScale, y: spriteNode.yScale), endScale:CGPoint(x: 1, y: 1)) 320 | scaleEffect.timingFunction = SKTTimingFunctionBackEaseOut 321 | 322 | spriteNode.run(SKAction.actionWithEffect(scaleEffect)) 323 | } 324 | 325 | /** 326 | * Adds a label with instructions. 327 | */ 328 | func showLabel() { 329 | let labelNode = SKLabelNode(fontNamed: "HelveticaNeue-Light") 330 | labelNode.text = NSLocalizedString("Tap to apply random impulse", comment: "IntroMessage") 331 | labelNode.fontSize = 12 332 | addChild(labelNode) 333 | 334 | labelNode.position = labelNode.position.offset(dx: 0, dy: 100) 335 | 336 | let moveEffect = SKTMoveEffect(node: labelNode, duration: 4, startPosition: labelNode.position, endPosition:labelNode.position.offset(dx: 0, dy: 20)) 337 | 338 | moveEffect.timingFunction = SKTTimingFunctionSmoothstep 339 | labelNode.run(SKAction.actionWithEffect(moveEffect)) 340 | 341 | labelNode.alpha = 0 342 | labelNode.run(SKAction.sequence([ 343 | SKAction.wait(forDuration: 0.5), 344 | SKAction.fadeIn(withDuration: 2), 345 | SKAction.wait(forDuration: 1), 346 | SKAction.fadeOut(withDuration: 1) 347 | ])) 348 | } 349 | 350 | // ---- Touch Handling ---- 351 | 352 | /** 353 | * Adds a random impulse to the balls whenever the user taps the screen. 354 | */ 355 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 356 | worldLayer.enumerateChildNodes(withName: "ball") {(node, stop) in 357 | let speed: CGFloat = 50 358 | let impulse = CGVector(dx: CGFloat.random(min: -speed, max: speed), dy: CGFloat.random(min: -speed, max: speed)) 359 | node.physicsBody!.applyImpulse(impulse) 360 | 361 | if STRETCH_BALL { 362 | self.stretchBall(node: node.children[0] as SKNode) 363 | } 364 | } 365 | } 366 | 367 | // ---- Game Logic ---- 368 | 369 | /** 370 | * Rotates the balls into the direction that they're flying. 371 | */ 372 | override func didSimulatePhysics() { 373 | worldLayer.enumerateChildNodes(withName: "ball") {(node, stop) in 374 | if node.physicsBody!.velocity.length() > 0 { 375 | node.rotateToVelocity(node.physicsBody!.velocity, rate:0.1) 376 | } 377 | } 378 | } 379 | 380 | func didBegin(_ contact: SKPhysicsContact) { 381 | checkContactBetween(body1: contact.bodyA, body2: contact.bodyB, contactPoint: contact.contactPoint) 382 | checkContactBetween(body1: contact.bodyB, body2: contact.bodyA, contactPoint: contact.contactPoint) 383 | } 384 | 385 | func checkContactBetween(body1: SKPhysicsBody, body2: SKPhysicsBody, contactPoint: CGPoint) { 386 | if body1.categoryBitMask & BallCategory != 0 { 387 | handleBallCollision(node: body1.node!) 388 | 389 | if body2.categoryBitMask & BorderCategory != 0 { 390 | handleCollisionBetweenBall(ball: body1.node!, border:body2.node!, contactPoint:contactPoint) 391 | } else if body2.categoryBitMask & BarrierCategory != 0 { 392 | handleCollisionBetweenBall(ball: body1.node!, barrier:body2.node!) 393 | } 394 | } 395 | } 396 | 397 | /** 398 | * This method gets called when a ball hits any other node. 399 | */ 400 | func handleBallCollision(node: SKNode) { 401 | let ballSprite = node.children[0] as! SKSpriteNode 402 | 403 | if FLASH_BALL { 404 | flashSpriteNode(spriteNode: ballSprite, withColor: ballFlashColor) 405 | } 406 | 407 | if SCALE_BALL { 408 | scaleBall(node: ballSprite) 409 | } 410 | 411 | if SQUASH_BALL { 412 | squashBall(node: ballSprite) 413 | } 414 | 415 | if SCREEN_SHAKE { 416 | screenShakeWithVelocity(velocity: node.physicsBody!.velocity) 417 | } 418 | 419 | if SCREEN_ZOOM { 420 | screenZoomWithVelocity(velocity: node.physicsBody!.velocity) 421 | } 422 | } 423 | 424 | func handleCollisionBetweenBall(ball: SKNode, border: SKNode, contactPoint: CGPoint) { 425 | let borderShapeNode = border.children[0] as! SKShapeNode 426 | 427 | // Draw the flashing border above the other borders. 428 | border.bringToFront() 429 | 430 | if FLASH_BORDER { 431 | flashShapeNode(node: borderShapeNode, fromColor: borderFlashColor, toColor: borderColor) 432 | } 433 | 434 | if BARRIER_JELLY { 435 | jelly(node: barrierNode()) 436 | } 437 | 438 | if SCREEN_TUMBLE { 439 | screenTumbleAtContactPoint(contactPoint: contactPoint, border: borderShapeNode) 440 | } 441 | 442 | if SCALE_BORDER { 443 | scaleBorder(node: borderShapeNode) 444 | } 445 | } 446 | 447 | func handleCollisionBetweenBall(ball: SKNode, barrier: SKNode) { 448 | if SCALE_BARRIER { 449 | scaleBarrier(node: barrier) 450 | } 451 | 452 | if FLASH_BARRIER { 453 | let containerNode = barrier.children[0] as SKNode 454 | let shapeNode = containerNode.children[0] as! SKShapeNode 455 | flashShapeNode(node: shapeNode, fromColor: barrierFlashColor, toColor: barrierColor) 456 | } 457 | 458 | if COLOR_GLITCH { 459 | run(SKAction.colorGlitchWithScene(self, originalColor: sceneBackgroundColor, duration:0.1)) 460 | } 461 | } 462 | 463 | // ---- Special Effects ---- 464 | 465 | /** 466 | * Colorizes the node for a brief moment and then fades back to 467 | * the original color. 468 | */ 469 | func flashSpriteNode(spriteNode: SKSpriteNode, withColor color: SKColor) { 470 | 471 | let action = SKAction.sequence([ 472 | SKAction.colorize(with: color, colorBlendFactor: 1, duration: 0.025), 473 | SKAction.wait(forDuration: 0.05), 474 | SKAction.colorize(withColorBlendFactor: 0, duration:0.1)]) 475 | 476 | spriteNode.run(action) 477 | } 478 | 479 | /** 480 | * Changes the fill color of the node for a brief moment and then 481 | * restores the original color. 482 | */ 483 | func flashShapeNode(node: SKShapeNode, fromColor: SKColor, toColor: SKColor) { 484 | node.fillColor = fromColor 485 | 486 | let action = SKAction.sequence([ 487 | SKAction.wait(forDuration: 0.15), 488 | SKAction.run { node.fillColor = toColor }]) 489 | 490 | node.run(action) 491 | } 492 | 493 | /** 494 | * Scales the ball up and then down again. This effect is cumulative; if 495 | * the ball collides again while still scaled up, it scales up even more. 496 | */ 497 | func scaleBall(node: SKSpriteNode) { 498 | let currentScale = CGPoint(x: node.xScale, y: node.yScale) 499 | let newScale = currentScale * 1.2 500 | 501 | let scaleEffect = SKTScaleEffect(node: node, duration: 1.5, startScale: newScale, endScale: currentScale) 502 | scaleEffect.timingFunction = SKTTimingFunctionElasticEaseOut 503 | 504 | node.run(SKAction.actionWithEffect(scaleEffect)) 505 | } 506 | 507 | /** 508 | * Makes the ball wider but flatter, keeping the overall volume the same. 509 | * Squashing is useful for when an object collides with another object. 510 | */ 511 | func squashBall(node: SKNode) { 512 | let ratio: CGFloat = 1.5 513 | let currentScale = CGPoint(x: node.xScale, y: node.yScale) 514 | let newScale = currentScale * CGPoint(x: ratio, y: 1/ratio) 515 | 516 | let scaleEffect = SKTScaleEffect(node: node, duration: 1.5, startScale: newScale, endScale: currentScale) 517 | scaleEffect.timingFunction = SKTTimingFunctionElasticEaseOut 518 | 519 | node.run(SKAction.actionWithEffect(scaleEffect)) 520 | } 521 | 522 | /** 523 | * Makes the ball thinner but taller, keeping the overall volume the same. 524 | * Stretching is useful for when an object accelerates. 525 | */ 526 | func stretchBall(node: SKNode) { 527 | let ratio: CGFloat = 1.5 528 | let currentScale = CGPoint(x: node.xScale, y: node.yScale) 529 | let newScale = currentScale * CGPoint(x: 1/ratio, y: ratio) 530 | 531 | let scaleEffect = SKTScaleEffect(node: node, duration: 0.5, startScale: newScale, endScale: currentScale) 532 | scaleEffect.timingFunction = SKTTimingFunctionCubicEaseOut 533 | 534 | node.run(SKAction.actionWithEffect(scaleEffect)) 535 | } 536 | 537 | /** 538 | * Scales the border in the X direction. Because shape nodes do not have an 539 | * anchor point, this keeps the bottom-left corner fixed. Because the border 540 | * nodes are rotated, this makes them grow into the scene, which looks cool. 541 | */ 542 | func scaleBorder(node: SKNode) { 543 | let currentScale = CGPoint(x: node.xScale, y: node.yScale) 544 | let newScale = CGPoint(x: currentScale.x * 2, y: currentScale.y) 545 | 546 | let scaleEffect = SKTScaleEffect(node: node, duration: 1, startScale: newScale, endScale: currentScale) 547 | scaleEffect.timingFunction = SKTTimingFunctionElasticEaseOut 548 | 549 | node.run(SKAction.actionWithEffect(scaleEffect)) 550 | } 551 | 552 | /** 553 | * Quickly scales the barrier down and up again. 554 | */ 555 | func scaleBarrier(node: SKNode) { 556 | // This is the SKnode that holds the SKShapeNode. We need to scale this 557 | // container node and not the shape node directly, so that the barrier 558 | // shape appears to scale from the center instead of one of its corners. 559 | let containerNode = node.children[0] as SKNode 560 | 561 | let currentScale = CGPoint(x: containerNode.xScale, y: containerNode.yScale) 562 | let newScale = currentScale * 0.5 563 | 564 | let scaleEffect = SKTScaleEffect(node: containerNode, duration: 0.5, startScale: newScale, endScale: currentScale) 565 | scaleEffect.timingFunction = SKTTimingFunctionElasticEaseOut 566 | 567 | containerNode.run(SKAction.actionWithEffect(scaleEffect)) 568 | } 569 | 570 | /** 571 | * Creates a screen shake in the direction of the velocity vector, with 572 | * an intensity that is proportional to the velocity's magnitude. 573 | */ 574 | func screenShakeWithVelocity(velocity: CGVector) { 575 | // Note: The velocity is from *after* the collision, so the ball is already 576 | // travelling in the opposite direction. To find the impact vector we have 577 | // to negate the velocity. Unfortunately, if the collision is only in the X 578 | // direction, the Y direction also gets flipped (and vice versa). It would 579 | // be better if we could get the velocity at exactly the moment of impact, 580 | // but Sprite Kit doesn't seem to make this easy. 581 | 582 | let inverseVelocity = CGPoint(x: -velocity.dx, y: -velocity.dy) 583 | let hitVector = inverseVelocity / 50 584 | 585 | worldLayer.run(SKAction.screenShakeWithNode(worldLayer, amount: hitVector, oscillations: 10, duration:3)) 586 | } 587 | 588 | /** 589 | * Magnifies the screen by a tiny amount (102%) and bounce back to 100%. 590 | */ 591 | func screenZoomWithVelocity(velocity: CGVector) { 592 | let amount = CGPoint(x: 1.02, y: 1.02) 593 | worldPivot.run(SKAction.screenZoomWithNode(worldPivot, amount: amount, oscillations: 10, duration: 3)) 594 | } 595 | 596 | /** 597 | * Rotates the scene around its center. The amount of rotation depends on 598 | * where the ball hit the border (further from the center is a bigger angle). 599 | */ 600 | func screenTumbleAtContactPoint(contactPoint: CGPoint, border: SKShapeNode) { 601 | let length: CGFloat = (border.name == "horizontalBorder") ? size.width / 2 : size.height / 2 602 | 603 | let point = border.convert(contactPoint, from: worldLayer) 604 | let distanceToCenter = (point.y - length) / length 605 | let angle = CGFloat(10).degreesToRadians() * distanceToCenter 606 | 607 | worldPivot.run(SKAction.screenRotateWithNode(worldPivot, angle: angle, oscillations: 1, duration: 1)) 608 | } 609 | 610 | /** 611 | * Scales up the node and then scales it back down with "bounce ease out" 612 | * timing, making it wobble like jelly. 613 | */ 614 | func jelly(node: SKNode) { 615 | let containerNode = node.children[0] as SKNode 616 | 617 | let scaleEffect = SKTScaleEffect(node: containerNode, duration: 0.25, startScale: CGPoint(x: 1.25, y: 1.25), endScale: CGPoint(x: containerNode.xScale, y: containerNode.yScale)) 618 | 619 | scaleEffect.timingFunction = SKTTimingFunctionBounceEaseOut 620 | 621 | containerNode.run(SKAction.actionWithEffect(scaleEffect)) 622 | } 623 | } 624 | -------------------------------------------------------------------------------- /Examples/Tests/SKTUtils.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7B0F9FC419C448D500538BC0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FC319C448D500538BC0 /* AppDelegate.swift */; }; 11 | 7B0F9FC619C448D500538BC0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FC519C448D500538BC0 /* ViewController.swift */; }; 12 | 7B0F9FC919C448D500538BC0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7B0F9FC719C448D500538BC0 /* Main.storyboard */; }; 13 | 7B0F9FCB19C448D500538BC0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7B0F9FCA19C448D500538BC0 /* Images.xcassets */; }; 14 | 7B0F9FE719C4490500538BC0 /* CGPointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FE319C4490500538BC0 /* CGPointTests.swift */; }; 15 | 7B0F9FE819C4490500538BC0 /* CGVectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FE419C4490500538BC0 /* CGVectorTests.swift */; }; 16 | 7B0F9FE919C4490500538BC0 /* FloatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FE519C4490500538BC0 /* FloatTests.swift */; }; 17 | 7B0F9FEA19C4490500538BC0 /* IntTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FE619C4490500538BC0 /* IntTests.swift */; }; 18 | 7B0F9FF819C4496E00538BC0 /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FEC19C4496E00538BC0 /* CGFloat+Extensions.swift */; }; 19 | 7B0F9FF919C4496E00538BC0 /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FEC19C4496E00538BC0 /* CGFloat+Extensions.swift */; }; 20 | 7B0F9FFA19C4496E00538BC0 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FED19C4496E00538BC0 /* CGPoint+Extensions.swift */; }; 21 | 7B0F9FFB19C4496E00538BC0 /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FED19C4496E00538BC0 /* CGPoint+Extensions.swift */; }; 22 | 7B0F9FFC19C4496E00538BC0 /* CGVector+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FEE19C4496E00538BC0 /* CGVector+Extensions.swift */; }; 23 | 7B0F9FFD19C4496E00538BC0 /* CGVector+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FEE19C4496E00538BC0 /* CGVector+Extensions.swift */; }; 24 | 7B0F9FFE19C4496E00538BC0 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FEF19C4496E00538BC0 /* Int+Extensions.swift */; }; 25 | 7B0F9FFF19C4496E00538BC0 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FEF19C4496E00538BC0 /* Int+Extensions.swift */; }; 26 | 7B0FA00019C4496E00538BC0 /* SKAction+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF019C4496E00538BC0 /* SKAction+Extensions.swift */; }; 27 | 7B0FA00119C4496E00538BC0 /* SKAction+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF019C4496E00538BC0 /* SKAction+Extensions.swift */; }; 28 | 7B0FA00219C4496E00538BC0 /* SKAction+SpecialEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF119C4496E00538BC0 /* SKAction+SpecialEffects.swift */; }; 29 | 7B0FA00319C4496E00538BC0 /* SKAction+SpecialEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF119C4496E00538BC0 /* SKAction+SpecialEffects.swift */; }; 30 | 7B0FA00419C4496E00538BC0 /* SKColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF219C4496E00538BC0 /* SKColor+Extensions.swift */; }; 31 | 7B0FA00519C4496E00538BC0 /* SKColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF219C4496E00538BC0 /* SKColor+Extensions.swift */; }; 32 | 7B0FA00619C4496E00538BC0 /* SKNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF319C4496E00538BC0 /* SKNode+Extensions.swift */; }; 33 | 7B0FA00719C4496E00538BC0 /* SKNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF319C4496E00538BC0 /* SKNode+Extensions.swift */; }; 34 | 7B0FA00819C4496E00538BC0 /* SKTAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF419C4496E00538BC0 /* SKTAudio.swift */; }; 35 | 7B0FA00919C4496E00538BC0 /* SKTAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF419C4496E00538BC0 /* SKTAudio.swift */; }; 36 | 7B0FA00A19C4496E00538BC0 /* SKTEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF519C4496E00538BC0 /* SKTEffects.swift */; }; 37 | 7B0FA00B19C4496E00538BC0 /* SKTEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF519C4496E00538BC0 /* SKTEffects.swift */; }; 38 | 7B0FA00C19C4496E00538BC0 /* SKTTimingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF619C4496E00538BC0 /* SKTTimingFunctions.swift */; }; 39 | 7B0FA00D19C4496E00538BC0 /* SKTTimingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF619C4496E00538BC0 /* SKTTimingFunctions.swift */; }; 40 | 7B0FA00E19C4496E00538BC0 /* Vector3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF719C4496E00538BC0 /* Vector3.swift */; }; 41 | 7B0FA00F19C4496E00538BC0 /* Vector3.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F9FF719C4496E00538BC0 /* Vector3.swift */; }; 42 | 7BDFA8421A13B2C900AC68C9 /* Vector3Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDFA8411A13B2C900AC68C9 /* Vector3Tests.swift */; }; 43 | /* End PBXBuildFile section */ 44 | 45 | /* Begin PBXContainerItemProxy section */ 46 | 7B0F9FD419C448D500538BC0 /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = 7B0F9FB619C448D500538BC0 /* Project object */; 49 | proxyType = 1; 50 | remoteGlobalIDString = 7B0F9FBD19C448D500538BC0; 51 | remoteInfo = SKTUtils; 52 | }; 53 | /* End PBXContainerItemProxy section */ 54 | 55 | /* Begin PBXFileReference section */ 56 | 7B0F9FBE19C448D500538BC0 /* SKTUtils.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SKTUtils.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 7B0F9FC219C448D500538BC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 7B0F9FC319C448D500538BC0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 7B0F9FC519C448D500538BC0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 60 | 7B0F9FC819C448D500538BC0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 61 | 7B0F9FCA19C448D500538BC0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 62 | 7B0F9FD319C448D500538BC0 /* SKTUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SKTUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 7B0F9FD819C448D500538BC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 7B0F9FE319C4490500538BC0 /* CGPointTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGPointTests.swift; sourceTree = ""; }; 65 | 7B0F9FE419C4490500538BC0 /* CGVectorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGVectorTests.swift; sourceTree = ""; }; 66 | 7B0F9FE519C4490500538BC0 /* FloatTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatTests.swift; sourceTree = ""; }; 67 | 7B0F9FE619C4490500538BC0 /* IntTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntTests.swift; sourceTree = ""; }; 68 | 7B0F9FEC19C4496E00538BC0 /* CGFloat+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Extensions.swift"; sourceTree = ""; }; 69 | 7B0F9FED19C4496E00538BC0 /* CGPoint+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint+Extensions.swift"; sourceTree = ""; }; 70 | 7B0F9FEE19C4496E00538BC0 /* CGVector+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGVector+Extensions.swift"; sourceTree = ""; }; 71 | 7B0F9FEF19C4496E00538BC0 /* Int+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Extensions.swift"; sourceTree = ""; }; 72 | 7B0F9FF019C4496E00538BC0 /* SKAction+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKAction+Extensions.swift"; sourceTree = ""; }; 73 | 7B0F9FF119C4496E00538BC0 /* SKAction+SpecialEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKAction+SpecialEffects.swift"; sourceTree = ""; }; 74 | 7B0F9FF219C4496E00538BC0 /* SKColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKColor+Extensions.swift"; sourceTree = ""; }; 75 | 7B0F9FF319C4496E00538BC0 /* SKNode+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SKNode+Extensions.swift"; sourceTree = ""; }; 76 | 7B0F9FF419C4496E00538BC0 /* SKTAudio.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTAudio.swift; sourceTree = ""; }; 77 | 7B0F9FF519C4496E00538BC0 /* SKTEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTEffects.swift; sourceTree = ""; }; 78 | 7B0F9FF619C4496E00538BC0 /* SKTTimingFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTTimingFunctions.swift; sourceTree = ""; }; 79 | 7B0F9FF719C4496E00538BC0 /* Vector3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vector3.swift; sourceTree = ""; }; 80 | 7BDFA8411A13B2C900AC68C9 /* Vector3Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vector3Tests.swift; sourceTree = ""; }; 81 | /* End PBXFileReference section */ 82 | 83 | /* Begin PBXFrameworksBuildPhase section */ 84 | 7B0F9FBB19C448D500538BC0 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | 7B0F9FD019C448D500538BC0 /* Frameworks */ = { 92 | isa = PBXFrameworksBuildPhase; 93 | buildActionMask = 2147483647; 94 | files = ( 95 | ); 96 | runOnlyForDeploymentPostprocessing = 0; 97 | }; 98 | /* End PBXFrameworksBuildPhase section */ 99 | 100 | /* Begin PBXGroup section */ 101 | 7B0F9FB519C448D500538BC0 = { 102 | isa = PBXGroup; 103 | children = ( 104 | 7B0F9FC019C448D500538BC0 /* App */, 105 | 7B0F9FEB19C4496E00538BC0 /* SKTUtils */, 106 | 7B0F9FD619C448D500538BC0 /* SKTUtilsTests */, 107 | 7B0F9FBF19C448D500538BC0 /* Products */, 108 | ); 109 | sourceTree = ""; 110 | }; 111 | 7B0F9FBF19C448D500538BC0 /* Products */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 7B0F9FBE19C448D500538BC0 /* SKTUtils.app */, 115 | 7B0F9FD319C448D500538BC0 /* SKTUtilsTests.xctest */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | 7B0F9FC019C448D500538BC0 /* App */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 7B0F9FC319C448D500538BC0 /* AppDelegate.swift */, 124 | 7B0F9FC519C448D500538BC0 /* ViewController.swift */, 125 | 7B0F9FC719C448D500538BC0 /* Main.storyboard */, 126 | 7B0F9FCA19C448D500538BC0 /* Images.xcassets */, 127 | 7B0F9FC119C448D500538BC0 /* Supporting Files */, 128 | ); 129 | name = App; 130 | path = SKTUtils; 131 | sourceTree = ""; 132 | }; 133 | 7B0F9FC119C448D500538BC0 /* Supporting Files */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 7B0F9FC219C448D500538BC0 /* Info.plist */, 137 | ); 138 | name = "Supporting Files"; 139 | sourceTree = ""; 140 | }; 141 | 7B0F9FD619C448D500538BC0 /* SKTUtilsTests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 7B0F9FE319C4490500538BC0 /* CGPointTests.swift */, 145 | 7B0F9FE419C4490500538BC0 /* CGVectorTests.swift */, 146 | 7B0F9FE519C4490500538BC0 /* FloatTests.swift */, 147 | 7B0F9FE619C4490500538BC0 /* IntTests.swift */, 148 | 7BDFA8411A13B2C900AC68C9 /* Vector3Tests.swift */, 149 | 7B0F9FD719C448D500538BC0 /* Supporting Files */, 150 | ); 151 | path = SKTUtilsTests; 152 | sourceTree = ""; 153 | }; 154 | 7B0F9FD719C448D500538BC0 /* Supporting Files */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 7B0F9FD819C448D500538BC0 /* Info.plist */, 158 | ); 159 | name = "Supporting Files"; 160 | sourceTree = ""; 161 | }; 162 | 7B0F9FEB19C4496E00538BC0 /* SKTUtils */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 7B0F9FEC19C4496E00538BC0 /* CGFloat+Extensions.swift */, 166 | 7B0F9FED19C4496E00538BC0 /* CGPoint+Extensions.swift */, 167 | 7B0F9FEE19C4496E00538BC0 /* CGVector+Extensions.swift */, 168 | 7B0F9FEF19C4496E00538BC0 /* Int+Extensions.swift */, 169 | 7B0F9FF019C4496E00538BC0 /* SKAction+Extensions.swift */, 170 | 7B0F9FF119C4496E00538BC0 /* SKAction+SpecialEffects.swift */, 171 | 7B0F9FF219C4496E00538BC0 /* SKColor+Extensions.swift */, 172 | 7B0F9FF319C4496E00538BC0 /* SKNode+Extensions.swift */, 173 | 7B0F9FF419C4496E00538BC0 /* SKTAudio.swift */, 174 | 7B0F9FF519C4496E00538BC0 /* SKTEffects.swift */, 175 | 7B0F9FF619C4496E00538BC0 /* SKTTimingFunctions.swift */, 176 | 7B0F9FF719C4496E00538BC0 /* Vector3.swift */, 177 | ); 178 | name = SKTUtils; 179 | path = ../../SKTUtils; 180 | sourceTree = ""; 181 | }; 182 | /* End PBXGroup section */ 183 | 184 | /* Begin PBXNativeTarget section */ 185 | 7B0F9FBD19C448D500538BC0 /* SKTUtils */ = { 186 | isa = PBXNativeTarget; 187 | buildConfigurationList = 7B0F9FDD19C448D500538BC0 /* Build configuration list for PBXNativeTarget "SKTUtils" */; 188 | buildPhases = ( 189 | 7B0F9FBA19C448D500538BC0 /* Sources */, 190 | 7B0F9FBB19C448D500538BC0 /* Frameworks */, 191 | 7B0F9FBC19C448D500538BC0 /* Resources */, 192 | ); 193 | buildRules = ( 194 | ); 195 | dependencies = ( 196 | ); 197 | name = SKTUtils; 198 | productName = SKTUtils; 199 | productReference = 7B0F9FBE19C448D500538BC0 /* SKTUtils.app */; 200 | productType = "com.apple.product-type.application"; 201 | }; 202 | 7B0F9FD219C448D500538BC0 /* SKTUtilsTests */ = { 203 | isa = PBXNativeTarget; 204 | buildConfigurationList = 7B0F9FE019C448D500538BC0 /* Build configuration list for PBXNativeTarget "SKTUtilsTests" */; 205 | buildPhases = ( 206 | 7B0F9FCF19C448D500538BC0 /* Sources */, 207 | 7B0F9FD019C448D500538BC0 /* Frameworks */, 208 | 7B0F9FD119C448D500538BC0 /* Resources */, 209 | ); 210 | buildRules = ( 211 | ); 212 | dependencies = ( 213 | 7B0F9FD519C448D500538BC0 /* PBXTargetDependency */, 214 | ); 215 | name = SKTUtilsTests; 216 | productName = SKTUtilsTests; 217 | productReference = 7B0F9FD319C448D500538BC0 /* SKTUtilsTests.xctest */; 218 | productType = "com.apple.product-type.bundle.unit-test"; 219 | }; 220 | /* End PBXNativeTarget section */ 221 | 222 | /* Begin PBXProject section */ 223 | 7B0F9FB619C448D500538BC0 /* Project object */ = { 224 | isa = PBXProject; 225 | attributes = { 226 | LastSwiftMigration = 0700; 227 | LastSwiftUpdateCheck = 0700; 228 | LastUpgradeCheck = 0800; 229 | ORGANIZATIONNAME = Razeware; 230 | TargetAttributes = { 231 | 7B0F9FBD19C448D500538BC0 = { 232 | CreatedOnToolsVersion = 6.0; 233 | LastSwiftMigration = 0800; 234 | }; 235 | 7B0F9FD219C448D500538BC0 = { 236 | CreatedOnToolsVersion = 6.0; 237 | LastSwiftMigration = 0800; 238 | TestTargetID = 7B0F9FBD19C448D500538BC0; 239 | }; 240 | }; 241 | }; 242 | buildConfigurationList = 7B0F9FB919C448D500538BC0 /* Build configuration list for PBXProject "SKTUtils" */; 243 | compatibilityVersion = "Xcode 3.2"; 244 | developmentRegion = English; 245 | hasScannedForEncodings = 0; 246 | knownRegions = ( 247 | en, 248 | Base, 249 | ); 250 | mainGroup = 7B0F9FB519C448D500538BC0; 251 | productRefGroup = 7B0F9FBF19C448D500538BC0 /* Products */; 252 | projectDirPath = ""; 253 | projectRoot = ""; 254 | targets = ( 255 | 7B0F9FBD19C448D500538BC0 /* SKTUtils */, 256 | 7B0F9FD219C448D500538BC0 /* SKTUtilsTests */, 257 | ); 258 | }; 259 | /* End PBXProject section */ 260 | 261 | /* Begin PBXResourcesBuildPhase section */ 262 | 7B0F9FBC19C448D500538BC0 /* Resources */ = { 263 | isa = PBXResourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | 7B0F9FC919C448D500538BC0 /* Main.storyboard in Resources */, 267 | 7B0F9FCB19C448D500538BC0 /* Images.xcassets in Resources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | 7B0F9FD119C448D500538BC0 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | }; 278 | /* End PBXResourcesBuildPhase section */ 279 | 280 | /* Begin PBXSourcesBuildPhase section */ 281 | 7B0F9FBA19C448D500538BC0 /* Sources */ = { 282 | isa = PBXSourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | 7B0FA00219C4496E00538BC0 /* SKAction+SpecialEffects.swift in Sources */, 286 | 7B0FA00A19C4496E00538BC0 /* SKTEffects.swift in Sources */, 287 | 7B0F9FFC19C4496E00538BC0 /* CGVector+Extensions.swift in Sources */, 288 | 7B0F9FC619C448D500538BC0 /* ViewController.swift in Sources */, 289 | 7B0FA00019C4496E00538BC0 /* SKAction+Extensions.swift in Sources */, 290 | 7B0FA00419C4496E00538BC0 /* SKColor+Extensions.swift in Sources */, 291 | 7B0FA00E19C4496E00538BC0 /* Vector3.swift in Sources */, 292 | 7B0FA00819C4496E00538BC0 /* SKTAudio.swift in Sources */, 293 | 7B0F9FF819C4496E00538BC0 /* CGFloat+Extensions.swift in Sources */, 294 | 7B0F9FFE19C4496E00538BC0 /* Int+Extensions.swift in Sources */, 295 | 7B0FA00C19C4496E00538BC0 /* SKTTimingFunctions.swift in Sources */, 296 | 7B0FA00619C4496E00538BC0 /* SKNode+Extensions.swift in Sources */, 297 | 7B0F9FFA19C4496E00538BC0 /* CGPoint+Extensions.swift in Sources */, 298 | 7B0F9FC419C448D500538BC0 /* AppDelegate.swift in Sources */, 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | }; 302 | 7B0F9FCF19C448D500538BC0 /* Sources */ = { 303 | isa = PBXSourcesBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | 7B0FA00519C4496E00538BC0 /* SKColor+Extensions.swift in Sources */, 307 | 7B0FA00D19C4496E00538BC0 /* SKTTimingFunctions.swift in Sources */, 308 | 7B0FA00119C4496E00538BC0 /* SKAction+Extensions.swift in Sources */, 309 | 7B0F9FEA19C4490500538BC0 /* IntTests.swift in Sources */, 310 | 7B0F9FFD19C4496E00538BC0 /* CGVector+Extensions.swift in Sources */, 311 | 7B0FA00919C4496E00538BC0 /* SKTAudio.swift in Sources */, 312 | 7B0F9FF919C4496E00538BC0 /* CGFloat+Extensions.swift in Sources */, 313 | 7B0FA00719C4496E00538BC0 /* SKNode+Extensions.swift in Sources */, 314 | 7B0FA00B19C4496E00538BC0 /* SKTEffects.swift in Sources */, 315 | 7B0F9FE919C4490500538BC0 /* FloatTests.swift in Sources */, 316 | 7B0F9FE719C4490500538BC0 /* CGPointTests.swift in Sources */, 317 | 7B0F9FFF19C4496E00538BC0 /* Int+Extensions.swift in Sources */, 318 | 7B0F9FFB19C4496E00538BC0 /* CGPoint+Extensions.swift in Sources */, 319 | 7B0FA00319C4496E00538BC0 /* SKAction+SpecialEffects.swift in Sources */, 320 | 7B0FA00F19C4496E00538BC0 /* Vector3.swift in Sources */, 321 | 7B0F9FE819C4490500538BC0 /* CGVectorTests.swift in Sources */, 322 | 7BDFA8421A13B2C900AC68C9 /* Vector3Tests.swift in Sources */, 323 | ); 324 | runOnlyForDeploymentPostprocessing = 0; 325 | }; 326 | /* End PBXSourcesBuildPhase section */ 327 | 328 | /* Begin PBXTargetDependency section */ 329 | 7B0F9FD519C448D500538BC0 /* PBXTargetDependency */ = { 330 | isa = PBXTargetDependency; 331 | target = 7B0F9FBD19C448D500538BC0 /* SKTUtils */; 332 | targetProxy = 7B0F9FD419C448D500538BC0 /* PBXContainerItemProxy */; 333 | }; 334 | /* End PBXTargetDependency section */ 335 | 336 | /* Begin PBXVariantGroup section */ 337 | 7B0F9FC719C448D500538BC0 /* Main.storyboard */ = { 338 | isa = PBXVariantGroup; 339 | children = ( 340 | 7B0F9FC819C448D500538BC0 /* Base */, 341 | ); 342 | name = Main.storyboard; 343 | sourceTree = ""; 344 | }; 345 | /* End PBXVariantGroup section */ 346 | 347 | /* Begin XCBuildConfiguration section */ 348 | 7B0F9FDB19C448D500538BC0 /* Debug */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ALWAYS_SEARCH_USER_PATHS = NO; 352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 353 | CLANG_CXX_LIBRARY = "libc++"; 354 | CLANG_ENABLE_MODULES = YES; 355 | CLANG_ENABLE_OBJC_ARC = YES; 356 | CLANG_WARN_BOOL_CONVERSION = YES; 357 | CLANG_WARN_CONSTANT_CONVERSION = YES; 358 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 359 | CLANG_WARN_EMPTY_BODY = YES; 360 | CLANG_WARN_ENUM_CONVERSION = YES; 361 | CLANG_WARN_INT_CONVERSION = YES; 362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 366 | COPY_PHASE_STRIP = NO; 367 | ENABLE_STRICT_OBJC_MSGSEND = YES; 368 | ENABLE_TESTABILITY = YES; 369 | GCC_C_LANGUAGE_STANDARD = gnu99; 370 | GCC_DYNAMIC_NO_PIC = NO; 371 | GCC_NO_COMMON_BLOCKS = YES; 372 | GCC_OPTIMIZATION_LEVEL = 0; 373 | GCC_PREPROCESSOR_DEFINITIONS = ( 374 | "DEBUG=1", 375 | "$(inherited)", 376 | ); 377 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 378 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 379 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 380 | GCC_WARN_UNDECLARED_SELECTOR = YES; 381 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 382 | GCC_WARN_UNUSED_FUNCTION = YES; 383 | GCC_WARN_UNUSED_VARIABLE = YES; 384 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 385 | MTL_ENABLE_DEBUG_INFO = YES; 386 | ONLY_ACTIVE_ARCH = YES; 387 | SDKROOT = iphoneos; 388 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 389 | TARGETED_DEVICE_FAMILY = "1,2"; 390 | }; 391 | name = Debug; 392 | }; 393 | 7B0F9FDC19C448D500538BC0 /* Release */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ALWAYS_SEARCH_USER_PATHS = NO; 397 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 398 | CLANG_CXX_LIBRARY = "libc++"; 399 | CLANG_ENABLE_MODULES = YES; 400 | CLANG_ENABLE_OBJC_ARC = YES; 401 | CLANG_WARN_BOOL_CONVERSION = YES; 402 | CLANG_WARN_CONSTANT_CONVERSION = YES; 403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 404 | CLANG_WARN_EMPTY_BODY = YES; 405 | CLANG_WARN_ENUM_CONVERSION = YES; 406 | CLANG_WARN_INT_CONVERSION = YES; 407 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 411 | COPY_PHASE_STRIP = YES; 412 | ENABLE_NS_ASSERTIONS = NO; 413 | ENABLE_STRICT_OBJC_MSGSEND = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu99; 415 | GCC_NO_COMMON_BLOCKS = YES; 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 423 | MTL_ENABLE_DEBUG_INFO = NO; 424 | SDKROOT = iphoneos; 425 | TARGETED_DEVICE_FAMILY = "1,2"; 426 | VALIDATE_PRODUCT = YES; 427 | }; 428 | name = Release; 429 | }; 430 | 7B0F9FDE19C448D500538BC0 /* Debug */ = { 431 | isa = XCBuildConfiguration; 432 | buildSettings = { 433 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 434 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 435 | INFOPLIST_FILE = SKTUtils/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = "com.razeware.$(PRODUCT_NAME:rfc1034identifier)"; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_VERSION = 3.0; 440 | }; 441 | name = Debug; 442 | }; 443 | 7B0F9FDF19C448D500538BC0 /* Release */ = { 444 | isa = XCBuildConfiguration; 445 | buildSettings = { 446 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 447 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 448 | INFOPLIST_FILE = SKTUtils/Info.plist; 449 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 450 | PRODUCT_BUNDLE_IDENTIFIER = "com.razeware.$(PRODUCT_NAME:rfc1034identifier)"; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 453 | SWIFT_VERSION = 3.0; 454 | }; 455 | name = Release; 456 | }; 457 | 7B0F9FE119C448D500538BC0 /* Debug */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | BUNDLE_LOADER = "$(TEST_HOST)"; 461 | FRAMEWORK_SEARCH_PATHS = ( 462 | "$(SDKROOT)/Developer/Library/Frameworks", 463 | "$(inherited)", 464 | ); 465 | GCC_PREPROCESSOR_DEFINITIONS = ( 466 | "DEBUG=1", 467 | "$(inherited)", 468 | ); 469 | INFOPLIST_FILE = SKTUtilsTests/Info.plist; 470 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 471 | PRODUCT_BUNDLE_IDENTIFIER = "com.razeware.$(PRODUCT_NAME:rfc1034identifier)"; 472 | PRODUCT_NAME = "$(TARGET_NAME)"; 473 | SWIFT_VERSION = 3.0; 474 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SKTUtils.app/SKTUtils"; 475 | }; 476 | name = Debug; 477 | }; 478 | 7B0F9FE219C448D500538BC0 /* Release */ = { 479 | isa = XCBuildConfiguration; 480 | buildSettings = { 481 | BUNDLE_LOADER = "$(TEST_HOST)"; 482 | FRAMEWORK_SEARCH_PATHS = ( 483 | "$(SDKROOT)/Developer/Library/Frameworks", 484 | "$(inherited)", 485 | ); 486 | INFOPLIST_FILE = SKTUtilsTests/Info.plist; 487 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 488 | PRODUCT_BUNDLE_IDENTIFIER = "com.razeware.$(PRODUCT_NAME:rfc1034identifier)"; 489 | PRODUCT_NAME = "$(TARGET_NAME)"; 490 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 491 | SWIFT_VERSION = 3.0; 492 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SKTUtils.app/SKTUtils"; 493 | }; 494 | name = Release; 495 | }; 496 | /* End XCBuildConfiguration section */ 497 | 498 | /* Begin XCConfigurationList section */ 499 | 7B0F9FB919C448D500538BC0 /* Build configuration list for PBXProject "SKTUtils" */ = { 500 | isa = XCConfigurationList; 501 | buildConfigurations = ( 502 | 7B0F9FDB19C448D500538BC0 /* Debug */, 503 | 7B0F9FDC19C448D500538BC0 /* Release */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Release; 507 | }; 508 | 7B0F9FDD19C448D500538BC0 /* Build configuration list for PBXNativeTarget "SKTUtils" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | 7B0F9FDE19C448D500538BC0 /* Debug */, 512 | 7B0F9FDF19C448D500538BC0 /* Release */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Release; 516 | }; 517 | 7B0F9FE019C448D500538BC0 /* Build configuration list for PBXNativeTarget "SKTUtilsTests" */ = { 518 | isa = XCConfigurationList; 519 | buildConfigurations = ( 520 | 7B0F9FE119C448D500538BC0 /* Debug */, 521 | 7B0F9FE219C448D500538BC0 /* Release */, 522 | ); 523 | defaultConfigurationIsVisible = 0; 524 | defaultConfigurationName = Release; 525 | }; 526 | /* End XCConfigurationList section */ 527 | }; 528 | rootObject = 7B0F9FB619C448D500538BC0 /* Project object */; 529 | } 530 | --------------------------------------------------------------------------------