Flatiron School Week 7 – BocceGame built with UIDynamicAnimator

UIKit Dynamics

In my spare time, I wanted to look at animating physics collisions so I found UI Dynamics. It’s a part of UIKit that has some build-in functionality to animate physics based interactions. It’s generally not used for games, but rather for transitions and cool UI tricks.

A bocce game is a simple way to demonstrate this functionality. In bocce, there’s a target ball called the jack and two teams try to get their ball as close to the jack as possible. The team gets points for the number of balls that are closer to the jack than the nearest opponent ball.

Bocce Game Demo

Github Repo

How to use UIDynamicAnimator

You have an animator.

Animators can add behaviors.

Behaviors can add items that they affect.

My animator

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

One behavior is the UIDynamicItemBehavior behavior that allows the ball to continue moving after released by the hand with the add

self.linearVelocity = [[UIDynamicItemBehavior alloc] init];
[self.linearVelocity addLinearVelocity:velocity forItem:gesture.view];
[self.animator addBehavior:self.linearVelocity];

Another behavior is the collision behavior that collides with either a wall or another item.

//initialize collision behavior
self.collision = [[UICollisionBehavior alloc] init];
[self.animator addBehavior:self.collision];

How to make this game

Dragging a ball

Set up a UIPanGestureRecognizer that calls the dragged: method.

    UIPanGestureRecognizer *draggable = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragged:)];
    [self.currentBlock addGestureRecognizer:draggable];

The dragged method has the form:

- (void) dragged:(UIPanGestureRecognizer *)gesture
{
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
//save the starting location
break;

case UIGestureRecognizerStateChanged:
{
CGPoint translation = [gesture translationInView:self.view];
 
//calculate the new location with the starting location and the translation
//set the location to the new location
 
break;
}
 
case UIGestureRecognizerStateEnded:
{
 
//figure out the velocity when the ball is released and add it to the item with the UIDynamicItemBehavior
}
 
break;
}
 
default:
break;
}

}
}

Continue a ball moving after release

Right now, the ball would stop when I let go of it. To keep it going at the same speed, get the velocity in view from the gestureRecognizer and add it to the UIDynamicItemBehavior with addLinearVelocity: forItem:

//Inside the case UIGestureRecognizerStateEnded
CGPoint velocity = [gesture velocityInView:self.view];
[self.linearVelocity addLinearVelocity:velocity forItem:gesture.view];
[self.animator addBehavior:self.linearVelocity];
 
//necessary to make the motion smooth, otherwise it will be jumpy if you hold down the 
[self.animator updateItemUsingCurrentState:gesture.view];

Stopping the ball

The ball will keep going on for a long time, so I needed to stop it somehow.

Stop the ball by adding friction. In UIDynamicItemBehavior, friction is the resistive force between two objects. I wanted resistance to simulate air resistance. Angular resistance was needed to stop the balls from spinning too.

self.linearVelocity.resistance = 8;
self.linearVelocity.angularResistance = 8;

Only allow one animation to run at a time

I found that trying to flick a ball before another had finished moving was making a really jumpy animation, so checked if the animation is running with each gesture recognized and only let the new block move after the other was finished moving.

