SpriteKit physics in Swift - Ball slides against wall instead of reflecting

后端 未结 6 572
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-01 12:36

I have been creating my own very simple test game based on Breakout while learning SpriteKit (using iOS Games by Tutorials by Ray Wenderlich et al.) to see if I can apply co

6条回答
  •  没有蜡笔的小新
    2020-12-01 13:16

    The problem is twofold in that 1) it will not be solved by altering friction/restitution of the physics bodies and 2) will not be reliably addressed by a return impulse in the renderer() loop due to the contact occurring after the body has already begun decelerating.

    Issue 1: Adjusting physics properties has no effect -- Because the angular component of the collision is below some predetermined threshold, the physics engine will not register it as a physical collision and therefore, the bodies will not react per the physics properties you've set. In this case, restitution will not be considered, regardless of the setting.

    Issue 2: Applying an impulse force when the collision is detected will not produce consistent results -- This is due to the fact that in order to simulate restitution, one needs the velocity of the object just prior to impact.

    -->For instance, if an object hits the floor at -10m/s and you want to simulate 0.8 restitution, you would want that object to be propelled 8m/s in the oppostie direction.

    Unfortunately, due to the render loop, the velocity registered when the collision occurs is much lower since the object has already decelerated.

    -->For example, in the simulations I was running, a ball hitting a floor at a low angle was arriving at -9m/s, but the velocity registered when the collision was detected was -2m/s.

    This is important since in order to create a consistent representation of restitution, we must know the pre-collision velocity in order to arrive at our desired post-collision velocity...you can't ascertain this in the Swift collision callback delegate.

    Solution: Step 1. During the render cycle, record the velocity of the object.

    //Prior to the extension define two variables:
    var objectNode : SCNNode!
    var objectVelocity : SCNVector3!
    
    //Then, in the renderer delegate, capture the velocity of the object
    extension GameViewController: SCNSceneRendererDelegate 
    {
        func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval)
        {
        if objectNode != nil {
            //Capture the object's velocity here, which will be saved prior to the collision
            if objectNode.physicsBody != nil {          
               objectVelocity = objectNode.physicsBody!.velocity
            }
         }
        }
    }
    

    Step 2: Apply a return impulse when the object collides, using the velocity saved prior to the collision. In this example, I am only using the y-component since I am simulating restitution in that axis.

    extension GameViewController: SCNPhysicsContactDelegate {
    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
    
      let contactNode: SCNNode!
    
      //Bounceback factor is in essence restitution.  It is negative signifying the direction of the vector will be opposite the impact
      let bounceBackFactor : Float! = -0.8
    
      //This is the slowest impact registered before the restitution will no longer take place
      let minYVelocity : Float! = -2.5
    
      // This is the smallest return force that can be applied (optional)
      let minBounceBack : Float! = 2.5
    
      if contact.nodeA.name == "YourMovingObjectName" && contact.nodeB.name == "Border" {
        //Using the velocity saved during the render loop
        let yVel = objectVelocity.y
        let vel = contact.nodeA.physicsBody?.velocity
        let bounceBack : Float! = yVel * bounceBackFactor
    
        if yVel < minYVelocity
        {
          // Here, the opposite force is applied (in the y axis in this example)
          contact.nodeA.physicsBody?.velocity = SCNVector3(x: vel!.x, y: bounceBack, z: vel!.z)
        }
      }
    
      if contact.nodeB.name == "YourMovingObjectName" && contact.nodeA.name == "Border" {
        //Using the velocity saved during the render loop
        let yVel = objectVelocity.y
        let vel = contact.nodeB.physicsBody?.velocity
        let bounceBack : Float! = yVel * bounceBackFactor
    
        if yVel < minYVelocity
        {
          // Here, the opposite force is applied (in the y axis in this example)
          contact.nodeB.physicsBody?.velocity = SCNVector3(x: vel!.x, y: bounceBack, z: vel!.z)
        }
      }
      }
    }
    

提交回复
热议问题