为什么绑定物理体的SKSpriteNode移动后会出现橡皮筋式弹跳现象?
场景说明
我在屏幕底部下方创建了一个greyBar节点,随后将greyBar向上移动到border上方位置。移动动作完成后,我会修改greyBar的碰撞掩码,使得重力生效将greyBar下拉时,border可以承接住greyBar。
逻辑整体运行正常,但当我将移动动作的时长从1.0调整为10.0后,移动动作结束时greyBar会直接向上弹出屏幕,并非碰撞到borderBar后反弹,而是在移动结束的位置直接“向上跳”,表现类似橡皮筋拉伸后回弹的效果,弹出屏幕后才会在重力作用下回落。
我暂未理解该向上弹跳现象的触发原因,这是我编写的greyBar移动逻辑的全部代码,动作时长较短时不会出现这类剧烈弹跳的问题,有人知道该“弹跳”现象的成因吗?
相关代码
greyBar.physicsBody = SKPhysicsBody(rectangleOf: size, center: CGPoint(x: size.width/2, y: size.height/2)) greyBar.physicsBody!.isDynamic = true greyBar.physicsBody!.affectedByGravity = true greyBar.physicsBody!.restitution = 0.3 let xConstraint = SKConstraint.positionX(SKRange(constantValue: (safeSceneRect.width/2) - (greyBar.size.width/2))) greyBar.constraints = [xConstraint] let moveAction = SKAction.move(to: CGPoint(x: (safeSceneRect.width/2) - (greyBar.size.width/2), y: (myBase.size.height)! + (myBorder.size.height)! + 5), duration: 1.0) greyBar.run(moveAction, completion: { greyBar .physicsBody?.collisionBitMask = bodyMasks.borderMask.rawValue|bodyMasks.greyBarMask.rawValue})
示意图

补充信息
我已经找到了问题成因和临时解决方案,但还没有找到最优解:
border节点的碰撞掩码从一开始就设置了greyBar的标识,看起来greyBar慢速穿过border的过程中会累积大量动能,穿过的速度越慢,累积的动能越大。
我当前的临时解决方案如下:
* All collision bit masks set to blank * set greyBar to not be affected by gravity * when done moving greyBar collision bit masks set to border mask border collision bit masks set to greyBar * set greyBar to be affected by gravity
这个方案感觉不够简洁规范,有没有专门的指令可以清除物理体累积的动能?
问题本质
这个弹跳现象是SpriteKit物理引擎和SKAction显式移动冲突导致的:
移动过程中greyBar的isDynamic始终为true,物理引擎会实时检测碰撞,你用移动动作强行把greyBar推入和border重叠的位置,物理引擎为了修正碰撞体重叠的异常状态,会持续给greyBar施加排斥力。移动速度越慢,碰撞体重叠的时间越长,累积的排斥力对应的动能就越大,移动结束放开碰撞规则的瞬间,这部分累积的动能就会一次性释放,出现弹射效果。
最优解决方案
SpriteKit提供了直接清空物理体动能的接口,你只需要在移动完成的回调里,修改碰撞掩码前先清空速度即可:
greyBar.run(moveAction, completion: { // 清空所有累积的线性动能和旋转动能 greyBar.physicsBody?.velocity = .zero greyBar.physicsBody?.angularVelocity = 0 // 再设置碰撞掩码 greyBar.physicsBody?.collisionBitMask = bodyMasks.borderMask.rawValue | bodyMasks.greyBarMask.rawValue })
如果想彻底避免移动过程中物理引擎的无效计算,还可以在移动前临时关闭物理动态属性,移动完成后再开启,连清空速度的步骤都可以省略:
// 移动前关闭物理模拟,避免碰撞检测产生额外动能 greyBar.physicsBody!.isDynamic = false greyBar.run(moveAction, completion: { // 移动完成后重新开启物理模拟 greyBar.physicsBody!.isDynamic = true greyBar.physicsBody?.collisionBitMask = bodyMasks.borderMask.rawValue | bodyMasks.greyBarMask.rawValue })
这个方案比临时修改碰撞掩码、开关重力的写法更简洁,也符合SpriteKit的官方开发规范。
内容的提问来源于stack exchange,提问作者LuisC329

