Accessing the number of elements in an array and applying gravity behaviour

115 Views Asked by At

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!

2

There are 2 best solutions below

6
On

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

1
On

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.