diff --git a/FlappyBird.xcodeproj/project.pbxproj b/FlappyBird.xcodeproj/project.pbxproj index b0120f5..cadd2f5 100644 --- a/FlappyBird.xcodeproj/project.pbxproj +++ b/FlappyBird.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -15,6 +15,7 @@ 4374520B193D163800654986 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4374520A193D163800654986 /* Images.xcassets */; }; 43745217193D163800654986 /* FlappyBirdTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43745216193D163800654986 /* FlappyBirdTests.swift */; }; 437A3DBA193D326A00909BA0 /* bird.atlas in Resources */ = {isa = PBXBuildFile; fileRef = 437A3DB9193D326A00909BA0 /* bird.atlas */; }; + 5CE2274B24C112310060AE20 /* Optimizely in Frameworks */ = {isa = PBXBuildFile; productRef = 5CE2274A24C112310060AE20 /* Optimizely */; }; 6E1623E01E24FC4E00FB2C72 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E1623DF1E24FC4E00FB2C72 /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ @@ -49,6 +50,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5CE2274B24C112310060AE20 /* Optimizely in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -138,6 +140,9 @@ dependencies = ( ); name = FlappyBird; + packageProductDependencies = ( + 5CE2274A24C112310060AE20 /* Optimizely */, + ); productName = FlappyBird; productReference = 437451FA193D163700654986 /* FlappyBird.app */; productType = "com.apple.product-type.application"; @@ -191,6 +196,9 @@ Base, ); mainGroup = 437451F1193D163700654986; + packageReferences = ( + 5CE2274924C112310060AE20 /* XCRemoteSwiftPackageReference "swift-sdk" */, + ); productRefGroup = 437451FB193D163700654986 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -310,7 +318,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; METAL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -358,10 +366,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; METAL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -372,7 +381,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = FlappyBird/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = "io.fullstack.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; @@ -385,7 +397,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = FlappyBird/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = "io.fullstack.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; @@ -406,7 +421,11 @@ "$(inherited)", ); INFOPLIST_FILE = FlappyBirdTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); METAL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "io.fullstack.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -425,7 +444,11 @@ "$(inherited)", ); INFOPLIST_FILE = FlappyBirdTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); METAL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "io.fullstack.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -466,6 +489,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 5CE2274924C112310060AE20 /* XCRemoteSwiftPackageReference "swift-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/optimizely/swift-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.4.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 5CE2274A24C112310060AE20 /* Optimizely */ = { + isa = XCSwiftPackageProductDependency; + package = 5CE2274924C112310060AE20 /* XCRemoteSwiftPackageReference "swift-sdk" */; + productName = Optimizely; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 437451F2193D163700654986 /* Project object */; } diff --git a/FlappyBird.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/FlappyBird.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 379da4d..919434a 100644 --- a/FlappyBird.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/FlappyBird.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/FlappyBird.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlappyBird.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..c7f8c52 --- /dev/null +++ b/FlappyBird.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "Optimizely", + "repositoryURL": "https://github.com/optimizely/swift-sdk.git", + "state": { + "branch": null, + "revision": "dbfaa6665365bdcb2e8d023de9f0b7a4b2322e8d", + "version": "3.4.0" + } + } + ] + }, + "version": 1 +} diff --git a/FlappyBird/AppDelegate.swift b/FlappyBird/AppDelegate.swift index b3bdcbd..e0bfd0a 100644 --- a/FlappyBird/AppDelegate.swift +++ b/FlappyBird/AppDelegate.swift @@ -7,18 +7,30 @@ // import UIKit +import Optimizely @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? - + var window: UIWindow? + + // Build OptimizelyClient -> REPLACE_WITH_SDK_KEY + let optimizely = OptimizelyClient(sdkKey: "REPLACE_WITH_SDK_KEY", periodicDownloadInterval: 30) func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + + optimizely.start { result in + switch result { + case .failure(let error): + print("Optimizely SDK initiliazation failed: \(error)") + case .success: + print("Optimizely SDK initialized successfully!") + } + } + return true } + func applicationWillResignActive(_ application: UIApplication) { // 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. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. diff --git a/FlappyBird/GameScene.swift b/FlappyBird/GameScene.swift index af93d4d..c8c1f83 100644 --- a/FlappyBird/GameScene.swift +++ b/FlappyBird/GameScene.swift @@ -9,7 +9,12 @@ import SpriteKit class GameScene: SKScene, SKPhysicsContactDelegate{ - let verticalPipeGap = 150.0 + var appDelegate: AppDelegate? + + //remotely configurable variables + var verticalPipeGap = 200.0 + var gravity:Double = 4 + var gameSpeed: CGFloat = 100 var bird:SKSpriteNode! var skyColor:SKColor! @@ -32,7 +37,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate{ canRestart = true // setup physics - self.physicsWorld.gravity = CGVector( dx: 0.0, dy: -5.0 ) + self.physicsWorld.gravity = CGVector( dx: 0.0, dy: -gravity ) self.physicsWorld.contactDelegate = self // setup background color @@ -85,18 +90,8 @@ class GameScene: SKScene, SKPhysicsContactDelegate{ pipeTextureDown = SKTexture(imageNamed: "PipeDown") pipeTextureDown.filteringMode = .nearest - // create the pipes movement actions - let distanceToMove = CGFloat(self.frame.size.width + 2.0 * pipeTextureUp.size().width) - let movePipes = SKAction.moveBy(x: -distanceToMove, y:0.0, duration:TimeInterval(0.01 * distanceToMove)) - let removePipes = SKAction.removeFromParent() - movePipesAndRemove = SKAction.sequence([movePipes, removePipes]) - - // spawn the pipes - let spawn = SKAction.run(spawnPipes) - let delay = SKAction.wait(forDuration: TimeInterval(2.0)) - let spawnThenDelay = SKAction.sequence([spawn, delay]) - let spawnThenDelayForever = SKAction.repeatForever(spawnThenDelay) - self.run(spawnThenDelayForever) + resetPipes() + startNewPipes() // setup our bird let birdTexture1 = SKTexture(imageNamed: "bird-01") @@ -151,7 +146,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate{ let pipeDown = SKSpriteNode(texture: pipeTextureDown) pipeDown.setScale(2.0) - pipeDown.position = CGPoint(x: 0.0, y: y + Double(pipeDown.size.height) + verticalPipeGap) + pipeDown.position = CGPoint(x: 0.0, y: y + Double(pipeDown.size.height) + self.verticalPipeGap) pipeDown.physicsBody = SKPhysicsBody(rectangleOf: pipeDown.size) @@ -178,12 +173,65 @@ class GameScene: SKScene, SKPhysicsContactDelegate{ contactNode.physicsBody?.contactTestBitMask = birdCategory pipePair.addChild(contactNode) - pipePair.run(movePipesAndRemove) + pipePair.run(movePipesAndRemove, withKey: "movePipesAndRemove") pipes.addChild(pipePair) + } + + func resetPipes() { + // create the pipes movement actions + let distanceToMove = CGFloat(self.frame.size.width + 2.0 * pipeTextureUp.size().width) + let movePipes = SKAction.moveBy(x: -distanceToMove, y:0.0, duration:TimeInterval((1/self.gameSpeed) * distanceToMove)) + let removePipes = SKAction.removeFromParent() + self.movePipesAndRemove = SKAction.sequence([movePipes, removePipes]) + } + + func startNewPipes() { + let spawn = SKAction.run(spawnPipes) + let delay = SKAction.wait(forDuration: TimeInterval(2.0)) + let spawnThenDelay = SKAction.sequence([spawn, delay]) + let spawnThenDelayForever = SKAction.repeatForever(spawnThenDelay) + self.run(spawnThenDelayForever, withKey: "spawnThenDelayForever") } func resetScene (){ + + // Remove all existing pipes + pipes.removeAllChildren() + let userId = "user123" + let attributes: [String: Any] = [ + "gamePhysicsDifficulty": "hard" + ] + + let enabled = self.appDelegate!.optimizely.isFeatureEnabled(featureKey: "remoteConfig", userId: userId, attributes: attributes) + + if enabled { + print("remoteConfig feature enabled!!") + + do { + let remoteConfigGameSpeed = try self.appDelegate!.optimizely.getFeatureVariableInteger(featureKey: "remoteConfig", variableKey: "gameSpeed", userId: userId, attributes: attributes) + let remoteConfigPipeGap = try self.appDelegate!.optimizely.getFeatureVariableInteger(featureKey: "remoteConfig", variableKey: "pipeGap", userId: userId, attributes: attributes) + let remoteConfigGravity = try self.appDelegate!.optimizely.getFeatureVariableInteger(featureKey: "remoteConfig", variableKey: "gravity", userId: userId, attributes: attributes) + + self.gameSpeed = CGFloat(remoteConfigGameSpeed) + self.verticalPipeGap = Double(remoteConfigPipeGap) + self.gravity = Double(remoteConfigGravity) + self.physicsWorld.gravity = CGVector( dx: 0.0, dy: -self.gravity ) + + print("self.gameSpeed: \(self.gameSpeed)") + print("self.verticalPipeGap: \(self.verticalPipeGap)") + print("self.gravity: \(self.gravity)") + + } catch { + print("error getting feature variable from optimizely: \(error)") + } + resetPipes() + startNewPipes() + } else { + print("remoteConfig feature NOT enabled") + } + + // Move bird to original position and reset velocity bird.position = CGPoint(x: self.frame.size.width / 2.5, y: self.frame.midY) bird.physicsBody?.velocity = CGVector( dx: 0, dy: 0 ) @@ -191,8 +239,7 @@ class GameScene: SKScene, SKPhysicsContactDelegate{ bird.speed = 1.0 bird.zRotation = 0.0 - // Remove all existing pipes - pipes.removeAllChildren() + // Reset _canRestart canRestart = false diff --git a/FlappyBird/GameViewController.swift b/FlappyBird/GameViewController.swift index 6761d84..85fccae 100644 --- a/FlappyBird/GameViewController.swift +++ b/FlappyBird/GameViewController.swift @@ -30,6 +30,8 @@ extension SKNode { } class GameViewController: UIViewController { + + let delegate = UIApplication.shared.delegate as! AppDelegate override func viewDidLoad() { super.viewDidLoad() @@ -46,6 +48,9 @@ class GameViewController: UIViewController { /* Set the scale mode to scale to fit the window */ scene.scaleMode = .aspectFill + //attach delegate to scene from linked AppDelegate above + scene.appDelegate = delegate + skView.presentScene(scene) } } diff --git a/README.md b/README.md new file mode 100644 index 0000000..54ed0b6 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ + +# TappyBird 🦜 + +![Remotely configuring game variables](https://user-images.githubusercontent.com/5668620/87964577-0cf18180-ca80-11ea-941a-9c3bf6339f4e.png) + +Tutorial App for [Optimizely Remote Config Tutorial for iOS how-to guide](https://www.linkedin.com/pulse/optimizely-remote-config-tutorial-ios-kody-o-connell/). + +If you’ve ever thought, “if *only* I could just send an instant update to my deployed iOS app!” you wouldn’t be alone. Who wouldn’t want the ability to quickly squash a bug in their deployed iOS app, or remotely tweak the behavior of their app in response to user feedback? + +To quickly learn how to get started using Optimizely to power fast, reliable (and [free](https://www.optimizely.com/rollouts-signup/ios/?utm_campaign=feature-flags-swift)!) remote configuration on iOS, follow the detailed how-to guide [Optimizely Remote Config Tutorial for iOS](https://www.linkedin.com/pulse/optimizely-remote-config-tutorial-ios-kody-o-connell/), and use this completed tutorial app or the app at the commit specific zip file (linked in steps in the how-to guide) necessary to complete that step. + +## Scenario +Imagine we are an indie game developer team building iOS games with Apple’s [SpriteKit](https://developer.apple.com/spritekit/) framework. We go by the team name “Game Wizards” 🧙‍♂️🧙‍♀️, because our apps just have that special magic 💫! We’re thrilled because our most recent app **Tappy Bird** suddenly went viral last week. But the cost of our success means that we are already starting to get requests from users on social media and the App Store to change the way the game works. + +## What You'll Learn +In [this tutorial](https://www.linkedin.com/pulse/optimizely-remote-config-tutorial-ios-kody-o-connell/) you’ll learn how to remotely change variables in your deployed iOS app, enabling you to quickly change the behavior of your app, without needing to wait for an app store review or resubmission process 🕒. + +## Getting Started +With Optimizely’s [free](https://www.optimizely.com/rollouts-signup/ios/?utm_campaign=feature-flags-swift) & open source [SDKs](https://docs.developers.optimizely.com/full-stack/docs/sdk-reference-guides) for iOS and Android, you can customize the rate at which your mobile app polls the Optimizely API for new variable configurations (and feature rollouts) and rest assured that your app won’t be delayed waiting on a network call to return the remote configuration values, because they’re stored locally on the SDK client. And with local & deterministic user bucketing, you can confidently deliver different configurations to different users/filters across implementation contexts. + +Follow the detailed how-to guide: [Optimizely Remote Config Tutorial for iOS](https://www.linkedin.com/pulse/optimizely-remote-config-tutorial-ios-kody-o-connell/), for steps on running the tutorial app. + + + + + + + + + +### With thanks 👋 ⑂ Forked from [FlappySwift](https://github.com/fullstackio/FlappySwift) originally written by + +- Nate Murray - [@eigenjoy](https://twitter.com/eigenjoy) +- Ari Lerner - [@auser](https://twitter.com/auser) +- and based on code by [Matthias Gall](http://digitalbreed.com/2014/how-to-build-a-game-like-flappy-bird-with-xcode-and-sprite-kit) + diff --git a/README.mkd b/README.mkd deleted file mode 100644 index 5ee3918..0000000 --- a/README.mkd +++ /dev/null @@ -1,20 +0,0 @@ -[![BuddyBuild](https://dashboard.buddybuild.com/api/statusImage?appID=55dd277abbda430100397040&branch=master&build=latest)](https://dashboard.buddybuild.com/apps/55dd277abbda430100397040/build/latest) - -# FlappySwift - -An implementation of Flappy Bird in Swift for iOS 8. - -![FlappySwift](http://i.imgur.com/1NLoToU.gif) - -# Notes - -We're launching a course [Game Programming with Swift](https://fullstackedu.com) - -If you are interested in early access to the course, head to [fullstackedu.com](https://www.fullstackedu.com) and _enter in your email in the signup box at the bottom of the page_ to be notified of updates on the course itself. - -# Authors - -- Nate Murray - [@eigenjoy](https://twitter.com/eigenjoy) -- Ari Lerner - [@auser](https://twitter.com/auser) -- Based on code by [Matthias Gall](http://digitalbreed.com/2014/how-to-build-a-game-like-flappy-bird-with-xcode-and-sprite-kit) -