I am very new to programming and am making my first app using Swift and scenekit.
I have a grocery cart that is given velocity, and its physics body has friction = 0 and damping = 0. As the app runs, after about like 20 seconds the cart node just goes at about half speed, then resumes, and then keeps doing this. I'm assuming this issue is caused by lag, but I really have no idea what to do or how to debug this issue.
If you could take a look at the renderer, that is probably where the issue is?
Any help would be greatly appreciated. My code is below.
// // GameViewController.swift // CrazyCarts // // Created by SuchyMac2 on 11/18/20. // Copyright © 2020 ITP. All rights reserved. // import UIKit import SceneKit import SpriteKit //Global Variables to use between classes struct globalVariables { static var doCheckMenus = false //any gamemode active static var isRunning = false //the score static var score = 0 //the level static var level = 40 //freeplay static var isPlayingFreeplay = false //Buttons static var levelButton = false static var mainMenuButton = false static var restartButton = false static var freeplayButton = false } // Main Game View Controller & Handler class GameViewController: UIViewController, SCNSceneRendererDelegate, SCNPhysicsContactDelegate { var sceneView:SCNView! var scene:SCNScene! //Physics Collission Handler var physicsWorld:SCNPhysicsWorld! // Does the user have movement control? var isUserStopped = false // Is the cart moving? var isMoving = false var isJumping = false //Is it safe to place an obstacle? var doPlace = true //Send a request for map generation? var doMapLoad = false //How much map should load after the visible distance is done loaded? (In M) //CartVelocity var cartSpeed = 1.0 //Movement Speeds var cartXSpeed = 0.2 var cartYSpeed = 0.32 //Position of the cart node's Z Axis, Intervals for map generation var aisleInterval = 0.0 // Interval used in obstacle generation var generatorInterval = 0.0 //How much should the map load, in M, before starting? var preloadDistance = 50 //Interval between obstacle generation var obstacleInterval = 4.0 //How much empty space to leave at the start var emptySpace = 0.0 //Skip Generation Rarity var skipGroceryRarity = 12.0 var skipObstacleRarity = 15.0 //The floor var floorNode:SCNNode! // The cart node or main player var cartNode:SCNNode! // The camera var cameraNode:SCNNode! // Aisle Nodes var aisleNode:SCNNode! //Obstacle Nodes var box2Node:SCNNode! var box1Node:SCNNode! var kidCartNode:SCNNode! var jumpPipelineNode:SCNNode! var jumpPipelineValveNode:SCNNode! var table1:SCNNode! //Grocery Nodes var groceryGenerator:SCNNode! var appleGroceryNode:SCNNode! //Light Node var omniLightNode:SCNNode! //Collection of the maps nodes var groceryNodes:SCNNode! var obstacleNodes:SCNNode! var aisleNodes:SCNNode! //Map Node Arrays var obstacles: [SCNNode] = [] var groceries: [SCNNode] = [] var aisles: [SCNNode] = [] //Freeplay Stuff var freeplayAmplifier = 1.0 var amplifyFreq = -1 // Cart Position in the 3 lanes. It starts in the middle or lane 2 var cartPosition = 2 // DATA SAVING let savedData = UserDefaults.standard // Begins running code on app start override func viewDidLoad() { //Setup visual scene & nodes. setupScene() setupNodes() // USER INPUT RECOGNIZER // Swipe Left let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(gestureResponse)) swipeLeft.direction = .left self.view.addGestureRecognizer(swipeLeft) // Swipe Right let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(gestureResponse)) swipeRight.direction = .right self.view.addGestureRecognizer(swipeRight) // Swipe Up let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(gestureResponse)) swipeRight.direction = .up self.view.addGestureRecognizer(swipeUp) } // Defines view and scene names func setupScene(){ sceneView = (self.view as! SCNView) scene = SCNScene(named: "art.scnassets/MainScene.scn") sceneView.scene = scene sceneView.delegate = self scene.physicsWorld.contactDelegate = self let mainMenuScene = SKScene(fileNamed: "MainMenuScene") sceneView.overlaySKScene = mainMenuScene } // Defines names to nodes within the MainScene and defines nodes that require physics func setupNodes(){ //The cart cartNode = scene.rootNode.childNode(withName: "cart", recursively: true) cartNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) cartNode.physicsBody?.mass = 10 cartNode.physicsBody?.isAffectedByGravity = false cartNode.runAction(SCNAction.scale(by: 0.8, duration: 0.0)) //Important, tells the physics engine to check for all types of collisions cartNode.physicsBody!.contactTestBitMask = cartNode.physicsBody!.collisionBitMask //AISLES aisleNode = scene.rootNode.childNode (withName: "aisle", recursively: true) aisleNode.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil) aisleNodes = scene.rootNode.childNode (withName: "aisleNodes", recursively: true) //OBSTACLES obstacleNodes = scene.rootNode.childNode(withName: "obstacleNodes", recursively: true) //Boxes //Small, short box box2Node = scene.rootNode.childNode(withName: "box2", recursively: true) box2Node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) box2Node.physicsBody?.mass = 2 box2Node.runAction(SCNAction.scale(by: 1.5, duration: 0.0)) // Tall Box box1Node = scene.rootNode.childNode(withName: "box1", recursively: true) box1Node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) box1Node.physicsBody?.mass = 4 box1Node.runAction(SCNAction.scale(by: 1.5, duration: 0.0)) //Kid Cart kidCartNode = scene.rootNode.childNode(withName: "kidCart", recursively: true) kidCartNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) kidCartNode.physicsBody?.mass = 20 kidCartNode.runAction(SCNAction.scale(by: 0.9, duration: 0.0)) //jump pipe jumpPipelineNode = scene.rootNode.childNode(withName: "jumpPipeline", recursively: true) jumpPipelineNode.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil) jumpPipelineNode.runAction(SCNAction.scale(by: 1.2, duration: 0.0)) //jump pipe w/valve jumpPipelineValveNode = scene.rootNode.childNode(withName: "jumpPipelineValve", recursively: true) jumpPipelineValveNode.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil) jumpPipelineValveNode.runAction(SCNAction.scale(by: 1.2, duration: 0.0)) //Table 1 table1 = scene.rootNode.childNode(withName: "table1", recursively: true) table1.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) table1.physicsBody?.mass = 35 //Camera && Lights cameraNode = scene.rootNode.childNode(withName: "camera", recursively: true) omniLightNode = scene.rootNode.childNode(withName: "omniLight", recursively: true) //GROCERIES groceryNodes = scene.rootNode.childNode(withName: "groceryNodes", recursively: true) //apple appleGroceryNode = scene.rootNode.childNode(withName: "appleGroceryNode", recursively: true) appleGroceryNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) } //Resets carts positioning & deletes all local map generation data func resetScene() { globalVariables.isRunning = false globalVariables.isPlayingFreeplay = false var continueDegeneration = true while continueDegeneration == true { // Obstacles Map Degenerator if obstacles.count > 0 { obstacles[0].removeFromParentNode() obstacles.removeFirst() } //Aisle Degenerator if aisles.count > 0 { aisles[0].removeFromParentNode() aisles.removeFirst() } //Groceries Degenerator if groceries.count > 0 { groceries[0].removeFromParentNode() groceries.removeFirst() } if obstacles.count == 0 && aisles.count == 0 && groceries.count == 0 { continueDegeneration = false } doMapLoad = true } emptySpace = 15.0 generatorInterval = 0 aisleInterval = 0 isMoving = false isJumping = false isUserStopped = false cartNode.physicsBody?.isAffectedByGravity = false cartNode.physicsBody?.velocity=SCNVector3(0,0,0) cartNode.physicsBody?.angularVelocity=SCNVector4(0,0,0,0) cartNode.physicsBody?.friction = 0.0 cartNode.physicsBody?.damping = 0.0 omniLightNode.position = SCNVector3(0, 3,-2) cameraNode.position = SCNVector3(0, 2, -1.4) cartNode.position = SCNVector3(0,0,0) cartPosition = 2 globalVariables.score = 0 } //Changes level difficulty factors: Cart Speed, Obstacle types & frequency, etc func levelSetup () { resetScene() cartSpeed = 3+(Double(globalVariables.level)*0.005) cartXSpeed = 0.4-(Double(globalVariables.level)*0.0015) cartYSpeed = 0.32-(Double(globalVariables.level)*0.002) emptySpace = 15+(Double(globalVariables.level)*0.05) skipGroceryRarity = 12+(Double(globalVariables.level)/5).rounded() skipObstacleRarity = 15-(Double(globalVariables.level)/10).rounded() obstacleInterval = 5-(Double(globalVariables.level)/50) //Limits if cartSpeed > 10 { cartSpeed = 10 } if cartXSpeed < 0.10 { cartXSpeed = 0.10 } if cartYSpeed < 0.16 { cartYSpeed = 0.16 } if emptySpace > 25 { emptySpace = 25 } if skipGroceryRarity > 100 { skipGroceryRarity = 100 } if skipObstacleRarity < 8 { skipObstacleRarity = 8 } if obstacleInterval < 3.0 { obstacleInterval = 3.0 } cartNode.physicsBody!.velocity.z = Float(cartSpeed) } //Sets up freeplay mode func freeplaySetup () { resetScene() freeplayAmplifier = 1 amplifyFreq = -1 emptySpace = 15 globalVariables.isPlayingFreeplay = true } //Amplifies difficulty factors in Freeplay func freeplayAmplify() { freeplayAmplifier += 1 //Code Should Have the same values as levelSetup() cartSpeed = 3+(freeplayAmplifier*0.005) cartXSpeed = 0.4-(freeplayAmplifier*0.0015) cartYSpeed = 0.32-(freeplayAmplifier*0.002) skipGroceryRarity = (12+(freeplayAmplifier)/5).rounded() skipObstacleRarity = (15-(freeplayAmplifier)/10).rounded() obstacleInterval = 5-(freeplayAmplifier/50) //Limits if cartSpeed > 10 { cartSpeed = 10 } if cartXSpeed < 0.10 { cartXSpeed = 0.10 } if cartYSpeed < 0.16 { cartYSpeed = 0.16 } if skipGroceryRarity > 100 { skipGroceryRarity = 100 } if skipObstacleRarity < 8 { skipObstacleRarity = 8 } if obstacleInterval < 3.0 { obstacleInterval = 3.0 } cartNode.physicsBody?.velocity=SCNVector3(0,0,cartSpeed) } override var shouldAutorotate: Bool { return false } override var prefersStatusBarHidden: Bool { return true } //GESTURE MOVEMENT CONTROLLER @objc func gestureResponse(gesture: UIGestureRecognizer) { // Declarations //Individual Moves let moveLeft = SCNAction.moveBy(x: 0.87, y: 0, z: 0, duration: cartXSpeed) let moveRight = SCNAction.moveBy(x: -0.87, y: 0, z: 0, duration: cartXSpeed) let moveUp = SCNAction.moveBy(x: 0, y: 0.6, z: 0, duration: cartYSpeed) let moveHover = SCNAction.moveBy(x: 0, y: 0.2, z: 0, duration: cartYSpeed) let moveDown = SCNAction.moveBy(x: 0, y: -0.8, z:0, duration: cartYSpeed) let moveFinish: SCNAction = SCNAction.customAction(duration: 0, action: {_,_ in self.isMoving = false}) let jumpFinish: SCNAction = SCNAction.customAction(duration: 0, action: {_,_ in self.isJumping = false}) //Move Sequences let leftSequence = SCNAction.sequence([moveLeft, moveFinish]) let rightSequence = SCNAction.sequence([moveRight, moveFinish]) let jumpSequence = SCNAction.sequence([moveUp,moveHover,moveDown,jumpFinish]) // Actions for gesture swipes. if let swipeGesture = gesture as? UISwipeGestureRecognizer { if isMoving == false && globalVariables.isRunning == true && isUserStopped == false { switch swipeGesture.direction { case .left: if cartPosition > 1 { isMoving = true cartPosition = cartPosition-1 cartNode.runAction(leftSequence) } case .right: if cartPosition < 3 { isMoving = true cartPosition = cartPosition+1 cartNode.runAction(rightSequence) } case .up: if isJumping == false { isJumping = true cartNode.runAction(jumpSequence) } default: break } } } } //Collision Detection For Obstacles and Groceries public func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) { if isUserStopped == false { //If the cart interacts with ... if contact.nodeA.name == "cart" { // an obstacle if contact.nodeB.name == "obstacle" { cartNode.physicsBody?.isAffectedByGravity = true cartNode.physicsBody?.friction = 1.0 cartNode.physicsBody?.damping = 0.5 isUserStopped = true //Gives all obstacles physics for obstacle in obstacles { obstacle.physicsBody?.isAffectedByGravity = true obstacle.physicsBody?.friction = 0.5 obstacle.physicsBody?.damping = 0.5 } // a grocery } else if contact.nodeB.name == "grocery" { contact.nodeB.removeFromParentNode() } } } } // MAIN GAME LOOP - Executes once per frame public func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval){ //Check if an interaction with any menu occured, doCheckMenus is for optimization if globalVariables.doCheckMenus == true { globalVariables.doCheckMenus = false //CLICKED 'LEVEL _" if globalVariables.levelButton == true { globalVariables.levelButton = false levelSetup() } //Clicked "Main Menu" if globalVariables.mainMenuButton == true { globalVariables.mainMenuButton = false sceneView.overlaySKScene = SKScene(fileNamed: "MainMenuScene") } //Clicked "restart" if globalVariables.restartButton == true { globalVariables.restartButton = false if globalVariables.isPlayingFreeplay == true { freeplaySetup() } else { levelSetup() } } //Clicked "freeplay" if globalVariables.freeplayButton == true { globalVariables.freeplayButton = false freeplaySetup() } } // FREE PLAY MODE if globalVariables.isPlayingFreeplay == true && globalVariables.isRunning == true { //Amplify Freeplay every x Meters if Int(cartNode.position.z) > amplifyFreq { amplifyFreq += 10 freeplayAmplify() } } //The Score (displayed through GameSceneOverlay) globalVariables.score = Int(cartNode.position.z) //Level Fail Prompt if isUserStopped == true && Double((cartNode.physicsBody?.velocity.z)!) <= 0.05 && Double((cartNode.physicsBody?.velocity.z)!) >= -0.2 && globalVariables.isRunning == true { globalVariables.isRunning = false let failScene = SKScene(fileNamed: "GameOverOverlay") sceneView.overlaySKScene = failScene //Disable physics for obstacles for obstacle in obstacles { obstacle.physicsBody?.damping = 0.5 obstacle.physicsBody?.isAffectedByGravity = false } } //Generation for wall aisles if Float(aisleInterval) <= cartNode.position.z+Float(preloadDistance){ aisleInterval += 1.2 let leftAisleGenerator = aisleNode.clone() leftAisleGenerator.position = SCNVector3Make(2, 0, Float(aisleInterval)) self.aisleNodes.addChildNode(leftAisleGenerator) aisles.append(leftAisleGenerator) let rightAisleGenerator = aisleNode.clone() rightAisleGenerator.eulerAngles = SCNVector3(1.5707964,-4.712389,0) rightAisleGenerator.position = SCNVector3Make(-2, 0, Float(aisleInterval)) self.aisleNodes.addChildNode(rightAisleGenerator) aisles.append(rightAisleGenerator) } //Obstacle Generator if Float(generatorInterval) <= cartNode.position.z+Float(preloadDistance) { //Minimum distance an obstacle must be from another obstacle var minObstacleInterval = 0.0 var isHalf = false //Skip obstacle xPos selector? var doSkipXPos = false //Is this position clear of an obstacle var leftFree = false var middleFree = false var rightFree = false var xGroceryPos = 0.00 var yGroceryPos = 0.00 var skipGrocery = false var xPos = 0.00 var yPos = 0.00 var skipAPick = false var obstacleGenerator = box2Node //OBSTACLES //Selects which obstacle to place let obstaclePicker = Int(arc4random_uniform(UInt32(skipObstacleRarity))) switch obstaclePicker { //Box2 case 0...4: obstacleGenerator = box2Node.clone() minObstacleInterval = 2 //Box 1 case 5...6: obstacleGenerator = box1Node.clone() minObstacleInterval = 2 //Pipe Jump case 7: obstacleGenerator = jumpPipelineNode.copy() as? SCNNode yPos = 0.3 doSkipXPos = true minObstacleInterval = 5 //Kid Cart case 8...9: obstacleGenerator = kidCartNode.copy() as? SCNNode isHalf = true minObstacleInterval = 8 //Table 1 case 10...11: obstacleGenerator = table1.copy() as? SCNNode minObstacleInterval = 8 //Pipe jump w/valve case 12: obstacleGenerator = jumpPipelineValveNode.copy() as? SCNNode yPos = 0.3 doSkipXPos = true minObstacleInterval = 5 //Skip an obstacle case 13...15: skipAPick = true leftFree = true middleFree = true rightFree = true default: break } //The interval for how often obstacles spawn, also Sets the obstacleInterval to the minimum, if neccessary. if Double(minObstacleInterval) > obstacleInterval { generatorInterval += minObstacleInterval } else { generatorInterval += obstacleInterval } if isHalf == false && doSkipXPos == false { //Picks a # 0, 1, or 2 for obstacles that cover one space let xFullPosPicker = Int(arc4random_uniform(3)) switch xFullPosPicker { case 0: xPos = 0.87 rightFree = true middleFree = true case 1: xPos = 0 leftFree = true rightFree = true case 2: xPos = -0.87 leftFree = true middleFree = true default: break } } else if doSkipXPos == false { //Picks a # 0 or 1 for obstacles that cover two spaces let xHalfPosPicker = Int(arc4random_uniform(2)) switch xHalfPosPicker { case 0: xPos = 0.8 obstacleGenerator!.eulerAngles = SCNVector3(1.5707964,4.712389,0) rightFree = true case 1: xPos = -0.8 obstacleGenerator!.eulerAngles = SCNVector3(1.5707964,-4.712389,0) leftFree = true default: break } } if skipAPick == false { obstacleGenerator?.position = SCNVector3Make(Float(xPos), Float(yPos), Float(generatorInterval)+Float(emptySpace)) obstacleGenerator!.name = "obstacle" obstacleGenerator!.physicsBody?.isAffectedByGravity = false obstacleGenerator!.physicsBody?.angularVelocity = SCNVector4(0,0,0,0) obstacleGenerator!.physicsBody?.damping = 1.0 obstacles.append(obstacleGenerator!) self.obstacleNodes.addChildNode(obstacleGenerator!) } //GROCERY GENERATOR let groceryPicker = Int(arc4random_uniform(UInt32(skipGroceryRarity))) switch groceryPicker { case 0: groceryGenerator = appleGroceryNode.clone() yGroceryPos = 0.165 case 0...99: skipGrocery = true default: break } //xPos Picker for Groceries if skipGrocery == false && (leftFree == true || middleFree == true || rightFree == true) { var isXPosDone = false while isXPosDone == false { //xPos Location Generator let xPosPicker = Int(arc4random_uniform(3)) switch xPosPicker { case 0: if leftFree == true { xGroceryPos = 0.87 isXPosDone = true } case 1: if middleFree == true { xGroceryPos = 0 isXPosDone = true } case 2: if rightFree == true { xGroceryPos = -0.87 isXPosDone = true } default: break } } } else { skipGrocery = true } //Finalizes whether the grocery is good to place if skipGrocery == false { groceryGenerator?.position = SCNVector3Make(Float(xGroceryPos), Float(yGroceryPos), Float(generatorInterval)+Float(emptySpace)) groceryGenerator!.name = "grocery" groceries.append(groceryGenerator!) groceryGenerator!.physicsBody?.angularVelocity = SCNVector4(0,0,0,0) groceryGenerator!.physicsBody?.mass = 0.001 groceryGenerator!.physicsBody?.isAffectedByGravity = false groceryGenerator!.physicsBody?.damping = 1 self.groceryNodes.addChildNode(groceryGenerator!) } } //Obstacles Degenerator if obstacles.count > 0 && obstacles[0].position.z < cartNode.position.z-3 { obstacles[0].removeFromParentNode() obstacles.removeFirst() } //Aisle Degenerator if aisles.count > 0 && aisles[0].position.z < cartNode.position.z-3 { aisles[0].removeFromParentNode() aisles.removeFirst() } //Grocery Degenerator if groceries.count > 0 && groceries[0].position.z < cartNode.position.z-3 { groceries[0].removeFromParentNode() groceries.removeFirst() } //Verifies map is fully loaded if doMapLoad == true { if Double(aisleInterval) >= Double(preloadDistance) { doMapLoad = false sceneView.overlaySKScene = SKScene(fileNamed: "GameSceneOverlay") cartNode.physicsBody?.velocity=SCNVector3(0,0,cartSpeed) globalVariables.isRunning = true } } //Cart, Camera, score, and light speed and position if globalVariables.isRunning == true && isUserStopped == false { let moveX = (cartNode.position.x - cameraNode.position.x) * 0.15 let moveZ = (cartNode.position.z-1.4 - cameraNode.position.z) cameraNode.position.x += moveX cameraNode.position.z += moveZ omniLightNode.position = SCNVector3(0,4,cartNode.position.z-3) // Dummy Move that forces the camera to update let updateMoves = SCNAction.moveBy(x: 0, y: 0, z: 0, duration: 0) cartNode.runAction(updateMoves) } } public override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Release any cached data, images, etc that aren't in use. } } https://stackoverflow.com/questions/66717313/swift-scenekit-node-speed-randomly-changes-why-is-it-lagging March 20, 2021 at 08:59AM
没有评论:
发表评论