Swift中的SpriteKit物理 - Ball滑向墙壁而不是反射

时间:2014-12-27 20:56:29

标签: swift sprite-kit box2d game-physics

我一直在创建我自己非常简单的基于Breakout的测试游戏,同时学习SpriteKit(使用Ray Wenderlich等教授的iOS游戏)来看看我是否可以运用我学到的概念。我决定通过使用.sks文件来创建精灵节点并用物理实体替换我的手动边界检查和碰撞来简化我的代码。

然而,我的球在任何时候以陡峭的角度与它们碰撞时,都会保持平行于墙壁/其他矩形(例如,只是向上和向下滑动)。这是相关的代码 - 我已将物理主体属性移动到代码中以使它们更加可见:

import SpriteKit

struct PhysicsCategory {
  static let None:        UInt32 = 0      //  0
  static let Edge:        UInt32 = 0b1    //  1
  static let Paddle:      UInt32 = 0b10   //  2
  static let Ball:        UInt32 = 0b100  //  4
}

var paddle: SKSpriteNode!
var ball: SKSpriteNode!

class GameScene: SKScene, SKPhysicsContactDelegate {

  override func didMoveToView(view: SKView) {

    physicsWorld.gravity = CGVector.zeroVector

    let edge = SKNode()
    edge.physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
    edge.physicsBody!.usesPreciseCollisionDetection = true
    edge.physicsBody!.categoryBitMask = PhysicsCategory.Edge
    edge.physicsBody!.friction = 0
    edge.physicsBody!.restitution = 1
    edge.physicsBody!.angularDamping = 0
    edge.physicsBody!.linearDamping = 0
    edge.physicsBody!.dynamic = false
    addChild(edge)

    ball = childNodeWithName("ball") as SKSpriteNode
    ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.size))
    ball.physicsBody!.usesPreciseCollisionDetection = true
    ball.physicsBody!.categoryBitMask = PhysicsCategory.Ball
    ball.physicsBody!.collisionBitMask = PhysicsCategory.Edge | PhysicsCategory.Paddle
    ball.physicsBody!.allowsRotation = false
    ball.physicsBody!.friction = 0
    ball.physicsBody!.restitution = 1
    ball.physicsBody!.angularDamping = 0
    ball.physicsBody!.linearDamping = 0

    physicsWorld.contactDelegate = self
}

之前忘了提这个,但我添加了一个简单的touchesBegan函数来调试弹跳 - 它只是调整速度以将球指向触点:

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
  let touch = touches.anyObject() as UITouch
  let moveToward = touch.locationInNode(self)
  let targetVector = (moveToward - ball.position).normalized() * 300.0
  ball.physicsBody!.velocity = CGVector(point: targetVector)
}

normalized()函数只是将球/触摸位置增量减少为单位矢量,并且有一个允许CGPoint减法的减号运算符的覆盖。

球/边缘碰撞应始终以精确相反的角度反射球但由于某种原因,球似乎确实有一个直角的东西。我当然可以实现一些解决方法来手动反映球的角度,但重点是我想要使用SpriteKit中的内置物理功能来完成所有这些。有什么明显的东西我不见了吗?

6 个答案:

答案 0 :(得分:8)

这似乎是碰撞检测的问题。大多数人通过使用didBeginContact并在相反方向上重新施加力来找到解决方案。请注意,他说didMoveToView,但在稍后对didBeginContact的评论中纠正了自己。

请参阅Ray Wenderlich教程here

底部的评论
  

我已经解决了球的问题"骑着铁路"如果它   以较小的角度击打(@ aziz76和@colinf)。我又添了一个   类别," BorderCategory"并将其分配到边界PhysicsBody   我们在didMoveToView中创建。

和类似的问题here解释了为什么会这样。

  

即使你这样做,也有许多物理引擎(包括   SpriteKits因为这样的情况而遇到麻烦   浮点舍入误差。我发现当我需要身体的时候   碰撞后保持恒定速度,最好强制它 -   使用didEndContact:或didSimulatePhysics处理程序来重置移动   身体的速度因此它的速度与之前相同   碰撞(但方向相反)。

我注意到的另一件事是你正在使用方形而不是圆形的球,你可能想考虑使用...

ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)

所以事实证明你并不疯狂,总是很高兴听到别人的意见,希望这会帮助你找到最适合你应用的解决方案。

答案 1 :(得分:7)

我提出了一个令人惊讶的临时解决方案。只需在边界对面施加一个非常小的冲动。您可能需要根据系统中的质量更改strength

