1

I'm having issues with getting ALL elements of an array to fall using the Gravity module. I have managed to get the LAST element in the array to fall and then the remaining elements just stay at the top of the screen during testing. Upon debugging

I am using UIKit and want to understand this language thoroughly before using other various engines such as SpriteKit and GameplayKit.

func mainGame()
    {
    let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
    var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
    for i in 1...random2
        {
        let image = UIImage(named: cars[i - 1])
        let carView = UIImageView(image: image!)
        carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
        view.addSubview(carView)
        dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
        gravityBehavior = UIDynamicItemBehavior(items: [carView]) //cars falling
        dynamicAnimator.addBehavior(gravityBehavior)
        collisionBehavior = UICollisionBehavior(items: [carView, mainCar]) //collide
        collisionBehavior.translatesReferenceBoundsIntoBoundary = false
        gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
        dynamicAnimator.addBehavior(collisionBehavior)

        }
    collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
    collisionBehavior.removeAllBoundaries()
    }

With the game so far the last car in the array falls and the main player car that I control has collision behaviour, which is a big step for me!

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
  • While it's a worthy goal to learn from the bottom up, UIKitDynamics (which includes UIDynamicAnimator) is very unlike SpriteKit and is not a foundational technology. UIKitDynamics is very awkward (and frankly it's designed backwards IMO). It hasn't been updated much since it's initial introduction, isn't widely used, and is poorly documented. If you want to learn SpriteKit, I recommend just jumping straight to SpriteKit. That said, if you want some non-trivial examples (including gravity), see https://github.com/iosptl/ios7ptl/tree/master/ch19-dynamics – Rob Napier Feb 27 '18 at 21:48
  • I wrote a lot about UIKitDynamics when it first came out. I'm not aware of a lot of other materials on it. If it does interest you, see https://www.amazon.com/iOS-Programming-Pushing-Limits-Applications/dp/1118818342 (which is where the sample code comes from). – Rob Napier Feb 27 '18 at 21:49

2 Answers2

1

You are creating a new UIDynamicAnimator with each iteration of the loop and assigning it to dynamicAnimator. That is why only the last element is working, because it is the last one assigned to that variable.

To fix it, just move this line to somewhere that would only be called once.

dynamicAnimator = UIDynamicAnimator(referenceView: self.view)

viewDidLoad is a possible place that should work.

Craig Siemens
  • 12,942
  • 1
  • 34
  • 51
  • Thanks for the quick reply. Even with the line suggested out of the loop the last element of th array falls where the others stay static. Any other suggestions? –  Feb 27 '18 at 20:46
0

UIKitDynamics is backwards of most similar frameworks. You don't animate the object. You have an animator and attach objects to it. As Clever Error notes, you only want one animator in this case.

The key point is that you don't attach gravity to cars; you attach cars to behaviors (gravity), and then behaviors to the animator. Yes, that's bizarre and backwards.

I haven't tested this, but the correct code would be closer to this:

func mainGame()
    {
    let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
    var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1

    var carViews: [UIImageView] = []
    dynamicAnimator = UIDynamicAnimator(referenceView: self.view)

    // First create all the views
    for i in 1...random2
        {
        let image = UIImage(named: cars[i - 1])
        let carView = UIImageView(image: image!)
        carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
        view.addSubview(carView)
        carViews.append(carView)
        }

    // and then attach those to behaviors:

    gravityBehavior = UIGravityBehavior(items: carViews) //cars falling
    dynamicAnimator.addBehavior(gravityBehavior)

    collisionBehavior = UICollisionBehavior(items: carView + mainCar) //collide
    collisionBehavior.translatesReferenceBoundsIntoBoundary = false
    dynamicAnimator.addBehavior(collisionBehavior)

    collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
    collisionBehavior.removeAllBoundaries()

    // You don't need this; it's built into Gravity
    // gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
    }

The main way that UIKitDynamics is different than most animation frameworks is that things that are animated don't know they're being animated. You can't ask a car what behaviors it has, because it doesn't have any. A UIDynamicAnimator basically is a timing loop that updates the center and transform of its targets. There's really not anything fancy about it (in contrast to something like Core Animation which has many fancy things going on). With a little iOS experience, you could probably implement all of UIKitDynamics by hand with a single GCD queue (it probably doesn't even need that, since it runs everything on main....)

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Hi I understand your method and logic here but I have an issue with a couple of errors that this produced. When I attempt to attached the cars to the gravity behaviour and collision behaviour it throws. gravity: Cannot assign value of type 'UIGravityBehavior' to type 'UIDynamicItemBehavior!' and collision: Cannot invoke initializer for type 'UICollisionBehavior' with an argument list of type '(items: [UIImageView], UIImageView!)' –  Feb 28 '18 at 00:21
  • 1
    Yeah…I forgot about the difference between UIDynamicItemBehaviors and UIDynamicBehaviors. Look at https://github.com/iosptl/ios7ptl/blob/master/ch19-dynamics/Dynamics/Dynamics/ViewController.m. It's really close to what you're doing here. – Rob Napier Feb 28 '18 at 00:39
  • so the above answer isn't the format I should follow? I looked at the dynamics git but it looks too far off from how I wanted to design this. –  Feb 28 '18 at 00:48
  • The gravity section comes under "- (IBAction)gravity" where my cars are images which are generated in an array and not placed on the storyboard. The cars fall from the top of the screen at random times and continue respawning throughout throughout the game –  Feb 28 '18 at 00:58
  • You'll need to keep track of the gravity behavior in a property (I think you're already doing that), and add/remove cars to it as they appear or disappear. Alternately you can use a Push behavior rather than gravity, but still, you'll add the items to the behavior as they're created and remove them from the behavior when they go away. – Rob Napier Feb 28 '18 at 17:29
  • There's no concept in UIKitDynamics of an item having a behavior. The behavior has a list of items that it applies to. Removing a view does not automatically remove it from a behavior. This is weird and often awkward to use in practice. I am not a fan of UIKitDynamics… – Rob Napier Feb 28 '18 at 17:34