├── SixFeetBetween_WWDC20SwiftChallenge ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Sound Effect │ ├── jump.wav │ ├── clock1.wav │ ├── coin1.mp3 │ ├── coin2.wav │ ├── coin3.wav │ ├── coin4.wav │ ├── coin5.wav │ ├── coin6.wav │ ├── coin7.wav │ ├── error1.wav │ ├── error2.wav │ ├── failure1.wav │ ├── failure2.wav │ ├── failure3.wav │ ├── success1.wav │ ├── success2.wav │ ├── success3.wav │ └── success4.wav ├── Visual Assets │ ├── factory1.png │ ├── factory2.png │ ├── research.png │ ├── production.png │ ├── wash_hand.png │ └── humanw_mask.png ├── WWDC20PlaygroundTest.xcdatamodeld │ ├── .xccurrentversion │ └── WWDC20PlaygroundTest.xcdatamodel │ │ └── contents ├── Views │ ├── IconView.swift │ ├── BackgroundView.swift │ ├── StoryView.swift │ ├── BriefLogisticExplanationView.swift │ ├── TutorialView.swift │ ├── ButtonStackView.swift │ ├── NPCUI.swift │ ├── GameFailureView.swift │ ├── StoryTextIntroView.swift │ ├── ContentView.swift │ ├── GameSuccessView.swift │ ├── InternalTutorialView.swift │ ├── InternalTutorialPartialView.swift │ └── PlayerUI.swift ├── Support │ ├── AnimationPresets.swift │ ├── MediaSupport.swift │ └── GameLogistics.swift ├── Data │ ├── DataModel.swift │ └── Calculation.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── SceneDelegate.swift └── AppDelegate.swift ├── 6 Feet Between.playgroundbook └── Contents │ ├── UserModules │ └── GameFoundationModule.playgroundmodule │ │ └── Sources │ │ ├── SharedCode.swift │ │ ├── IconView.swift │ │ ├── AnimationPresets.swift │ │ ├── DataModel.swift │ │ ├── BackgroundView.swift │ │ ├── StoryView.swift │ │ ├── MediaSupport.swift │ │ ├── BriefLogisticExplanationView.swift │ │ ├── GameLogistics.swift │ │ ├── TutorialView.swift │ │ ├── ButtonStackView.swift │ │ ├── NPCUI.swift │ │ ├── Calculation.swift │ │ ├── GameFailureView.swift │ │ ├── StoryTextIntroView.swift │ │ ├── ContentView.swift │ │ ├── GameSuccessView.swift │ │ ├── InternalTutorialView.swift │ │ ├── InternalTutorialPartialView.swift │ │ └── PlayerUI.swift │ ├── PrivateResources │ ├── jump.wav │ ├── clock1.wav │ ├── clock2.wav │ ├── coin7.wav │ ├── error1.wav │ ├── error2.wav │ ├── factory1.png │ ├── factory2.png │ ├── research.png │ ├── success2.wav │ ├── wash_hand.png │ ├── 6FeetBetween.png │ ├── Ninja_Circle.png │ ├── humanw:mask.png │ └── production.png │ ├── Chapters │ └── 6FeetBetween.playgroundchapter │ │ ├── Pages │ │ ├── Go Game.playgroundpage │ │ │ ├── Manifest.plist │ │ │ └── main.swift │ │ ├── Template.playgroundpage │ │ │ ├── Manifest.plist │ │ │ └── main.swift │ │ ├── Get Some Training.playgroundpage │ │ │ ├── Manifest.plist │ │ │ └── main.swift │ │ └── The Story Behind.playgroundpage │ │ │ ├── Manifest.plist │ │ │ └── main.swift │ │ └── Manifest.plist │ └── Manifest.plist ├── SixFeetBetween_WWDC20SwiftChallenge.xcodeproj ├── xcuserdata │ └── tony.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── LICENSE └── README.md /SixFeetBetween_WWDC20SwiftChallenge/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/SharedCode.swift: -------------------------------------------------------------------------------- 1 | // Code inside modules can be shared between pages and other source files. 2 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/jump.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/jump.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/clock1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/clock1.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin1.mp3 -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin2.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin3.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin4.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin5.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin6.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/coin7.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/error1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/error1.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/error2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/error2.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure1.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure2.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/failure3.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success1.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success2.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success3.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Sound Effect/success4.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/factory1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/factory1.png -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/factory2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/factory2.png -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/research.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/research.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/jump.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/jump.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/production.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/production.png -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/wash_hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/wash_hand.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/clock1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/clock1.wav -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/clock2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/clock2.wav -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/coin7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/coin7.wav -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/error1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/error1.wav -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/error2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/error2.wav -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/humanw_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/SixFeetBetween_WWDC20SwiftChallenge/Visual Assets/humanw_mask.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/factory1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/factory1.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/factory2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/factory2.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/research.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/research.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/success2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/success2.wav -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/wash_hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/wash_hand.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/6FeetBetween.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/6FeetBetween.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/Ninja_Circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/Ninja_Circle.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/humanw:mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/humanw:mask.png -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/PrivateResources/production.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TonyTang2001/SixFeetBetween_WWDC20SwiftChallenge/HEAD/6 Feet Between.playgroundbook/Contents/PrivateResources/production.png -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/xcuserdata/tony.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/WWDC20PlaygroundTest.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | WWDC20PlaygroundTest.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/WWDC20PlaygroundTest.xcdatamodeld/WWDC20PlaygroundTest.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Go Game.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | Go Game 7 | LiveViewEdgeToEdge 8 | 9 | LiveViewMode 10 | HiddenByDefault 11 | 12 | 13 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Template.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | Template Page 7 | LiveViewEdgeToEdge 8 | 9 | LiveViewMode 10 | HiddenByDefault 11 | 12 | 13 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Get Some Training.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | Get Some Training 7 | LiveViewEdgeToEdge 8 | 9 | LiveViewMode 10 | HiddenByDefault 11 | 12 | 13 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/The Story Behind.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | The Story Behind 7 | LiveViewEdgeToEdge 8 | 9 | LiveViewMode 10 | HiddenByDefault 11 | 12 | 13 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/xcuserdata/tony.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WWDC20PlaygroundTest.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | 6FeetBetween 7 | TemplatePageFilename 8 | Template.playgroundpage 9 | InitialUserPages 10 | 11 | The Story Behind.playgroundpage 12 | Get Some Training.playgroundpage 13 | Go Game.playgroundpage 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/IconView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct FactoryView: View { 12 | var body: some View { 13 | Image(uiImage: UIImage(named: "factory1")!) 14 | .resizable() 15 | .renderingMode(.template) 16 | .foregroundColor(Color(UIColor.label)) 17 | } 18 | } 19 | 20 | struct LabView: View { 21 | var body: some View { 22 | Image(uiImage: UIImage(named: "research")!) 23 | .resizable() 24 | .renderingMode(.template) 25 | .foregroundColor(Color(UIColor.label)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/IconView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct FactoryView: View { 12 | var body: some View { 13 | Image(uiImage: UIImage(named: "factory1")!) 14 | .resizable() 15 | .renderingMode(.template) 16 | .foregroundColor(Color(UIColor.label)) 17 | } 18 | } 19 | 20 | struct LabView: View { 21 | var body: some View { 22 | Image(uiImage: UIImage(named: "research")!) 23 | .resizable() 24 | .renderingMode(.template) 25 | .foregroundColor(Color(UIColor.label)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Support/AnimationPresets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationPresets.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct Shake: GeometryEffect { 13 | var amount: CGFloat = 8 14 | var shakesPerUnit: CGFloat = 5 15 | 16 | public var animatableData: CGFloat 17 | 18 | public func effectValue(size: CGSize) -> ProjectionTransform { 19 | ProjectionTransform( 20 | CGAffineTransform(translationX: amount * sin(animatableData * .pi * shakesPerUnit), y: 0) 21 | ) 22 | } 23 | } 24 | 25 | public extension Animation { 26 | 27 | static let playerMove = Animation.spring(response: 0.3, dampingFraction: 0.75, blendDuration: 0) 28 | 29 | static let npcTransition = Animation.easeInOut.speed(0.6) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/The Story Behind.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # 6 Feet Between - The Story Behind 3 | 4 | ## Welcome to 6 Feet Between! 5 | This is a 🎮 of future. On this page, you will get to knwo about the story behind the 🎮 and your duty as part of it. 6 | 7 | Click the **Run** button located on the lower right of your screen to read the animated story. 8 | 9 | - - - 10 | 11 | - Important: 12 | To improve the program's overall performance, make sure to toggle off **Results** in the **Performance Panel** ⏱ located at the bottom of the screen, to the left of the Swift Playground Run/Stop Button. 13 | 14 | - It is recommended to run this project on a 2018 and later iPad Pro model. Older devices may need longer compile and run time. 15 | */ 16 | 17 | //#-hidden-code 18 | // The Story Behind Page 19 | import PlaygroundSupport 20 | PlaygroundPage.current.setLiveView(StoryView()) 21 | //#-end-hidden-code -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/AnimationPresets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationPresets.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct Shake: GeometryEffect { 13 | var amount: CGFloat = 8 14 | var shakesPerUnit: CGFloat = 5 15 | 16 | public var animatableData: CGFloat 17 | 18 | public func effectValue(size: CGSize) -> ProjectionTransform { 19 | ProjectionTransform( 20 | CGAffineTransform(translationX: amount * sin(animatableData * .pi * shakesPerUnit), y: 0) 21 | ) 22 | } 23 | } 24 | 25 | public extension Animation { 26 | 27 | static let playerMove = Animation.spring(response: 0.3, dampingFraction: 0.75, blendDuration: 0) 28 | 29 | static let npcTransition = Animation.easeInOut.speed(0.6) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Description 6 | A game to educate people about keeping social distance. 7 | ImageReference 8 | 6FeetBetween.png 9 | Chapters 10 | 11 | 6FeetBetween.playgroundchapter 12 | 13 | ContentIdentifier 14 | com.tonytangzixuan.playgroundbook.sixFeetBetween 15 | ContentVersion 16 | 1.0 17 | DeploymentTarget 18 | ios-current 19 | DevelopmentRegion 20 | en 21 | SwiftVersion 22 | 5.1 23 | Version 24 | 7.0 25 | UserAutoImportedAuxiliaryModules 26 | 27 | UserModuleMode 28 | Full 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 TonyTang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Get Some Training.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # 6 Feet Between - Get Some Training 3 | 4 | Now you know the story, please get some training. You will need to know some basic actions to nagivate through this game. This won't take long. 5 | 6 | - - - 7 | 8 | - callout(Anonymous Man): 9 | Hi there, be sure to practice some basic actions before you start your 🎮! 10 | You know... Practice makes perfect! 11 | 12 | 13 | Here are a few **tips** for you: 14 | * Protect Yourself, be sure to stay 6 feet away from others! Even during your blink! Otherwise you may get infected and lose the game. 15 | * Mind your setps, try **not** to go out of screen. The system won't let you do so. 16 | * If you are unsure about where to blink to, drag and hold to freeze the time for some thinking. 17 | 18 | - Important: 19 | To improve the program's overall performance, make sure to toggle off **Results** in the **Performance Panel** ⏱ located at the bottom of the screen, to the left of the Swift Playground Run/Stop Button. 20 | 21 | - Remember to switch on the ringtone and turn volumn to 50% 🔊 for the sound effects! 22 | 23 | - It is recommended to run this project on a 2018 and later iPad Pro model. Older devices may need longer compile and run time. 24 | */ 25 | 26 | //#-hidden-code 27 | // et Some Training 28 | import PlaygroundSupport 29 | PlaygroundPage.current.setLiveView(TutorialView()) 30 | //#-end-hidden-code -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Data/DataModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataModel.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/8/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct ScreenCoordinate: Hashable { 13 | public var x: CGFloat 14 | public var y: CGFloat 15 | 16 | public init(x: CGFloat, y: CGFloat) { 17 | self.x = x 18 | self.y = y 19 | } 20 | } 21 | 22 | // MARK: - View Information 23 | public var viewWidth: CGFloat = 0 24 | public var viewHeight: CGFloat = 0 25 | 26 | // MARK: - Game Setting 27 | public let mapIconSize: CGFloat = 50 28 | public let npcCount: Int = 15 29 | public let npcSize: CGFloat = 30 30 | public let playerSize: CGFloat = 35 31 | public let npcWarningRangeSize: CGFloat = npcSize * 1.5 32 | public let safetyDistance: CGFloat = (npcSize*2 + playerSize)/2 33 | 34 | // MARK: - Player Info 35 | public var previousPosition = ScreenCoordinate(x: 0, y: 0) 36 | public var currentPosition: CGSize = .zero 37 | public var newPosition: CGSize = .zero 38 | public var playerColor: Color = Color(UIColor.systemBlue) 39 | public var playerPathColor: Color = Color(UIColor.systemBlue) 40 | 41 | // MARK: - NPC Coordination 42 | public var npcCoords: [[ScreenCoordinate]] = [[]] 43 | 44 | // MARK: - Game Status 45 | public var playerWon: Bool = false 46 | public var started: Bool = false 47 | public var endOnHold: Bool = false 48 | public var startTime: Date = Date() 49 | public var endTime: Date = Date() 50 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/DataModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataModel.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/8/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct ScreenCoordinate: Hashable { 13 | public var x: CGFloat 14 | public var y: CGFloat 15 | 16 | public init(x: CGFloat, y: CGFloat) { 17 | self.x = x 18 | self.y = y 19 | } 20 | } 21 | 22 | // MARK: - View Information 23 | public var viewWidth: CGFloat = 0 24 | public var viewHeight: CGFloat = 0 25 | 26 | // MARK: - Game Setting 27 | public let mapIconSize: CGFloat = 50 28 | public let npcCount: Int = 15 29 | public let npcSize: CGFloat = 30 30 | public let playerSize: CGFloat = 35 31 | public let npcWarningRangeSize: CGFloat = npcSize * 1.5 32 | public let safetyDistance: CGFloat = (npcSize*2 + playerSize)/2 33 | 34 | // MARK: - Player Info 35 | public var previousPosition = ScreenCoordinate(x: 0, y: 0) 36 | public var currentPosition: CGSize = .zero 37 | public var newPosition: CGSize = .zero 38 | public var playerColor: Color = Color(UIColor.systemBlue) 39 | public var playerPathColor: Color = Color(UIColor.systemBlue) 40 | 41 | // MARK: - NPC Coordination 42 | public var npcCoords: [[ScreenCoordinate]] = [[]] 43 | 44 | // MARK: - Game Status 45 | public var playerWon: Bool = false 46 | public var started: Bool = false 47 | public var endOnHold: Bool = false 48 | public var startTime: Date = Date() 49 | public var endTime: Date = Date() 50 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Go Game.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # 6 Feet Between - Go Game! 3 | 4 | After your previous training, I am sure you know what to do by now. You are doing great, and I am fully confident in you! 😃 5 | 6 | - - - 7 | 8 | But before you start your challenge, here are a few **last tips** for you: 9 | * Protect Yourself, be sure to stay 6 feet away from others! Even during your blink! 10 | * Mind your setps, try **not** to go out of screen. 11 | * If you are unsure about where to blink to, drag and hold to freeze the time. 12 | * Restart the game any time using the Swift Playground **Run/Stop** Button. 13 | 14 | - Important: 15 | To improve the program's overall performance, make sure to toggle off **Results** in the **Performance Panel** ⏱ located at the bottom of the screen, to the left of the Swift Playground Run/Stop Button. 16 | 17 | - Remember to switch on the ringtone and turn volumn to 50% 🔊 for the sound effects! 18 | 19 | - It is recommended to run this project on a 2018 and later iPad Pro model. Older devices may need longer compile and run time. 20 | 21 | - Ready, Set, Go! The clock is ticking. 22 | */ 23 | 24 | //#-hidden-code 25 | // Go Game Page 26 | import PlaygroundSupport 27 | PlaygroundPage.current.setLiveView(ContentView()) 28 | //#-end-hidden-code 29 | 30 | /*: 31 | - Have you noticed the ending words are different each time you replay the game? 😉 32 | 33 | - Do you know you can drag the floating ending(success/failure) card? 🤩 34 | 35 | - This playground support Dark Mode! Now you are a ninja in the dark! 😎 36 | 37 | - Go find some more easter eggs! 38 | */ -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/Chapters/6FeetBetween.playgroundchapter/Pages/Template.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Customization 3 | 4 | Enjoying this game? The current version feels too easy? 5 | 6 | **Try make your own customized settings!** 7 | 8 | - Important: 9 | This modification is only recommended for the ones who have prior coding experience. Carefully follow the instructions below to customize **6 Feet Between** to your favor! 10 | 11 | - - - 12 | # Customizing Instruction 13 | Everyone has their preffered 🎮 modes, may be you want this game to be simpler or harder, or may be you just want to change the color theme of the game to match your preference. You are covered! Just follow these simple steps to build your own version of **6 Feet Between**! 14 | 15 | - All the game setting variables are located in the **DataModel.swift** file inside the **GameFoundationModule** 16 | 17 | - To avoid causing errors, it is recommended to keep a copy of the previous version that works. 18 | 19 | - Should you messed up the codes by chance, just **quite and reset** this playground. The original version work will be recovered. 20 | 21 | - Important: 22 | To improve the program's overall performance, make sure to toggle off **Results** in the **Performance Panel** ⏱ located at the bottom of the screen, to the left of the Swift Playground Run/Stop Button. 23 | 24 | - Remember to switch on the ringtone and turn volumn to 50% 🔊 for the sound effects! 25 | 26 | - It is recommended to run this project on a 2018 and later iPad Pro model. Older devices may need longer compile and run time. 27 | */ 28 | 29 | import PlaygroundSupport 30 | 31 | // Do some changes here to your favor! 32 | 33 | 34 | //#-hidden-code 35 | PlaygroundPage.current.setLiveView(ContentView()) 36 | //#-end-hidden-code -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/BackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct BackgroundView: View { 12 | 13 | @State var animateStart: Bool = false 14 | 15 | @Binding var canvasWidth: CGFloat 16 | @Binding var canvasHeight: CGFloat 17 | 18 | public init(canvasWidth: Binding, canvasHeight: Binding) { 19 | self._canvasWidth = canvasWidth 20 | self._canvasHeight = canvasHeight 21 | } 22 | 23 | var totalShapeCount = 21 24 | let colors = [UIColor.systemRed, UIColor.systemYellow, UIColor.systemBlue, UIColor.systemPink, UIColor.systemTeal, UIColor.systemGray2, UIColor.systemIndigo, UIColor.systemOrange, UIColor.systemGreen, UIColor.systemPink] 25 | 26 | 27 | 28 | public var body: some View { 29 | ZStack { 30 | ForEach(0.., canvasHeight: Binding) { 19 | self._canvasWidth = canvasWidth 20 | self._canvasHeight = canvasHeight 21 | } 22 | 23 | var totalShapeCount = 21 24 | let colors = [UIColor.systemRed, UIColor.systemYellow, UIColor.systemBlue, UIColor.systemPink, UIColor.systemTeal, UIColor.systemGray2, UIColor.systemIndigo, UIColor.systemOrange, UIColor.systemGreen, UIColor.systemPink] 25 | 26 | 27 | 28 | public var body: some View { 29 | ZStack { 30 | ForEach(0.. 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/StoryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/16/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct StoryView: View { 12 | 13 | var endingString = "Go to the next page for some training!" 14 | 15 | @State var animate1Start = false 16 | @State var animate1End = false 17 | @State var animate2Start = false 18 | @State var animate2End = false 19 | @State var animate3Start = false 20 | @State var animate3End = false 21 | @State var animate4Start = false 22 | @State var animate4End = false 23 | @State var animate5Start = false 24 | 25 | public init() {} 26 | 27 | public var body: some View { 28 | ZStack { 29 | 30 | StoryTextIntroView(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start, animate3End: $animate3End) 31 | .opacity(animate3Start ? 0 : 1) 32 | .onAppear { 33 | self.animate1Start = true 34 | DispatchQueue.main.asyncAfter(deadline: .now() + 7) { 35 | self.animate1End = true 36 | } 37 | } 38 | 39 | BriefLogisticExplanationView(animate3Start: $animate3Start, animate3End: $animate3End, animate4Start: $animate4Start, animate4End: $animate4End, animate5Start: $animate5Start) 40 | .opacity(animate5Start ? 0 : 1) 41 | 42 | VStack { 43 | Text(endingString) 44 | .fontWeight(.semibold) 45 | .font(.system(.title, design: .rounded)) 46 | .multilineTextAlignment(.center) 47 | .opacity(self.animate5Start ? 1 : 0) 48 | .offset(y: self.animate5Start ? 0 : 16) 49 | .animation(Animation.easeInOut(duration: 1)) 50 | } 51 | .opacity(animate5Start ? 1 : 0) 52 | 53 | } 54 | 55 | } 56 | } 57 | 58 | struct StoryView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | StoryView() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/StoryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/16/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct StoryView: View { 12 | 13 | var endingString = "Go to the next page for some training!" 14 | 15 | @State var animate1Start = false 16 | @State var animate1End = false 17 | @State var animate2Start = false 18 | @State var animate2End = false 19 | @State var animate3Start = false 20 | @State var animate3End = false 21 | @State var animate4Start = false 22 | @State var animate4End = false 23 | @State var animate5Start = false 24 | 25 | public init() {} 26 | 27 | public var body: some View { 28 | ZStack { 29 | 30 | StoryTextIntroView(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start, animate3End: $animate3End) 31 | .opacity(animate3Start ? 0 : 1) 32 | .onAppear { 33 | self.animate1Start = true 34 | DispatchQueue.main.asyncAfter(deadline: .now() + 7) { 35 | self.animate1End = true 36 | } 37 | } 38 | 39 | BriefLogisticExplanationView(animate3Start: $animate3Start, animate3End: $animate3End, animate4Start: $animate4Start, animate4End: $animate4End, animate5Start: $animate5Start) 40 | .opacity(animate5Start ? 0 : 1) 41 | 42 | VStack { 43 | Text(endingString) 44 | .fontWeight(.semibold) 45 | .font(.system(.title, design: .rounded)) 46 | .multilineTextAlignment(.center) 47 | .opacity(self.animate5Start ? 1 : 0) 48 | .offset(y: self.animate5Start ? 0 : 16) 49 | .animation(Animation.easeInOut(duration: 1)) 50 | } 51 | .opacity(animate5Start ? 1 : 0) 52 | 53 | } 54 | 55 | } 56 | } 57 | 58 | struct StoryView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | StoryView() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Support/MediaSupport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MediaModule.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/16/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | 12 | public var audioPlayer: AVAudioPlayer? 13 | public var audioPlayer2: AVAudioPlayer? 14 | public var audioPlayerBG: AVAudioPlayer? 15 | 16 | extension AVAudioPlayer { 17 | 18 | static func playSound(sound: String, type: String) { 19 | guard let path = Bundle.main.path(forResource: sound, ofType: type) else { 20 | fatalError("Could not find path for audio file named: \(sound)") 21 | } 22 | do { 23 | // var audioPlayer: AVAudioPlayer? 24 | audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) 25 | audioPlayer?.volume = 0.5 26 | audioPlayer?.play() 27 | } catch { 28 | print("Error: Could not play sound file") 29 | } 30 | 31 | } 32 | 33 | static func playSound2(sound: String, type: String) { 34 | guard let path = Bundle.main.path(forResource: sound, ofType: type) else { 35 | fatalError("Could not find path for audio file named: \(sound)") 36 | } 37 | do { 38 | audioPlayer2 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) 39 | audioPlayer2?.volume = 0.1 40 | audioPlayer2?.play() 41 | } catch { 42 | print("Error: Could not play sound file") 43 | } 44 | 45 | } 46 | 47 | static func startPlaySoundBG() { 48 | guard let path = Bundle.main.path(forResource: "clock1", ofType: "wav") else { 49 | fatalError("Could not find background audio file") 50 | } 51 | do { 52 | audioPlayerBG = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) 53 | audioPlayerBG?.rate = 4 54 | audioPlayerBG?.numberOfLoops = -1 55 | audioPlayerBG?.volume = 0.08 56 | audioPlayerBG?.play() 57 | } catch { 58 | print("Error: Could not play sound file") 59 | } 60 | 61 | } 62 | 63 | static func stopPlaySoundBG() { 64 | audioPlayerBG?.stop() 65 | } 66 | 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/MediaSupport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MediaModule.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/16/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | 12 | public var audioPlayer: AVAudioPlayer? 13 | public var audioPlayer2: AVAudioPlayer? 14 | public var audioPlayerBG: AVAudioPlayer? 15 | 16 | extension AVAudioPlayer { 17 | 18 | static func playSound(sound: String, type: String) { 19 | guard let path = Bundle.main.path(forResource: sound, ofType: type) else { 20 | fatalError("Could not find path for audio file named: \(sound)") 21 | } 22 | do { 23 | // var audioPlayer: AVAudioPlayer? 24 | audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) 25 | audioPlayer?.volume = 0.5 26 | audioPlayer?.play() 27 | } catch { 28 | print("Error: Could not play sound file") 29 | } 30 | 31 | } 32 | 33 | static func playSound2(sound: String, type: String) { 34 | guard let path = Bundle.main.path(forResource: sound, ofType: type) else { 35 | fatalError("Could not find path for audio file named: \(sound)") 36 | } 37 | do { 38 | audioPlayer2 = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) 39 | audioPlayer2?.volume = 0.1 40 | audioPlayer2?.play() 41 | } catch { 42 | print("Error: Could not play sound file") 43 | } 44 | 45 | } 46 | 47 | static func startPlaySoundBG() { 48 | guard let path = Bundle.main.path(forResource: "clock1", ofType: "wav") else { 49 | fatalError("Could not find background audio file") 50 | } 51 | do { 52 | audioPlayerBG = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) 53 | audioPlayerBG?.rate = 4 54 | audioPlayerBG?.numberOfLoops = -1 55 | audioPlayerBG?.volume = 0.08 56 | audioPlayerBG?.play() 57 | } catch { 58 | print("Error: Could not play sound file") 59 | } 60 | 61 | } 62 | 63 | static func stopPlaySoundBG() { 64 | audioPlayerBG?.stop() 65 | } 66 | 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/BriefLogisticExplanationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriefLogisticExplanationView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct BriefLogisticExplanationView: View { 12 | 13 | @Binding var animate3Start: Bool 14 | @Binding var animate3End : Bool 15 | @Binding var animate4Start: Bool 16 | @Binding var animate4End : Bool 17 | @Binding var animate5Start: Bool 18 | 19 | public init( 20 | animate3Start: Binding, 21 | animate3End: Binding, 22 | animate4Start: Binding, 23 | animate4End: Binding, 24 | animate5Start: Binding) { 25 | 26 | self._animate3Start = animate3Start 27 | self._animate3End = animate3End 28 | self._animate4Start = animate4Start 29 | self._animate4End = animate4End 30 | self._animate5Start = animate5Start 31 | 32 | } 33 | 34 | public var body: some View { 35 | VStack { 36 | Spacer() 37 | 38 | FactoryView() 39 | .frame(width: 100, height: 100) 40 | .opacity(animate3Start ? 1 : 0) 41 | .animation(Animation.easeInOut) 42 | .offset(y: self.animate5Start ? -1000 : 0) 43 | 44 | Image(uiImage: UIImage(named: "Ninja_Circle")!) 45 | .resizable() 46 | .frame(width: playerSize, height: playerSize) 47 | .opacity(animate4Start ? 1 : 0) 48 | .animation(Animation.easeInOut) 49 | .offset(y: animate4Start ? 0 : 20) 50 | 51 | Spacer() 52 | 53 | Image(systemName: "chevron.left.2") 54 | .resizable() 55 | .foregroundColor(playerPathColor) 56 | .rotationEffect(Angle(degrees: 90)) 57 | .frame(width: playerSize, height: playerSize) 58 | .opacity((animate3Start && !animate4Start) ? 1 : 0) 59 | .animation(Animation.easeInOut) 60 | .offset(y: animate4Start ? -40 : 0) 61 | 62 | Image(uiImage: UIImage(named: "Ninja_Circle")!) 63 | .resizable() 64 | .frame(width: playerSize, height: playerSize) 65 | .opacity((animate3Start && !animate4Start) ? 1 : 0) 66 | .animation(Animation.easeInOut) 67 | .offset(y: animate4Start ? -60 : 0) 68 | 69 | LabView() 70 | .frame(width: 100, height: 100) 71 | .opacity(animate3Start ? 1 : 0) 72 | .animation(Animation.easeInOut) 73 | .offset(y: self.animate5Start ? 1000 : 0) 74 | 75 | Spacer() 76 | 77 | ButtonStackView2(animate3End: $animate3End, animate4Start: $animate4Start, animate4End: $animate4End, animate5Start: $animate5Start) 78 | 79 | Spacer() 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/BriefLogisticExplanationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriefLogisticExplanationView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct BriefLogisticExplanationView: View { 12 | 13 | @Binding var animate3Start: Bool 14 | @Binding var animate3End : Bool 15 | @Binding var animate4Start: Bool 16 | @Binding var animate4End : Bool 17 | @Binding var animate5Start: Bool 18 | 19 | public init( 20 | animate3Start: Binding, 21 | animate3End: Binding, 22 | animate4Start: Binding, 23 | animate4End: Binding, 24 | animate5Start: Binding) { 25 | 26 | self._animate3Start = animate3Start 27 | self._animate3End = animate3End 28 | self._animate4Start = animate4Start 29 | self._animate4End = animate4End 30 | self._animate5Start = animate5Start 31 | 32 | } 33 | 34 | public var body: some View { 35 | VStack { 36 | Spacer() 37 | 38 | FactoryView() 39 | .frame(width: 100, height: 100) 40 | .opacity(animate3Start ? 1 : 0) 41 | .animation(Animation.easeInOut) 42 | .offset(y: self.animate5Start ? -1000 : 0) 43 | 44 | Image(uiImage: UIImage(named: "Ninja_Circle")!) 45 | .resizable() 46 | .frame(width: playerSize, height: playerSize) 47 | .opacity(animate4Start ? 1 : 0) 48 | .animation(Animation.easeInOut) 49 | .offset(y: animate4Start ? 0 : 20) 50 | 51 | Spacer() 52 | 53 | Image(systemName: "chevron.left.2") 54 | .resizable() 55 | .foregroundColor(playerPathColor) 56 | .rotationEffect(Angle(degrees: 90)) 57 | .frame(width: playerSize, height: playerSize) 58 | .opacity((animate3Start && !animate4Start) ? 1 : 0) 59 | .animation(Animation.easeInOut) 60 | .offset(y: animate4Start ? -40 : 0) 61 | 62 | Image(uiImage: UIImage(named: "Ninja_Circle")!) 63 | .resizable() 64 | .frame(width: playerSize, height: playerSize) 65 | .opacity((animate3Start && !animate4Start) ? 1 : 0) 66 | .animation(Animation.easeInOut) 67 | .offset(y: animate4Start ? -60 : 0) 68 | 69 | LabView() 70 | .frame(width: 100, height: 100) 71 | .opacity(animate3Start ? 1 : 0) 72 | .animation(Animation.easeInOut) 73 | .offset(y: self.animate5Start ? 1000 : 0) 74 | 75 | Spacer() 76 | 77 | ButtonStackView2(animate3End: $animate3End, animate4Start: $animate4Start, animate4End: $animate4End, animate5Start: $animate5Start) 78 | 79 | Spacer() 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Support/GameLogistics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameLogistics.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | // generate NPC coordinate when game starts 13 | public func generateNPCCoords(viewWidth: CGFloat, viewHeight: CGFloat) -> [[ScreenCoordinate]] { 14 | var resultCoords: [[ScreenCoordinate]] = [[]] 15 | 16 | for _ in 0.. Bool { 49 | let playerCoord = getPlayerCoord() 50 | if playerCoord.x <= viewWidth/2 + 2*mapIconSize && 51 | playerCoord.x >= viewWidth/2 - 2*mapIconSize && 52 | playerCoord.y <= 2*mapIconSize { 53 | return true 54 | } 55 | return false 56 | } 57 | 58 | // check if game ends 59 | public func gameStateCheck() -> (ended: Bool, succeeded: Bool) { 60 | var isEnded = false 61 | var isSucceeded = false 62 | let playerPosition = getPlayerCoord() 63 | 64 | if playerArriveDest() { 65 | isEnded = true 66 | isSucceeded = true 67 | return (isEnded, isSucceeded) 68 | } 69 | 70 | npcCoords.forEach { npcCoordArray in 71 | var npcCoordNow: ScreenCoordinate 72 | 73 | if npcCoordArray.endIndex < 2 { 74 | npcCoordNow = npcCoordArray[npcCoordArray.endIndex-1] 75 | } else { 76 | npcCoordNow = npcCoordArray[npcCoordArray.endIndex-1] 77 | } 78 | 79 | let distance = distanceFromPoint(p: npcCoordNow, toLineSegment: previousPosition, and: playerPosition) 80 | 81 | if distance < safetyDistance { 82 | isEnded = true 83 | isSucceeded = false 84 | } 85 | } 86 | 87 | return (isEnded, isSucceeded) 88 | } 89 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/GameLogistics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameLogistics.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | // generate NPC coordinate when game starts 13 | public func generateNPCCoords(viewWidth: CGFloat, viewHeight: CGFloat) -> [[ScreenCoordinate]] { 14 | var resultCoords: [[ScreenCoordinate]] = [[]] 15 | 16 | for _ in 0.. Bool { 49 | let playerCoord = getPlayerCoord() 50 | if playerCoord.x <= viewWidth/2 + 2*mapIconSize && 51 | playerCoord.x >= viewWidth/2 - 2*mapIconSize && 52 | playerCoord.y <= 2*mapIconSize { 53 | return true 54 | } 55 | return false 56 | } 57 | 58 | // check if game ends 59 | public func gameStateCheck() -> (ended: Bool, succeeded: Bool) { 60 | var isEnded = false 61 | var isSucceeded = false 62 | let playerPosition = getPlayerCoord() 63 | 64 | if playerArriveDest() { 65 | isEnded = true 66 | isSucceeded = true 67 | return (isEnded, isSucceeded) 68 | } 69 | 70 | npcCoords.forEach { npcCoordArray in 71 | var npcCoordNow: ScreenCoordinate 72 | 73 | if npcCoordArray.endIndex < 2 { 74 | npcCoordNow = npcCoordArray[npcCoordArray.endIndex-1] 75 | } else { 76 | npcCoordNow = npcCoordArray[npcCoordArray.endIndex-1] 77 | } 78 | 79 | let distance = distanceFromPoint(p: npcCoordNow, toLineSegment: previousPosition, and: playerPosition) 80 | 81 | if distance < safetyDistance { 82 | isEnded = true 83 | isSucceeded = false 84 | } 85 | } 86 | 87 | return (isEnded, isSucceeded) 88 | } 89 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/TutorialView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TutorialView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/16/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct TutorialView: View { 12 | 13 | var tutorial1String = ["Welcome to training session,", 14 | "You will be taught several skills", 15 | "to better navigate through difficulties."] 16 | 17 | public init() {} 18 | 19 | @State var animate1Start = false 20 | @State var animate1End = false 21 | @State var animate2Start = false 22 | @State var animate2End = false 23 | @State var animate3Start = false 24 | @State var animate3End = false 25 | 26 | public var body: some View { 27 | ZStack { 28 | VStack { 29 | Spacer() 30 | 31 | VStack { 32 | ForEach(tutorial1String, id: \.self) { string in 33 | Text(string) 34 | .fontWeight(.semibold) 35 | .font(.system(.title, design: .rounded)) 36 | .multilineTextAlignment(.center) 37 | .opacity(self.animate1Start ? 1 : 0) 38 | .offset(y: self.animate1Start ? 0 : 16) 39 | .offset(y: 10 * CGFloat(self.tutorial1String.firstIndex(of: string)!)) 40 | .animation(Animation.easeInOut(duration: 1).delay( 41 | Double(self.tutorial1String.firstIndex(of: string)!) 42 | ) 43 | ) 44 | } 45 | .opacity(animate2Start ? 0 : 1) 46 | } 47 | 48 | Spacer() 49 | 50 | ZStack { 51 | Button(action: { 52 | self.animate2Start = true 53 | 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 55 | self.animate2End = true 56 | } 57 | }) { 58 | Text("Next") 59 | .fontWeight(.semibold) 60 | .font(.system(.title, design: .rounded)) 61 | } 62 | .opacity((animate1End && !animate2Start) ? 1 : 0) 63 | 64 | } 65 | Spacer() 66 | } 67 | .opacity(animate3Start ? 0 : 1) 68 | .onAppear { 69 | self.animate1Start = true 70 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 71 | self.animate1End = true 72 | } 73 | } 74 | 75 | VStack { 76 | Spacer() 77 | if animate2Start { 78 | InTutorialView() 79 | } 80 | Spacer() 81 | } 82 | 83 | } 84 | 85 | } 86 | } 87 | 88 | 89 | struct TutorialView_Previews: PreviewProvider { 90 | static var previews: some View { 91 | TutorialView() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/TutorialView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TutorialView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/16/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct TutorialView: View { 12 | 13 | var tutorial1String = ["Welcome to training session,", 14 | "You will be taught several skills", 15 | "to better navigate through difficulties."] 16 | 17 | public init() {} 18 | 19 | @State var animate1Start = false 20 | @State var animate1End = false 21 | @State var animate2Start = false 22 | @State var animate2End = false 23 | @State var animate3Start = false 24 | @State var animate3End = false 25 | 26 | public var body: some View { 27 | ZStack { 28 | VStack { 29 | Spacer() 30 | 31 | VStack { 32 | ForEach(tutorial1String, id: \.self) { string in 33 | Text(string) 34 | .fontWeight(.semibold) 35 | .font(.system(.title, design: .rounded)) 36 | .multilineTextAlignment(.center) 37 | .opacity(self.animate1Start ? 1 : 0) 38 | .offset(y: self.animate1Start ? 0 : 16) 39 | .offset(y: 10 * CGFloat(self.tutorial1String.firstIndex(of: string)!)) 40 | .animation(Animation.easeInOut(duration: 1).delay( 41 | Double(self.tutorial1String.firstIndex(of: string)!) 42 | ) 43 | ) 44 | } 45 | .opacity(animate2Start ? 0 : 1) 46 | } 47 | 48 | Spacer() 49 | 50 | ZStack { 51 | Button(action: { 52 | self.animate2Start = true 53 | 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 55 | self.animate2End = true 56 | } 57 | }) { 58 | Text("Next") 59 | .fontWeight(.semibold) 60 | .font(.system(.title, design: .rounded)) 61 | } 62 | .opacity((animate1End && !animate2Start) ? 1 : 0) 63 | 64 | } 65 | Spacer() 66 | } 67 | .opacity(animate3Start ? 0 : 1) 68 | .onAppear { 69 | self.animate1Start = true 70 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 71 | self.animate1End = true 72 | } 73 | } 74 | 75 | VStack { 76 | Spacer() 77 | if animate2Start { 78 | InTutorialView() 79 | } 80 | Spacer() 81 | } 82 | 83 | } 84 | 85 | } 86 | } 87 | 88 | 89 | struct TutorialView_Previews: PreviewProvider { 90 | static var previews: some View { 91 | TutorialView() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/6/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Get the managed object context from the shared persistent container. 23 | let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 24 | 25 | // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath. 26 | // Add `@Environment(\.managedObjectContext)` in the views that will need the context. 27 | let contentView = ContentView().environment(\.managedObjectContext, context) 28 | 29 | // Use a UIHostingController as window root view controller. 30 | if let windowScene = scene as? UIWindowScene { 31 | let window = UIWindow(windowScene: windowScene) 32 | window.rootViewController = UIHostingController(rootView: contentView) 33 | self.window = window 34 | window.makeKeyAndVisible() 35 | } 36 | } 37 | 38 | func sceneDidDisconnect(_ scene: UIScene) { 39 | // Called as the scene is being released by the system. 40 | // This occurs shortly after the scene enters the background, or when its session is discarded. 41 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 42 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 43 | } 44 | 45 | func sceneDidBecomeActive(_ scene: UIScene) { 46 | // Called when the scene has moved from an inactive state to an active state. 47 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 48 | } 49 | 50 | func sceneWillResignActive(_ scene: UIScene) { 51 | // Called when the scene will move from an active state to an inactive state. 52 | // This may occur due to temporary interruptions (ex. an incoming phone call). 53 | } 54 | 55 | func sceneWillEnterForeground(_ scene: UIScene) { 56 | // Called as the scene transitions from the background to the foreground. 57 | // Use this method to undo the changes made on entering the background. 58 | } 59 | 60 | func sceneDidEnterBackground(_ scene: UIScene) { 61 | // Called as the scene transitions from the foreground to the background. 62 | // Use this method to save data, release shared resources, and store enough scene-specific state information 63 | // to restore the scene back to its current state. 64 | 65 | // Save changes in the application's managed object context when the application transitions to the background. 66 | (UIApplication.shared.delegate as? AppDelegate)?.saveContext() 67 | } 68 | 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/ButtonStackView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonStackView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct ButtonStackView1: View { 12 | 13 | @Binding var animate1Start: Bool 14 | @Binding var animate1End : Bool 15 | @Binding var animate2Start: Bool 16 | @Binding var animate2End : Bool 17 | @Binding var animate3Start: Bool 18 | @Binding var animate3End : Bool 19 | 20 | public init( 21 | animate1Start: Binding, 22 | animate1End: Binding, 23 | animate2Start: Binding, 24 | animate2End: Binding, 25 | animate3Start: Binding, 26 | animate3End: Binding) { 27 | self._animate1Start = animate1Start 28 | self._animate1End = animate1End 29 | self._animate2Start = animate2Start 30 | self._animate2End = animate2End 31 | self._animate3Start = animate3Start 32 | self._animate3End = animate3End 33 | 34 | } 35 | 36 | public var body: some View { 37 | ZStack { 38 | Button(action: { 39 | self.animate2Start = true 40 | 41 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 42 | self.animate2End = true 43 | } 44 | }) { 45 | Text("Next") 46 | .fontWeight(.semibold) 47 | .font(.system(.title, design: .rounded)) 48 | } 49 | .opacity((animate1End && !animate2Start) ? 1 : 0) 50 | 51 | Button(action: { 52 | self.animate3Start = true 53 | 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 55 | self.animate3End = true 56 | } 57 | }) { 58 | Text("Next") 59 | .fontWeight(.semibold) 60 | .font(.system(.title, design: .rounded)) 61 | } 62 | .opacity((animate2End && !animate3Start) ? 1 : 0) 63 | } 64 | } 65 | } 66 | 67 | public struct ButtonStackView2: View { 68 | 69 | @Binding var animate3End : Bool 70 | @Binding var animate4Start: Bool 71 | @Binding var animate4End : Bool 72 | @Binding var animate5Start: Bool 73 | 74 | public init( 75 | animate3End: Binding, 76 | animate4Start: Binding, 77 | animate4End: Binding, 78 | animate5Start: Binding) { 79 | 80 | self._animate3End = animate3End 81 | self._animate4Start = animate4Start 82 | self._animate4End = animate4End 83 | self._animate5Start = animate5Start 84 | 85 | } 86 | 87 | public var body: some View { 88 | ZStack { 89 | Button(action: { 90 | self.animate4Start = true 91 | 92 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 93 | self.animate4End = true 94 | } 95 | }) { 96 | Text("Next") 97 | .fontWeight(.semibold) 98 | .font(.system(.title, design: .rounded)) 99 | } 100 | .opacity((animate3End && !animate4Start) ? 1 : 0) 101 | 102 | Button(action: { 103 | self.animate5Start = true 104 | }) { 105 | Text("Next") 106 | .fontWeight(.semibold) 107 | .font(.system(.title, design: .rounded)) 108 | } 109 | .opacity((animate4End && !animate5Start) ? 1 : 0) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SixFeetBetween_WWDC20SwiftChallenge 2 | Swift Playground Project "6 Feet Between" written by Zixuan(Tony) Tang is a game written completely with SwiftUI and without any game Kit or engine. The project has been accepted by Apple, being one of the 350 winning projects around the globe. 3 | 4 | You may watch the demo screenrecording of this project on [YouTube](https://youtu.be/sj_laBHKu6I) at your convenience. 5 | 6 | ## Context 7 | In year 2020, Apple holds the [Swift Student Challenge](https://developer.apple.com/wwdc20/swift-student-challenge/) for the first time. Candidates are required to showcase their love of coding by creating an incredible Swift playground on the topic of your choice. This challenge is open to students around the world. 8 | 9 | ## Words from the Author 10 | My iPad Playground Book project named “6 Feet Between” is an interactive game that harnesses the power of the latest Apple software technologies to provide an immersive experience. This game is intended to educate people about keeping social distance as a hygiene practice. In the game story, the human is facing a crisis caused by a virus. The player will become a ninja who tries to save people’s lives by transporting a research sample from the lab to the factory. The ninja needs to keep at least 6 feet away from other pedestrians on the way to the destination. 11 | 12 | 13 | Providing the player with an interactive and entertaining experience, the game comes in three parts, the background story, the instruction, and the game scene. The background story page greets the player when they first open this project. The whole story will be told concisely, along with visual patterns and animations. Simple diagrams combined with animated symbols help the player to understand the game context. The instruction page introduces basic playing actions and rules to the user, providing an experimental ground for the player’s moves. After the user is fully loaded, they will enter the game page to put all they have just learned into practice. After the game ends, the player will receive a card showcasing their game performance. The game also lively adapts when user switch to dark mode. 14 | 15 | 16 | The latest Apple software technologies, including “SwiftUI,” its internal Metal off-screen rendering, along with “AVFoundation” and “PlaygroundSupport,” are implemented to build this interactive game. In creating “6 Feet Between”, I took a fundamentally different technical approach from other games. More specifically, it is a game that is built up without a game framework. While other 2D games are mostly based on “SpriteKit” or “SceneKit,” I built “6 Feet Between” entirely on the “SwiftUI,” which just came out barely a year ago. Although being comparatively new, SwiftUI provides surprisingly high performance and rich animations that allowed me to build a game from the ground up. Although it was not easy to start off with, I managed to dive deep into the documentation for the powerful technology of “SwiftUI,” including its off-screen view rendering using “Metal.” I also used multithreading technology throughout the project for smooth animation rendering for every UI element in this project. However, interaction is more than just visual appearance. By using “AVFoundation,” I have integrated user actions with sound effects. Therefore, every time the player clicks a button, moves on the screen, or ends the game, subtle sound effects provide clear feedback. 17 | 18 | 19 | ## More Info 20 | At the time of this commit, I just finished my first year of college as an international undergraduate at UCSD(University of California San Diego). Growing up as a techie, and now as a developer, I am aware of the importance of being supported and the feeling of belonging. Feel free to ask me any question you have encountered, and I am more than happy to help out if I am able to! 21 | 22 | Follow me at: 23 | - GitHub: https://github.com/TonyTang2001 24 | - Twitter: @TonyTang_here 25 | - Weibo: @TonyTang_Dev and @TonyTang2001 26 | - LinkedIn: Zixuan Tang 27 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/ButtonStackView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonStackView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct ButtonStackView1: View { 12 | 13 | @Binding var animate1Start: Bool 14 | @Binding var animate1End : Bool 15 | @Binding var animate2Start: Bool 16 | @Binding var animate2End : Bool 17 | @Binding var animate3Start: Bool 18 | @Binding var animate3End : Bool 19 | 20 | public init( 21 | animate1Start: Binding, 22 | animate1End: Binding, 23 | animate2Start: Binding, 24 | animate2End: Binding, 25 | animate3Start: Binding, 26 | animate3End: Binding) { 27 | self._animate1Start = animate1Start 28 | self._animate1End = animate1End 29 | self._animate2Start = animate2Start 30 | self._animate2End = animate2End 31 | self._animate3Start = animate3Start 32 | self._animate3End = animate3End 33 | 34 | } 35 | 36 | public var body: some View { 37 | ZStack { 38 | Button(action: { 39 | self.animate2Start = true 40 | 41 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 42 | self.animate2End = true 43 | } 44 | }) { 45 | Text("Next") 46 | .fontWeight(.semibold) 47 | .font(.system(.title, design: .rounded)) 48 | } 49 | .opacity((animate1End && !animate2Start) ? 1 : 0) 50 | 51 | Button(action: { 52 | self.animate3Start = true 53 | 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 55 | self.animate3End = true 56 | } 57 | }) { 58 | Text("Next") 59 | .fontWeight(.semibold) 60 | .font(.system(.title, design: .rounded)) 61 | } 62 | .opacity((animate2End && !animate3Start) ? 1 : 0) 63 | } 64 | } 65 | } 66 | 67 | public struct ButtonStackView2: View { 68 | 69 | @Binding var animate3End : Bool 70 | @Binding var animate4Start: Bool 71 | @Binding var animate4End : Bool 72 | @Binding var animate5Start: Bool 73 | 74 | public init( 75 | animate3End: Binding, 76 | animate4Start: Binding, 77 | animate4End: Binding, 78 | animate5Start: Binding) { 79 | 80 | self._animate3End = animate3End 81 | self._animate4Start = animate4Start 82 | self._animate4End = animate4End 83 | self._animate5Start = animate5Start 84 | 85 | } 86 | 87 | public var body: some View { 88 | ZStack { 89 | Button(action: { 90 | self.animate4Start = true 91 | 92 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 93 | self.animate4End = true 94 | } 95 | }) { 96 | Text("Next") 97 | .fontWeight(.semibold) 98 | .font(.system(.title, design: .rounded)) 99 | } 100 | .opacity((animate3End && !animate4Start) ? 1 : 0) 101 | 102 | Button(action: { 103 | self.animate5Start = true 104 | }) { 105 | Text("Next") 106 | .fontWeight(.semibold) 107 | .font(.system(.title, design: .rounded)) 108 | } 109 | .opacity((animate4End && !animate5Start) ? 1 : 0) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/6/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | // MARK: UISceneSession Lifecycle 23 | 24 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 25 | // Called when a new scene session is being created. 26 | // Use this method to select a configuration to create the new scene with. 27 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 28 | } 29 | 30 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 31 | // Called when the user discards a scene session. 32 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 33 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 34 | } 35 | 36 | // MARK: - Core Data stack 37 | 38 | lazy var persistentContainer: NSPersistentContainer = { 39 | /* 40 | The persistent container for the application. This implementation 41 | creates and returns a container, having loaded the store for the 42 | application to it. This property is optional since there are legitimate 43 | error conditions that could cause the creation of the store to fail. 44 | */ 45 | let container = NSPersistentContainer(name: "WWDC20PlaygroundTest") 46 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 47 | if let error = error as NSError? { 48 | // Replace this implementation with code to handle the error appropriately. 49 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 50 | 51 | /* 52 | Typical reasons for an error here include: 53 | * The parent directory does not exist, cannot be created, or disallows writing. 54 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 55 | * The device is out of space. 56 | * The store could not be migrated to the current model version. 57 | Check the error message to determine what the actual problem was. 58 | */ 59 | fatalError("Unresolved error \(error), \(error.userInfo)") 60 | } 61 | }) 62 | return container 63 | }() 64 | 65 | // MARK: - Core Data Saving support 66 | 67 | func saveContext () { 68 | let context = persistentContainer.viewContext 69 | if context.hasChanges { 70 | do { 71 | try context.save() 72 | } catch { 73 | // Replace this implementation with code to handle the error appropriately. 74 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 75 | let nserror = error as NSError 76 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/NPCUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NPCUI.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct NPCInternalView: View { 13 | 14 | @Binding var isDragging: Bool 15 | 16 | public init(isDragging: Binding) { 17 | self._isDragging = isDragging 18 | } 19 | 20 | public var body: some View { 21 | ZStack { 22 | Image(systemName: "person.circle.fill") 23 | .resizable() 24 | .frame(width: npcSize, height: npcSize) 25 | .opacity(self.isDragging ? 0.6 : 1.0) 26 | .animation(.npcTransition) 27 | .clipShape(Circle()) 28 | 29 | // warning range indicator 30 | Circle() 31 | .frame(width: npcWarningRangeSize, height: npcWarningRangeSize) 32 | .opacity(self.isDragging ? 0 : 0) 33 | .overlay( 34 | Circle() 35 | .stroke(Color.red.opacity(self.isDragging ? 0.9 : 0), lineWidth: self.isDragging ? npcSize/2 : 0) 36 | .animation(.npcTransition) 37 | ) 38 | } 39 | .frame(width: npcSize * 2, height: npcSize * 2) 40 | .drawingGroup() // enable off-screen Metal rendering 41 | } 42 | } 43 | 44 | struct NPCView: View { 45 | 46 | @Binding var isDragging: Bool 47 | 48 | @State var index: Int = 0 49 | @State var npcCurrentCoords: [ScreenCoordinate] = [] 50 | 51 | var body: some View { 52 | 53 | NPCInternalView(isDragging: $isDragging) 54 | .position(x: 0, y: 0) 55 | .offset(x: npcCurrentCoords[npcCurrentCoords.endIndex-1].x, y: npcCurrentCoords[npcCurrentCoords.endIndex-1].y) 56 | .animation(Animation.linear(duration: 1)) 57 | .onAppear { 58 | self.toNewPosition() 59 | } 60 | 61 | } 62 | 63 | func toNewPosition() { 64 | if endOnHold { 65 | return 66 | } 67 | 68 | if !isDragging { 69 | let const: CGFloat = 8 70 | 71 | let previousCoord = npcCurrentCoords[npcCurrentCoords.endIndex-1] 72 | 73 | var xMove = CGFloat.random(in: -const...const) 74 | var yMove = CGFloat.random(in: -const...const) 75 | 76 | // let destCoord = ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove) 77 | let playerPosition = getPlayerCoord() 78 | 79 | while outOfView(coord: ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove)) || approachPlayer(ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove), playerPosition) { 80 | xMove = CGFloat.random(in: -const...const) 81 | yMove = CGFloat.random(in: -const...const) 82 | } 83 | 84 | npcCurrentCoords.append(ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove)) 85 | 86 | npcCoords[self.index] = self.npcCurrentCoords 87 | 88 | } 89 | 90 | DispatchQueue.main.asyncAfter(deadline: .now() + .random(in: 0.8...1.2)) { 91 | 92 | npcCoords[self.index] = self.npcCurrentCoords 93 | self.toNewPosition() 94 | // npcCoords[self.index] = self.npcCurrentCoords 95 | } 96 | } 97 | 98 | } 99 | 100 | public struct NPCMap: View { 101 | 102 | @Binding var isDragging: Bool 103 | 104 | public init(isDragging: Binding) { 105 | self._isDragging = isDragging 106 | } 107 | 108 | public var body: some View { 109 | ZStack { 110 | ForEach(npcCoords, id: \.self) { coord in 111 | NPCView(isDragging: self.$isDragging, index: npcCoords.firstIndex(of: coord)!, npcCurrentCoords: coord) 112 | } 113 | } 114 | .drawingGroup() // enable off-screen Metal rendering 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/NPCUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NPCUI.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct NPCInternalView: View { 13 | 14 | @Binding var isDragging: Bool 15 | 16 | public init(isDragging: Binding) { 17 | self._isDragging = isDragging 18 | } 19 | 20 | public var body: some View { 21 | ZStack { 22 | Image(systemName: "person.circle.fill") 23 | .resizable() 24 | .frame(width: npcSize, height: npcSize) 25 | .opacity(self.isDragging ? 0.6 : 1.0) 26 | .animation(.npcTransition) 27 | .clipShape(Circle()) 28 | 29 | // warning range indicator 30 | Circle() 31 | .frame(width: npcWarningRangeSize, height: npcWarningRangeSize) 32 | .opacity(self.isDragging ? 0 : 0) 33 | .overlay( 34 | Circle() 35 | .stroke(Color.red.opacity(self.isDragging ? 0.9 : 0), lineWidth: self.isDragging ? npcSize/2 : 0) 36 | .animation(.npcTransition) 37 | ) 38 | } 39 | .frame(width: npcSize * 2, height: npcSize * 2) 40 | .drawingGroup() // enable off-screen Metal rendering 41 | } 42 | } 43 | 44 | struct NPCView: View { 45 | 46 | @Binding var isDragging: Bool 47 | 48 | @State var index: Int = 0 49 | @State var npcCurrentCoords: [ScreenCoordinate] = [] 50 | 51 | var body: some View { 52 | 53 | NPCInternalView(isDragging: $isDragging) 54 | .position(x: 0, y: 0) 55 | .offset(x: npcCurrentCoords[npcCurrentCoords.endIndex-1].x, y: npcCurrentCoords[npcCurrentCoords.endIndex-1].y) 56 | .animation(Animation.linear(duration: 1)) 57 | .onAppear { 58 | self.toNewPosition() 59 | } 60 | 61 | } 62 | 63 | func toNewPosition() { 64 | if endOnHold { 65 | return 66 | } 67 | 68 | if !isDragging { 69 | let const: CGFloat = 8 70 | 71 | let previousCoord = npcCurrentCoords[npcCurrentCoords.endIndex-1] 72 | 73 | var xMove = CGFloat.random(in: -const...const) 74 | var yMove = CGFloat.random(in: -const...const) 75 | 76 | // let destCoord = ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove) 77 | let playerPosition = getPlayerCoord() 78 | 79 | while outOfView(coord: ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove)) || approachPlayer(ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove), playerPosition) { 80 | xMove = CGFloat.random(in: -const...const) 81 | yMove = CGFloat.random(in: -const...const) 82 | } 83 | 84 | npcCurrentCoords.append(ScreenCoordinate(x: previousCoord.x + xMove, y: previousCoord.y + yMove)) 85 | 86 | npcCoords[self.index] = self.npcCurrentCoords 87 | 88 | } 89 | 90 | DispatchQueue.main.asyncAfter(deadline: .now() + .random(in: 0.8...1.2)) { 91 | 92 | npcCoords[self.index] = self.npcCurrentCoords 93 | self.toNewPosition() 94 | // npcCoords[self.index] = self.npcCurrentCoords 95 | } 96 | } 97 | 98 | } 99 | 100 | public struct NPCMap: View { 101 | 102 | @Binding var isDragging: Bool 103 | 104 | public init(isDragging: Binding) { 105 | self._isDragging = isDragging 106 | } 107 | 108 | public var body: some View { 109 | ZStack { 110 | ForEach(npcCoords, id: \.self) { coord in 111 | NPCView(isDragging: self.$isDragging, index: npcCoords.firstIndex(of: coord)!, npcCurrentCoords: coord) 112 | } 113 | } 114 | .drawingGroup() // enable off-screen Metal rendering 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Data/Calculation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Calculation.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | // get length using sqrt of x and y squares 13 | public func getLength(x: CGFloat, y: CGFloat) -> CGFloat { 14 | return (x * x + y * y).squareRoot() 15 | } 16 | 17 | // get length between two screen coordinates 18 | public func getDistance(_ point1: ScreenCoordinate, _ point2: ScreenCoordinate) -> CGFloat { 19 | let p1X = point1.x 20 | let p1Y = point1.y 21 | let p2X = point2.x 22 | let p2Y = point2.y 23 | 24 | let xDiff = p1X - p2X 25 | let yDiff = p1Y - p2Y 26 | 27 | let ptDistance = (xDiff * xDiff + yDiff * yDiff).squareRoot() 28 | 29 | return ptDistance 30 | } 31 | 32 | // check if npc approches player 33 | public func approachPlayer(_ npc: ScreenCoordinate, _ player: ScreenCoordinate) -> Bool { 34 | let distance = getDistance(npc, player) 35 | if distance <= safetyDistance { 36 | return true 37 | } else { 38 | return false 39 | } 40 | } 41 | 42 | // calculate player exact coordinate 43 | public func getPlayerCoord() -> ScreenCoordinate { 44 | let playerPosition = ScreenCoordinate(x: currentPosition.width + viewWidth/2 , y: currentPosition.height + viewHeight - playerSize/2) 45 | return playerPosition 46 | } 47 | 48 | // check if the coordinate is out of view 49 | public func outOfView(coord: ScreenCoordinate) -> Bool { 50 | let coordLeftX = coord.x - 20 51 | let coordRightX = coord.x + 20 52 | let coordTopY = coord.y - 20 53 | let coordBtmY = coord.y + 20 54 | if coordLeftX < 0 || coordRightX > viewWidth || coordTopY < 0 || coordBtmY > viewHeight { 55 | return true 56 | } else { 57 | return false 58 | } 59 | } 60 | 61 | // calculate shortest length from a point to a line segment 62 | public func distanceFromPoint(p: ScreenCoordinate, toLineSegment v: ScreenCoordinate, and w: ScreenCoordinate) -> CGFloat { 63 | let pv_dx = p.x - v.x 64 | let pv_dy = p.y - v.y 65 | let wv_dx = w.x - v.x 66 | let wv_dy = w.y - v.y 67 | 68 | let dot = pv_dx * wv_dx + pv_dy * wv_dy 69 | let len_sq = wv_dx * wv_dx + wv_dy * wv_dy 70 | let param = dot / len_sq 71 | 72 | var int_x, int_y: CGFloat /* intersection of normal to vw that goes through p */ 73 | 74 | if param < 0 || (v.x == w.x && v.y == w.y) { 75 | int_x = v.x 76 | int_y = v.y 77 | } else if param > 1 { 78 | int_x = w.x 79 | int_y = w.y 80 | } else { 81 | int_x = v.x + param * wv_dx 82 | int_y = v.y + param * wv_dy 83 | } 84 | 85 | /* Components of normal */ 86 | let dx = p.x - int_x 87 | let dy = p.y - int_y 88 | 89 | return sqrt(dx * dx + dy * dy) 90 | } 91 | 92 | // get game duration from start to end 93 | public func getGameDuration(from start: Date, to end: Date) -> Int { 94 | let calendar = Calendar.current 95 | let dateComponents = calendar.dateComponents([Calendar.Component.second], from: start, to: end) 96 | 97 | let seconds = dateComponents.second 98 | return Int(seconds!) 99 | } 100 | 101 | // get game rating 102 | public func getGameRating(duration: Int) -> Int { 103 | if duration < npcCount/2 { 104 | return 3 105 | } else if duration < npcCount { 106 | return 2 107 | } else { 108 | return 1 109 | } 110 | } 111 | 112 | // check if player is out of screen view 113 | public func playerOutOfView() -> Bool { 114 | let playerDest = getPlayerCoord() 115 | let playerLeftX = playerDest.x - playerSize/2 116 | let playerRightX = playerDest.x + playerSize/2 117 | let playerTopY = playerDest.y - playerSize/2 118 | let playerBottomY = playerDest.y + playerSize/2 119 | 120 | if playerLeftX < 0 || playerRightX > viewWidth || 121 | playerTopY < 0 || playerBottomY > viewHeight { 122 | return true 123 | } 124 | 125 | return false 126 | } 127 | 128 | // calculate player destination using user dragging one-axis distance 129 | public func calculateMoveEstimate(input: CGFloat) -> CGFloat { 130 | let square = input * input 131 | let ans = 30 * square/(9999 + (square-input)) 132 | return ans 133 | } 134 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/Calculation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Calculation.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | // get length using sqrt of x and y squares 13 | public func getLength(x: CGFloat, y: CGFloat) -> CGFloat { 14 | return (x * x + y * y).squareRoot() 15 | } 16 | 17 | // get length between two screen coordinates 18 | public func getDistance(_ point1: ScreenCoordinate, _ point2: ScreenCoordinate) -> CGFloat { 19 | let p1X = point1.x 20 | let p1Y = point1.y 21 | let p2X = point2.x 22 | let p2Y = point2.y 23 | 24 | let xDiff = p1X - p2X 25 | let yDiff = p1Y - p2Y 26 | 27 | let ptDistance = (xDiff * xDiff + yDiff * yDiff).squareRoot() 28 | 29 | return ptDistance 30 | } 31 | 32 | // check if npc approches player 33 | public func approachPlayer(_ npc: ScreenCoordinate, _ player: ScreenCoordinate) -> Bool { 34 | let distance = getDistance(npc, player) 35 | if distance <= safetyDistance { 36 | return true 37 | } else { 38 | return false 39 | } 40 | } 41 | 42 | // calculate player exact coordinate 43 | public func getPlayerCoord() -> ScreenCoordinate { 44 | let playerPosition = ScreenCoordinate(x: currentPosition.width + viewWidth/2 , y: currentPosition.height + viewHeight - playerSize/2) 45 | return playerPosition 46 | } 47 | 48 | // check if the coordinate is out of view 49 | public func outOfView(coord: ScreenCoordinate) -> Bool { 50 | let coordLeftX = coord.x - 20 51 | let coordRightX = coord.x + 20 52 | let coordTopY = coord.y - 20 53 | let coordBtmY = coord.y + 20 54 | if coordLeftX < 0 || coordRightX > viewWidth || coordTopY < 0 || coordBtmY > viewHeight { 55 | return true 56 | } else { 57 | return false 58 | } 59 | } 60 | 61 | // calculate shortest length from a point to a line segment 62 | public func distanceFromPoint(p: ScreenCoordinate, toLineSegment v: ScreenCoordinate, and w: ScreenCoordinate) -> CGFloat { 63 | let pv_dx = p.x - v.x 64 | let pv_dy = p.y - v.y 65 | let wv_dx = w.x - v.x 66 | let wv_dy = w.y - v.y 67 | 68 | let dot = pv_dx * wv_dx + pv_dy * wv_dy 69 | let len_sq = wv_dx * wv_dx + wv_dy * wv_dy 70 | let param = dot / len_sq 71 | 72 | var int_x, int_y: CGFloat /* intersection of normal to vw that goes through p */ 73 | 74 | if param < 0 || (v.x == w.x && v.y == w.y) { 75 | int_x = v.x 76 | int_y = v.y 77 | } else if param > 1 { 78 | int_x = w.x 79 | int_y = w.y 80 | } else { 81 | int_x = v.x + param * wv_dx 82 | int_y = v.y + param * wv_dy 83 | } 84 | 85 | /* Components of normal */ 86 | let dx = p.x - int_x 87 | let dy = p.y - int_y 88 | 89 | return sqrt(dx * dx + dy * dy) 90 | } 91 | 92 | // get game duration from start to end 93 | public func getGameDuration(from start: Date, to end: Date) -> Int { 94 | let calendar = Calendar.current 95 | let dateComponents = calendar.dateComponents([Calendar.Component.second], from: start, to: end) 96 | 97 | let seconds = dateComponents.second 98 | return Int(seconds!) 99 | } 100 | 101 | // get game rating 102 | public func getGameRating(duration: Int) -> Int { 103 | if duration < npcCount/2 { 104 | return 3 105 | } else if duration < npcCount { 106 | return 2 107 | } else { 108 | return 1 109 | } 110 | } 111 | 112 | // check if player is out of screen view 113 | public func playerOutOfView() -> Bool { 114 | let playerDest = getPlayerCoord() 115 | let playerLeftX = playerDest.x - playerSize/2 116 | let playerRightX = playerDest.x + playerSize/2 117 | let playerTopY = playerDest.y - playerSize/2 118 | let playerBottomY = playerDest.y + playerSize/2 119 | 120 | if playerLeftX < 0 || playerRightX > viewWidth || 121 | playerTopY < 0 || playerBottomY > viewHeight { 122 | return true 123 | } 124 | 125 | return false 126 | } 127 | 128 | // calculate player destination using user dragging one-axis distance 129 | public func calculateMoveEstimate(input: CGFloat) -> CGFloat { 130 | let square = input * input 131 | let ans = 30 * square/(9999 + (square-input)) 132 | return ans 133 | } 134 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/GameFailureView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameFailureView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/13/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import AVFoundation 11 | 12 | struct FailureSignView: View { 13 | var body: some View { 14 | HStack { 15 | Image(systemName: "xmark.seal.fill") 16 | .resizable() 17 | .frame(width: 30, height: 30) 18 | 19 | Text("You Lost") 20 | .fontWeight(.semibold) 21 | .font(.system(.title, design: .rounded)) 22 | } 23 | } 24 | } 25 | 26 | struct FaliureThumbNailView: View { 27 | @State var animate: Bool = false 28 | 29 | var body: some View { 30 | ZStack { 31 | RoundedRectangle(cornerRadius: 14, style: .continuous) 32 | .frame(width: 80, height: 14) 33 | .foregroundColor(Color.red) 34 | .rotationEffect(self.animate ? Angle(degrees: 45) : Angle(degrees: 0)) 35 | 36 | 37 | RoundedRectangle(cornerRadius: 14, style: .continuous) 38 | .frame(width: 80, height: 14) 39 | .foregroundColor(Color.red) 40 | .rotationEffect(self.animate ? Angle(degrees: -45) : Angle(degrees: 0)) 41 | } 42 | .frame(width: 60, height: 60) 43 | .animation(Animation.spring().delay(0.1)) 44 | .onAppear { 45 | withAnimation { 46 | self.animate = true 47 | } 48 | } 49 | 50 | } 51 | } 52 | 53 | public struct GameFailureView: View { 54 | 55 | @State var appear = false 56 | @State var userDrag = CGSize.zero 57 | 58 | public init() {} 59 | 60 | let textOption = Int.random(in: 0..<6) 61 | let failureSentences: [String] = ["Good Work, \nbut Not Enough.", 62 | "Still a Way to Go", 63 | "Keep up with It, \nYou Got This!", 64 | "Just... a bit More.", 65 | "Better Luck Next Time!", 66 | "Oops, didn't see that coming..."] 67 | 68 | public var body: some View { 69 | VStack { 70 | Spacer() 71 | VStack { 72 | Spacer() 73 | FailureSignView() 74 | 75 | Spacer() 76 | FaliureThumbNailView() 77 | 78 | Spacer() 79 | Text(failureSentences[textOption]) 80 | .fontWeight(.bold) 81 | .font(.system(.largeTitle, design: .rounded)) 82 | .multilineTextAlignment(.center) 83 | 84 | Spacer() 85 | } 86 | .frame(width: 420, height: 330) 87 | .background(Color(UIColor.systemBackground)) 88 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) 89 | .shadow(color: Color.black.opacity(0.3), radius: 22, x: 16, y: 16) 90 | .offset(x: 0, y: self.appear ? 0 : 16) 91 | .rotation3DEffect(Angle(degrees: Double(0.07 * getLength(x: userDrag.width, y: userDrag.height))), axis: (x: userDrag.width * 0.1, y: userDrag.height * 0.1, z: 0.0)) 92 | .animation(.easeInOut) 93 | 94 | Spacer() 95 | 96 | Text("To restart the game, \ntap on Playground Start/Stop Button.") 97 | .fontWeight(.semibold) 98 | .font(.system(.footnote, design: .rounded)) 99 | .multilineTextAlignment(.center) 100 | 101 | Spacer() 102 | } 103 | .opacity(self.appear ? 1 : 0) 104 | .onAppear { 105 | self.appear = true 106 | AVAudioPlayer.stopPlaySoundBG() 107 | AVAudioPlayer.playSound(sound: "error2", type: "wav") 108 | } 109 | .gesture( 110 | DragGesture().onChanged { value in 111 | self.userDrag = value.translation 112 | } 113 | .onEnded { value in 114 | self.userDrag = .zero 115 | } 116 | ) 117 | 118 | 119 | 120 | 121 | } 122 | } 123 | 124 | struct GameFailureView_Previews: PreviewProvider { 125 | static var previews: some View { 126 | GameFailureView() 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/GameFailureView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameFailureView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/13/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import AVFoundation 11 | 12 | struct FailureSignView: View { 13 | var body: some View { 14 | HStack { 15 | Image(systemName: "xmark.seal.fill") 16 | .resizable() 17 | .frame(width: 30, height: 30) 18 | 19 | Text("You Lost") 20 | .fontWeight(.semibold) 21 | .font(.system(.title, design: .rounded)) 22 | } 23 | } 24 | } 25 | 26 | struct FaliureThumbNailView: View { 27 | @State var animate: Bool = false 28 | 29 | var body: some View { 30 | ZStack { 31 | RoundedRectangle(cornerRadius: 14, style: .continuous) 32 | .frame(width: 80, height: 14) 33 | .foregroundColor(Color.red) 34 | .rotationEffect(self.animate ? Angle(degrees: 45) : Angle(degrees: 0)) 35 | 36 | 37 | RoundedRectangle(cornerRadius: 14, style: .continuous) 38 | .frame(width: 80, height: 14) 39 | .foregroundColor(Color.red) 40 | .rotationEffect(self.animate ? Angle(degrees: -45) : Angle(degrees: 0)) 41 | } 42 | .frame(width: 60, height: 60) 43 | .animation(Animation.spring().delay(0.1)) 44 | .onAppear { 45 | withAnimation { 46 | self.animate = true 47 | } 48 | } 49 | 50 | } 51 | } 52 | 53 | public struct GameFailureView: View { 54 | 55 | @State var appear = false 56 | @State var userDrag = CGSize.zero 57 | 58 | public init() {} 59 | 60 | let textOption = Int.random(in: 0..<6) 61 | let failureSentences: [String] = ["Good Work, \nbut Not Enough.", 62 | "Still a Way to Go", 63 | "Keep up with It, \nYou Got This!", 64 | "Just... a bit More.", 65 | "Better Luck Next Time!", 66 | "Oops, didn't see that coming..."] 67 | 68 | public var body: some View { 69 | VStack { 70 | Spacer() 71 | VStack { 72 | Spacer() 73 | FailureSignView() 74 | 75 | Spacer() 76 | FaliureThumbNailView() 77 | 78 | Spacer() 79 | Text(failureSentences[textOption]) 80 | .fontWeight(.bold) 81 | .font(.system(.largeTitle, design: .rounded)) 82 | .multilineTextAlignment(.center) 83 | 84 | Spacer() 85 | } 86 | .frame(width: 420, height: 330) 87 | .background(Color(UIColor.systemBackground)) 88 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) 89 | .shadow(color: Color.black.opacity(0.3), radius: 22, x: 16, y: 16) 90 | .offset(x: 0, y: self.appear ? 0 : 16) 91 | .rotation3DEffect(Angle(degrees: Double(0.07 * getLength(x: userDrag.width, y: userDrag.height))), axis: (x: userDrag.width * 0.1, y: userDrag.height * 0.1, z: 0.0)) 92 | .animation(.easeInOut) 93 | 94 | Spacer() 95 | 96 | Text("To restart the game, \ntap on Playground Start/Stop Button.") 97 | .fontWeight(.semibold) 98 | .font(.system(.footnote, design: .rounded)) 99 | .multilineTextAlignment(.center) 100 | 101 | Spacer() 102 | } 103 | .opacity(self.appear ? 1 : 0) 104 | .onAppear { 105 | self.appear = true 106 | AVAudioPlayer.stopPlaySoundBG() 107 | AVAudioPlayer.playSound(sound: "error2", type: "wav") 108 | } 109 | .gesture( 110 | DragGesture().onChanged { value in 111 | self.userDrag = value.translation 112 | } 113 | .onEnded { value in 114 | self.userDrag = .zero 115 | } 116 | ) 117 | 118 | 119 | 120 | 121 | } 122 | } 123 | 124 | struct GameFailureView_Previews: PreviewProvider { 125 | static var previews: some View { 126 | GameFailureView() 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/StoryTextIntroView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryTextIntroView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct StoryTextIntroView: View { 12 | 13 | var context1String = ["Sometime in the future,", 14 | "an virus named C9 is emerging worldwide.", 15 | "It has infected a huge amount of people,", 16 | "and everyone is waiting for help.", 17 | "Fortunately, the cure has been developed.", 18 | "", 19 | "However, the research sample has not been delivered", 20 | "to the factory for mass production."] 21 | var context2String = ["You are a Ninja.", 22 | "The people need your help!", 23 | "", 24 | "You need to transport the sample", 25 | "from the lab to the factory."] 26 | 27 | @Binding var animate1Start: Bool 28 | @Binding var animate1End : Bool 29 | @Binding var animate2Start: Bool 30 | @Binding var animate2End : Bool 31 | @Binding var animate3Start: Bool 32 | @Binding var animate3End : Bool 33 | 34 | public init( 35 | animate1Start: Binding, 36 | animate1End: Binding, 37 | animate2Start: Binding, 38 | animate2End: Binding, 39 | animate3Start: Binding, 40 | animate3End: Binding) { 41 | self._animate1Start = animate1Start 42 | self._animate1End = animate1End 43 | self._animate2Start = animate2Start 44 | self._animate2End = animate2End 45 | self._animate3Start = animate3Start 46 | self._animate3End = animate3End 47 | 48 | } 49 | 50 | var body: some View { 51 | VStack { 52 | Spacer() 53 | ZStack { 54 | VStack { 55 | ForEach(context1String, id: \.self) { string in 56 | Text(string) 57 | .fontWeight(.semibold) 58 | .font(.system(.title, design: .rounded)) 59 | .multilineTextAlignment(.center) 60 | .opacity(self.animate1Start ? 1 : 0) 61 | .offset(y: self.animate1Start ? 0 : 16) 62 | .offset(y: 10 * CGFloat(self.context1String.firstIndex(of: string)!)) 63 | .animation(Animation.easeInOut(duration: 1).delay( 64 | Double(self.context1String.firstIndex(of: string)!) 65 | ) 66 | ) 67 | } 68 | .opacity(animate2Start ? 0 : 1) 69 | } 70 | 71 | VStack { 72 | Image(uiImage: UIImage(named: "Ninja_Circle")!) 73 | .resizable() 74 | .frame(width: playerSize, height: playerSize) 75 | .opacity(self.animate2Start ? 1 : 0) 76 | 77 | ForEach(context2String, id: \.self) { string in 78 | Text(string) 79 | .fontWeight(.semibold) 80 | .font(.system(.title, design: .rounded)) 81 | .multilineTextAlignment(.center) 82 | .opacity(self.animate2Start ? 1 : 0) 83 | .offset(y: self.animate2Start ? 0 : 16) 84 | .offset(y: 10 * CGFloat(self.context2String.firstIndex(of: string)!)) 85 | .animation(Animation.easeInOut(duration: 1).delay( 86 | Double(self.context2String.firstIndex(of: string)!) 87 | ) 88 | ) 89 | } 90 | .opacity(animate3Start ? 0 : 1) 91 | } 92 | } 93 | 94 | 95 | Spacer() 96 | 97 | ButtonStackView1(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start, animate3End: $animate3End) 98 | 99 | Spacer() 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/StoryTextIntroView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoryTextIntroView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct StoryTextIntroView: View { 12 | 13 | var context1String = ["Sometime in the future,", 14 | "an virus named C9 is emerging worldwide.", 15 | "It has infected a huge amount of people,", 16 | "and everyone is waiting for help.", 17 | "Fortunately, the cure has been developed.", 18 | "", 19 | "However, the research sample has not been delivered", 20 | "to the factory for mass production."] 21 | var context2String = ["You are a Ninja.", 22 | "The people need your help!", 23 | "", 24 | "You need to transport the sample", 25 | "from the lab to the factory."] 26 | 27 | @Binding var animate1Start: Bool 28 | @Binding var animate1End : Bool 29 | @Binding var animate2Start: Bool 30 | @Binding var animate2End : Bool 31 | @Binding var animate3Start: Bool 32 | @Binding var animate3End : Bool 33 | 34 | public init( 35 | animate1Start: Binding, 36 | animate1End: Binding, 37 | animate2Start: Binding, 38 | animate2End: Binding, 39 | animate3Start: Binding, 40 | animate3End: Binding) { 41 | self._animate1Start = animate1Start 42 | self._animate1End = animate1End 43 | self._animate2Start = animate2Start 44 | self._animate2End = animate2End 45 | self._animate3Start = animate3Start 46 | self._animate3End = animate3End 47 | 48 | } 49 | 50 | var body: some View { 51 | VStack { 52 | Spacer() 53 | ZStack { 54 | VStack { 55 | ForEach(context1String, id: \.self) { string in 56 | Text(string) 57 | .fontWeight(.semibold) 58 | .font(.system(.title, design: .rounded)) 59 | .multilineTextAlignment(.center) 60 | .opacity(self.animate1Start ? 1 : 0) 61 | .offset(y: self.animate1Start ? 0 : 16) 62 | .offset(y: 10 * CGFloat(self.context1String.firstIndex(of: string)!)) 63 | .animation(Animation.easeInOut(duration: 1).delay( 64 | Double(self.context1String.firstIndex(of: string)!) 65 | ) 66 | ) 67 | } 68 | .opacity(animate2Start ? 0 : 1) 69 | } 70 | 71 | VStack { 72 | Image(uiImage: UIImage(named: "Ninja_Circle")!) 73 | .resizable() 74 | .frame(width: playerSize, height: playerSize) 75 | .opacity(self.animate2Start ? 1 : 0) 76 | 77 | ForEach(context2String, id: \.self) { string in 78 | Text(string) 79 | .fontWeight(.semibold) 80 | .font(.system(.title, design: .rounded)) 81 | .multilineTextAlignment(.center) 82 | .opacity(self.animate2Start ? 1 : 0) 83 | .offset(y: self.animate2Start ? 0 : 16) 84 | .offset(y: 10 * CGFloat(self.context2String.firstIndex(of: string)!)) 85 | .animation(Animation.easeInOut(duration: 1).delay( 86 | Double(self.context2String.firstIndex(of: string)!) 87 | ) 88 | ) 89 | } 90 | .opacity(animate3Start ? 0 : 1) 91 | } 92 | } 93 | 94 | 95 | Spacer() 96 | 97 | ButtonStackView1(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start, animate3End: $animate3End) 98 | 99 | Spacer() 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/6/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import AVFoundation 11 | 12 | // MARK: - MapSetting 13 | struct GroundMapView: View { 14 | var iconSize: CGFloat = mapIconSize 15 | 16 | var body: some View { 17 | VStack { 18 | FactoryView() 19 | .frame(width: iconSize, height: iconSize) 20 | .foregroundColor(.white) 21 | 22 | Spacer() 23 | 24 | LabView() 25 | .frame(width: iconSize, height: iconSize) 26 | } 27 | } 28 | } 29 | 30 | // MARK: - Interface 31 | struct InterfaceView: View { 32 | 33 | @Binding var distance: CGFloat 34 | @Binding var xDistance: CGFloat 35 | @Binding var yDistance: CGFloat 36 | @Binding var isDragging: Bool 37 | 38 | @Binding var gameEnded: Bool 39 | 40 | var body: some View { 41 | 42 | VStack { 43 | 44 | Spacer() 45 | 46 | NPCMap(isDragging: $isDragging) 47 | 48 | PlayerView(inputX: self.$xDistance, inputY: self.$yDistance, showPathPreview: self.$isDragging, gameEnded: $gameEnded, tutorialMode: false) 49 | 50 | } 51 | .background(Color.white.opacity(0.00001)) 52 | 53 | } 54 | } 55 | 56 | // MARK: - ContentView 57 | public struct ContentView: View { 58 | 59 | @State private var xDistance: CGFloat = 0 60 | @State private var yDistance: CGFloat = 0 61 | @State private var distance: CGFloat = 0 62 | @State private var isDragging: Bool = false 63 | 64 | @State private var viewW: CGFloat = 400 65 | @State private var viewH: CGFloat = 600 66 | 67 | // @State private var npcCount = npcCount 68 | 69 | @State private var initialized = false 70 | 71 | @State private var gameEnded = false 72 | 73 | public init() {} 74 | 75 | public var body: some View { 76 | ZStack { 77 | 78 | BackgroundView(canvasWidth: $viewW, canvasHeight: $viewH) 79 | 80 | VStack { 81 | 82 | if !initialized { 83 | GeometryReader { geometry in 84 | Button(action: { 85 | // get and setup canvas size 86 | viewWidth = geometry.size.width 87 | viewHeight = geometry.size.height 88 | self.viewW = viewWidth 89 | self.viewH = viewHeight 90 | 91 | // start game timer 92 | startTime = Date() 93 | 94 | // start playing background sound effect 95 | AVAudioPlayer.startPlaySoundBG() 96 | 97 | npcCoords = generateNPCCoords(viewWidth: viewWidth, viewHeight: viewHeight) 98 | self.initialized = true 99 | }) { 100 | Text("Start Game") 101 | .foregroundColor(Color(UIColor.systemBackground)) 102 | .fontWeight(.semibold) 103 | .font(.system(.title, design: .rounded)) 104 | .multilineTextAlignment(.center) 105 | } 106 | .frame(width: 200, height: 68) 107 | .background(Color(UIColor.systemYellow)) 108 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) 109 | } 110 | 111 | } else { 112 | InterfaceView(distance: $distance, xDistance: $xDistance, yDistance: $yDistance, isDragging: $isDragging, gameEnded: $gameEnded) 113 | .gesture(DragGesture() 114 | .onChanged({ value in 115 | self.isDragging = true 116 | self.xDistance = value.translation.width 117 | self.yDistance = value.translation.height 118 | self.distance = getLength(x: self.xDistance, y: self.yDistance) 119 | 120 | }) 121 | .onEnded({ (value) in 122 | self.isDragging = false 123 | }) 124 | ) 125 | } 126 | 127 | } 128 | .edgesIgnoringSafeArea(.all) 129 | .blur(radius: gameEnded ? 26 : 0) 130 | 131 | GroundMapView() 132 | 133 | if gameEnded && playerWon { 134 | GameSuccessView() 135 | } else if gameEnded && !playerWon { 136 | GameFailureView() 137 | } 138 | 139 | } 140 | 141 | } 142 | 143 | 144 | } 145 | 146 | 147 | // MARK: - Preview 148 | struct ContentView_Previews: PreviewProvider { 149 | static var previews: some View { 150 | ContentView() 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/6/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import AVFoundation 11 | 12 | // MARK: - MapSetting 13 | struct GroundMapView: View { 14 | var iconSize: CGFloat = mapIconSize 15 | 16 | var body: some View { 17 | VStack { 18 | FactoryView() 19 | .frame(width: iconSize, height: iconSize) 20 | .foregroundColor(.white) 21 | 22 | Spacer() 23 | 24 | LabView() 25 | .frame(width: iconSize, height: iconSize) 26 | } 27 | } 28 | } 29 | 30 | // MARK: - Interface 31 | struct InterfaceView: View { 32 | 33 | @Binding var distance: CGFloat 34 | @Binding var xDistance: CGFloat 35 | @Binding var yDistance: CGFloat 36 | @Binding var isDragging: Bool 37 | 38 | @Binding var gameEnded: Bool 39 | 40 | var body: some View { 41 | 42 | VStack { 43 | 44 | Spacer() 45 | 46 | NPCMap(isDragging: $isDragging) 47 | 48 | PlayerView(inputX: self.$xDistance, inputY: self.$yDistance, showPathPreview: self.$isDragging, gameEnded: $gameEnded, tutorialMode: false) 49 | 50 | } 51 | .background(Color.white.opacity(0.00001)) 52 | 53 | } 54 | } 55 | 56 | // MARK: - ContentView 57 | public struct ContentView: View { 58 | 59 | @State private var xDistance: CGFloat = 0 60 | @State private var yDistance: CGFloat = 0 61 | @State private var distance: CGFloat = 0 62 | @State private var isDragging: Bool = false 63 | 64 | @State private var viewW: CGFloat = 400 65 | @State private var viewH: CGFloat = 600 66 | 67 | // @State private var npcCount = npcCount 68 | 69 | @State private var initialized = false 70 | 71 | @State private var gameEnded = false 72 | 73 | public init() {} 74 | 75 | public var body: some View { 76 | ZStack { 77 | 78 | BackgroundView(canvasWidth: $viewW, canvasHeight: $viewH) 79 | 80 | VStack { 81 | 82 | if !initialized { 83 | GeometryReader { geometry in 84 | Button(action: { 85 | // get and setup canvas size 86 | viewWidth = geometry.size.width 87 | viewHeight = geometry.size.height 88 | self.viewW = viewWidth 89 | self.viewH = viewHeight 90 | 91 | // start game timer 92 | startTime = Date() 93 | 94 | // start playing background sound effect 95 | AVAudioPlayer.startPlaySoundBG() 96 | 97 | npcCoords = generateNPCCoords(viewWidth: viewWidth, viewHeight: viewHeight) 98 | self.initialized = true 99 | }) { 100 | Text("Start Game") 101 | .foregroundColor(Color(UIColor.systemBackground)) 102 | .fontWeight(.semibold) 103 | .font(.system(.title, design: .rounded)) 104 | .multilineTextAlignment(.center) 105 | } 106 | .frame(width: 200, height: 68) 107 | .background(Color(UIColor.systemYellow)) 108 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) 109 | } 110 | 111 | } else { 112 | InterfaceView(distance: $distance, xDistance: $xDistance, yDistance: $yDistance, isDragging: $isDragging, gameEnded: $gameEnded) 113 | .gesture(DragGesture() 114 | .onChanged({ value in 115 | self.isDragging = true 116 | self.xDistance = value.translation.width 117 | self.yDistance = value.translation.height 118 | self.distance = getLength(x: self.xDistance, y: self.yDistance) 119 | 120 | }) 121 | .onEnded({ (value) in 122 | self.isDragging = false 123 | }) 124 | ) 125 | } 126 | 127 | } 128 | .edgesIgnoringSafeArea(.all) 129 | .blur(radius: gameEnded ? 26 : 0) 130 | 131 | GroundMapView() 132 | .blur(radius: gameEnded ? 26 : 0) 133 | 134 | if gameEnded && playerWon { 135 | GameSuccessView() 136 | } else if gameEnded && !playerWon { 137 | GameFailureView() 138 | } 139 | 140 | } 141 | 142 | } 143 | 144 | 145 | } 146 | 147 | 148 | // MARK: - Preview 149 | struct ContentView_Previews: PreviewProvider { 150 | static var previews: some View { 151 | ContentView() 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/GameSuccessView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameSuccessView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/13/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import AVFoundation 11 | 12 | struct SuccessSignView: View { 13 | var body: some View { 14 | HStack { 15 | Image(systemName: "checkmark.seal.fill") 16 | .resizable() 17 | .frame(width: 30, height: 30) 18 | 19 | Text("You Won") 20 | .fontWeight(.semibold) 21 | .font(.system(.title, design: .rounded)) 22 | } 23 | } 24 | } 25 | 26 | struct SuccessRatingView: View { 27 | let starCount: Int 28 | @State var start: Bool = false 29 | 30 | func determineColor(index: Int) -> Color { 31 | if index <= starCount { 32 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3 * Double(index)) { 33 | AVAudioPlayer.playSound2(sound: "coin7", type: "wav") 34 | } 35 | return Color.yellow 36 | } else { 37 | return Color.gray 38 | } 39 | } 40 | 41 | func determineOpacity(index: Int) -> Double { 42 | if index <= starCount { 43 | return 1 44 | } else { 45 | return 0 46 | } 47 | } 48 | 49 | func determineAnimation(index: Int) -> Animation { 50 | return Animation.easeOut.delay(Double(index) * 0.3) 51 | } 52 | 53 | var body: some View { 54 | HStack { 55 | ForEach(1..<4, id: \.self) { i in 56 | ZStack { 57 | Image(systemName: "star.circle.fill") 58 | .resizable() 59 | .frame(width: 60, height: 60) 60 | .foregroundColor(self.determineColor(index: i)) 61 | 62 | Image(systemName: "star.circle.fill") 63 | .resizable() 64 | .frame(width: 60, height: 60) 65 | .scaleEffect(self.start ? 1.8 : 1) 66 | .opacity(self.start ? 0 : self.determineOpacity(index: i)) 67 | .foregroundColor(Color.yellow) 68 | .animation(self.determineAnimation(index: i)) 69 | } 70 | 71 | 72 | } 73 | 74 | } 75 | .onAppear { 76 | withAnimation { 77 | self.start = true 78 | } 79 | } 80 | } 81 | } 82 | 83 | public struct GameSuccessView: View { 84 | 85 | @State var appear = false 86 | @State var userDrag = CGSize.zero 87 | 88 | let gameDuration = getGameDuration(from: startTime, to: endTime) 89 | 90 | public init() {} 91 | 92 | let textOption = Int.random(in: 0..<5) 93 | let successSentences: [String] = ["You are a Hero!", 94 | "Lengendary!", 95 | "Congratulations!", 96 | "Good Job!", 97 | "Play of the Game!"] 98 | 99 | public var body: some View { 100 | VStack { 101 | Spacer() 102 | VStack { 103 | Spacer() 104 | SuccessSignView() 105 | 106 | Spacer() 107 | SuccessRatingView(starCount: getGameRating(duration: gameDuration)) 108 | 109 | Spacer() 110 | Text(successSentences[textOption]) 111 | .fontWeight(.bold) 112 | .font(.system(.largeTitle, design: .rounded)) 113 | .multilineTextAlignment(.center) 114 | 115 | Spacer() 116 | } 117 | .frame(width: 420, height: 330) 118 | .background(Color(UIColor.systemBackground)) 119 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) 120 | .shadow(color: Color.black.opacity(0.3), radius: 22, x: 16, y: 16) 121 | .offset(x: 0, y: self.appear ? 0 : 16) 122 | .rotation3DEffect(Angle(degrees: Double(0.07 * getLength(x: userDrag.width, y: userDrag.height))), axis: (x: userDrag.width * 0.1, y: userDrag.height * 0.1, z: 0.0)) 123 | .animation(.easeInOut) 124 | 125 | Spacer() 126 | 127 | Text("To restart the game, \ntap on Playground Start/Stop Button.") 128 | .fontWeight(.semibold) 129 | .font(.system(.footnote, design: .rounded)) 130 | .multilineTextAlignment(.center) 131 | 132 | Spacer() 133 | } 134 | .opacity(self.appear ? 1 : 0) 135 | .onAppear { 136 | withAnimation { 137 | self.appear = true 138 | } 139 | AVAudioPlayer.stopPlaySoundBG() 140 | AVAudioPlayer.playSound(sound: "success2", type: "wav") 141 | } 142 | .gesture( 143 | DragGesture().onChanged { value in 144 | self.userDrag = value.translation 145 | } 146 | .onEnded { value in 147 | self.userDrag = .zero 148 | } 149 | ) 150 | 151 | } 152 | } 153 | 154 | struct GameSuccessView_Previews: PreviewProvider { 155 | static var previews: some View { 156 | GameSuccessView() 157 | } 158 | } 159 | 160 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/InternalTutorialView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct InTutorialView: View { 4 | 5 | @State private var xDistance: CGFloat = 0 6 | @State private var yDistance: CGFloat = 0 7 | @State private var isDragging: Bool = false 8 | 9 | @State var animate1Start = false 10 | @State var animate1End = false 11 | @State var animate2Start = false 12 | @State var animate2End = false 13 | @State var animate3Start = false 14 | @State var animate3End = false 15 | @State var animate4Start = false 16 | @State var animate4End = false 17 | @State var animate5Start = false 18 | @State var animate5End = false 19 | @State var animate6Start = false 20 | 21 | public init() {} 22 | public var body: some View { 23 | ZStack { 24 | VStack { 25 | FactoryView() 26 | .frame(width: mapIconSize, height: mapIconSize) 27 | .opacity(self.animate2Start ? 1 : 0) 28 | 29 | Spacer() 30 | 31 | LabView() 32 | .frame(width: mapIconSize, height: mapIconSize) 33 | .opacity(self.animate1Start ? 1 : 0) 34 | } 35 | .animation(Animation.easeInOut(duration: 0.8)) 36 | 37 | InternalTutorialPart1View(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start) 38 | .opacity((self.animate1Start && !self.animate2End) ? 1 : 0) 39 | .animation(Animation.easeInOut(duration: 0.8)) 40 | 41 | InternalTutorialPart2View(animate3Start: $animate3Start, animate3End: $animate3End, animate4Start: $animate4Start) 42 | .opacity((self.animate3Start && !self.animate3End) ? 1 : 0) 43 | .animation(Animation.easeInOut(duration: 0.8)) 44 | 45 | VStack { 46 | Spacer() 47 | 48 | Text("To reach your destination, \nyou will need to use \"Bink\".\n\nTo move, just drag anywhere on screen, \nninja will blink once you release your finger.\n\nJust TRY IT OUT!") 49 | .fontWeight(.semibold) 50 | .font(.system(.title, design: .rounded)) 51 | .multilineTextAlignment(.center) 52 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0) 53 | .animation(Animation.easeInOut(duration: 0.8)) 54 | 55 | Spacer() 56 | 57 | PlayerView(inputX: $xDistance, inputY: $yDistance, showPathPreview: $isDragging, gameEnded: .constant(false), tutorialMode: true) 58 | 59 | Spacer() 60 | 61 | Button(action: { 62 | self.animate4End = true 63 | self.animate5Start = true 64 | }) { 65 | Text("Next") 66 | .fontWeight(.semibold) 67 | .font(.system(.title, design: .rounded)) 68 | } 69 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0) 70 | .animation(Animation.easeInOut(duration: 0.8)) 71 | 72 | Spacer() 73 | } 74 | .background(Color.white.opacity(0.00001)) 75 | .gesture(DragGesture() 76 | .onChanged({ value in 77 | self.isDragging = true 78 | self.xDistance = value.translation.width 79 | self.yDistance = value.translation.height 80 | }) 81 | .onEnded({ (value) in 82 | self.isDragging = false 83 | }) 84 | ) 85 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0) 86 | 87 | InternalTutorialPart3View(animate5End: $animate5End, animate6Start: $animate6Start) 88 | .opacity((self.animate5Start && !self.animate5End) ? 1 : 0) 89 | .animation(Animation.easeInOut(duration: 0.8)) 90 | 91 | VStack { 92 | Spacer() 93 | Spacer() 94 | Text("That's all.\nIt's your time to save the world.") 95 | .fontWeight(.semibold) 96 | .font(.system(.title, design: .rounded)) 97 | .multilineTextAlignment(.center) 98 | .opacity(self.animate6Start ? 1 : 0) 99 | Spacer() 100 | Text("Go to the Next page to start.") 101 | .fontWeight(.semibold) 102 | .font(.system(.callout, design: .rounded)) 103 | .multilineTextAlignment(.center) 104 | .opacity(self.animate6Start ? 1 : 0) 105 | Spacer() 106 | } 107 | .opacity(self.animate6Start ? 1 : 0) 108 | .animation(Animation.easeInOut(duration: 0.8)) 109 | 110 | } 111 | .onAppear { 112 | self.animate1Start = true 113 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 114 | self.animate2Start = true 115 | } 116 | } 117 | 118 | 119 | } 120 | 121 | } 122 | 123 | struct InTutorialView_Previews: PreviewProvider { 124 | static var previews: some View { 125 | InTutorialView() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/GameSuccessView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameSuccessView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/13/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import AVFoundation 11 | 12 | struct SuccessSignView: View { 13 | var body: some View { 14 | HStack { 15 | Image(systemName: "checkmark.seal.fill") 16 | .resizable() 17 | .frame(width: 30, height: 30) 18 | 19 | Text("You Won") 20 | .fontWeight(.semibold) 21 | .font(.system(.title, design: .rounded)) 22 | } 23 | } 24 | } 25 | 26 | struct SuccessRatingView: View { 27 | let starCount: Int 28 | @State var start: Bool = false 29 | 30 | func determineColor(index: Int) -> Color { 31 | if index <= starCount { 32 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3 * Double(index)) { 33 | AVAudioPlayer.playSound2(sound: "coin7", type: "wav") 34 | } 35 | return Color.yellow 36 | } else { 37 | return Color.gray 38 | } 39 | } 40 | 41 | func determineOpacity(index: Int) -> Double { 42 | if index <= starCount { 43 | return 1 44 | } else { 45 | return 0 46 | } 47 | } 48 | 49 | func determineAnimation(index: Int) -> Animation { 50 | return Animation.easeOut.delay(Double(index) * 0.3) 51 | } 52 | 53 | var body: some View { 54 | HStack { 55 | ForEach(1..<4, id: \.self) { i in 56 | ZStack { 57 | Image(systemName: "star.circle.fill") 58 | .resizable() 59 | .frame(width: 60, height: 60) 60 | .foregroundColor(self.determineColor(index: i)) 61 | 62 | Image(systemName: "star.circle.fill") 63 | .resizable() 64 | .frame(width: 60, height: 60) 65 | .scaleEffect(self.start ? 1.8 : 1) 66 | .opacity(self.start ? 0 : self.determineOpacity(index: i)) 67 | .foregroundColor(Color.yellow) 68 | .animation(self.determineAnimation(index: i)) 69 | } 70 | 71 | 72 | } 73 | 74 | } 75 | .onAppear { 76 | withAnimation { 77 | self.start = true 78 | } 79 | } 80 | } 81 | } 82 | 83 | public struct GameSuccessView: View { 84 | 85 | @State var appear = false 86 | @State var userDrag = CGSize.zero 87 | 88 | let gameDuration = getGameDuration(from: startTime, to: endTime) 89 | 90 | public init() {} 91 | 92 | let textOption = Int.random(in: 0..<5) 93 | let successSentences: [String] = ["You are a Hero!", 94 | "Lengendary!", 95 | "Congratulations!", 96 | "Good Job!", 97 | "Play of the Game!"] 98 | 99 | public var body: some View { 100 | VStack { 101 | Spacer() 102 | VStack { 103 | Spacer() 104 | SuccessSignView() 105 | 106 | Spacer() 107 | SuccessRatingView(starCount: getGameRating(duration: gameDuration)) 108 | 109 | Spacer() 110 | Text(successSentences[textOption]) 111 | .fontWeight(.bold) 112 | .font(.system(.largeTitle, design: .rounded)) 113 | .multilineTextAlignment(.center) 114 | 115 | Spacer() 116 | } 117 | .frame(width: 420, height: 330) 118 | .background(Color(UIColor.systemBackground)) 119 | .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) 120 | .shadow(color: Color.black.opacity(0.3), radius: 22, x: 16, y: 16) 121 | .offset(x: 0, y: self.appear ? 0 : 16) 122 | .rotation3DEffect(Angle(degrees: Double(0.07 * getLength(x: userDrag.width, y: userDrag.height))), axis: (x: userDrag.width * 0.1, y: userDrag.height * 0.1, z: 0.0)) 123 | .animation(.easeInOut) 124 | 125 | Spacer() 126 | 127 | Text("To restart the game, \ntap on Playground Start/Stop Button.") 128 | .fontWeight(.semibold) 129 | .font(.system(.footnote, design: .rounded)) 130 | .multilineTextAlignment(.center) 131 | 132 | Spacer() 133 | } 134 | .opacity(self.appear ? 1 : 0) 135 | .onAppear { 136 | withAnimation { 137 | self.appear = true 138 | } 139 | AVAudioPlayer.stopPlaySoundBG() 140 | AVAudioPlayer.playSound(sound: "success2", type: "wav") 141 | } 142 | .gesture( 143 | DragGesture().onChanged { value in 144 | self.userDrag = value.translation 145 | } 146 | .onEnded { value in 147 | self.userDrag = .zero 148 | } 149 | ) 150 | 151 | } 152 | } 153 | 154 | struct GameSuccessView_Previews: PreviewProvider { 155 | static var previews: some View { 156 | GameSuccessView() 157 | } 158 | } 159 | 160 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/InternalTutorialView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct InTutorialView: View { 4 | 5 | @State private var xDistance: CGFloat = 0 6 | @State private var yDistance: CGFloat = 0 7 | @State private var isDragging: Bool = false 8 | 9 | @State var animate1Start = false 10 | @State var animate1End = false 11 | @State var animate2Start = false 12 | @State var animate2End = false 13 | @State var animate3Start = false 14 | @State var animate3End = false 15 | @State var animate4Start = false 16 | @State var animate4End = false 17 | @State var animate5Start = false 18 | @State var animate5End = false 19 | @State var animate6Start = false 20 | 21 | public init() {} 22 | public var body: some View { 23 | ZStack { 24 | VStack { 25 | FactoryView() 26 | .frame(width: mapIconSize, height: mapIconSize) 27 | .opacity(self.animate2Start ? 1 : 0) 28 | 29 | Spacer() 30 | 31 | LabView() 32 | .frame(width: mapIconSize, height: mapIconSize) 33 | .opacity(self.animate1Start ? 1 : 0) 34 | } 35 | .animation(Animation.easeInOut(duration: 0.8)) 36 | 37 | InternalTutorialPart1View(animate1Start: $animate1Start, animate1End: $animate1End, animate2Start: $animate2Start, animate2End: $animate2End, animate3Start: $animate3Start) 38 | .opacity((self.animate1Start && !self.animate2End) ? 1 : 0) 39 | .animation(Animation.easeInOut(duration: 0.8)) 40 | 41 | InternalTutorialPart2View(animate3Start: $animate3Start, animate3End: $animate3End, animate4Start: $animate4Start) 42 | .opacity((self.animate3Start && !self.animate3End) ? 1 : 0) 43 | .animation(Animation.easeInOut(duration: 0.8)) 44 | 45 | VStack { 46 | Spacer() 47 | 48 | Text("To reach your destination, \nyou will need to use \"Bink\".\n\nTo move, just drag anywhere on screen, \nninja will blink once you release your finger.\n\nJust TRY IT OUT!") 49 | .fontWeight(.semibold) 50 | .font(.system(.title, design: .rounded)) 51 | .multilineTextAlignment(.center) 52 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0) 53 | .animation(Animation.easeInOut(duration: 0.8)) 54 | 55 | Spacer() 56 | 57 | PlayerView(inputX: $xDistance, inputY: $yDistance, showPathPreview: $isDragging, gameEnded: .constant(false), tutorialMode: true) 58 | 59 | Spacer() 60 | 61 | Button(action: { 62 | self.animate4End = true 63 | self.animate5Start = true 64 | }) { 65 | Text("Next") 66 | .fontWeight(.semibold) 67 | .font(.system(.title, design: .rounded)) 68 | } 69 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0) 70 | .animation(Animation.easeInOut(duration: 0.8)) 71 | 72 | Spacer() 73 | } 74 | .background(Color.white.opacity(0.00001)) 75 | .gesture(DragGesture() 76 | .onChanged({ value in 77 | self.isDragging = true 78 | self.xDistance = value.translation.width 79 | self.yDistance = value.translation.height 80 | }) 81 | .onEnded({ (value) in 82 | self.isDragging = false 83 | }) 84 | ) 85 | .opacity((self.animate4Start && !self.animate4End) ? 1 : 0) 86 | 87 | InternalTutorialPart3View(animate5End: $animate5End, animate6Start: $animate6Start) 88 | .opacity((self.animate5Start && !self.animate5End) ? 1 : 0) 89 | .animation(Animation.easeInOut(duration: 0.8)) 90 | 91 | VStack { 92 | Spacer() 93 | Spacer() 94 | Text("That's all.\nIt's your time to save the world.") 95 | .fontWeight(.semibold) 96 | .font(.system(.title, design: .rounded)) 97 | .multilineTextAlignment(.center) 98 | .opacity(self.animate6Start ? 1 : 0) 99 | Spacer() 100 | Text("Go to the Next page to start.") 101 | .fontWeight(.semibold) 102 | .font(.system(.callout, design: .rounded)) 103 | .multilineTextAlignment(.center) 104 | .opacity(self.animate6Start ? 1 : 0) 105 | Spacer() 106 | } 107 | .opacity(self.animate6Start ? 1 : 0) 108 | .animation(Animation.easeInOut(duration: 0.8)) 109 | 110 | } 111 | .onAppear { 112 | self.animate1Start = true 113 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 114 | self.animate2Start = true 115 | } 116 | } 117 | 118 | 119 | } 120 | 121 | } 122 | 123 | struct InTutorialView_Previews: PreviewProvider { 124 | static var previews: some View { 125 | InTutorialView() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/InternalTutorialPartialView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InternalTutorialPartialView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct InternalTutorialPart1View: View { 12 | 13 | @Binding var animate1Start: Bool 14 | @Binding var animate1End : Bool 15 | @Binding var animate2Start: Bool 16 | @Binding var animate2End : Bool 17 | @Binding var animate3Start: Bool 18 | 19 | public init( 20 | animate1Start: Binding, 21 | animate1End: Binding, 22 | animate2Start: Binding, 23 | animate2End: Binding, 24 | animate3Start: Binding) { 25 | self._animate1Start = animate1Start 26 | self._animate1End = animate1End 27 | self._animate2Start = animate2Start 28 | self._animate2End = animate2End 29 | self._animate3Start = animate3Start 30 | 31 | } 32 | 33 | public var body: some View { 34 | VStack { 35 | Spacer() 36 | Text("Your destination is the factory, \non the top of your screen.") 37 | .fontWeight(.semibold) 38 | .font(.system(.title, design: .rounded)) 39 | .multilineTextAlignment(.center) 40 | .opacity((self.animate2Start && !self.animate2End) ? 1 : 0) 41 | 42 | Spacer() 43 | 44 | Button(action: { 45 | self.animate1End = true 46 | self.animate2End = true 47 | self.animate3Start = true 48 | }) { 49 | Text("Next") 50 | .fontWeight(.semibold) 51 | .font(.system(.title, design: .rounded)) 52 | } 53 | .opacity((animate2Start && !animate1End) ? 1 : 0) 54 | .animation(Animation.easeInOut) 55 | 56 | Spacer() 57 | 58 | Text("You will start off at the Lab, \non the bottom of your screen.") 59 | .fontWeight(.semibold) 60 | .font(.system(.title, design: .rounded)) 61 | .multilineTextAlignment(.center) 62 | .opacity((self.animate1Start && !self.animate1End) ? 1 : 0) 63 | Spacer() 64 | } 65 | } 66 | } 67 | 68 | public struct InternalTutorialPart2View: View { 69 | 70 | @Binding var animate3Start: Bool 71 | @Binding var animate3End : Bool 72 | @Binding var animate4Start: Bool 73 | 74 | public init( 75 | animate3Start: Binding, 76 | animate3End: Binding, 77 | animate4Start: Binding) { 78 | 79 | self._animate3Start = animate3Start 80 | self._animate3End = animate3End 81 | self._animate4Start = animate4Start 82 | 83 | } 84 | 85 | public var body: some View { 86 | VStack { 87 | Spacer() 88 | Text("There will be people walking around.\nBeware that you don't know about \ntheir health conditions, \nand most of them are infected.") 89 | .fontWeight(.semibold) 90 | .font(.system(.title, design: .rounded)) 91 | .multilineTextAlignment(.center) 92 | .animation(Animation.easeInOut(duration: 0.8)) 93 | 94 | HStack { 95 | NPCInternalView(isDragging: .constant(false)) 96 | NPCInternalView(isDragging: .constant(false)) 97 | NPCInternalView(isDragging: .constant(false)) 98 | } 99 | .animation(Animation.easeInOut(duration: 0.8)) 100 | 101 | Text("To protect yourself, \nkeep at lease 6 feet away from others!") 102 | .fontWeight(.semibold) 103 | .font(.system(.title, design: .rounded)) 104 | .multilineTextAlignment(.center) 105 | .animation(Animation.easeInOut(duration: 0.8)) 106 | 107 | Spacer() 108 | 109 | Button(action: { 110 | self.animate3End = true 111 | self.animate4Start = true 112 | }) { 113 | Text("Next") 114 | .fontWeight(.semibold) 115 | .font(.system(.title, design: .rounded)) 116 | } 117 | .animation(Animation.easeInOut) 118 | 119 | Spacer() 120 | } 121 | } 122 | } 123 | 124 | public struct InternalTutorialPart3View: View { 125 | 126 | @Binding var animate5End : Bool 127 | @Binding var animate6Start: Bool 128 | 129 | public init( 130 | animate5End: Binding, 131 | animate6Start: Binding) { 132 | 133 | self._animate5End = animate5End 134 | self._animate6Start = animate6Start 135 | 136 | } 137 | 138 | public var body: some View { 139 | VStack { 140 | Spacer() 141 | Text("When you are trying to blink, \nyou will freeze the time and see \nthe 6-feet safety distance around other people. \nPedestrians will freeze until you complete your \"Blink\"") 142 | .fontWeight(.semibold) 143 | .font(.system(.title, design: .rounded)) 144 | .multilineTextAlignment(.center) 145 | .animation(Animation.easeInOut(duration: 0.8)) 146 | 147 | Spacer() 148 | 149 | HStack { 150 | Spacer() 151 | NPCInternalView(isDragging: .constant(true)) 152 | Spacer() 153 | NPCInternalView(isDragging: .constant(true)) 154 | Spacer() 155 | NPCInternalView(isDragging: .constant(true)) 156 | Spacer() 157 | } 158 | .animation(Animation.easeInOut(duration: 0.8)) 159 | 160 | Spacer() 161 | 162 | Text("Remember, \nkeep at lease 6 feet away from others!") 163 | .fontWeight(.semibold) 164 | .font(.system(.title, design: .rounded)) 165 | .multilineTextAlignment(.center) 166 | .animation(Animation.easeInOut(duration: 0.8)) 167 | 168 | Spacer() 169 | 170 | Button(action: { 171 | self.animate5End = true 172 | self.animate6Start = true 173 | }) { 174 | Text("Next") 175 | .fontWeight(.semibold) 176 | .font(.system(.title, design: .rounded)) 177 | } 178 | .animation(Animation.easeInOut) 179 | 180 | Spacer() 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/InternalTutorialPartialView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InternalTutorialPartialView.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/17/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct InternalTutorialPart1View: View { 12 | 13 | @Binding var animate1Start: Bool 14 | @Binding var animate1End : Bool 15 | @Binding var animate2Start: Bool 16 | @Binding var animate2End : Bool 17 | @Binding var animate3Start: Bool 18 | 19 | public init( 20 | animate1Start: Binding, 21 | animate1End: Binding, 22 | animate2Start: Binding, 23 | animate2End: Binding, 24 | animate3Start: Binding) { 25 | self._animate1Start = animate1Start 26 | self._animate1End = animate1End 27 | self._animate2Start = animate2Start 28 | self._animate2End = animate2End 29 | self._animate3Start = animate3Start 30 | 31 | } 32 | 33 | public var body: some View { 34 | VStack { 35 | Spacer() 36 | Text("Your destination is the factory, \non the top of your screen.") 37 | .fontWeight(.semibold) 38 | .font(.system(.title, design: .rounded)) 39 | .multilineTextAlignment(.center) 40 | .opacity((self.animate2Start && !self.animate2End) ? 1 : 0) 41 | 42 | Spacer() 43 | 44 | Button(action: { 45 | self.animate1End = true 46 | self.animate2End = true 47 | self.animate3Start = true 48 | }) { 49 | Text("Next") 50 | .fontWeight(.semibold) 51 | .font(.system(.title, design: .rounded)) 52 | } 53 | .opacity((animate2Start && !animate1End) ? 1 : 0) 54 | .animation(Animation.easeInOut) 55 | 56 | Spacer() 57 | 58 | Text("You will start off at the Lab, \non the bottom of your screen.") 59 | .fontWeight(.semibold) 60 | .font(.system(.title, design: .rounded)) 61 | .multilineTextAlignment(.center) 62 | .opacity((self.animate1Start && !self.animate1End) ? 1 : 0) 63 | Spacer() 64 | } 65 | } 66 | } 67 | 68 | public struct InternalTutorialPart2View: View { 69 | 70 | @Binding var animate3Start: Bool 71 | @Binding var animate3End : Bool 72 | @Binding var animate4Start: Bool 73 | 74 | public init( 75 | animate3Start: Binding, 76 | animate3End: Binding, 77 | animate4Start: Binding) { 78 | 79 | self._animate3Start = animate3Start 80 | self._animate3End = animate3End 81 | self._animate4Start = animate4Start 82 | 83 | } 84 | 85 | public var body: some View { 86 | VStack { 87 | Spacer() 88 | Text("There will be people walking around.\nBeware that you don't know about \ntheir health conditions, \nand most of them are infected.") 89 | .fontWeight(.semibold) 90 | .font(.system(.title, design: .rounded)) 91 | .multilineTextAlignment(.center) 92 | .animation(Animation.easeInOut(duration: 0.8)) 93 | 94 | HStack { 95 | NPCInternalView(isDragging: .constant(false)) 96 | NPCInternalView(isDragging: .constant(false)) 97 | NPCInternalView(isDragging: .constant(false)) 98 | } 99 | .animation(Animation.easeInOut(duration: 0.8)) 100 | 101 | Text("To protect yourself, \nkeep at lease 6 feet away from others!") 102 | .fontWeight(.semibold) 103 | .font(.system(.title, design: .rounded)) 104 | .multilineTextAlignment(.center) 105 | .animation(Animation.easeInOut(duration: 0.8)) 106 | 107 | Spacer() 108 | 109 | Button(action: { 110 | self.animate3End = true 111 | self.animate4Start = true 112 | }) { 113 | Text("Next") 114 | .fontWeight(.semibold) 115 | .font(.system(.title, design: .rounded)) 116 | } 117 | .animation(Animation.easeInOut) 118 | 119 | Spacer() 120 | } 121 | } 122 | } 123 | 124 | public struct InternalTutorialPart3View: View { 125 | 126 | @Binding var animate5End : Bool 127 | @Binding var animate6Start: Bool 128 | 129 | public init( 130 | animate5End: Binding, 131 | animate6Start: Binding) { 132 | 133 | self._animate5End = animate5End 134 | self._animate6Start = animate6Start 135 | 136 | } 137 | 138 | public var body: some View { 139 | VStack { 140 | Spacer() 141 | Text("When you are trying to blink, \nyou will freeze the time and see \nthe 6-feet safety distance around other people. \nPedestrians will freeze until you complete your \"Blink\"") 142 | .fontWeight(.semibold) 143 | .font(.system(.title, design: .rounded)) 144 | .multilineTextAlignment(.center) 145 | .animation(Animation.easeInOut(duration: 0.8)) 146 | 147 | Spacer() 148 | 149 | HStack { 150 | Spacer() 151 | NPCInternalView(isDragging: .constant(true)) 152 | Spacer() 153 | NPCInternalView(isDragging: .constant(true)) 154 | Spacer() 155 | NPCInternalView(isDragging: .constant(true)) 156 | Spacer() 157 | } 158 | .animation(Animation.easeInOut(duration: 0.8)) 159 | 160 | Spacer() 161 | 162 | Text("Remember, \nkeep at lease 6 feet away from others!") 163 | .fontWeight(.semibold) 164 | .font(.system(.title, design: .rounded)) 165 | .multilineTextAlignment(.center) 166 | .animation(Animation.easeInOut(duration: 0.8)) 167 | 168 | Spacer() 169 | 170 | Button(action: { 171 | self.animate5End = true 172 | self.animate6Start = true 173 | }) { 174 | Text("Next") 175 | .fontWeight(.semibold) 176 | .font(.system(.title, design: .rounded)) 177 | } 178 | .animation(Animation.easeInOut) 179 | 180 | Spacer() 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge/Views/PlayerUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerUI.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | import AVFoundation 12 | 13 | // MARK: - Player 14 | struct MovePathIndicatorView: View { 15 | @Binding var showPathPreview: Bool 16 | @State var destCoord: CGSize 17 | 18 | var body: some View { 19 | Image(systemName: "xmark.circle") 20 | .resizable() 21 | .frame(width: playerSize, height: playerSize) 22 | .offset(destCoord) 23 | .foregroundColor(Color.blue.opacity(showPathPreview ? 1 : 0)) 24 | .font(.system(size: 25, weight: .bold)) 25 | } 26 | } 27 | 28 | public struct PlayerView: View { 29 | 30 | @Binding var inputX: CGFloat 31 | @Binding var inputY: CGFloat 32 | @Binding var showPathPreview: Bool 33 | @Binding var gameEnded: Bool 34 | 35 | @State var playerFailed = false 36 | @State var invalidMoveCount: Int = 0 37 | 38 | var tutorialMode: Bool 39 | 40 | public init(inputX: Binding, inputY: Binding, showPathPreview: Binding, gameEnded: Binding, tutorialMode: Bool) { 41 | self._inputX = inputX 42 | self._inputY = inputY 43 | self._showPathPreview = showPathPreview 44 | self._gameEnded = gameEnded 45 | self.tutorialMode = tutorialMode 46 | } 47 | 48 | 49 | public var body: some View { 50 | 51 | ZStack { 52 | 53 | // path preview 54 | Image(systemName: "xmark.circle") 55 | .resizable() 56 | .frame(width: playerSize, height: playerSize) 57 | .offset(destinationPreviewCalculation(inputX: inputX, inputY: inputY)) 58 | .foregroundColor(playerPathColor.opacity(showPathPreview ? 1 : 0)) 59 | .font(.system(size: 25, weight: .bold)) 60 | 61 | // player icon 62 | Image(uiImage: UIImage(named: "Ninja_Circle")!) 63 | .resizable() 64 | .frame(width: playerSize, height: playerSize) 65 | // .foregroundColor(playerColor) 66 | // invalid move warning indication 67 | .modifier(Shake(animatableData: CGFloat(invalidMoveCount))) 68 | // movement animation 69 | .animation(.playerMove) 70 | .overlay( 71 | Circle() 72 | .foregroundColor(Color(UIColor.systemRed)) 73 | .frame(width: playerSize, height: playerSize) 74 | .opacity((!self.tutorialMode && self.playerFailed) ? 0.9 : 0) 75 | .animation(.easeInOut) 76 | ) 77 | 78 | } 79 | .offset(showPathPreview ? newPosition : playerMove()) 80 | 81 | } 82 | 83 | private func playerMove() -> CGSize { 84 | 85 | if tutorialMode { 86 | let jump = destinationPreviewCalculation(inputX: inputX, inputY: inputY) 87 | 88 | currentPosition = CGSize(width: jump.width + newPosition.width, height: jump.height + newPosition.height) 89 | 90 | AVAudioPlayer.playSound(sound: "jump", type: "wav") 91 | previousPosition = ScreenCoordinate(x: newPosition.width + viewWidth/2 , y: newPosition.height + viewHeight - playerSize/2) 92 | newPosition = currentPosition 93 | 94 | return newPosition 95 | } 96 | 97 | if endOnHold { 98 | return newPosition 99 | } 100 | 101 | let jump = destinationPreviewCalculation(inputX: inputX, inputY: inputY) 102 | 103 | currentPosition = CGSize(width: jump.width + newPosition.width, height: jump.height + newPosition.height) 104 | 105 | if playerOutOfView() { 106 | 107 | // use async to prevent updating state variable during UI rerendering 108 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 109 | // trigger shaking animation to indicate invalid move warning 110 | withAnimation(.default) { 111 | AVAudioPlayer.playSound(sound: "error1", type: "wav") 112 | self.invalidMoveCount += 1 113 | self.showPathPreview = true 114 | } 115 | } 116 | return newPosition 117 | } else { 118 | // valid move 119 | AVAudioPlayer.playSound(sound: "jump", type: "wav") 120 | previousPosition = ScreenCoordinate(x: newPosition.width + viewWidth/2 , y: newPosition.height + viewHeight - playerSize/2) 121 | newPosition = currentPosition 122 | } 123 | 124 | if started { 125 | let gameStatus = gameStateCheck() 126 | if gameStatus.ended { 127 | // game ended 128 | endOnHold = true 129 | endTime = Date() 130 | 131 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { 132 | self.gameEnded = true 133 | } 134 | 135 | if gameStatus.succeeded { 136 | // player won 137 | playerWon = true 138 | } else { 139 | // player failed 140 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { 141 | self.playerFailed = true 142 | } 143 | 144 | playerWon = false 145 | 146 | } 147 | } else { 148 | // game still going on 149 | } 150 | } 151 | 152 | started = true 153 | 154 | return newPosition 155 | } 156 | 157 | private func destinationPreviewCalculation(inputX: CGFloat, inputY: CGFloat) -> CGSize { 158 | let vectorDirectoin = CGSize(width: -inputX, height: -inputY) 159 | 160 | var vX = vectorDirectoin.width 161 | var vY = vectorDirectoin.height 162 | 163 | let const: CGFloat = 10 164 | 165 | if vX <= 0 { 166 | vX = -calculateMoveEstimate(input: -vX) * const 167 | } else { 168 | vX = calculateMoveEstimate(input: vX) * const 169 | } 170 | 171 | if vY <= 0 { 172 | vY = -calculateMoveEstimate(input: -vY) * const 173 | } else { 174 | vY = calculateMoveEstimate(input: vY) * const 175 | } 176 | 177 | let result = CGSize(width: vX, height: vY) 178 | 179 | return result 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /6 Feet Between.playgroundbook/Contents/UserModules/GameFoundationModule.playgroundmodule/Sources/PlayerUI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerUI.swift 3 | // WWDC20PlaygroundTest 4 | // 5 | // Created by Tony Tang on 5/15/20. 6 | // Copyright © 2020 TonyTang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | import AVFoundation 12 | 13 | // MARK: - Player 14 | struct MovePathIndicatorView: View { 15 | @Binding var showPathPreview: Bool 16 | @State var destCoord: CGSize 17 | 18 | var body: some View { 19 | Image(systemName: "xmark.circle") 20 | .resizable() 21 | .frame(width: playerSize, height: playerSize) 22 | .offset(destCoord) 23 | .foregroundColor(Color.blue.opacity(showPathPreview ? 1 : 0)) 24 | .font(.system(size: 25, weight: .bold)) 25 | } 26 | } 27 | 28 | public struct PlayerView: View { 29 | 30 | @Binding var inputX: CGFloat 31 | @Binding var inputY: CGFloat 32 | @Binding var showPathPreview: Bool 33 | @Binding var gameEnded: Bool 34 | 35 | @State var playerFailed = false 36 | @State var invalidMoveCount: Int = 0 37 | 38 | var tutorialMode: Bool 39 | 40 | public init(inputX: Binding, inputY: Binding, showPathPreview: Binding, gameEnded: Binding, tutorialMode: Bool) { 41 | self._inputX = inputX 42 | self._inputY = inputY 43 | self._showPathPreview = showPathPreview 44 | self._gameEnded = gameEnded 45 | self.tutorialMode = tutorialMode 46 | } 47 | 48 | 49 | public var body: some View { 50 | 51 | ZStack { 52 | 53 | // path preview 54 | Image(systemName: "xmark.circle") 55 | .resizable() 56 | .frame(width: playerSize, height: playerSize) 57 | .offset(destinationPreviewCalculation(inputX: inputX, inputY: inputY)) 58 | .foregroundColor(playerPathColor.opacity(showPathPreview ? 1 : 0)) 59 | .font(.system(size: 25, weight: .bold)) 60 | 61 | // player icon 62 | Image(uiImage: UIImage(named: "Ninja_Circle")!) 63 | .resizable() 64 | .frame(width: playerSize, height: playerSize) 65 | // .foregroundColor(playerColor) 66 | // invalid move warning indication 67 | .modifier(Shake(animatableData: CGFloat(invalidMoveCount))) 68 | // movement animation 69 | .animation(.playerMove) 70 | .overlay( 71 | Circle() 72 | .foregroundColor(Color(UIColor.systemRed)) 73 | .frame(width: playerSize, height: playerSize) 74 | .opacity((!self.tutorialMode && self.playerFailed) ? 0.9 : 0) 75 | .animation(.easeInOut) 76 | ) 77 | 78 | } 79 | .offset(showPathPreview ? newPosition : playerMove()) 80 | 81 | } 82 | 83 | private func playerMove() -> CGSize { 84 | 85 | if tutorialMode { 86 | let jump = destinationPreviewCalculation(inputX: inputX, inputY: inputY) 87 | 88 | currentPosition = CGSize(width: jump.width + newPosition.width, height: jump.height + newPosition.height) 89 | 90 | AVAudioPlayer.playSound(sound: "jump", type: "wav") 91 | previousPosition = ScreenCoordinate(x: newPosition.width + viewWidth/2 , y: newPosition.height + viewHeight - playerSize/2) 92 | newPosition = currentPosition 93 | 94 | return newPosition 95 | } 96 | 97 | if endOnHold { 98 | return newPosition 99 | } 100 | 101 | let jump = destinationPreviewCalculation(inputX: inputX, inputY: inputY) 102 | 103 | currentPosition = CGSize(width: jump.width + newPosition.width, height: jump.height + newPosition.height) 104 | 105 | if playerOutOfView() { 106 | return newPosition 107 | // use async to prevent updating state variable during UI rerendering 108 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 109 | // trigger shaking animation to indicate invalid move warning 110 | withAnimation(.default) { 111 | AVAudioPlayer.playSound(sound: "error1", type: "wav") 112 | self.invalidMoveCount += 1 113 | self.showPathPreview = true 114 | } 115 | } 116 | 117 | } else { 118 | // valid move 119 | AVAudioPlayer.playSound(sound: "jump", type: "wav") 120 | previousPosition = ScreenCoordinate(x: newPosition.width + viewWidth/2 , y: newPosition.height + viewHeight - playerSize/2) 121 | newPosition = currentPosition 122 | } 123 | 124 | if started { 125 | let gameStatus = gameStateCheck() 126 | if gameStatus.ended { 127 | // game ended 128 | endOnHold = true 129 | endTime = Date() 130 | 131 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { 132 | self.gameEnded = true 133 | } 134 | 135 | if gameStatus.succeeded { 136 | // player won 137 | playerWon = true 138 | } else { 139 | // player failed 140 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { 141 | self.playerFailed = true 142 | } 143 | 144 | playerWon = false 145 | 146 | } 147 | } else { 148 | // game still going on 149 | } 150 | } 151 | 152 | started = true 153 | 154 | return newPosition 155 | } 156 | 157 | private func destinationPreviewCalculation(inputX: CGFloat, inputY: CGFloat) -> CGSize { 158 | let vectorDirectoin = CGSize(width: -inputX, height: -inputY) 159 | 160 | var vX = vectorDirectoin.width 161 | var vY = vectorDirectoin.height 162 | 163 | let const: CGFloat = 10 164 | 165 | if vX <= 0 { 166 | vX = -calculateMoveEstimate(input: -vX) * const 167 | } else { 168 | vX = calculateMoveEstimate(input: vX) * const 169 | } 170 | 171 | if vY <= 0 { 172 | vY = -calculateMoveEstimate(input: -vY) * const 173 | } else { 174 | vY = calculateMoveEstimate(input: vY) * const 175 | } 176 | 177 | let result = CGSize(width: vX, height: vY) 178 | 179 | return result 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /SixFeetBetween_WWDC20SwiftChallenge.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EA078F902471365700B31133 /* InternalTutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA078F8F2471365700B31133 /* InternalTutorialView.swift */; }; 11 | EA2618602462A7E300ADFE5C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA26185F2462A7E300ADFE5C /* AppDelegate.swift */; }; 12 | EA2618622462A7E300ADFE5C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2618612462A7E300ADFE5C /* SceneDelegate.swift */; }; 13 | EA2618652462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = EA2618632462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld */; }; 14 | EA2618672462A7E300ADFE5C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2618662462A7E300ADFE5C /* ContentView.swift */; }; 15 | EA2618692462A7EA00ADFE5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA2618682462A7EA00ADFE5C /* Assets.xcassets */; }; 16 | EA26186C2462A7EA00ADFE5C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA26186B2462A7EA00ADFE5C /* Preview Assets.xcassets */; }; 17 | EA26186F2462A7EA00ADFE5C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EA26186D2462A7EA00ADFE5C /* LaunchScreen.storyboard */; }; 18 | EA29397D246FC55A00B8A898 /* BackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA29397C246FC55A00B8A898 /* BackgroundView.swift */; }; 19 | EA293981246FCBDA00B8A898 /* jump.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29397E246FCBDA00B8A898 /* jump.wav */; }; 20 | EA293982246FCBDA00B8A898 /* error2.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29397F246FCBDA00B8A898 /* error2.wav */; }; 21 | EA293983246FCBDA00B8A898 /* error1.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293980246FCBDA00B8A898 /* error1.wav */; }; 22 | EA293985246FCC4900B8A898 /* MediaSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA293984246FCC4900B8A898 /* MediaSupport.swift */; }; 23 | EA29398E246FE60000B8A898 /* success2.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293986246FE60000B8A898 /* success2.wav */; }; 24 | EA29398F246FE60000B8A898 /* success4.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293987246FE60000B8A898 /* success4.wav */; }; 25 | EA293990246FE60000B8A898 /* coin3.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293988246FE60000B8A898 /* coin3.wav */; }; 26 | EA293991246FE60000B8A898 /* coin1.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EA293989246FE60000B8A898 /* coin1.mp3 */; }; 27 | EA293992246FE60000B8A898 /* success3.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29398A246FE60000B8A898 /* success3.wav */; }; 28 | EA293993246FE60000B8A898 /* success1.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29398B246FE60000B8A898 /* success1.wav */; }; 29 | EA293994246FE60000B8A898 /* coin4.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29398C246FE60000B8A898 /* coin4.wav */; }; 30 | EA293995246FE60000B8A898 /* coin2.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29398D246FE60000B8A898 /* coin2.wav */; }; 31 | EA293999246FE92800B8A898 /* failure3.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293996246FE92800B8A898 /* failure3.wav */; }; 32 | EA29399A246FE92800B8A898 /* failure1.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293997246FE92800B8A898 /* failure1.wav */; }; 33 | EA29399B246FE92800B8A898 /* failure2.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA293998246FE92800B8A898 /* failure2.wav */; }; 34 | EA29399D2470B2A300B8A898 /* coin5.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29399C2470B2A300B8A898 /* coin5.wav */; }; 35 | EA29399F2470B30A00B8A898 /* coin6.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA29399E2470B30A00B8A898 /* coin6.wav */; }; 36 | EA2939A12470B33D00B8A898 /* coin7.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA2939A02470B33D00B8A898 /* coin7.wav */; }; 37 | EA2939A32470B76500B8A898 /* clock1.wav in Resources */ = {isa = PBXBuildFile; fileRef = EA2939A22470B76500B8A898 /* clock1.wav */; }; 38 | EA2CE767246CB775004B8A34 /* GameSuccessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2CE766246CB775004B8A34 /* GameSuccessView.swift */; }; 39 | EA2CE769246CB77F004B8A34 /* GameFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2CE768246CB77F004B8A34 /* GameFailureView.swift */; }; 40 | EA49601324713B0E00EBDA55 /* ButtonStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601224713B0E00EBDA55 /* ButtonStackView.swift */; }; 41 | EA49601524713BD900EBDA55 /* BriefLogisticExplanationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601424713BD900EBDA55 /* BriefLogisticExplanationView.swift */; }; 42 | EA49601724713D1100EBDA55 /* StoryTextIntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601624713D1100EBDA55 /* StoryTextIntroView.swift */; }; 43 | EA49601924713E9A00EBDA55 /* InternalTutorialPartialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601824713E9A00EBDA55 /* InternalTutorialPartialView.swift */; }; 44 | EA49601B2471468500EBDA55 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA49601A2471468500EBDA55 /* IconView.swift */; }; 45 | EA7AF2C6246F9F360004BDC4 /* Calculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2C5246F9F360004BDC4 /* Calculation.swift */; }; 46 | EA7AF2C8246F9FFD0004BDC4 /* GameLogistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2C7246F9FFD0004BDC4 /* GameLogistics.swift */; }; 47 | EA7AF2CA246FA0180004BDC4 /* AnimationPresets.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2C9246FA0180004BDC4 /* AnimationPresets.swift */; }; 48 | EA7AF2CC246FA1530004BDC4 /* NPCUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2CB246FA1530004BDC4 /* NPCUI.swift */; }; 49 | EA7AF2CE246FA1A90004BDC4 /* PlayerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7AF2CD246FA1A90004BDC4 /* PlayerUI.swift */; }; 50 | EA8B674A2470E7C400240747 /* factory2.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67462470E7C300240747 /* factory2.png */; }; 51 | EA8B674B2470E7C400240747 /* wash_hand.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67472470E7C400240747 /* wash_hand.png */; }; 52 | EA8B674C2470E7C400240747 /* humanw_mask.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67482470E7C400240747 /* humanw_mask.png */; }; 53 | EA8B674D2470E7C400240747 /* factory1.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67492470E7C400240747 /* factory1.png */; }; 54 | EA8B674F2470E7DF00240747 /* StoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8B674E2470E7DF00240747 /* StoryView.swift */; }; 55 | EA8B67512470E7F400240747 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8B67502470E7F400240747 /* TutorialView.swift */; }; 56 | EA8B67542470E95000240747 /* production.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67522470E95000240747 /* production.png */; }; 57 | EA8B67552470E95000240747 /* research.png in Resources */ = {isa = PBXBuildFile; fileRef = EA8B67532470E95000240747 /* research.png */; }; 58 | EAC000BE24727ED3009A19B5 /* Ninja_Circle.png in Resources */ = {isa = PBXBuildFile; fileRef = EAC000BD24727ED3009A19B5 /* Ninja_Circle.png */; }; 59 | EAFAA58124653BB00006CAFF /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAFAA58024653BB00006CAFF /* DataModel.swift */; }; 60 | /* End PBXBuildFile section */ 61 | 62 | /* Begin PBXFileReference section */ 63 | EA078F8F2471365700B31133 /* InternalTutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalTutorialView.swift; sourceTree = ""; }; 64 | EA26185C2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SixFeetBetween_WWDC20SwiftChallenge.app; sourceTree = BUILT_PRODUCTS_DIR; }; 65 | EA26185F2462A7E300ADFE5C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 66 | EA2618612462A7E300ADFE5C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 67 | EA2618642462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = WWDC20PlaygroundTest.xcdatamodel; sourceTree = ""; }; 68 | EA2618662462A7E300ADFE5C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 69 | EA2618682462A7EA00ADFE5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 70 | EA26186B2462A7EA00ADFE5C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 71 | EA26186E2462A7EA00ADFE5C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 72 | EA2618702462A7EA00ADFE5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | EA29397C246FC55A00B8A898 /* BackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundView.swift; sourceTree = ""; }; 74 | EA29397E246FCBDA00B8A898 /* jump.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = jump.wav; sourceTree = ""; }; 75 | EA29397F246FCBDA00B8A898 /* error2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = error2.wav; sourceTree = ""; }; 76 | EA293980246FCBDA00B8A898 /* error1.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = error1.wav; sourceTree = ""; }; 77 | EA293984246FCC4900B8A898 /* MediaSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSupport.swift; sourceTree = ""; }; 78 | EA293986246FE60000B8A898 /* success2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = success2.wav; sourceTree = ""; }; 79 | EA293987246FE60000B8A898 /* success4.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = success4.wav; sourceTree = ""; }; 80 | EA293988246FE60000B8A898 /* coin3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin3.wav; sourceTree = ""; }; 81 | EA293989246FE60000B8A898 /* coin1.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = coin1.mp3; sourceTree = ""; }; 82 | EA29398A246FE60000B8A898 /* success3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = success3.wav; sourceTree = ""; }; 83 | EA29398B246FE60000B8A898 /* success1.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = success1.wav; sourceTree = ""; }; 84 | EA29398C246FE60000B8A898 /* coin4.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin4.wav; sourceTree = ""; }; 85 | EA29398D246FE60000B8A898 /* coin2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin2.wav; sourceTree = ""; }; 86 | EA293996246FE92800B8A898 /* failure3.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = failure3.wav; sourceTree = ""; }; 87 | EA293997246FE92800B8A898 /* failure1.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = failure1.wav; sourceTree = ""; }; 88 | EA293998246FE92800B8A898 /* failure2.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = failure2.wav; sourceTree = ""; }; 89 | EA29399C2470B2A300B8A898 /* coin5.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin5.wav; sourceTree = ""; }; 90 | EA29399E2470B30A00B8A898 /* coin6.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin6.wav; sourceTree = ""; }; 91 | EA2939A02470B33D00B8A898 /* coin7.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = coin7.wav; sourceTree = ""; }; 92 | EA2939A22470B76500B8A898 /* clock1.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = clock1.wav; sourceTree = ""; }; 93 | EA2CE766246CB775004B8A34 /* GameSuccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSuccessView.swift; sourceTree = ""; }; 94 | EA2CE768246CB77F004B8A34 /* GameFailureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameFailureView.swift; sourceTree = ""; }; 95 | EA49601224713B0E00EBDA55 /* ButtonStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStackView.swift; sourceTree = ""; }; 96 | EA49601424713BD900EBDA55 /* BriefLogisticExplanationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BriefLogisticExplanationView.swift; sourceTree = ""; }; 97 | EA49601624713D1100EBDA55 /* StoryTextIntroView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryTextIntroView.swift; sourceTree = ""; }; 98 | EA49601824713E9A00EBDA55 /* InternalTutorialPartialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalTutorialPartialView.swift; sourceTree = ""; }; 99 | EA49601A2471468500EBDA55 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 100 | EA7AF2C5246F9F360004BDC4 /* Calculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calculation.swift; sourceTree = ""; }; 101 | EA7AF2C7246F9FFD0004BDC4 /* GameLogistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameLogistics.swift; sourceTree = ""; }; 102 | EA7AF2C9246FA0180004BDC4 /* AnimationPresets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationPresets.swift; sourceTree = ""; }; 103 | EA7AF2CB246FA1530004BDC4 /* NPCUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NPCUI.swift; sourceTree = ""; }; 104 | EA7AF2CD246FA1A90004BDC4 /* PlayerUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerUI.swift; sourceTree = ""; }; 105 | EA8B67462470E7C300240747 /* factory2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = factory2.png; sourceTree = ""; }; 106 | EA8B67472470E7C400240747 /* wash_hand.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wash_hand.png; sourceTree = ""; }; 107 | EA8B67482470E7C400240747 /* humanw_mask.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = humanw_mask.png; sourceTree = ""; }; 108 | EA8B67492470E7C400240747 /* factory1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = factory1.png; sourceTree = ""; }; 109 | EA8B674E2470E7DF00240747 /* StoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryView.swift; sourceTree = ""; }; 110 | EA8B67502470E7F400240747 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = ""; }; 111 | EA8B67522470E95000240747 /* production.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = production.png; sourceTree = ""; }; 112 | EA8B67532470E95000240747 /* research.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = research.png; sourceTree = ""; }; 113 | EAC000BD24727ED3009A19B5 /* Ninja_Circle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Ninja_Circle.png; sourceTree = ""; }; 114 | EAFAA58024653BB00006CAFF /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = ""; }; 115 | /* End PBXFileReference section */ 116 | 117 | /* Begin PBXFrameworksBuildPhase section */ 118 | EA2618592462A7E300ADFE5C /* Frameworks */ = { 119 | isa = PBXFrameworksBuildPhase; 120 | buildActionMask = 2147483647; 121 | files = ( 122 | ); 123 | runOnlyForDeploymentPostprocessing = 0; 124 | }; 125 | /* End PBXFrameworksBuildPhase section */ 126 | 127 | /* Begin PBXGroup section */ 128 | EA2618532462A7E300ADFE5C = { 129 | isa = PBXGroup; 130 | children = ( 131 | EA26185E2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge */, 132 | EA26185D2462A7E300ADFE5C /* Products */, 133 | ); 134 | sourceTree = ""; 135 | }; 136 | EA26185D2462A7E300ADFE5C /* Products */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | EA26185C2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge.app */, 140 | ); 141 | name = Products; 142 | sourceTree = ""; 143 | }; 144 | EA26185E2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | EA26185F2462A7E300ADFE5C /* AppDelegate.swift */, 148 | EA2618612462A7E300ADFE5C /* SceneDelegate.swift */, 149 | EA8B67432470DE2700240747 /* Views */, 150 | EA8B67452470DE7D00240747 /* Support */, 151 | EA8B67442470DE3100240747 /* Data */, 152 | EA2939A52470CC4A00B8A898 /* Visual Assets */, 153 | EA2939A42470BC1000B8A898 /* Sound Effect */, 154 | EA2618682462A7EA00ADFE5C /* Assets.xcassets */, 155 | EA26186D2462A7EA00ADFE5C /* LaunchScreen.storyboard */, 156 | EA2618702462A7EA00ADFE5C /* Info.plist */, 157 | EA2618632462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld */, 158 | EA26186A2462A7EA00ADFE5C /* Preview Content */, 159 | ); 160 | path = SixFeetBetween_WWDC20SwiftChallenge; 161 | sourceTree = ""; 162 | }; 163 | EA26186A2462A7EA00ADFE5C /* Preview Content */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | EA26186B2462A7EA00ADFE5C /* Preview Assets.xcassets */, 167 | ); 168 | path = "Preview Content"; 169 | sourceTree = ""; 170 | }; 171 | EA2939A42470BC1000B8A898 /* Sound Effect */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | EA293980246FCBDA00B8A898 /* error1.wav */, 175 | EA29397F246FCBDA00B8A898 /* error2.wav */, 176 | EA29397E246FCBDA00B8A898 /* jump.wav */, 177 | EA293989246FE60000B8A898 /* coin1.mp3 */, 178 | EA29398D246FE60000B8A898 /* coin2.wav */, 179 | EA293988246FE60000B8A898 /* coin3.wav */, 180 | EA29398C246FE60000B8A898 /* coin4.wav */, 181 | EA29399C2470B2A300B8A898 /* coin5.wav */, 182 | EA29399E2470B30A00B8A898 /* coin6.wav */, 183 | EA2939A02470B33D00B8A898 /* coin7.wav */, 184 | EA293997246FE92800B8A898 /* failure1.wav */, 185 | EA293998246FE92800B8A898 /* failure2.wav */, 186 | EA293996246FE92800B8A898 /* failure3.wav */, 187 | EA29398B246FE60000B8A898 /* success1.wav */, 188 | EA293986246FE60000B8A898 /* success2.wav */, 189 | EA29398A246FE60000B8A898 /* success3.wav */, 190 | EA293987246FE60000B8A898 /* success4.wav */, 191 | EA2939A22470B76500B8A898 /* clock1.wav */, 192 | ); 193 | path = "Sound Effect"; 194 | sourceTree = ""; 195 | }; 196 | EA2939A52470CC4A00B8A898 /* Visual Assets */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | EAC000BD24727ED3009A19B5 /* Ninja_Circle.png */, 200 | EA8B67492470E7C400240747 /* factory1.png */, 201 | EA8B67462470E7C300240747 /* factory2.png */, 202 | EA8B67482470E7C400240747 /* humanw_mask.png */, 203 | EA8B67472470E7C400240747 /* wash_hand.png */, 204 | EA8B67522470E95000240747 /* production.png */, 205 | EA8B67532470E95000240747 /* research.png */, 206 | ); 207 | path = "Visual Assets"; 208 | sourceTree = ""; 209 | }; 210 | EA8B67432470DE2700240747 /* Views */ = { 211 | isa = PBXGroup; 212 | children = ( 213 | EA8B674E2470E7DF00240747 /* StoryView.swift */, 214 | EA49601224713B0E00EBDA55 /* ButtonStackView.swift */, 215 | EA8B67502470E7F400240747 /* TutorialView.swift */, 216 | EA49601824713E9A00EBDA55 /* InternalTutorialPartialView.swift */, 217 | EA078F8F2471365700B31133 /* InternalTutorialView.swift */, 218 | EA49601A2471468500EBDA55 /* IconView.swift */, 219 | EA49601424713BD900EBDA55 /* BriefLogisticExplanationView.swift */, 220 | EA49601624713D1100EBDA55 /* StoryTextIntroView.swift */, 221 | EA2618662462A7E300ADFE5C /* ContentView.swift */, 222 | EA29397C246FC55A00B8A898 /* BackgroundView.swift */, 223 | EA7AF2CB246FA1530004BDC4 /* NPCUI.swift */, 224 | EA7AF2CD246FA1A90004BDC4 /* PlayerUI.swift */, 225 | EA2CE766246CB775004B8A34 /* GameSuccessView.swift */, 226 | EA2CE768246CB77F004B8A34 /* GameFailureView.swift */, 227 | ); 228 | path = Views; 229 | sourceTree = ""; 230 | }; 231 | EA8B67442470DE3100240747 /* Data */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | EAFAA58024653BB00006CAFF /* DataModel.swift */, 235 | EA7AF2C5246F9F360004BDC4 /* Calculation.swift */, 236 | ); 237 | path = Data; 238 | sourceTree = ""; 239 | }; 240 | EA8B67452470DE7D00240747 /* Support */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | EA7AF2C7246F9FFD0004BDC4 /* GameLogistics.swift */, 244 | EA293984246FCC4900B8A898 /* MediaSupport.swift */, 245 | EA7AF2C9246FA0180004BDC4 /* AnimationPresets.swift */, 246 | ); 247 | path = Support; 248 | sourceTree = ""; 249 | }; 250 | /* End PBXGroup section */ 251 | 252 | /* Begin PBXNativeTarget section */ 253 | EA26185B2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge */ = { 254 | isa = PBXNativeTarget; 255 | buildConfigurationList = EA2618732462A7EA00ADFE5C /* Build configuration list for PBXNativeTarget "SixFeetBetween_WWDC20SwiftChallenge" */; 256 | buildPhases = ( 257 | EA2618582462A7E300ADFE5C /* Sources */, 258 | EA2618592462A7E300ADFE5C /* Frameworks */, 259 | EA26185A2462A7E300ADFE5C /* Resources */, 260 | ); 261 | buildRules = ( 262 | ); 263 | dependencies = ( 264 | ); 265 | name = SixFeetBetween_WWDC20SwiftChallenge; 266 | productName = WWDC20PlaygroundTest; 267 | productReference = EA26185C2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge.app */; 268 | productType = "com.apple.product-type.application"; 269 | }; 270 | /* End PBXNativeTarget section */ 271 | 272 | /* Begin PBXProject section */ 273 | EA2618542462A7E300ADFE5C /* Project object */ = { 274 | isa = PBXProject; 275 | attributes = { 276 | LastSwiftUpdateCheck = 1110; 277 | LastUpgradeCheck = 1110; 278 | ORGANIZATIONNAME = TonyTang; 279 | TargetAttributes = { 280 | EA26185B2462A7E300ADFE5C = { 281 | CreatedOnToolsVersion = 11.1; 282 | }; 283 | }; 284 | }; 285 | buildConfigurationList = EA2618572462A7E300ADFE5C /* Build configuration list for PBXProject "SixFeetBetween_WWDC20SwiftChallenge" */; 286 | compatibilityVersion = "Xcode 9.3"; 287 | developmentRegion = en; 288 | hasScannedForEncodings = 0; 289 | knownRegions = ( 290 | en, 291 | Base, 292 | ); 293 | mainGroup = EA2618532462A7E300ADFE5C; 294 | productRefGroup = EA26185D2462A7E300ADFE5C /* Products */; 295 | projectDirPath = ""; 296 | projectRoot = ""; 297 | targets = ( 298 | EA26185B2462A7E300ADFE5C /* SixFeetBetween_WWDC20SwiftChallenge */, 299 | ); 300 | }; 301 | /* End PBXProject section */ 302 | 303 | /* Begin PBXResourcesBuildPhase section */ 304 | EA26185A2462A7E300ADFE5C /* Resources */ = { 305 | isa = PBXResourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | EA8B674D2470E7C400240747 /* factory1.png in Resources */, 309 | EA293994246FE60000B8A898 /* coin4.wav in Resources */, 310 | EA2939A12470B33D00B8A898 /* coin7.wav in Resources */, 311 | EA29399A246FE92800B8A898 /* failure1.wav in Resources */, 312 | EA8B67552470E95000240747 /* research.png in Resources */, 313 | EA293990246FE60000B8A898 /* coin3.wav in Resources */, 314 | EA293992246FE60000B8A898 /* success3.wav in Resources */, 315 | EA293982246FCBDA00B8A898 /* error2.wav in Resources */, 316 | EA293983246FCBDA00B8A898 /* error1.wav in Resources */, 317 | EA29398E246FE60000B8A898 /* success2.wav in Resources */, 318 | EA293995246FE60000B8A898 /* coin2.wav in Resources */, 319 | EA293981246FCBDA00B8A898 /* jump.wav in Resources */, 320 | EAC000BE24727ED3009A19B5 /* Ninja_Circle.png in Resources */, 321 | EA29399D2470B2A300B8A898 /* coin5.wav in Resources */, 322 | EA29399F2470B30A00B8A898 /* coin6.wav in Resources */, 323 | EA8B674B2470E7C400240747 /* wash_hand.png in Resources */, 324 | EA8B674C2470E7C400240747 /* humanw_mask.png in Resources */, 325 | EA293993246FE60000B8A898 /* success1.wav in Resources */, 326 | EA26186F2462A7EA00ADFE5C /* LaunchScreen.storyboard in Resources */, 327 | EA8B67542470E95000240747 /* production.png in Resources */, 328 | EA8B674A2470E7C400240747 /* factory2.png in Resources */, 329 | EA29398F246FE60000B8A898 /* success4.wav in Resources */, 330 | EA293991246FE60000B8A898 /* coin1.mp3 in Resources */, 331 | EA26186C2462A7EA00ADFE5C /* Preview Assets.xcassets in Resources */, 332 | EA29399B246FE92800B8A898 /* failure2.wav in Resources */, 333 | EA2939A32470B76500B8A898 /* clock1.wav in Resources */, 334 | EA2618692462A7EA00ADFE5C /* Assets.xcassets in Resources */, 335 | EA293999246FE92800B8A898 /* failure3.wav in Resources */, 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | /* End PBXResourcesBuildPhase section */ 340 | 341 | /* Begin PBXSourcesBuildPhase section */ 342 | EA2618582462A7E300ADFE5C /* Sources */ = { 343 | isa = PBXSourcesBuildPhase; 344 | buildActionMask = 2147483647; 345 | files = ( 346 | EA293985246FCC4900B8A898 /* MediaSupport.swift in Sources */, 347 | EA7AF2CA246FA0180004BDC4 /* AnimationPresets.swift in Sources */, 348 | EAFAA58124653BB00006CAFF /* DataModel.swift in Sources */, 349 | EA7AF2C8246F9FFD0004BDC4 /* GameLogistics.swift in Sources */, 350 | EA7AF2C6246F9F360004BDC4 /* Calculation.swift in Sources */, 351 | EA49601B2471468500EBDA55 /* IconView.swift in Sources */, 352 | EA2618602462A7E300ADFE5C /* AppDelegate.swift in Sources */, 353 | EA49601324713B0E00EBDA55 /* ButtonStackView.swift in Sources */, 354 | EA7AF2CC246FA1530004BDC4 /* NPCUI.swift in Sources */, 355 | EA49601724713D1100EBDA55 /* StoryTextIntroView.swift in Sources */, 356 | EA2CE767246CB775004B8A34 /* GameSuccessView.swift in Sources */, 357 | EA8B674F2470E7DF00240747 /* StoryView.swift in Sources */, 358 | EA2618652462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld in Sources */, 359 | EA7AF2CE246FA1A90004BDC4 /* PlayerUI.swift in Sources */, 360 | EA49601524713BD900EBDA55 /* BriefLogisticExplanationView.swift in Sources */, 361 | EA8B67512470E7F400240747 /* TutorialView.swift in Sources */, 362 | EA078F902471365700B31133 /* InternalTutorialView.swift in Sources */, 363 | EA29397D246FC55A00B8A898 /* BackgroundView.swift in Sources */, 364 | EA2CE769246CB77F004B8A34 /* GameFailureView.swift in Sources */, 365 | EA2618672462A7E300ADFE5C /* ContentView.swift in Sources */, 366 | EA49601924713E9A00EBDA55 /* InternalTutorialPartialView.swift in Sources */, 367 | EA2618622462A7E300ADFE5C /* SceneDelegate.swift in Sources */, 368 | ); 369 | runOnlyForDeploymentPostprocessing = 0; 370 | }; 371 | /* End PBXSourcesBuildPhase section */ 372 | 373 | /* Begin PBXVariantGroup section */ 374 | EA26186D2462A7EA00ADFE5C /* LaunchScreen.storyboard */ = { 375 | isa = PBXVariantGroup; 376 | children = ( 377 | EA26186E2462A7EA00ADFE5C /* Base */, 378 | ); 379 | name = LaunchScreen.storyboard; 380 | sourceTree = ""; 381 | }; 382 | /* End PBXVariantGroup section */ 383 | 384 | /* Begin XCBuildConfiguration section */ 385 | EA2618712462A7EA00ADFE5C /* Debug */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | ALWAYS_SEARCH_USER_PATHS = NO; 389 | CLANG_ANALYZER_NONNULL = YES; 390 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 391 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 392 | CLANG_CXX_LIBRARY = "libc++"; 393 | CLANG_ENABLE_MODULES = YES; 394 | CLANG_ENABLE_OBJC_ARC = YES; 395 | CLANG_ENABLE_OBJC_WEAK = YES; 396 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 397 | CLANG_WARN_BOOL_CONVERSION = YES; 398 | CLANG_WARN_COMMA = YES; 399 | CLANG_WARN_CONSTANT_CONVERSION = YES; 400 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 401 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 402 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 403 | CLANG_WARN_EMPTY_BODY = YES; 404 | CLANG_WARN_ENUM_CONVERSION = YES; 405 | CLANG_WARN_INFINITE_RECURSION = YES; 406 | CLANG_WARN_INT_CONVERSION = YES; 407 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 408 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 409 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 410 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 411 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 412 | CLANG_WARN_STRICT_PROTOTYPES = YES; 413 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 414 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 415 | CLANG_WARN_UNREACHABLE_CODE = YES; 416 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 417 | COPY_PHASE_STRIP = NO; 418 | DEBUG_INFORMATION_FORMAT = dwarf; 419 | ENABLE_STRICT_OBJC_MSGSEND = YES; 420 | ENABLE_TESTABILITY = YES; 421 | GCC_C_LANGUAGE_STANDARD = gnu11; 422 | GCC_DYNAMIC_NO_PIC = NO; 423 | GCC_NO_COMMON_BLOCKS = YES; 424 | GCC_OPTIMIZATION_LEVEL = 0; 425 | GCC_PREPROCESSOR_DEFINITIONS = ( 426 | "DEBUG=1", 427 | "$(inherited)", 428 | ); 429 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 430 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 431 | GCC_WARN_UNDECLARED_SELECTOR = YES; 432 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 433 | GCC_WARN_UNUSED_FUNCTION = YES; 434 | GCC_WARN_UNUSED_VARIABLE = YES; 435 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 436 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 437 | MTL_FAST_MATH = YES; 438 | ONLY_ACTIVE_ARCH = YES; 439 | SDKROOT = iphoneos; 440 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 441 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 442 | }; 443 | name = Debug; 444 | }; 445 | EA2618722462A7EA00ADFE5C /* Release */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | ALWAYS_SEARCH_USER_PATHS = NO; 449 | CLANG_ANALYZER_NONNULL = YES; 450 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 451 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 452 | CLANG_CXX_LIBRARY = "libc++"; 453 | CLANG_ENABLE_MODULES = YES; 454 | CLANG_ENABLE_OBJC_ARC = YES; 455 | CLANG_ENABLE_OBJC_WEAK = YES; 456 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 457 | CLANG_WARN_BOOL_CONVERSION = YES; 458 | CLANG_WARN_COMMA = YES; 459 | CLANG_WARN_CONSTANT_CONVERSION = YES; 460 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 461 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 462 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 463 | CLANG_WARN_EMPTY_BODY = YES; 464 | CLANG_WARN_ENUM_CONVERSION = YES; 465 | CLANG_WARN_INFINITE_RECURSION = YES; 466 | CLANG_WARN_INT_CONVERSION = YES; 467 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 468 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 469 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 470 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 471 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 472 | CLANG_WARN_STRICT_PROTOTYPES = YES; 473 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 474 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 475 | CLANG_WARN_UNREACHABLE_CODE = YES; 476 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 477 | COPY_PHASE_STRIP = NO; 478 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 479 | ENABLE_NS_ASSERTIONS = NO; 480 | ENABLE_STRICT_OBJC_MSGSEND = YES; 481 | GCC_C_LANGUAGE_STANDARD = gnu11; 482 | GCC_NO_COMMON_BLOCKS = YES; 483 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 484 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 485 | GCC_WARN_UNDECLARED_SELECTOR = YES; 486 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 487 | GCC_WARN_UNUSED_FUNCTION = YES; 488 | GCC_WARN_UNUSED_VARIABLE = YES; 489 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 490 | MTL_ENABLE_DEBUG_INFO = NO; 491 | MTL_FAST_MATH = YES; 492 | SDKROOT = iphoneos; 493 | SWIFT_COMPILATION_MODE = wholemodule; 494 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 495 | VALIDATE_PRODUCT = YES; 496 | }; 497 | name = Release; 498 | }; 499 | EA2618742462A7EA00ADFE5C /* Debug */ = { 500 | isa = XCBuildConfiguration; 501 | buildSettings = { 502 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 503 | CODE_SIGN_STYLE = Automatic; 504 | DEVELOPMENT_ASSET_PATHS = "\"SixFeetBetween_WWDC20SwiftChallenge/Preview Content\""; 505 | DEVELOPMENT_TEAM = 3K4PT9RJ98; 506 | ENABLE_PREVIEWS = YES; 507 | INFOPLIST_FILE = SixFeetBetween_WWDC20SwiftChallenge/Info.plist; 508 | LD_RUNPATH_SEARCH_PATHS = ( 509 | "$(inherited)", 510 | "@executable_path/Frameworks", 511 | ); 512 | PRODUCT_BUNDLE_IDENTIFIER = "com.tonytangzixuan.SixFeetBetween-WWDC20SwiftChallenge"; 513 | PRODUCT_NAME = SixFeetBetween_WWDC20SwiftChallenge; 514 | SWIFT_VERSION = 5.0; 515 | TARGETED_DEVICE_FAMILY = "1,2"; 516 | }; 517 | name = Debug; 518 | }; 519 | EA2618752462A7EA00ADFE5C /* Release */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 523 | CODE_SIGN_STYLE = Automatic; 524 | DEVELOPMENT_ASSET_PATHS = "\"SixFeetBetween_WWDC20SwiftChallenge/Preview Content\""; 525 | DEVELOPMENT_TEAM = 3K4PT9RJ98; 526 | ENABLE_PREVIEWS = YES; 527 | INFOPLIST_FILE = SixFeetBetween_WWDC20SwiftChallenge/Info.plist; 528 | LD_RUNPATH_SEARCH_PATHS = ( 529 | "$(inherited)", 530 | "@executable_path/Frameworks", 531 | ); 532 | PRODUCT_BUNDLE_IDENTIFIER = "com.tonytangzixuan.SixFeetBetween-WWDC20SwiftChallenge"; 533 | PRODUCT_NAME = SixFeetBetween_WWDC20SwiftChallenge; 534 | SWIFT_VERSION = 5.0; 535 | TARGETED_DEVICE_FAMILY = "1,2"; 536 | }; 537 | name = Release; 538 | }; 539 | /* End XCBuildConfiguration section */ 540 | 541 | /* Begin XCConfigurationList section */ 542 | EA2618572462A7E300ADFE5C /* Build configuration list for PBXProject "SixFeetBetween_WWDC20SwiftChallenge" */ = { 543 | isa = XCConfigurationList; 544 | buildConfigurations = ( 545 | EA2618712462A7EA00ADFE5C /* Debug */, 546 | EA2618722462A7EA00ADFE5C /* Release */, 547 | ); 548 | defaultConfigurationIsVisible = 0; 549 | defaultConfigurationName = Release; 550 | }; 551 | EA2618732462A7EA00ADFE5C /* Build configuration list for PBXNativeTarget "SixFeetBetween_WWDC20SwiftChallenge" */ = { 552 | isa = XCConfigurationList; 553 | buildConfigurations = ( 554 | EA2618742462A7EA00ADFE5C /* Debug */, 555 | EA2618752462A7EA00ADFE5C /* Release */, 556 | ); 557 | defaultConfigurationIsVisible = 0; 558 | defaultConfigurationName = Release; 559 | }; 560 | /* End XCConfigurationList section */ 561 | 562 | /* Begin XCVersionGroup section */ 563 | EA2618632462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodeld */ = { 564 | isa = XCVersionGroup; 565 | children = ( 566 | EA2618642462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodel */, 567 | ); 568 | currentVersion = EA2618642462A7E300ADFE5C /* WWDC20PlaygroundTest.xcdatamodel */; 569 | path = WWDC20PlaygroundTest.xcdatamodeld; 570 | sourceTree = ""; 571 | versionGroupType = wrapper.xcdatamodel; 572 | }; 573 | /* End XCVersionGroup section */ 574 | }; 575 | rootObject = EA2618542462A7E300ADFE5C /* Project object */; 576 | } 577 | --------------------------------------------------------------------------------