- (void) dragged:(UIPanGestureRecognizer *)gesture
{
    if (![self.animator isRunning]) {
        switch (gesture.state) {
...

Detecting when the ball was stopped

To find out when to make the next block, I needed to know when the ball had stopped moving. I used the

- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator

method. It’s a delegate method so I had to set the

    self.animator.delegate = self;

Making the target ball bounce off the walls

I didn’t want the target ball to go off the screen, so I set the screen boundaries as walls,

[self.collision setTranslatesReferenceBoundsIntoBoundary:YES];

made the ball bound off all objects including boundaries (referenceBounds being those of self.view because the animator has self.view as the reference view),

[self.collision setCollisionMode:UICollisionBehaviorModeEverything];

and added another invisible wall, so that the ball would not end up on the top edge of the screen.

[self.collision addBoundaryWithIdentifier:@"end" fromPoint:CGPointMake(0, self.view.frame.size.height*0.1) toPoint:CGPointMake(self.view.frame.size.width, self.view.frame.size.height*0.1)];

Take the target ball out of collision

I wanted to make the target ball immovable, so I just had to remove it as an item of the collision.

[self.collision removeItem:self.currentBlock];

I also wanted future object to be able to go off the screen, so I removed the “end” boundary and the window boundaries.

[self.collision removeBoundaryWithIdentifier:@"end"];
[self.collision setTranslatesReferenceBoundsIntoBoundary:NO];

Ignoring most the call to dynamicAnimatorDidPause after a ball is made

After a new object is added, dynamicAnimatorDidPause is called, so I had to add a BOOL property justMadeBlock to ignore those calls.

Stop the moving block when it goes offscreen

If a ball was flicked too hard, then it would go off the screen and I would have to wait for it to stop before the next ball would load. To shorten that time, I made another boundary outside the edge of the screen and overwrote a delegate method to make the ball stop moving and not able to collide with other blocks.

[self.collision addBoundaryWithIdentifier:@"outOfBounds" forPath:[UIBezierPath bezierPathWithRect:CGRectMake(-90, -90, self.view.frame.size.width+180, self.view.frame.size.height+180)]];

Delegate method for the UICollisionBehavor

self.collision.collisionDelegate = self;
- (void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
    if ([((NSString *)identifier) isEqualToString:@"outOfBounds"]) {
        //not allow it to move
        [self.linearVelocity removeItem:item];
        //not allow it to interact with future blocks
        [self.collision removeItem:item];
    }
}

Calculate the nearest ball and marking the nearest ones

Once a colored ball had been thrown, there is now a winning ball (closest to the target).

I took all the balls on screen.

    NSArray *blocksOnScreen = [self.collision items];

Got their locations

Sorted the array of balls by location

And found the balls of the same color that were closest to the target ball

With those “winning balls”, I added a CALayer to them to mark them as winning.

The next to go would be the team further away from the target.

At the end of the game, a UIAlertController asks if you want to play again.

Adding indicators for the number of balls remaining per team

I used CAShapeLayers and added them to the self.view.layer. They would update as balls were made.

Adding a flashing turnIndicator

The alpha controls the opacity of the text label, so setAlpha:1.0 means make it appear.

UIViewAnimationOptionAutoreverse makes it disappear again.
UIViewAnimationOptionRepeat makes the animation keep going until you stop it.
UIViewAnimationOptionAllowUserInteraction allows you to remove the animation later on.

[UIView animateWithDuration:1.5 delay:0 options:UIViewAnimationOptionAutoreverse| UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction  animations:^{
        [self.turnIndicator setAlpha:1.0];
    } completion:nil];

Stopping animations

When I tried to reset the game, setting all the properties back to nil worked well, except for stopping the turnIndicator animation.

There I found that the animation is in the layer of the animated object.

[self.turnIndicator.layer removeAllAnimations];

Choosing a more attractive color scheme

Adobe’s color wheel was really cool. I like pastel colors, so this is what I chose.

Notes to Self

Writing code takes way longer than expected

I didn’t think that it would take very long to make this, (just slap some blocks on a screen right?) but building features takes time and focus. Really quickly, seeing jumpy animations makes you want to fix them and that’s the 20% that takes the most time. Writing blogs about code also takes hours…

User testing is super important to do throughout

I knew how the game was played, but it wasn’t obvious to people I showed it to. I really wouldn’t have known if I hadn’t had someone use the game.

Don’t let sidetracked on super advanced features when the basics are not done

The flip side of asking for feedback is that people start giving you really good suggestions, but I think you have to finish one phase before hacking away at the next thing.

Hacking is fun with something quick, but it’s no fun with a big project

This is the second project that I’ve hacked to together. It’s been fun, but I really would have liked to have classes for the data model. This game was made over three weekends and I was finding that when I wanted to add new features, I had to recall the mental model that I had for the project. Instead I should have had a game class that remembered the model for me.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s