2021年3月19日星期五

Swift Scenekit - Node speed randomly changes (why is it lagging?)

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

没有评论:

发表评论