├── .gitignore ├── 1605417e98d57f47.gif ├── LICENSE ├── README.md ├── step1-ARStack ├── ARStack.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── ARStack │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── art.scnassets │ └── SceneKit Scene.scn ├── step2-High-Rise-Final ├── High Rise.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── High Rise │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-60.png │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-Small-40.png │ │ │ ├── Icon-Small-40@2x.png │ │ │ ├── Icon-Small-40@3x.png │ │ │ ├── Icon-Small@2x.png │ │ │ └── Icon-Small@3x.png │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Gradient.png │ ├── Info.plist │ ├── SCNVector3 Extensions.swift │ └── ViewController.swift └── HighRise.scnassets │ ├── Audio │ ├── GameOver.wav │ ├── PerfectFit.wav │ └── SliceBlock.wav │ └── Scenes │ └── GameScene.scn └── step3-ARStack-Final ├── ARStack.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── ARStack ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── ViewController.swift └── art.scnassets ├── Audio ├── GameOver.wav ├── PerfectFit.wav └── SliceBlock.wav └── Scenes └── GameScene.scn /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /1605417e98d57f47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/1605417e98d57f47.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ARStack 2 | 3步制作AR版堆方块游戏 3 | 4 | make ARStack game in 3 steps 5 | 6 | 7 | 过程讲解在[中文教程](https://juejin.im/post/5a32331f6fb9a0450671a50b) 8 | 9 | [Tutorial in Chinese](https://juejin.im/post/5a32331f6fb9a0450671a50b) 10 | 11 | ![gif](https://github.com/XanderXu/ARStack/blob/master/1605417e98d57f47.gif) 12 | 13 | 14 | -------------------------------------------------------------------------------- /step1-ARStack/ARStack.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6CE095231F922EE400495679 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE095221F922EE400495679 /* AppDelegate.swift */; }; 11 | 6CE095251F922EE400495679 /* art.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CE095241F922EE400495679 /* art.scnassets */; }; 12 | 6CE095271F922EE400495679 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE095261F922EE400495679 /* ViewController.swift */; }; 13 | 6CE0952A1F922EE400495679 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6CE095281F922EE400495679 /* Main.storyboard */; }; 14 | 6CE0952C1F922EE400495679 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CE0952B1F922EE400495679 /* Assets.xcassets */; }; 15 | 6CE0952F1F922EE400495679 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6CE0952D1F922EE400495679 /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 6CE0951F1F922EE400495679 /* ARStack.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ARStack.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 6CE095221F922EE400495679 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 6CE095241F922EE400495679 /* art.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = art.scnassets; sourceTree = ""; }; 22 | 6CE095261F922EE400495679 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 23 | 6CE095291F922EE400495679 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 24 | 6CE0952B1F922EE400495679 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | 6CE0952E1F922EE400495679 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 26 | 6CE095301F922EE400495679 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 6CE0951C1F922EE400495679 /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 6CE095161F922EE400495679 = { 41 | isa = PBXGroup; 42 | children = ( 43 | 6CE095211F922EE400495679 /* ARStack */, 44 | 6CE095201F922EE400495679 /* Products */, 45 | ); 46 | sourceTree = ""; 47 | }; 48 | 6CE095201F922EE400495679 /* Products */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 6CE0951F1F922EE400495679 /* ARStack.app */, 52 | ); 53 | name = Products; 54 | sourceTree = ""; 55 | }; 56 | 6CE095211F922EE400495679 /* ARStack */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 6CE095221F922EE400495679 /* AppDelegate.swift */, 60 | 6CE095241F922EE400495679 /* art.scnassets */, 61 | 6CE095261F922EE400495679 /* ViewController.swift */, 62 | 6CE095281F922EE400495679 /* Main.storyboard */, 63 | 6CE0952B1F922EE400495679 /* Assets.xcassets */, 64 | 6CE0952D1F922EE400495679 /* LaunchScreen.storyboard */, 65 | 6CE095301F922EE400495679 /* Info.plist */, 66 | ); 67 | path = ARStack; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | 6CE0951E1F922EE400495679 /* ARStack */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = 6CE095331F922EE400495679 /* Build configuration list for PBXNativeTarget "ARStack" */; 76 | buildPhases = ( 77 | 6CE0951B1F922EE400495679 /* Sources */, 78 | 6CE0951C1F922EE400495679 /* Frameworks */, 79 | 6CE0951D1F922EE400495679 /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = ARStack; 86 | productName = ARStack; 87 | productReference = 6CE0951F1F922EE400495679 /* ARStack.app */; 88 | productType = "com.apple.product-type.application"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | 6CE095171F922EE400495679 /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastSwiftUpdateCheck = 0900; 97 | LastUpgradeCheck = 0900; 98 | ORGANIZATIONNAME = XanderXu; 99 | TargetAttributes = { 100 | 6CE0951E1F922EE400495679 = { 101 | CreatedOnToolsVersion = 9.0; 102 | ProvisioningStyle = Automatic; 103 | }; 104 | }; 105 | }; 106 | buildConfigurationList = 6CE0951A1F922EE400495679 /* Build configuration list for PBXProject "ARStack" */; 107 | compatibilityVersion = "Xcode 8.0"; 108 | developmentRegion = en; 109 | hasScannedForEncodings = 0; 110 | knownRegions = ( 111 | en, 112 | Base, 113 | ); 114 | mainGroup = 6CE095161F922EE400495679; 115 | productRefGroup = 6CE095201F922EE400495679 /* Products */; 116 | projectDirPath = ""; 117 | projectRoot = ""; 118 | targets = ( 119 | 6CE0951E1F922EE400495679 /* ARStack */, 120 | ); 121 | }; 122 | /* End PBXProject section */ 123 | 124 | /* Begin PBXResourcesBuildPhase section */ 125 | 6CE0951D1F922EE400495679 /* Resources */ = { 126 | isa = PBXResourcesBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | 6CE095251F922EE400495679 /* art.scnassets in Resources */, 130 | 6CE0952F1F922EE400495679 /* LaunchScreen.storyboard in Resources */, 131 | 6CE0952C1F922EE400495679 /* Assets.xcassets in Resources */, 132 | 6CE0952A1F922EE400495679 /* Main.storyboard in Resources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | 6CE0951B1F922EE400495679 /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 6CE095271F922EE400495679 /* ViewController.swift in Sources */, 144 | 6CE095231F922EE400495679 /* AppDelegate.swift in Sources */, 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXSourcesBuildPhase section */ 149 | 150 | /* Begin PBXVariantGroup section */ 151 | 6CE095281F922EE400495679 /* Main.storyboard */ = { 152 | isa = PBXVariantGroup; 153 | children = ( 154 | 6CE095291F922EE400495679 /* Base */, 155 | ); 156 | name = Main.storyboard; 157 | sourceTree = ""; 158 | }; 159 | 6CE0952D1F922EE400495679 /* LaunchScreen.storyboard */ = { 160 | isa = PBXVariantGroup; 161 | children = ( 162 | 6CE0952E1F922EE400495679 /* Base */, 163 | ); 164 | name = LaunchScreen.storyboard; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXVariantGroup section */ 168 | 169 | /* Begin XCBuildConfiguration section */ 170 | 6CE095311F922EE400495679 /* Debug */ = { 171 | isa = XCBuildConfiguration; 172 | buildSettings = { 173 | ALWAYS_SEARCH_USER_PATHS = NO; 174 | CLANG_ANALYZER_NONNULL = YES; 175 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 177 | CLANG_CXX_LIBRARY = "libc++"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 181 | CLANG_WARN_BOOL_CONVERSION = YES; 182 | CLANG_WARN_COMMA = YES; 183 | CLANG_WARN_CONSTANT_CONVERSION = YES; 184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 185 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 186 | CLANG_WARN_EMPTY_BODY = YES; 187 | CLANG_WARN_ENUM_CONVERSION = YES; 188 | CLANG_WARN_INFINITE_RECURSION = YES; 189 | CLANG_WARN_INT_CONVERSION = YES; 190 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 191 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 192 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 193 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 194 | CLANG_WARN_STRICT_PROTOTYPES = YES; 195 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 196 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 197 | CLANG_WARN_UNREACHABLE_CODE = YES; 198 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 199 | CODE_SIGN_IDENTITY = "iPhone Developer"; 200 | COPY_PHASE_STRIP = NO; 201 | DEBUG_INFORMATION_FORMAT = dwarf; 202 | ENABLE_STRICT_OBJC_MSGSEND = YES; 203 | ENABLE_TESTABILITY = YES; 204 | GCC_C_LANGUAGE_STANDARD = gnu11; 205 | GCC_DYNAMIC_NO_PIC = NO; 206 | GCC_NO_COMMON_BLOCKS = YES; 207 | GCC_OPTIMIZATION_LEVEL = 0; 208 | GCC_PREPROCESSOR_DEFINITIONS = ( 209 | "DEBUG=1", 210 | "$(inherited)", 211 | ); 212 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 213 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 214 | GCC_WARN_UNDECLARED_SELECTOR = YES; 215 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 216 | GCC_WARN_UNUSED_FUNCTION = YES; 217 | GCC_WARN_UNUSED_VARIABLE = YES; 218 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 219 | MTL_ENABLE_DEBUG_INFO = YES; 220 | ONLY_ACTIVE_ARCH = YES; 221 | SDKROOT = iphoneos; 222 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 223 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 224 | }; 225 | name = Debug; 226 | }; 227 | 6CE095321F922EE400495679 /* Release */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | CLANG_ANALYZER_NONNULL = YES; 232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 234 | CLANG_CXX_LIBRARY = "libc++"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_COMMA = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 242 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INFINITE_RECURSION = YES; 246 | CLANG_WARN_INT_CONVERSION = YES; 247 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 249 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 250 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 251 | CLANG_WARN_STRICT_PROTOTYPES = YES; 252 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 253 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 254 | CLANG_WARN_UNREACHABLE_CODE = YES; 255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 256 | CODE_SIGN_IDENTITY = "iPhone Developer"; 257 | COPY_PHASE_STRIP = NO; 258 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 259 | ENABLE_NS_ASSERTIONS = NO; 260 | ENABLE_STRICT_OBJC_MSGSEND = YES; 261 | GCC_C_LANGUAGE_STANDARD = gnu11; 262 | GCC_NO_COMMON_BLOCKS = YES; 263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 265 | GCC_WARN_UNDECLARED_SELECTOR = YES; 266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 267 | GCC_WARN_UNUSED_FUNCTION = YES; 268 | GCC_WARN_UNUSED_VARIABLE = YES; 269 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 270 | MTL_ENABLE_DEBUG_INFO = NO; 271 | SDKROOT = iphoneos; 272 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 273 | VALIDATE_PRODUCT = YES; 274 | }; 275 | name = Release; 276 | }; 277 | 6CE095341F922EE400495679 /* Debug */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 281 | CODE_SIGN_STYLE = Automatic; 282 | DEVELOPMENT_TEAM = T59W99K34W; 283 | INFOPLIST_FILE = ARStack/Info.plist; 284 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 285 | PRODUCT_BUNDLE_IDENTIFIER = com.Texchi.ARStack; 286 | PRODUCT_NAME = "$(TARGET_NAME)"; 287 | SWIFT_VERSION = 4.0; 288 | TARGETED_DEVICE_FAMILY = "1,2"; 289 | }; 290 | name = Debug; 291 | }; 292 | 6CE095351F922EE400495679 /* Release */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 296 | CODE_SIGN_STYLE = Automatic; 297 | DEVELOPMENT_TEAM = T59W99K34W; 298 | INFOPLIST_FILE = ARStack/Info.plist; 299 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 300 | PRODUCT_BUNDLE_IDENTIFIER = com.Texchi.ARStack; 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | SWIFT_VERSION = 4.0; 303 | TARGETED_DEVICE_FAMILY = "1,2"; 304 | }; 305 | name = Release; 306 | }; 307 | /* End XCBuildConfiguration section */ 308 | 309 | /* Begin XCConfigurationList section */ 310 | 6CE0951A1F922EE400495679 /* Build configuration list for PBXProject "ARStack" */ = { 311 | isa = XCConfigurationList; 312 | buildConfigurations = ( 313 | 6CE095311F922EE400495679 /* Debug */, 314 | 6CE095321F922EE400495679 /* Release */, 315 | ); 316 | defaultConfigurationIsVisible = 0; 317 | defaultConfigurationName = Release; 318 | }; 319 | 6CE095331F922EE400495679 /* Build configuration list for PBXNativeTarget "ARStack" */ = { 320 | isa = XCConfigurationList; 321 | buildConfigurations = ( 322 | 6CE095341F922EE400495679 /* Debug */, 323 | 6CE095351F922EE400495679 /* Release */, 324 | ); 325 | defaultConfigurationIsVisible = 0; 326 | defaultConfigurationName = Release; 327 | }; 328 | /* End XCConfigurationList section */ 329 | }; 330 | rootObject = 6CE095171F922EE400495679 /* Project object */; 331 | } 332 | -------------------------------------------------------------------------------- /step1-ARStack/ARStack.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /step1-ARStack/ARStack/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ARStack 4 | // 5 | // Created by CoderXu on 2017/10/14. 6 | // Copyright © 2017年 XanderXu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /step1-ARStack/ARStack/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /step1-ARStack/ARStack/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 | -------------------------------------------------------------------------------- /step1-ARStack/ARStack/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 33 | 43 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /step1-ARStack/ARStack/Info.plist: -------------------------------------------------------------------------------- 1 | 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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | This application will use the camera for Augmented Reality. 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | arkit 33 | 34 | UIStatusBarHidden 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /step1-ARStack/ARStack/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ARStack 4 | // 5 | // Created by CoderXu on 2017/10/14. 6 | // Copyright © 2017年 XanderXu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SceneKit 11 | import ARKit 12 | 13 | class ViewController: UIViewController { 14 | 15 | @IBOutlet var sceneView: ARSCNView! 16 | 17 | @IBOutlet weak var sessionInfoLabel: UILabel! 18 | 19 | @IBOutlet weak var playButton: UIButton! 20 | 21 | @IBOutlet weak var restartButton: UIButton! 22 | // 识别出平面后,放上游戏的基础节点,相对固定于真实世界场景中 23 | weak var baseNode: SCNNode? 24 | // 识别出平面锚点后,用来标识识别的平面,会不断刷新大小和位置 25 | weak var planeNode: SCNNode? 26 | // 刷新次数,超过一定次数才说明这个平面足够明显,足够稳定.可以开始游戏 27 | var updateCount: NSInteger = 0 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | playButton.isHidden = true 31 | // Set the view's delegate 32 | sceneView.delegate = self 33 | 34 | // Show statistics such as fps and timing information 35 | sceneView.showsStatistics = true 36 | //显示debug特征点 37 | sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints] 38 | // Create a new scene 39 | let scene = SCNScene() 40 | // Set the scene to the view 41 | sceneView.scene = scene 42 | } 43 | 44 | override func viewWillAppear(_ animated: Bool) { 45 | super.viewWillAppear(animated) 46 | guard ARWorldTrackingConfiguration.isSupported else { 47 | fatalError(""" 48 | ARKit is not available on this device. For apps that require ARKit 49 | for core functionality, use the `arkit` key in the key in the 50 | `UIRequiredDeviceCapabilities` section of the Info.plist to prevent 51 | the app from installing. (If the app can't be installed, this error 52 | can't be triggered in a production scenario.) 53 | In apps where AR is an additive feature, use `isSupported` to 54 | determine whether to show UI for launching AR experiences. 55 | """) // For details, see https://developer.apple.com/documentation/arkit 56 | } 57 | //重置界面,参数,追踪配置 58 | resetAll() 59 | } 60 | 61 | override func viewWillDisappear(_ animated: Bool) { 62 | super.viewWillDisappear(animated) 63 | 64 | // Pause the view's session 65 | sceneView.session.pause() 66 | } 67 | 68 | override func didReceiveMemoryWarning() { 69 | super.didReceiveMemoryWarning() 70 | // Release any cached data, images, etc that aren't in use. 71 | } 72 | @IBAction func playButtonClick(_ sender: UIButton) { 73 | //0.隐藏按钮 74 | playButton.isHidden = true 75 | sessionInfoLabel.isHidden = true 76 | //1.停止平面检测 77 | stopTracking() 78 | //2.不显示辅助点 79 | sceneView.debugOptions = [] 80 | //3.更改平面的透明度和颜色 81 | planeNode?.geometry?.firstMaterial?.diffuse.contents = UIColor.clear 82 | planeNode?.opacity = 1 83 | //4.载入游戏场景 84 | } 85 | @IBAction func restartButtonClick(_ sender: UIButton) { 86 | resetAll() 87 | } 88 | 89 | } 90 | // MARK:- 私有方法 91 | extension ViewController { 92 | 93 | private func updateSessionInfoLabel(for frame: ARFrame, trackingState: ARCamera.TrackingState) { 94 | // 更新UI,反馈AR状态. 95 | let message: String 96 | print("status") 97 | switch trackingState { 98 | case .normal where frame.anchors.isEmpty: 99 | // 未检测到平面 100 | message = "移动设备来探测水平面." 101 | 102 | case .normal: 103 | // 平面可见,跟踪正常,无需反馈 104 | message = "" 105 | 106 | case .notAvailable: 107 | message = "无法追踪." 108 | 109 | case .limited(.excessiveMotion): 110 | message = "追踪受限-请缓慢移动设备." 111 | 112 | case .limited(.insufficientFeatures): 113 | message = "追踪受限-将设备对准平面上的可见花纹区域,或改善光照条件." 114 | 115 | case .limited(.initializing): 116 | message = "初始化AR中." 117 | 118 | } 119 | print(message) 120 | sessionInfoLabel.text = message 121 | sessionInfoLabel.isHidden = message.isEmpty 122 | } 123 | 124 | private func resetTracking() { 125 | let configuration = ARWorldTrackingConfiguration() 126 | configuration.planeDetection = .horizontal 127 | configuration.isLightEstimationEnabled = true 128 | sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) 129 | } 130 | private func stopTracking() { 131 | let configuration = ARWorldTrackingConfiguration() 132 | configuration.planeDetection = .init(rawValue: 0) 133 | configuration.isLightEstimationEnabled = true 134 | sceneView.session.run(configuration) 135 | } 136 | 137 | private func resetAll() { 138 | //0.显示按钮 139 | playButton.isHidden = true 140 | sessionInfoLabel.isHidden = false 141 | //1.重置平面检测配置,重启检测 142 | resetTracking() 143 | //2.重置更新次数 144 | updateCount = 0 145 | sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints] 146 | } 147 | } 148 | 149 | extension ViewController:ARSCNViewDelegate { 150 | // MARK: - ARSCNViewDelegate 151 | 152 | // 识别到新的锚点后,添加什么样的node.不实现该代理的话,会添加一个默认的空的node 153 | // ARKit会自动管理这个node的可见性及transform等属性等,所以一般把自己要显示的内容添加在这个node下面作为子节点 154 | // func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? { 155 | // 156 | // let node = SCNNode() 157 | // 158 | // return node 159 | // } 160 | 161 | // node添加到新的锚点上之后(一般在这个方法中添加几何体节点,作为node的子节点) 162 | func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { 163 | //1.获取捕捉到的平地锚点,只识别并添加一个平面 164 | if let planeAnchor = anchor as? ARPlaneAnchor,node.childNodes.count < 1,updateCount < 1 { 165 | print("捕捉到平地") 166 | //2.创建一个平面 (系统捕捉到的平地是一个不规则大小的长方形,这里笔者将其变成一个长方形) 167 | let plane = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z)) 168 | //3.使用Material渲染3D模型(默认模型是白色的,这里笔者改成红色) 169 | plane.firstMaterial?.diffuse.contents = UIColor.red 170 | //4.创建一个基于3D物体模型的节点 171 | planeNode = SCNNode(geometry: plane) 172 | //5.设置节点的位置为捕捉到的平地的锚点的中心位置 SceneKit框架中节点的位置position是一个基于3D坐标系的矢量坐标SCNVector3Make 173 | planeNode?.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z) 174 | //6.`SCNPlane`默认是竖着的,所以旋转一下以匹配水平的`ARPlaneAnchor` 175 | planeNode?.eulerAngles.x = -.pi / 2 176 | 177 | //7.更改透明度 178 | planeNode?.opacity = 0.25 179 | //8.添加到父节点中 180 | node.addChildNode(planeNode!) 181 | 182 | //9.上面的planeNode节点,大小/位置会随着检测到的平面而不断变化,方便起见,再添加一个相对固定的基准平面,用来放置游戏场景 183 | let base = SCNBox(width: 0.5, height: 0, length: 0.5, chamferRadius: 0); 184 | base.firstMaterial?.diffuse.contents = UIColor.gray; 185 | baseNode = SCNNode(geometry:base); 186 | baseNode?.position = SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z); 187 | 188 | node.addChildNode(baseNode!) 189 | } 190 | } 191 | 192 | // 更新锚点和对应的node之前调用,ARKit会自动更新anchor和node,使其相匹配 193 | func renderer(_ renderer: SCNSceneRenderer, willUpdate node: SCNNode, for anchor: ARAnchor) { 194 | // 只更新在`renderer(_:didAdd:for:)`中得到的配对的锚点和节点. 195 | guard let planeAnchor = anchor as? ARPlaneAnchor, 196 | let planeNode = node.childNodes.first, 197 | let plane = planeNode.geometry as? SCNPlane 198 | else { return } 199 | 200 | updateCount += 1 201 | if updateCount > 20 {//平面超过更新20次,捕捉到的特征点已经足够多了,可以显示进入游戏按钮 202 | DispatchQueue.main.async { 203 | self.playButton.isHidden = false 204 | } 205 | } 206 | 207 | // 平面的中心点可以会变动. 208 | planeNode.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z) 209 | 210 | /* 211 | 平面尺寸可能会变大,或者把几个小平面合并为一个大平面.合并时,`ARSCNView`自动删除同一个平面上的相应节点,然后调用该方法来更新保留的另一个平面的尺寸.(经过测试,合并时,保留第一个检测到的平面和对应节点) 212 | */ 213 | plane.width = CGFloat(planeAnchor.extent.x) 214 | plane.height = CGFloat(planeAnchor.extent.z) 215 | } 216 | 217 | // 更新锚点和对应的node之后调用 218 | func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { 219 | 220 | } 221 | // 移除锚点和对应node后 222 | func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) { 223 | 224 | } 225 | 226 | // MARK: - ARSessionObserver 227 | 228 | func session(_ session: ARSession, didFailWithError error: Error) { 229 | 230 | sessionInfoLabel.text = "Session失败: \(error.localizedDescription)" 231 | resetTracking() 232 | } 233 | 234 | func sessionWasInterrupted(_ session: ARSession) { 235 | 236 | sessionInfoLabel.text = "Session被打断" 237 | } 238 | 239 | func sessionInterruptionEnded(_ session: ARSession) { 240 | 241 | sessionInfoLabel.text = "Session打断结束" 242 | resetTracking() 243 | } 244 | 245 | func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) { 246 | updateSessionInfoLabel(for: session.currentFrame!, trackingState: camera.trackingState) 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /step1-ARStack/ARStack/art.scnassets/SceneKit Scene.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step1-ARStack/ARStack/art.scnassets/SceneKit Scene.scn -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AF1CD98E1E2D92AE00FC9AAE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1CD98D1E2D92AE00FC9AAE /* AppDelegate.swift */; }; 11 | AF1CD9901E2D92AE00FC9AAE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1CD98F1E2D92AE00FC9AAE /* ViewController.swift */; }; 12 | AF1CD9931E2D92AE00FC9AAE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AF1CD9911E2D92AE00FC9AAE /* Main.storyboard */; }; 13 | AF1CD9951E2D92AE00FC9AAE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AF1CD9941E2D92AE00FC9AAE /* Assets.xcassets */; }; 14 | AF1CD9981E2D92AE00FC9AAE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AF1CD9961E2D92AE00FC9AAE /* LaunchScreen.storyboard */; }; 15 | AF1CD9A01E2D92E300FC9AAE /* HighRise.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = AF1CD99F1E2D92E300FC9AAE /* HighRise.scnassets */; }; 16 | AF1CD9A21E2DC4E500FC9AAE /* SCNVector3 Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1CD9A11E2DC4E500FC9AAE /* SCNVector3 Extensions.swift */; }; 17 | AF51DC041E4FB55800FF75BD /* Gradient.png in Resources */ = {isa = PBXBuildFile; fileRef = AF51DC031E4FB55800FF75BD /* Gradient.png */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | AF1CD98A1E2D92AE00FC9AAE /* High Rise.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "High Rise.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | AF1CD98D1E2D92AE00FC9AAE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | AF1CD98F1E2D92AE00FC9AAE /* ViewController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; tabWidth = 2; wrapsLines = 1; }; 24 | AF1CD9921E2D92AE00FC9AAE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | AF1CD9941E2D92AE00FC9AAE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | AF1CD9971E2D92AE00FC9AAE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | AF1CD9991E2D92AE00FC9AAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | AF1CD99F1E2D92E300FC9AAE /* HighRise.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; name = HighRise.scnassets; path = ../HighRise.scnassets; sourceTree = ""; }; 29 | AF1CD9A11E2DC4E500FC9AAE /* SCNVector3 Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SCNVector3 Extensions.swift"; sourceTree = ""; }; 30 | AF51DC031E4FB55800FF75BD /* Gradient.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Gradient.png; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | AF1CD9871E2D92AD00FC9AAE /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | AF1CD9811E2D92AD00FC9AAE = { 45 | isa = PBXGroup; 46 | children = ( 47 | AF1CD98C1E2D92AE00FC9AAE /* High Rise */, 48 | AF1CD98B1E2D92AE00FC9AAE /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | AF1CD98B1E2D92AE00FC9AAE /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | AF1CD98A1E2D92AE00FC9AAE /* High Rise.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | AF1CD98C1E2D92AE00FC9AAE /* High Rise */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | AF51DC031E4FB55800FF75BD /* Gradient.png */, 64 | AF1CD99F1E2D92E300FC9AAE /* HighRise.scnassets */, 65 | AF1CD98D1E2D92AE00FC9AAE /* AppDelegate.swift */, 66 | AF1CD98F1E2D92AE00FC9AAE /* ViewController.swift */, 67 | AF1CD9911E2D92AE00FC9AAE /* Main.storyboard */, 68 | AF1CD9941E2D92AE00FC9AAE /* Assets.xcassets */, 69 | AF1CD9961E2D92AE00FC9AAE /* LaunchScreen.storyboard */, 70 | AF1CD9991E2D92AE00FC9AAE /* Info.plist */, 71 | AF1CD9A11E2DC4E500FC9AAE /* SCNVector3 Extensions.swift */, 72 | ); 73 | path = "High Rise"; 74 | sourceTree = ""; 75 | }; 76 | /* End PBXGroup section */ 77 | 78 | /* Begin PBXNativeTarget section */ 79 | AF1CD9891E2D92AD00FC9AAE /* High Rise */ = { 80 | isa = PBXNativeTarget; 81 | buildConfigurationList = AF1CD99C1E2D92AE00FC9AAE /* Build configuration list for PBXNativeTarget "High Rise" */; 82 | buildPhases = ( 83 | AF1CD9861E2D92AD00FC9AAE /* Sources */, 84 | AF1CD9871E2D92AD00FC9AAE /* Frameworks */, 85 | AF1CD9881E2D92AD00FC9AAE /* Resources */, 86 | ); 87 | buildRules = ( 88 | ); 89 | dependencies = ( 90 | ); 91 | name = "High Rise"; 92 | productName = "High Rise"; 93 | productReference = AF1CD98A1E2D92AE00FC9AAE /* High Rise.app */; 94 | productType = "com.apple.product-type.application"; 95 | }; 96 | /* End PBXNativeTarget section */ 97 | 98 | /* Begin PBXProject section */ 99 | AF1CD9821E2D92AD00FC9AAE /* Project object */ = { 100 | isa = PBXProject; 101 | attributes = { 102 | LastSwiftUpdateCheck = 0820; 103 | LastUpgradeCheck = 0820; 104 | ORGANIZATIONNAME = "Ray Wenderlich"; 105 | TargetAttributes = { 106 | AF1CD9891E2D92AD00FC9AAE = { 107 | CreatedOnToolsVersion = 8.2.1; 108 | DevelopmentTeam = T59W99K34W; 109 | ProvisioningStyle = Automatic; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = AF1CD9851E2D92AD00FC9AAE /* Build configuration list for PBXProject "High Rise" */; 114 | compatibilityVersion = "Xcode 3.2"; 115 | developmentRegion = English; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = AF1CD9811E2D92AD00FC9AAE; 122 | productRefGroup = AF1CD98B1E2D92AE00FC9AAE /* Products */; 123 | projectDirPath = ""; 124 | projectRoot = ""; 125 | targets = ( 126 | AF1CD9891E2D92AD00FC9AAE /* High Rise */, 127 | ); 128 | }; 129 | /* End PBXProject section */ 130 | 131 | /* Begin PBXResourcesBuildPhase section */ 132 | AF1CD9881E2D92AD00FC9AAE /* Resources */ = { 133 | isa = PBXResourcesBuildPhase; 134 | buildActionMask = 2147483647; 135 | files = ( 136 | AF1CD9A01E2D92E300FC9AAE /* HighRise.scnassets in Resources */, 137 | AF1CD9981E2D92AE00FC9AAE /* LaunchScreen.storyboard in Resources */, 138 | AF1CD9951E2D92AE00FC9AAE /* Assets.xcassets in Resources */, 139 | AF51DC041E4FB55800FF75BD /* Gradient.png in Resources */, 140 | AF1CD9931E2D92AE00FC9AAE /* Main.storyboard in Resources */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXResourcesBuildPhase section */ 145 | 146 | /* Begin PBXSourcesBuildPhase section */ 147 | AF1CD9861E2D92AD00FC9AAE /* Sources */ = { 148 | isa = PBXSourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | AF1CD9901E2D92AE00FC9AAE /* ViewController.swift in Sources */, 152 | AF1CD9A21E2DC4E500FC9AAE /* SCNVector3 Extensions.swift in Sources */, 153 | AF1CD98E1E2D92AE00FC9AAE /* AppDelegate.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin PBXVariantGroup section */ 160 | AF1CD9911E2D92AE00FC9AAE /* Main.storyboard */ = { 161 | isa = PBXVariantGroup; 162 | children = ( 163 | AF1CD9921E2D92AE00FC9AAE /* Base */, 164 | ); 165 | name = Main.storyboard; 166 | sourceTree = ""; 167 | }; 168 | AF1CD9961E2D92AE00FC9AAE /* LaunchScreen.storyboard */ = { 169 | isa = PBXVariantGroup; 170 | children = ( 171 | AF1CD9971E2D92AE00FC9AAE /* Base */, 172 | ); 173 | name = LaunchScreen.storyboard; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXVariantGroup section */ 177 | 178 | /* Begin XCBuildConfiguration section */ 179 | AF1CD99A1E2D92AE00FC9AAE /* Debug */ = { 180 | isa = XCBuildConfiguration; 181 | buildSettings = { 182 | ALWAYS_SEARCH_USER_PATHS = NO; 183 | CLANG_ANALYZER_NONNULL = YES; 184 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 185 | CLANG_CXX_LIBRARY = "libc++"; 186 | CLANG_ENABLE_MODULES = YES; 187 | CLANG_ENABLE_OBJC_ARC = YES; 188 | CLANG_WARN_BOOL_CONVERSION = YES; 189 | CLANG_WARN_CONSTANT_CONVERSION = YES; 190 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 191 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 192 | CLANG_WARN_EMPTY_BODY = YES; 193 | CLANG_WARN_ENUM_CONVERSION = YES; 194 | CLANG_WARN_INFINITE_RECURSION = YES; 195 | CLANG_WARN_INT_CONVERSION = YES; 196 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 197 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 198 | CLANG_WARN_UNREACHABLE_CODE = YES; 199 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 200 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 201 | COPY_PHASE_STRIP = NO; 202 | DEBUG_INFORMATION_FORMAT = dwarf; 203 | ENABLE_STRICT_OBJC_MSGSEND = YES; 204 | ENABLE_TESTABILITY = YES; 205 | GCC_C_LANGUAGE_STANDARD = gnu99; 206 | GCC_DYNAMIC_NO_PIC = NO; 207 | GCC_NO_COMMON_BLOCKS = YES; 208 | GCC_OPTIMIZATION_LEVEL = 0; 209 | GCC_PREPROCESSOR_DEFINITIONS = ( 210 | "DEBUG=1", 211 | "$(inherited)", 212 | ); 213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 215 | GCC_WARN_UNDECLARED_SELECTOR = YES; 216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 217 | GCC_WARN_UNUSED_FUNCTION = YES; 218 | GCC_WARN_UNUSED_VARIABLE = YES; 219 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 220 | MTL_ENABLE_DEBUG_INFO = YES; 221 | ONLY_ACTIVE_ARCH = YES; 222 | SDKROOT = iphoneos; 223 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 224 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 225 | }; 226 | name = Debug; 227 | }; 228 | AF1CD99B1E2D92AE00FC9AAE /* Release */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | ALWAYS_SEARCH_USER_PATHS = NO; 232 | CLANG_ANALYZER_NONNULL = YES; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 234 | CLANG_CXX_LIBRARY = "libc++"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_CONSTANT_CONVERSION = YES; 239 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 240 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 250 | COPY_PHASE_STRIP = NO; 251 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 252 | ENABLE_NS_ASSERTIONS = NO; 253 | ENABLE_STRICT_OBJC_MSGSEND = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu99; 255 | GCC_NO_COMMON_BLOCKS = YES; 256 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 257 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 258 | GCC_WARN_UNDECLARED_SELECTOR = YES; 259 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 260 | GCC_WARN_UNUSED_FUNCTION = YES; 261 | GCC_WARN_UNUSED_VARIABLE = YES; 262 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 263 | MTL_ENABLE_DEBUG_INFO = NO; 264 | SDKROOT = iphoneos; 265 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 266 | VALIDATE_PRODUCT = YES; 267 | }; 268 | name = Release; 269 | }; 270 | AF1CD99D1E2D92AE00FC9AAE /* Debug */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 275 | CODE_SIGN_STYLE = Automatic; 276 | DEVELOPMENT_TEAM = T59W99K34W; 277 | INFOPLIST_FILE = "High Rise/Info.plist"; 278 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 279 | PRODUCT_BUNDLE_IDENTIFIER = com.Texchi.ARStack; 280 | PRODUCT_NAME = "$(TARGET_NAME)"; 281 | PROVISIONING_PROFILE_SPECIFIER = ""; 282 | SWIFT_VERSION = 3.0; 283 | }; 284 | name = Debug; 285 | }; 286 | AF1CD99E1E2D92AE00FC9AAE /* Release */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 290 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 291 | CODE_SIGN_STYLE = Automatic; 292 | DEVELOPMENT_TEAM = T59W99K34W; 293 | INFOPLIST_FILE = "High Rise/Info.plist"; 294 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 295 | PRODUCT_BUNDLE_IDENTIFIER = com.Texchi.ARStack; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | PROVISIONING_PROFILE_SPECIFIER = ""; 298 | SWIFT_VERSION = 3.0; 299 | }; 300 | name = Release; 301 | }; 302 | /* End XCBuildConfiguration section */ 303 | 304 | /* Begin XCConfigurationList section */ 305 | AF1CD9851E2D92AD00FC9AAE /* Build configuration list for PBXProject "High Rise" */ = { 306 | isa = XCConfigurationList; 307 | buildConfigurations = ( 308 | AF1CD99A1E2D92AE00FC9AAE /* Debug */, 309 | AF1CD99B1E2D92AE00FC9AAE /* Release */, 310 | ); 311 | defaultConfigurationIsVisible = 0; 312 | defaultConfigurationName = Release; 313 | }; 314 | AF1CD99C1E2D92AE00FC9AAE /* Build configuration list for PBXNativeTarget "High Rise" */ = { 315 | isa = XCConfigurationList; 316 | buildConfigurations = ( 317 | AF1CD99D1E2D92AE00FC9AAE /* Debug */, 318 | AF1CD99E1E2D92AE00FC9AAE /* Release */, 319 | ); 320 | defaultConfigurationIsVisible = 0; 321 | defaultConfigurationName = Release; 322 | }; 323 | /* End XCConfigurationList section */ 324 | }; 325 | rootObject = AF1CD9821E2D92AD00FC9AAE /* Project object */; 326 | } 327 | -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | @UIApplicationMain 26 | class AppDelegate: UIResponder, UIApplicationDelegate { 27 | 28 | var window: UIWindow? 29 | 30 | 31 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 32 | return true 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-Small-40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-Small@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-Small@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-Small-40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-Small-40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-60@3x.png", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | } 56 | } -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-60.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/High Rise/Gradient.png -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/SCNVector3 Extensions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import SceneKit 24 | 25 | extension SCNVector3 { 26 | func absoluteValue() -> SCNVector3 { 27 | return SCNVector3Make(abs(self.x), abs(self.y), abs(self.z)) 28 | } 29 | } 30 | 31 | func + (first: SCNVector3, second: SCNVector3) -> SCNVector3 { 32 | return SCNVector3Make(first.x + second.x, first.y + second.y, first.z + second.z) 33 | } 34 | 35 | func - (first: SCNVector3, second: SCNVector3) -> SCNVector3 { 36 | return SCNVector3Make(first.x - second.x, first.y - second.y, first.z - second.z) 37 | } 38 | -------------------------------------------------------------------------------- /step2-High-Rise-Final/High Rise/ViewController.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | import SceneKit 25 | import SpriteKit 26 | 27 | let boxheight:CGFloat = 0.05 28 | let boxLengthWidth:CGFloat = 0.4 29 | let actionOffet:Float = 0.6 30 | let actionSpeed:Float = 0.011 31 | class ViewController: UIViewController { 32 | @IBOutlet weak var scnView: SCNView! 33 | @IBOutlet weak var playButton: UIButton! 34 | 35 | @IBOutlet weak var scoreLabel: UILabel! 36 | 37 | var scnScene: SCNScene! 38 | var direction = true 39 | var height = 0 40 | 41 | 42 | var previousSize = SCNVector3(boxLengthWidth, boxheight, boxLengthWidth) 43 | var previousPosition = SCNVector3(0, boxheight*0.5, 0) 44 | var currentSize = SCNVector3(boxLengthWidth, boxheight, boxLengthWidth) 45 | var currentPosition = SCNVector3Zero 46 | 47 | var offset = SCNVector3Zero 48 | var absoluteOffset = SCNVector3Zero 49 | var newSize = SCNVector3Zero 50 | 51 | 52 | var perfectMatches = 0 53 | var sounds = [String: SCNAudioSource]() 54 | 55 | override func viewDidLoad() { 56 | super.viewDidLoad() 57 | 58 | scnScene = SCNScene(named: "HighRise.scnassets/Scenes/GameScene.scn") 59 | scnView.scene = scnScene 60 | scnView.allowsCameraControl = true 61 | scnView.isPlaying = true 62 | scnView.delegate = self 63 | 64 | loadSound(name: "GameOver", path: "HighRise.scnassets/Audio/GameOver.wav") 65 | loadSound(name: "PerfectFit", path: "HighRise.scnassets/Audio/PerfectFit.wav") 66 | loadSound(name: "SliceBlock", path: "HighRise.scnassets/Audio/SliceBlock.wav") 67 | } 68 | 69 | func loadSound(name: String, path: String) { 70 | if let sound = SCNAudioSource(fileNamed: path) { 71 | sound.isPositional = false 72 | sound.volume = 1 73 | sound.load() 74 | sounds[name] = sound 75 | } 76 | } 77 | 78 | func playSound(sound: String, node: SCNNode) { 79 | node.runAction(SCNAction.playAudio(sounds[sound]!, waitForCompletion: false)) 80 | } 81 | 82 | @IBAction func playGame(_ sender: Any) { 83 | 84 | playButton.isHidden = true 85 | 86 | let gameScene = SCNScene(named: "HighRise.scnassets/Scenes/GameScene.scn")! 87 | let transition = SKTransition.fade(withDuration: 1.0) 88 | scnScene = gameScene 89 | scnView.present(gameScene, with: transition, incomingPointOfView: nil, completionHandler: nil) 90 | 91 | height = 0 92 | scoreLabel.text = "\(height)" 93 | 94 | direction = true 95 | perfectMatches = 0 96 | previousSize = SCNVector3(boxLengthWidth, boxheight, boxLengthWidth) 97 | previousPosition = SCNVector3(0, boxheight*0.5, 0) 98 | currentSize = SCNVector3(boxLengthWidth, boxheight, boxLengthWidth) 99 | currentPosition = SCNVector3Zero 100 | 101 | offset = SCNVector3Zero 102 | absoluteOffset = SCNVector3Zero 103 | newSize = SCNVector3Zero 104 | 105 | let boxNode = SCNNode(geometry: SCNBox(width: boxLengthWidth, height: boxheight, length: boxLengthWidth, chamferRadius: 0)) 106 | boxNode.position.z = -actionOffet 107 | boxNode.position.y = Float(boxheight * 0.5) 108 | boxNode.name = "Block\(height)" 109 | boxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat(height % 10), green: 0.03*CGFloat(height%30), blue: 1-0.1 * CGFloat(height % 10), alpha: 1) 110 | scnScene.rootNode.addChildNode(boxNode) 111 | } 112 | 113 | 114 | func gameOver() { 115 | 116 | let fullAction = SCNAction.customAction(duration: 0.3) { _,_ in 117 | let moveAction = SCNAction.move(to: SCNVector3Make(0, 0, 0), duration: 0.3) 118 | self.scnScene.rootNode.runAction(moveAction) 119 | } 120 | 121 | scnScene.rootNode.runAction(fullAction) 122 | playButton.isHidden = false 123 | } 124 | 125 | 126 | 127 | 128 | @IBAction func handleTap(_ sender: Any) { 129 | if let currentBoxNode = scnScene.rootNode.childNode(withName: "Block\(height)", recursively: false) { 130 | currentPosition = currentBoxNode.presentation.position 131 | let boundsMin = currentBoxNode.boundingBox.min 132 | let boundsMax = currentBoxNode.boundingBox.max 133 | currentSize = boundsMax - boundsMin 134 | 135 | offset = previousPosition - currentPosition 136 | absoluteOffset = offset.absoluteValue() 137 | newSize = currentSize - absoluteOffset 138 | 139 | if height % 2 == 0 && newSize.z <= 0 { 140 | gameOver() 141 | playSound(sound: "GameOver", node: currentBoxNode) 142 | height += 1 143 | currentBoxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: currentBoxNode.geometry!, options: nil)) 144 | return 145 | } else if height % 2 != 0 && newSize.x <= 0 { 146 | gameOver() 147 | playSound(sound: "GameOver", node: currentBoxNode) 148 | height += 1 149 | currentBoxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: currentBoxNode.geometry!, options: nil)) 150 | return 151 | } 152 | 153 | checkPerfectMatch(currentBoxNode) 154 | 155 | currentBoxNode.geometry = SCNBox(width: CGFloat(newSize.x), height: boxheight, length: CGFloat(newSize.z), chamferRadius: 0) 156 | currentBoxNode.position = SCNVector3Make(currentPosition.x + (offset.x/2), currentPosition.y, currentPosition.z + (offset.z/2)) 157 | currentBoxNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: currentBoxNode.geometry!, options: nil)) 158 | currentBoxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat(height % 10), green: 0.03*CGFloat(height%30), blue: 1-0.1 * CGFloat(height % 10), alpha: 1) 159 | addBrokenBlock(currentBoxNode) 160 | addNewBlock(currentBoxNode) 161 | playSound(sound: "SliceBlock", node: currentBoxNode) 162 | 163 | if height >= 5 { 164 | let moveUpAction = SCNAction.move(by: SCNVector3Make(0.0, Float(-boxheight), 0.0), duration: 0.2) 165 | 166 | scnScene.rootNode.runAction(moveUpAction) 167 | } 168 | 169 | scoreLabel.text = "\(height+1)" 170 | 171 | previousSize = SCNVector3Make(newSize.x, Float(boxheight), newSize.z) 172 | previousPosition = currentBoxNode.position 173 | height += 1 174 | } 175 | } 176 | 177 | func addNewBlock(_ currentBoxNode: SCNNode) { 178 | let newBoxNode = SCNNode(geometry: SCNBox(width: CGFloat(newSize.x), height: boxheight, length: CGFloat(newSize.z), chamferRadius: 0)) 179 | newBoxNode.position = SCNVector3Make(currentBoxNode.position.x, currentPosition.y + Float(boxheight), currentBoxNode.position.z) 180 | newBoxNode.name = "Block\(height+1)" 181 | newBoxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat((height+1) % 10), green: 0.03*CGFloat((height+1)%30), blue: 1-0.1 * CGFloat((height+1) % 10), alpha: 1) 182 | 183 | if height % 2 == 0 { 184 | newBoxNode.position.x = -actionOffet 185 | } else { 186 | newBoxNode.position.z = -actionOffet 187 | } 188 | 189 | scnScene.rootNode.addChildNode(newBoxNode) 190 | } 191 | 192 | func addBrokenBlock(_ currentBoxNode: SCNNode) { 193 | let brokenBoxNode = SCNNode() 194 | brokenBoxNode.name = "Broken \(height)" 195 | 196 | if height % 2 == 0 && absoluteOffset.z > 0 { 197 | // 1 198 | brokenBoxNode.geometry = SCNBox(width: CGFloat(currentSize.x), height: boxheight, length: CGFloat(absoluteOffset.z), chamferRadius: 0) 199 | 200 | // 2 201 | if offset.z > 0 { 202 | brokenBoxNode.position.z = currentBoxNode.position.z - (offset.z/2) - ((currentSize - offset).z/2) 203 | } else { 204 | brokenBoxNode.position.z = currentBoxNode.position.z - (offset.z/2) + ((currentSize + offset).z/2) 205 | } 206 | brokenBoxNode.position.x = currentBoxNode.position.x 207 | brokenBoxNode.position.y = currentPosition.y 208 | 209 | // 3 210 | brokenBoxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: brokenBoxNode.geometry!, options: nil)) 211 | brokenBoxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat(height % 10), green: 0.03*CGFloat(height%30), blue: 1-0.1 * CGFloat(height % 10), alpha: 1) 212 | scnScene.rootNode.addChildNode(brokenBoxNode) 213 | 214 | // 4 215 | } else if height % 2 != 0 && absoluteOffset.x > 0 { 216 | brokenBoxNode.geometry = SCNBox(width: CGFloat(absoluteOffset.x), height: boxheight, length: CGFloat(currentSize.z), chamferRadius: 0) 217 | 218 | if offset.x > 0 { 219 | brokenBoxNode.position.x = currentBoxNode.position.x - (offset.x/2) - ((currentSize - offset).x/2) 220 | } else { 221 | brokenBoxNode.position.x = currentBoxNode.position.x - (offset.x/2) + ((currentSize + offset).x/2) 222 | } 223 | brokenBoxNode.position.y = currentPosition.y 224 | brokenBoxNode.position.z = currentBoxNode.position.z 225 | 226 | brokenBoxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: brokenBoxNode.geometry!, options: nil)) 227 | brokenBoxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat(height % 10), green: 0.03*CGFloat(height%30), blue: 1-0.1 * CGFloat(height % 10), alpha: 1) 228 | scnScene.rootNode.addChildNode(brokenBoxNode) 229 | } 230 | } 231 | 232 | func checkPerfectMatch(_ currentBoxNode: SCNNode) { 233 | if height % 2 == 0 && absoluteOffset.z <= 0.005 { 234 | playSound(sound: "PerfectFit", node: currentBoxNode) 235 | currentBoxNode.position.z = previousPosition.z 236 | currentPosition.z = previousPosition.z 237 | perfectMatches += 1 238 | if perfectMatches >= 7 && currentSize.z < 1 { 239 | newSize.z += 0.005 240 | } 241 | 242 | offset = previousPosition - currentPosition 243 | absoluteOffset = offset.absoluteValue() 244 | newSize = currentSize - absoluteOffset 245 | } else if height % 2 != 0 && absoluteOffset.x <= 0.005 { 246 | playSound(sound: "PerfectFit", node: currentBoxNode) 247 | currentBoxNode.position.x = previousPosition.x 248 | currentPosition.x = previousPosition.x 249 | perfectMatches += 1 250 | if perfectMatches >= 7 && currentSize.x < 1 { 251 | newSize.x += 0.005 252 | } 253 | 254 | offset = previousPosition - currentPosition 255 | absoluteOffset = offset.absoluteValue() 256 | newSize = currentSize - absoluteOffset 257 | } else { 258 | perfectMatches = 0 259 | } 260 | } 261 | 262 | 263 | override var prefersStatusBarHidden: Bool { 264 | return true 265 | } 266 | } 267 | 268 | extension ViewController: SCNSceneRendererDelegate { 269 | func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { 270 | 271 | for node in scnScene.rootNode.childNodes { 272 | if node.presentation.position.y <= -10 { 273 | node.removeFromParentNode() 274 | } 275 | } 276 | 277 | // 1 278 | if let currentNode = scnScene.rootNode.childNode(withName: "Block\(height)", recursively: false) { 279 | // 2 280 | if height % 2 == 0 { 281 | // 3 282 | if currentNode.position.z >= actionOffet { 283 | direction = false 284 | } else if currentNode.position.z <= -actionOffet { 285 | direction = true 286 | } 287 | 288 | // 4 289 | switch direction { 290 | case true: 291 | currentNode.position.z += actionSpeed 292 | case false: 293 | currentNode.position.z -= actionSpeed 294 | } 295 | // 5 296 | } else { 297 | if currentNode.position.x >= actionOffet { 298 | direction = false 299 | } else if currentNode.position.x <= -actionOffet { 300 | direction = true 301 | } 302 | 303 | switch direction { 304 | case true: 305 | currentNode.position.x += actionSpeed 306 | case false: 307 | currentNode.position.x -= actionSpeed 308 | } 309 | } 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /step2-High-Rise-Final/HighRise.scnassets/Audio/GameOver.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/HighRise.scnassets/Audio/GameOver.wav -------------------------------------------------------------------------------- /step2-High-Rise-Final/HighRise.scnassets/Audio/PerfectFit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/HighRise.scnassets/Audio/PerfectFit.wav -------------------------------------------------------------------------------- /step2-High-Rise-Final/HighRise.scnassets/Audio/SliceBlock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/HighRise.scnassets/Audio/SliceBlock.wav -------------------------------------------------------------------------------- /step2-High-Rise-Final/HighRise.scnassets/Scenes/GameScene.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step2-High-Rise-Final/HighRise.scnassets/Scenes/GameScene.scn -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6CE095231F922EE400495679 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE095221F922EE400495679 /* AppDelegate.swift */; }; 11 | 6CE095251F922EE400495679 /* art.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CE095241F922EE400495679 /* art.scnassets */; }; 12 | 6CE095271F922EE400495679 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE095261F922EE400495679 /* ViewController.swift */; }; 13 | 6CE0952A1F922EE400495679 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6CE095281F922EE400495679 /* Main.storyboard */; }; 14 | 6CE0952C1F922EE400495679 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CE0952B1F922EE400495679 /* Assets.xcassets */; }; 15 | 6CE0952F1F922EE400495679 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6CE0952D1F922EE400495679 /* LaunchScreen.storyboard */; }; 16 | 6CE095371F932B2300495679 /* SCNVector3 Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE095361F932B2300495679 /* SCNVector3 Extensions.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 6CE0951F1F922EE400495679 /* ARStack.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ARStack.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 6CE095221F922EE400495679 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | 6CE095241F922EE400495679 /* art.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = art.scnassets; sourceTree = ""; }; 23 | 6CE095261F922EE400495679 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 24 | 6CE095291F922EE400495679 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 6CE0952B1F922EE400495679 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 6CE0952E1F922EE400495679 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | 6CE095301F922EE400495679 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 6CE095361F932B2300495679 /* SCNVector3 Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SCNVector3 Extensions.swift"; path = "../../step2-High-Rise-Final/High Rise/SCNVector3 Extensions.swift"; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 6CE0951C1F922EE400495679 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 6CE095161F922EE400495679 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 6CE095211F922EE400495679 /* ARStack */, 46 | 6CE095201F922EE400495679 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | 6CE095201F922EE400495679 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 6CE0951F1F922EE400495679 /* ARStack.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 6CE095211F922EE400495679 /* ARStack */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 6CE095361F932B2300495679 /* SCNVector3 Extensions.swift */, 62 | 6CE095221F922EE400495679 /* AppDelegate.swift */, 63 | 6CE095241F922EE400495679 /* art.scnassets */, 64 | 6CE095261F922EE400495679 /* ViewController.swift */, 65 | 6CE095281F922EE400495679 /* Main.storyboard */, 66 | 6CE0952B1F922EE400495679 /* Assets.xcassets */, 67 | 6CE0952D1F922EE400495679 /* LaunchScreen.storyboard */, 68 | 6CE095301F922EE400495679 /* Info.plist */, 69 | ); 70 | path = ARStack; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXNativeTarget section */ 76 | 6CE0951E1F922EE400495679 /* ARStack */ = { 77 | isa = PBXNativeTarget; 78 | buildConfigurationList = 6CE095331F922EE400495679 /* Build configuration list for PBXNativeTarget "ARStack" */; 79 | buildPhases = ( 80 | 6CE0951B1F922EE400495679 /* Sources */, 81 | 6CE0951C1F922EE400495679 /* Frameworks */, 82 | 6CE0951D1F922EE400495679 /* Resources */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = ARStack; 89 | productName = ARStack; 90 | productReference = 6CE0951F1F922EE400495679 /* ARStack.app */; 91 | productType = "com.apple.product-type.application"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 6CE095171F922EE400495679 /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastSwiftUpdateCheck = 0900; 100 | LastUpgradeCheck = 0900; 101 | ORGANIZATIONNAME = XanderXu; 102 | TargetAttributes = { 103 | 6CE0951E1F922EE400495679 = { 104 | CreatedOnToolsVersion = 9.0; 105 | ProvisioningStyle = Automatic; 106 | }; 107 | }; 108 | }; 109 | buildConfigurationList = 6CE0951A1F922EE400495679 /* Build configuration list for PBXProject "ARStack" */; 110 | compatibilityVersion = "Xcode 8.0"; 111 | developmentRegion = en; 112 | hasScannedForEncodings = 0; 113 | knownRegions = ( 114 | en, 115 | Base, 116 | ); 117 | mainGroup = 6CE095161F922EE400495679; 118 | productRefGroup = 6CE095201F922EE400495679 /* Products */; 119 | projectDirPath = ""; 120 | projectRoot = ""; 121 | targets = ( 122 | 6CE0951E1F922EE400495679 /* ARStack */, 123 | ); 124 | }; 125 | /* End PBXProject section */ 126 | 127 | /* Begin PBXResourcesBuildPhase section */ 128 | 6CE0951D1F922EE400495679 /* Resources */ = { 129 | isa = PBXResourcesBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | 6CE095251F922EE400495679 /* art.scnassets in Resources */, 133 | 6CE0952F1F922EE400495679 /* LaunchScreen.storyboard in Resources */, 134 | 6CE0952C1F922EE400495679 /* Assets.xcassets in Resources */, 135 | 6CE0952A1F922EE400495679 /* Main.storyboard in Resources */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXResourcesBuildPhase section */ 140 | 141 | /* Begin PBXSourcesBuildPhase section */ 142 | 6CE0951B1F922EE400495679 /* Sources */ = { 143 | isa = PBXSourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | 6CE095271F922EE400495679 /* ViewController.swift in Sources */, 147 | 6CE095371F932B2300495679 /* SCNVector3 Extensions.swift in Sources */, 148 | 6CE095231F922EE400495679 /* AppDelegate.swift in Sources */, 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | }; 152 | /* End PBXSourcesBuildPhase section */ 153 | 154 | /* Begin PBXVariantGroup section */ 155 | 6CE095281F922EE400495679 /* Main.storyboard */ = { 156 | isa = PBXVariantGroup; 157 | children = ( 158 | 6CE095291F922EE400495679 /* Base */, 159 | ); 160 | name = Main.storyboard; 161 | sourceTree = ""; 162 | }; 163 | 6CE0952D1F922EE400495679 /* LaunchScreen.storyboard */ = { 164 | isa = PBXVariantGroup; 165 | children = ( 166 | 6CE0952E1F922EE400495679 /* Base */, 167 | ); 168 | name = LaunchScreen.storyboard; 169 | sourceTree = ""; 170 | }; 171 | /* End PBXVariantGroup section */ 172 | 173 | /* Begin XCBuildConfiguration section */ 174 | 6CE095311F922EE400495679 /* Debug */ = { 175 | isa = XCBuildConfiguration; 176 | buildSettings = { 177 | ALWAYS_SEARCH_USER_PATHS = NO; 178 | CLANG_ANALYZER_NONNULL = YES; 179 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 180 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 181 | CLANG_CXX_LIBRARY = "libc++"; 182 | CLANG_ENABLE_MODULES = YES; 183 | CLANG_ENABLE_OBJC_ARC = YES; 184 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 185 | CLANG_WARN_BOOL_CONVERSION = YES; 186 | CLANG_WARN_COMMA = YES; 187 | CLANG_WARN_CONSTANT_CONVERSION = YES; 188 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 189 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 190 | CLANG_WARN_EMPTY_BODY = YES; 191 | CLANG_WARN_ENUM_CONVERSION = YES; 192 | CLANG_WARN_INFINITE_RECURSION = YES; 193 | CLANG_WARN_INT_CONVERSION = YES; 194 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 195 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 196 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 197 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 198 | CLANG_WARN_STRICT_PROTOTYPES = YES; 199 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 200 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 201 | CLANG_WARN_UNREACHABLE_CODE = YES; 202 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 203 | CODE_SIGN_IDENTITY = "iPhone Developer"; 204 | COPY_PHASE_STRIP = NO; 205 | DEBUG_INFORMATION_FORMAT = dwarf; 206 | ENABLE_STRICT_OBJC_MSGSEND = YES; 207 | ENABLE_TESTABILITY = YES; 208 | GCC_C_LANGUAGE_STANDARD = gnu11; 209 | GCC_DYNAMIC_NO_PIC = NO; 210 | GCC_NO_COMMON_BLOCKS = YES; 211 | GCC_OPTIMIZATION_LEVEL = 0; 212 | GCC_PREPROCESSOR_DEFINITIONS = ( 213 | "DEBUG=1", 214 | "$(inherited)", 215 | ); 216 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 217 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 218 | GCC_WARN_UNDECLARED_SELECTOR = YES; 219 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 220 | GCC_WARN_UNUSED_FUNCTION = YES; 221 | GCC_WARN_UNUSED_VARIABLE = YES; 222 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 223 | MTL_ENABLE_DEBUG_INFO = YES; 224 | ONLY_ACTIVE_ARCH = YES; 225 | SDKROOT = iphoneos; 226 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 227 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 228 | }; 229 | name = Debug; 230 | }; 231 | 6CE095321F922EE400495679 /* Release */ = { 232 | isa = XCBuildConfiguration; 233 | buildSettings = { 234 | ALWAYS_SEARCH_USER_PATHS = NO; 235 | CLANG_ANALYZER_NONNULL = YES; 236 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 237 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 238 | CLANG_CXX_LIBRARY = "libc++"; 239 | CLANG_ENABLE_MODULES = YES; 240 | CLANG_ENABLE_OBJC_ARC = YES; 241 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 242 | CLANG_WARN_BOOL_CONVERSION = YES; 243 | CLANG_WARN_COMMA = YES; 244 | CLANG_WARN_CONSTANT_CONVERSION = YES; 245 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 246 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 247 | CLANG_WARN_EMPTY_BODY = YES; 248 | CLANG_WARN_ENUM_CONVERSION = YES; 249 | CLANG_WARN_INFINITE_RECURSION = YES; 250 | CLANG_WARN_INT_CONVERSION = YES; 251 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 254 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 255 | CLANG_WARN_STRICT_PROTOTYPES = YES; 256 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 257 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | CODE_SIGN_IDENTITY = "iPhone Developer"; 261 | COPY_PHASE_STRIP = NO; 262 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 263 | ENABLE_NS_ASSERTIONS = NO; 264 | ENABLE_STRICT_OBJC_MSGSEND = YES; 265 | GCC_C_LANGUAGE_STANDARD = gnu11; 266 | GCC_NO_COMMON_BLOCKS = YES; 267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 269 | GCC_WARN_UNDECLARED_SELECTOR = YES; 270 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 271 | GCC_WARN_UNUSED_FUNCTION = YES; 272 | GCC_WARN_UNUSED_VARIABLE = YES; 273 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 274 | MTL_ENABLE_DEBUG_INFO = NO; 275 | SDKROOT = iphoneos; 276 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 277 | VALIDATE_PRODUCT = YES; 278 | }; 279 | name = Release; 280 | }; 281 | 6CE095341F922EE400495679 /* Debug */ = { 282 | isa = XCBuildConfiguration; 283 | buildSettings = { 284 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 285 | CODE_SIGN_STYLE = Automatic; 286 | DEVELOPMENT_TEAM = T59W99K34W; 287 | INFOPLIST_FILE = ARStack/Info.plist; 288 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 289 | PRODUCT_BUNDLE_IDENTIFIER = com.Texchi.ARStack; 290 | PRODUCT_NAME = "$(TARGET_NAME)"; 291 | SWIFT_VERSION = 4.0; 292 | TARGETED_DEVICE_FAMILY = "1,2"; 293 | }; 294 | name = Debug; 295 | }; 296 | 6CE095351F922EE400495679 /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 300 | CODE_SIGN_STYLE = Automatic; 301 | DEVELOPMENT_TEAM = T59W99K34W; 302 | INFOPLIST_FILE = ARStack/Info.plist; 303 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 304 | PRODUCT_BUNDLE_IDENTIFIER = com.Texchi.ARStack; 305 | PRODUCT_NAME = "$(TARGET_NAME)"; 306 | SWIFT_VERSION = 4.0; 307 | TARGETED_DEVICE_FAMILY = "1,2"; 308 | }; 309 | name = Release; 310 | }; 311 | /* End XCBuildConfiguration section */ 312 | 313 | /* Begin XCConfigurationList section */ 314 | 6CE0951A1F922EE400495679 /* Build configuration list for PBXProject "ARStack" */ = { 315 | isa = XCConfigurationList; 316 | buildConfigurations = ( 317 | 6CE095311F922EE400495679 /* Debug */, 318 | 6CE095321F922EE400495679 /* Release */, 319 | ); 320 | defaultConfigurationIsVisible = 0; 321 | defaultConfigurationName = Release; 322 | }; 323 | 6CE095331F922EE400495679 /* Build configuration list for PBXNativeTarget "ARStack" */ = { 324 | isa = XCConfigurationList; 325 | buildConfigurations = ( 326 | 6CE095341F922EE400495679 /* Debug */, 327 | 6CE095351F922EE400495679 /* Release */, 328 | ); 329 | defaultConfigurationIsVisible = 0; 330 | defaultConfigurationName = Release; 331 | }; 332 | /* End XCConfigurationList section */ 333 | }; 334 | rootObject = 6CE095171F922EE400495679 /* Project object */; 335 | } 336 | -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ARStack 4 | // 5 | // Created by CoderXu on 2017/10/14. 6 | // Copyright © 2017年 XanderXu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/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 | -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 37 | 47 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/Info.plist: -------------------------------------------------------------------------------- 1 | 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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | This application will use the camera for Augmented Reality. 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | arkit 33 | 34 | UIStatusBarHidden 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ARStack 4 | // 5 | // Created by CoderXu on 2017/10/14. 6 | // Copyright © 2017年 XanderXu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SceneKit 11 | import ARKit 12 | 13 | 14 | let boxheight:CGFloat = 0.05 15 | let boxLengthWidth:CGFloat = 0.4 16 | let actionOffet:Float = 0.6 17 | let actionSpeed:Float = 0.011 18 | 19 | class ViewController: UIViewController { 20 | 21 | @IBOutlet var sceneView: ARSCNView! 22 | 23 | @IBOutlet weak var sessionInfoLabel: UILabel! 24 | 25 | @IBOutlet weak var playButton: UIButton! 26 | 27 | @IBOutlet weak var restartButton: UIButton! 28 | 29 | @IBOutlet weak var scoreLabel: UILabel! 30 | 31 | // 识别出平面后,放上游戏的基础节点,相对固定于真实世界场景中 32 | weak var baseNode: SCNNode? 33 | // 识别出平面锚点后,用来标识识别的平面,会不断刷新大小和位置 34 | weak var planeNode: SCNNode? 35 | // 刷新次数,超过一定次数才说明这个平面足够明显,足够稳定.可以开始游戏 36 | var updateCount: NSInteger = 0 37 | 38 | var gameNode:SCNNode? 39 | 40 | var direction = true 41 | var height = 0 42 | 43 | 44 | var previousSize = SCNVector3(boxLengthWidth, boxheight, boxLengthWidth) 45 | var previousPosition = SCNVector3(0, boxheight*0.5, 0) 46 | var currentSize = SCNVector3(boxLengthWidth, boxheight, boxLengthWidth) 47 | var currentPosition = SCNVector3Zero 48 | 49 | var offset = SCNVector3Zero 50 | var absoluteOffset = SCNVector3Zero 51 | var newSize = SCNVector3Zero 52 | 53 | 54 | var perfectMatches = 0 55 | var sounds = [String: SCNAudioSource]() 56 | 57 | override func viewDidLoad() { 58 | super.viewDidLoad() 59 | playButton.isHidden = true 60 | // Set the view's delegate 61 | sceneView.delegate = self 62 | 63 | // Show statistics such as fps and timing information 64 | sceneView.showsStatistics = true 65 | //显示debug特征点 66 | sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints] 67 | // Create a new scene 68 | let scene = SCNScene() 69 | // Set the scene to the view 70 | sceneView.scene = scene 71 | 72 | loadSound(name: "GameOver", path: "art.scnassets/Audio/GameOver.wav") 73 | loadSound(name: "PerfectFit", path: "art.scnassets/Audio/PerfectFit.wav") 74 | loadSound(name: "SliceBlock", path: "art.scnassets/Audio/SliceBlock.wav") 75 | 76 | NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillEnterForeground, object: nil, queue: nil) { (noti) in 77 | self.resetAll() 78 | } 79 | } 80 | 81 | override func viewWillAppear(_ animated: Bool) { 82 | super.viewWillAppear(animated) 83 | guard ARWorldTrackingConfiguration.isSupported else { 84 | fatalError(""" 85 | ARKit is not available on this device. For apps that require ARKit 86 | for core functionality, use the `arkit` key in the key in the 87 | `UIRequiredDeviceCapabilities` section of the Info.plist to prevent 88 | the app from installing. (If the app can't be installed, this error 89 | can't be triggered in a production scenario.) 90 | In apps where AR is an additive feature, use `isSupported` to 91 | determine whether to show UI for launching AR experiences. 92 | """) // For details, see https://developer.apple.com/documentation/arkit 93 | } 94 | //重置界面,参数,追踪配置 95 | resetAll() 96 | print("viewWillAppear") 97 | } 98 | 99 | override func viewWillDisappear(_ animated: Bool) { 100 | super.viewWillDisappear(animated) 101 | 102 | // Pause the view's session 103 | sceneView.session.pause() 104 | } 105 | 106 | override func didReceiveMemoryWarning() { 107 | super.didReceiveMemoryWarning() 108 | // Release any cached data, images, etc that aren't in use. 109 | } 110 | @IBAction func playButtonClick(_ sender: UIButton) { 111 | //0.隐藏按钮 112 | playButton.isHidden = true 113 | sessionInfoLabel.isHidden = true 114 | //1.停止平面检测 115 | stopTracking() 116 | //2.不显示辅助点 117 | sceneView.debugOptions = [] 118 | //3.更改平面的透明度和颜色 119 | planeNode?.geometry?.firstMaterial?.diffuse.contents = UIColor.clear 120 | planeNode?.opacity = 1 121 | baseNode?.geometry?.firstMaterial?.diffuse.contents = UIColor.clear 122 | //4.载入游戏场景 123 | 124 | gameNode?.removeFromParentNode()//移除前一次游戏的场景节点 125 | gameNode = SCNNode() 126 | let gameChildNodes = SCNScene(named: "art.scnassets/Scenes/GameScene.scn")!.rootNode.childNodes 127 | for node in gameChildNodes { 128 | gameNode?.addChildNode(node) 129 | } 130 | baseNode?.addChildNode(gameNode!) 131 | resetGameData() 132 | 133 | 134 | let boxNode = SCNNode(geometry: SCNBox(width: boxLengthWidth, height: boxheight, length: boxLengthWidth, chamferRadius: 0)) 135 | boxNode.position.z = -actionOffet 136 | boxNode.position.y = Float(boxheight * 0.5) 137 | boxNode.name = "Block\(height)" 138 | boxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat(height % 10), green: 0.03*CGFloat(height%30), blue: 1-0.1 * CGFloat(height % 10), alpha: 1) 139 | boxNode.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: boxNode.geometry!, options: nil)) 140 | gameNode?.addChildNode(boxNode) 141 | } 142 | @IBAction func restartButtonClick(_ sender: UIButton) { 143 | resetAll() 144 | } 145 | @IBAction func handleTap(_ sender: Any) { 146 | if let currentBoxNode = gameNode?.childNode(withName: "Block\(height)", recursively: false) { 147 | currentPosition = currentBoxNode.presentation.position 148 | let boundsMin = currentBoxNode.boundingBox.min 149 | let boundsMax = currentBoxNode.boundingBox.max 150 | currentSize = boundsMax - boundsMin 151 | 152 | offset = previousPosition - currentPosition 153 | absoluteOffset = offset.absoluteValue() 154 | newSize = currentSize - absoluteOffset 155 | 156 | if height % 2 == 0 && newSize.z <= 0 { 157 | gameOver() 158 | playSound(sound: "GameOver", node: currentBoxNode) 159 | height += 1 160 | currentBoxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: currentBoxNode.geometry!, options: nil)) 161 | return 162 | } else if height % 2 != 0 && newSize.x <= 0 { 163 | gameOver() 164 | playSound(sound: "GameOver", node: currentBoxNode) 165 | height += 1 166 | currentBoxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: currentBoxNode.geometry!, options: nil)) 167 | return 168 | } 169 | 170 | checkPerfectMatch(currentBoxNode) 171 | 172 | currentBoxNode.geometry = SCNBox(width: CGFloat(newSize.x), height: boxheight, length: CGFloat(newSize.z), chamferRadius: 0) 173 | currentBoxNode.position = SCNVector3Make(currentPosition.x + (offset.x/2), currentPosition.y, currentPosition.z + (offset.z/2)) 174 | currentBoxNode.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: currentBoxNode.geometry!, options: nil)) 175 | currentBoxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat(height % 10), green: 0.03*CGFloat(height%30), blue: 1-0.1 * CGFloat(height % 10), alpha: 1) 176 | addBrokenBlock(currentBoxNode) 177 | addNewBlock(currentBoxNode) 178 | playSound(sound: "SliceBlock", node: currentBoxNode) 179 | 180 | if height >= 5 { 181 | gameNode?.enumerateChildNodes({ (node, stop) in 182 | if node.light != nil {//灯光节点不隐藏 183 | return 184 | } 185 | if node.position.y < Float(self.height-5) * Float(boxheight) { 186 | node.isHidden = true 187 | } 188 | }) 189 | 190 | let moveUpAction = SCNAction.move(by: SCNVector3Make(0.0, Float(-boxheight), 0.0), duration: 0.2) 191 | 192 | gameNode?.runAction(moveUpAction) 193 | } 194 | 195 | scoreLabel.text = "\(height+1)" 196 | 197 | previousSize = SCNVector3Make(newSize.x, Float(boxheight), newSize.z) 198 | previousPosition = currentBoxNode.position 199 | height += 1 200 | } 201 | } 202 | 203 | } 204 | // MARK:- 私有方法 205 | extension ViewController { 206 | func addNewBlock(_ currentBoxNode: SCNNode) { 207 | let newBoxNode = SCNNode(geometry: SCNBox(width: CGFloat(newSize.x), height: boxheight, length: CGFloat(newSize.z), chamferRadius: 0)) 208 | newBoxNode.position = SCNVector3Make(currentBoxNode.position.x, currentPosition.y + Float(boxheight), currentBoxNode.position.z) 209 | newBoxNode.name = "Block\(height+1)" 210 | newBoxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat((height+1) % 10), green: 0.03*CGFloat((height+1)%30), blue: 1-0.1 * CGFloat((height+1) % 10), alpha: 1) 211 | newBoxNode.physicsBody = SCNPhysicsBody(type: .kinematic, shape: SCNPhysicsShape(geometry: newBoxNode.geometry!, options: nil)) 212 | if height % 2 == 0 { 213 | newBoxNode.position.x = -actionOffet 214 | } else { 215 | newBoxNode.position.z = -actionOffet 216 | } 217 | 218 | gameNode?.addChildNode(newBoxNode) 219 | } 220 | 221 | func addBrokenBlock(_ currentBoxNode: SCNNode) { 222 | let brokenBoxNode = SCNNode() 223 | brokenBoxNode.name = "Broken \(height)" 224 | 225 | if height % 2 == 0 && absoluteOffset.z > 0 { 226 | // 1 227 | brokenBoxNode.geometry = SCNBox(width: CGFloat(currentSize.x), height: boxheight, length: CGFloat(absoluteOffset.z), chamferRadius: 0) 228 | 229 | // 2 230 | if offset.z > 0 { 231 | brokenBoxNode.position.z = currentBoxNode.position.z - (offset.z/2) - ((currentSize - offset).z/2) 232 | } else { 233 | brokenBoxNode.position.z = currentBoxNode.position.z - (offset.z/2) + ((currentSize + offset).z/2) 234 | } 235 | brokenBoxNode.position.x = currentBoxNode.position.x 236 | brokenBoxNode.position.y = currentPosition.y 237 | 238 | // 3 239 | brokenBoxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: brokenBoxNode.geometry!, options: nil)) 240 | brokenBoxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat(height % 10), green: 0.03*CGFloat(height%30), blue: 1-0.1 * CGFloat(height % 10), alpha: 1) 241 | gameNode?.addChildNode(brokenBoxNode) 242 | 243 | // 4 244 | } else if height % 2 != 0 && absoluteOffset.x > 0 { 245 | brokenBoxNode.geometry = SCNBox(width: CGFloat(absoluteOffset.x), height: boxheight, length: CGFloat(currentSize.z), chamferRadius: 0) 246 | 247 | if offset.x > 0 { 248 | brokenBoxNode.position.x = currentBoxNode.position.x - (offset.x/2) - ((currentSize - offset).x/2) 249 | } else { 250 | brokenBoxNode.position.x = currentBoxNode.position.x - (offset.x/2) + ((currentSize + offset).x/2) 251 | } 252 | brokenBoxNode.position.y = currentPosition.y 253 | brokenBoxNode.position.z = currentBoxNode.position.z 254 | 255 | brokenBoxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: brokenBoxNode.geometry!, options: nil)) 256 | brokenBoxNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 0.1 * CGFloat(height % 10), green: 0.03*CGFloat(height%30), blue: 1-0.1 * CGFloat(height % 10), alpha: 1) 257 | gameNode?.addChildNode(brokenBoxNode) 258 | } 259 | } 260 | 261 | func checkPerfectMatch(_ currentBoxNode: SCNNode) { 262 | if height % 2 == 0 && absoluteOffset.z <= 0.005 { 263 | playSound(sound: "PerfectFit", node: currentBoxNode) 264 | currentBoxNode.position.z = previousPosition.z 265 | currentPosition.z = previousPosition.z 266 | perfectMatches += 1 267 | if perfectMatches >= 7 && currentSize.z < 1 { 268 | newSize.z += 0.005 269 | } 270 | 271 | offset = previousPosition - currentPosition 272 | absoluteOffset = offset.absoluteValue() 273 | newSize = currentSize - absoluteOffset 274 | } else if height % 2 != 0 && absoluteOffset.x <= 0.005 { 275 | playSound(sound: "PerfectFit", node: currentBoxNode) 276 | currentBoxNode.position.x = previousPosition.x 277 | currentPosition.x = previousPosition.x 278 | perfectMatches += 1 279 | if perfectMatches >= 7 && currentSize.x < 1 { 280 | newSize.x += 0.005 281 | } 282 | 283 | offset = previousPosition - currentPosition 284 | absoluteOffset = offset.absoluteValue() 285 | newSize = currentSize - absoluteOffset 286 | } else { 287 | perfectMatches = 0 288 | } 289 | } 290 | 291 | func loadSound(name: String, path: String) { 292 | if let sound = SCNAudioSource(fileNamed: path) { 293 | sound.isPositional = false 294 | sound.volume = 1 295 | sound.load() 296 | sounds[name] = sound 297 | } 298 | } 299 | 300 | func playSound(sound: String, node: SCNNode) { 301 | node.runAction(SCNAction.playAudio(sounds[sound]!, waitForCompletion: false)) 302 | } 303 | 304 | 305 | func gameOver() { 306 | 307 | let fullAction = SCNAction.customAction(duration: 0.3) { _,_ in 308 | let moveAction = SCNAction.move(to: SCNVector3Make(0, 0, 0), duration: 0.3) 309 | self.gameNode?.runAction(moveAction) 310 | } 311 | 312 | gameNode?.runAction(fullAction) 313 | playButton.isHidden = false 314 | gameNode?.enumerateChildNodes({ (node, stop) in 315 | 316 | node.isHidden = false 317 | 318 | }) 319 | } 320 | private func updateSessionInfoLabel(for frame: ARFrame, trackingState: ARCamera.TrackingState) { 321 | // 更新UI,反馈AR状态. 322 | let message: String 323 | print("status") 324 | switch trackingState { 325 | case .normal where frame.anchors.isEmpty: 326 | // 未检测到平面 327 | message = "移动设备来探测水平面." 328 | 329 | case .normal: 330 | // 平面可见,跟踪正常,无需反馈 331 | message = "" 332 | 333 | case .notAvailable: 334 | message = "无法追踪." 335 | 336 | case .limited(.excessiveMotion): 337 | message = "追踪受限-请缓慢移动设备." 338 | 339 | case .limited(.insufficientFeatures): 340 | message = "追踪受限-将设备对准平面上的可见花纹区域,或改善光照条件." 341 | 342 | case .limited(.initializing): 343 | message = "初始化AR中." 344 | 345 | case .limited(.relocalizing): 346 | message = "重新定位AR中." 347 | 348 | case .limited(_): 349 | message = "有限." 350 | 351 | } 352 | print(message) 353 | sessionInfoLabel.text = message 354 | sessionInfoLabel.isHidden = message.isEmpty 355 | } 356 | 357 | private func resetTracking() { 358 | let configuration = ARWorldTrackingConfiguration() 359 | configuration.planeDetection = .horizontal 360 | configuration.isLightEstimationEnabled = true 361 | sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) 362 | } 363 | private func stopTracking() { 364 | let configuration = ARWorldTrackingConfiguration() 365 | configuration.planeDetection = .init(rawValue: 0) 366 | configuration.isLightEstimationEnabled = true 367 | sceneView.session.run(configuration) 368 | } 369 | 370 | private func resetAll() { 371 | //0.显示按钮 372 | playButton.isHidden = true 373 | sessionInfoLabel.isHidden = false 374 | //1.重置平面检测配置,重启检测 375 | resetTracking() 376 | //2.重置更新次数 377 | updateCount = 0 378 | sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints] 379 | //3.重置游戏数据 380 | resetGameData() 381 | print("resetAll") 382 | } 383 | private func resetGameData() { 384 | height = 0 385 | scoreLabel.text = "\(height)" 386 | 387 | direction = true 388 | perfectMatches = 0 389 | previousSize = SCNVector3(boxLengthWidth, boxheight, boxLengthWidth) 390 | previousPosition = SCNVector3(0, boxheight*0.5, 0) 391 | currentSize = SCNVector3(boxLengthWidth, boxheight, boxLengthWidth) 392 | currentPosition = SCNVector3Zero 393 | 394 | offset = SCNVector3Zero 395 | absoluteOffset = SCNVector3Zero 396 | newSize = SCNVector3Zero 397 | } 398 | } 399 | 400 | extension ViewController:ARSCNViewDelegate { 401 | // MARK: - ARSCNViewDelegate 402 | 403 | // 识别到新的锚点后,添加什么样的node.不实现该代理的话,会添加一个默认的空的node 404 | // ARKit会自动管理这个node的可见性及transform等属性等,所以一般把自己要显示的内容添加在这个node下面作为子节点 405 | // func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? { 406 | // 407 | // let node = SCNNode() 408 | // 409 | // return node 410 | // } 411 | 412 | // node添加到新的锚点上之后(一般在这个方法中添加几何体节点,作为node的子节点) 413 | func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { 414 | 415 | //1.获取捕捉到的平地锚点,只识别并添加一个平面 416 | if let planeAnchor = anchor as? ARPlaneAnchor,node.childNodes.count < 1,updateCount < 1 { 417 | print("捕捉到平地") 418 | //2.创建一个平面 (系统捕捉到的平地是一个不规则大小的长方形,这里笔者将其变成一个长方形) 419 | let plane = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z)) 420 | //3.使用Material渲染3D模型(默认模型是白色的,这里笔者改成红色) 421 | plane.firstMaterial?.diffuse.contents = UIColor.red 422 | //4.创建一个基于3D物体模型的节点 423 | planeNode = SCNNode(geometry: plane) 424 | //5.设置节点的位置为捕捉到的平地的锚点的中心位置 SceneKit框架中节点的位置position是一个基于3D坐标系的矢量坐标SCNVector3Make 425 | planeNode?.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z) 426 | //6.`SCNPlane`默认是竖着的,所以旋转一下以匹配水平的`ARPlaneAnchor` 427 | planeNode?.eulerAngles.x = -.pi / 2 428 | 429 | //7.更改透明度 430 | planeNode?.opacity = 0.25 431 | //8.添加到父节点中 432 | node.addChildNode(planeNode!) 433 | 434 | //9.上面的planeNode节点,大小/位置会随着检测到的平面而不断变化,方便起见,再添加一个相对固定的基准平面,用来放置游戏场景 435 | let base = SCNBox(width: 0.5, height: 0, length: 0.5, chamferRadius: 0); 436 | base.firstMaterial?.diffuse.contents = UIColor.gray; 437 | baseNode = SCNNode(geometry:base); 438 | baseNode?.position = SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z); 439 | 440 | node.addChildNode(baseNode!) 441 | } 442 | } 443 | 444 | // 更新锚点和对应的node之前调用,ARKit会自动更新anchor和node,使其相匹配 445 | func renderer(_ renderer: SCNSceneRenderer, willUpdate node: SCNNode, for anchor: ARAnchor) { 446 | // 只更新在`renderer(_:didAdd:for:)`中得到的配对的锚点和节点. 447 | guard let planeAnchor = anchor as? ARPlaneAnchor, 448 | let planeNode = node.childNodes.first, 449 | let plane = planeNode.geometry as? SCNPlane 450 | else { return } 451 | 452 | updateCount += 1 453 | if updateCount > 20 {//平面超过更新20次,捕捉到的特征点已经足够多了,可以显示进入游戏按钮 454 | DispatchQueue.main.async { 455 | self.playButton.isHidden = false 456 | } 457 | } 458 | 459 | // 平面的中心点可以会变动. 460 | planeNode.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z) 461 | 462 | /* 463 | 平面尺寸可能会变大,或者把几个小平面合并为一个大平面.合并时,`ARSCNView`自动删除同一个平面上的相应节点,然后调用该方法来更新保留的另一个平面的尺寸.(经过测试,合并时,保留第一个检测到的平面和对应节点) 464 | */ 465 | plane.width = CGFloat(planeAnchor.extent.x) 466 | plane.height = CGFloat(planeAnchor.extent.z) 467 | } 468 | 469 | // 更新锚点和对应的node之后调用 470 | func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { 471 | 472 | } 473 | // 移除锚点和对应node后 474 | func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) { 475 | 476 | } 477 | 478 | // MARK: - ARSessionObserver 479 | 480 | func session(_ session: ARSession, didFailWithError error: Error) { 481 | 482 | sessionInfoLabel.text = "Session失败: \(error.localizedDescription)" 483 | resetTracking() 484 | } 485 | 486 | func sessionWasInterrupted(_ session: ARSession) { 487 | 488 | sessionInfoLabel.text = "Session被打断" 489 | } 490 | 491 | func sessionInterruptionEnded(_ session: ARSession) { 492 | 493 | sessionInfoLabel.text = "Session打断结束" 494 | resetTracking() 495 | } 496 | 497 | func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) { 498 | updateSessionInfoLabel(for: session.currentFrame!, trackingState: camera.trackingState) 499 | } 500 | 501 | // MARK:- SCNSceneRendererDelegate 502 | func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { 503 | guard let gameNode2 = gameNode else { 504 | return 505 | } 506 | for node in gameNode2.childNodes { 507 | if node.presentation.position.y <= -10 { 508 | node.removeFromParentNode() 509 | } 510 | } 511 | 512 | // 1 513 | if let currentNode = gameNode?.childNode(withName: "Block\(height)", recursively: false) { 514 | // 2 515 | if height % 2 == 0 { 516 | // 3 517 | if currentNode.position.z >= actionOffet { 518 | direction = false 519 | } else if currentNode.position.z <= -actionOffet { 520 | direction = true 521 | } 522 | 523 | // 4 524 | switch direction { 525 | case true: 526 | currentNode.position.z += actionSpeed 527 | case false: 528 | currentNode.position.z -= actionSpeed 529 | } 530 | // 5 531 | } else { 532 | if currentNode.position.x >= actionOffet { 533 | direction = false 534 | } else if currentNode.position.x <= -actionOffet { 535 | direction = true 536 | } 537 | 538 | switch direction { 539 | case true: 540 | currentNode.position.x += actionSpeed 541 | case false: 542 | currentNode.position.x -= actionSpeed 543 | } 544 | } 545 | } 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/art.scnassets/Audio/GameOver.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step3-ARStack-Final/ARStack/art.scnassets/Audio/GameOver.wav -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/art.scnassets/Audio/PerfectFit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step3-ARStack-Final/ARStack/art.scnassets/Audio/PerfectFit.wav -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/art.scnassets/Audio/SliceBlock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step3-ARStack-Final/ARStack/art.scnassets/Audio/SliceBlock.wav -------------------------------------------------------------------------------- /step3-ARStack-Final/ARStack/art.scnassets/Scenes/GameScene.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XanderXu/ARStack/891146192db6555a0986c5fa98a359d5464a0ad4/step3-ARStack-Final/ARStack/art.scnassets/Scenes/GameScene.scn --------------------------------------------------------------------------------