从referencebounds视图边界添加碰撞检测来修复滑动问题



我正在创建一个具有随机圆圈在屏幕上随机弹跳功能的应用程序。我能够通过使用UIKitDynamics实现这一点。动画师的参考视图被设置为self.view(屏幕视图)。我将UICollisionBehavior添加到动画器中,并将UICollisionBehavior的translatesReferenceBoundsIntoBoundary属性设置为true,以便圆圈在屏幕上弹跳。

问题是:如果圆以一个陡峭的角度碰到边界,它最终会在一个角落上下或左右滑动。当前唯一的解决方案是将magnitude属性设置得非常高,但这会破坏外观,并且不是一个好的解决方案。经过调查,我看到许多关于同样问题的堆栈溢出文章,指出这是iOS自己的物理引擎的问题,以及他们如何处理浮点值。解决方案是用UICollisionBehaviorDelegate提供碰撞,并添加一个稍微偏移角度和/或速度的反弹。我不知道如何实现解决方案与UIKitDynamics。

我的问题是:

  1. 我如何在一个UICollisionDelegate中引用引用视图边界,考虑到碰撞行为函数需要一个边界标识符,或者2个不同的uiddynamics项,而translaterreferenceboundsintoboundary不是这两个。添加一个新的边界需要2 CGPoints或UIBezierPath,所以也许在屏幕的形式创建一个UIBezierPath ?我该怎么做呢?
  2. 在第一个问题解决之后,我该如何去抵消这个值,这样它就不会在一个角落里来回移动了?给出的解决方案包括使用SpriteKit中的applyImpulse,但我在UIKitDynamics中找不到类似的方法。最接近的是UIDynamicItemBehavior下的addAngularVelocityaddLinearVelocity

这是我的代码:

import UIKit
class ViewController: UIViewController, UICollisionBehaviorDelegate {
//    Reference for circles button in code, initilization of animator for circle movement
@IBOutlet var selfie: UIButton!
var animator: UIDynamicAnimator?
//    If the ball is touched trigger popup
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation  = touch!.location(in: self.view)
if self.selfie.layer.presentation()!.hitTest(touchLocation) != nil {
performSegue(withIdentifier: "toMessageView", sender: self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
animator = UIDynamicAnimator(referenceView: self.view)
//        Initial push for circles
let push = UIPushBehavior(items: [selfie], mode: .instantaneous)
push.magnitude = 3
push.angle = CGFloat.random(in: 0...2*CGFloat.pi)
animator?.addBehavior(push)
//        Add collision boundary around edges of the screen
let boundaries = UICollisionBehavior(items: [selfie])
boundaries.translatesReferenceBoundsIntoBoundary = true
boundaries.add
//        Add collision detection to circles, circles
boundaries.collisionDelegate = self
func collisionBehavior(_ behavior: UICollisionBehavior, endedContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?) {
//            1. Figure out how to reference boundary for collision
//            2. Figure out how to add bounce back to slightly offsetted angle and speed
}
animator?.addBehavior(boundaries)
//        Properties for circles
let circleProperties = UIDynamicItemBehavior(items: [selfie])
circleProperties.elasticity = 1
circleProperties.friction = 0
circleProperties.resistance = 0
circleProperties.density = 1
circleProperties.allowsRotation = false
animator?.addBehavior(circleProperties)
}
}

我算出来了。需要一点几何知识(单位圆)。解决这个问题的方法是正确的。你确实想要做一个collisionbehaviordelegate函数,这样当一个球最终沿着屏幕边缘滑动时,一个碰撞被检测到,一个小的动量将会把球推离边缘。您使用检测到碰撞的点来设置uiddynamicitem,使其在与碰撞相反的随机方向上反弹。球将永远不会以一个陡峭的角度击中外视图,也不会前后滑动。注意,这还包括将uiddynamicitems弹性更改为小于1,例如0.9下面是代码:

func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
let threshold: CGFloat = 1
var pushDirection: CGFloat = 0
// Collision detected on left side, set angle to be somewhere opposite
if p.x <= threshold {
pushDirection = CGFloat.random(in: degreesToRadians(degrees: 300)...degreesToRadians(degrees: 420))
// Collision detected on right side, set angle to be somewhere opposite
} else if p.x >= view.bounds.width - threshold {
pushDirection = CGFloat.random(in: degreesToRadians(degrees: 120)...degreesToRadians(degrees: 240))
// Collision detected on top, set angle to be somewhere opposite
} else if p.y <= threshold {
pushDirection = CGFloat.random(in: degreesToRadians(degrees: 210)...degreesToRadians(degrees: 330))
// Collision detected on bottom, set angle to be somewhere opposite
} else if p.y >= view.bounds.height - threshold {
pushDirection = CGFloat.random(in: degreesToRadians(degrees: 30)...degreesToRadians(degrees: 150))
}
if pushDirection != 0 {
let movement = UIPushBehavior(items: [item], mode: .instantaneous)
movement.magnitude = 0.4
movement.angle = pushDirection
animator?.addBehavior(movement)
}
}