func didBeginContact(contact: SKPhysicsContact) {

    let otherNode = contact.bodyA.node == ball.sprite ? contact.bodyB.node : contact.bodyA.node

    if let obstacle = otherNode as? Obstacle {
        ball.onCollision(obstacle)
    }
    else if let border = otherNode as? SKSpriteNode {

        assert(border.name == "border", "Bad assumption")

        let strength = 1.0 * (ball.sprite.position.x < frame.width / 2 ? 1 : -1)
        let body = ball.sprite.physicsBody!
        body.applyImpulse(CGVector(dx: strength, dy: 0))
    }
}

实际上,这不应该是必要的,因为正如问题中所描述的那样,无摩擦,完全弹性的碰撞规定,无论碰撞角度有多小,球都应该通过反转x速度(假设侧边界)来反弹。 / p>

相反,游戏中发生的事情就好像精灵套件如果小于某个值就会忽略X速度,使得球在没有反弹的情况下滑向墙壁。

最后的注释

在阅读thisthis之后,我明白真正的答案是你所拥有的任何严肃的物理游戏,你应该使用Box2D。你从迁移中得到了太多的好处。

答案 2 :(得分:1)

当两个方向的速度都很小时,似乎只会出现这个问题。然而,为了减少效果,可以降低physicsWorld的速度,例如,

physicsWorld.speed = 0.1

然后增加physicsBody的速度,例如,

let targetVector = (moveToward - ball.position).normalized() * 300.0 * 10
ball.physicsBody!.velocity = CGVector(point: targetVector)

答案 3 :(得分:1)

在下面添加代码:

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;

@ManagedBean
   public class DbConnect {

        public List<String> completeArea(String query1) {

            ResultSet rs;
            Statement st;
            Connection con;
            PreparedStatement pst;
            List<String> result = new ArrayList<String>();
            try {
                Class.forName("com.mysql.jdbc.Driver");
                con = DriverManager.getConnection("jdbc:mysql://localhost/company", "root", "");

                try {
                    query1 = "select * from labels";
                    pst = con.prepareStatement(query1);
                    rs = pst.executeQuery();
                    while (rs.next()) {
                        result.add(rs.getString("name"));
                    }
                } catch (Exception ex) {
                    System.out.println(ex);
                }
            } catch (Exception ex) {
                System.out.println("error occured" + ex);
            }
            System.out.println("size is " + result.size());
            return result;`
        }
    }

,当球与墙碰撞时,它会反弹。 恢复是物理物体的弹跳,因此将其设置为1会使弹回。

答案 4 :(得分:0)

我看到完全相同的问题,但对我的修复与其他答案中提到的碰撞检测问题无关。结果我使用永远重复的SKAction将球设置为运动。我最终发现这与SpriteKit的物理模拟冲突导致节点/球沿着墙壁行进而不是从它上面反弹。

我假设重复的SKAction继续被应用并覆盖/与物理模拟自动调整球的physicsBody.velocity属性相冲突。

解决这个问题的方法是通过在velocity属性上设置physicsBody来将球设置为运动。一旦我这样做,球开始正确弹跳。我猜测通过physicsBody使用力和冲动来操纵它的位置也会有效,因为它们是物理模拟的一部分。

我花了很多时间来实现这个问题,所以我在这里发布这个以防万一我可以节省其他人的时间。谢谢0x141e!你的comment把我(和我的球)放在了正确的道路上。

答案 5 :(得分:0)

问题是双重的,因为1)无法通过改变物理物体的摩擦/恢复来解决; 2)由于在接触后发生接触,renderer()循环中的返回脉冲不能可靠地解决该问题。车身已经开始减速。

问题1:调整物理属性无效- 由于碰撞的角度分量低于某个预定阈值,因此物理引擎不会将其记录为物理碰撞,因此,物体将不会根据您设置的物理属性进行反应。在这种情况下,无论设置如何,都不会考虑恢复原状。

问题2:在检测到碰撞时施加脉冲力不会产生一致的结果 –这是由于为了模拟恢复原状,人们仅需要物体的速度即可 影响之前。

->例如,如果某个物体以-10m / s的速度落地,并且您想模拟0.8的恢复,则您希望该物体在对立方向上被推进8m / s。

不幸的是,由于渲染循环,发生碰撞时记录的速度要低得多,因为对象已经已经减速

->例如,在模拟中,我正在跑步时,一个低角度击中地板的球达到了-9m / s,但是检测到碰撞时记录的速度为-2m / s

这很重要,因为为了创建一致的恢复表示,我们必须知道碰撞前的速度才能达到所需的碰撞后速度...您无法在Swift碰撞回调中确定这一点委托。

解决方案: 步骤1.在渲染周期中,记录对象的速度。

//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
        }
     }
    }
}

第2步:使用碰撞之前保存的速度,在物体碰撞时施加返回脉冲。在此示例中,我仅使用y分量,因为我正在沿该轴模拟恢复。

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)
    }
  }
  }
}