├── 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 | 
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 | 
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 |
--------------------------------------------------------------------------------