macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Animating Graphics in Cocoa, Part 1
Pages: 1, 2, 3, 4, 5

The Animation

Recall that when we set up our timer, we arranged it such that a method of our custom NSView called stepAnimation was invoked every time the timer fired. The contents of this method are the subject of this section. At this point in our application’s implementation, we have created a bezier path that is the ball we wish to animate moving around. We have also created a timer object which will provide us a way of timing the animation. Most importantly, we have created an NSAffineTransform object that will be used to move the ball around.



The last thing we have to do here is to move the ball every time the method stepAnimation: is called by the timer. What we want to do is apply the affine transformation to the bezier path. After we have done that, we simply tell the view that the contents of the view have changed, and that it needs to be redrawn. To accomplish these things we require only two lines of code. Add to the class implementation the following definition of the method stepAnimation::


- (void)stepAnimation:(NSTimer *)timer
{
    [ball transformUsingAffineTransform:at];
    [self setNeedsDisplay:YES];
}

Here, we change the bezier path into the translated path using the method transformUsingAffineTransform:, the argument of which is our transformation, at. What this method does is actually change the bezier path according to the transformation indicated in the argument. Thus, for the translation of x by 1 and y by 1 that we discussed earlier, 1 would be added to each coordinate of every point in the bezier path. In effect, the ball is moved up and to the right by 45 degrees and a distance of approximately 1.4 pixels. After changing the path, we tell the view that it is time to redraw, and the ball is drawn at the new location. This operation is repeated continuously, and we experience the sight of a ball moving across the screen.

With this code, you should be ready to compile and go and see the fruits of your labor. I imagine, however, that your enchantment will persist as long as it takes for the ball to disappear from the view. To remedy this, I offer you in the next section a way to make the ball bounce of the sides of the view bounds and remain in view.

Bouncing

To implement bouncing behavior we don’t have to do much. All we need to do is to check if the ball is crossing the bounds of the view, and if so, change the sign of dx or dy to create the appropriate horizontal or vertical bounce. What we’ll first do is make our code clean from the start by creating a method called checkCollision, which will contain the logic to enable bouncing. This method is called in the stepAnimation method between the two existing lines:


- (void)stepAnimation:(NSTimer *)timer
{
    [ball transformUsingAffineTransform:at];
    [self checkCollision];
    [self setNeedsDisplay:YES];
}

So what do we do in -checkCollision? Simple: the ball has a bounding NSRect that can be accessed by sending a -bounds message to the bezier path. This method returns an NSRect that provides us with the location and size of the ball in our view -— both crucial pieces of information. The idea is to check whether a the top edge of the ball is above the top edge of the view, or if the bottom edge of the ball is below the bottom of the view. If either of these conditions is true, we reverse the direction of the y translation; that is, we multiply dy by –1. Thus, if the ball was going up, it would go down when it hits the top. If it was going down, it would go up when it hits the bottom.

Similarly, we do the same for the x direction of motion. If the left edge of the ball goes beyond the left edge of the view, we change the sign on dx. If the right edge of the ball goes beyond right edge of the view, again we change the sign of dx. In all of these cases, we must take a further step: we must change the affine transformation to reflect the new values of dx and dy we get as a result of bouncing. This is easily done by releasing the object previously assigned to at, creating a new affine transformation, and then creating a translation transformation.

Let's look at our method checkCollission to see how all of this fits together.


- (void)checkCollision
{
    NSRect ballRect = [ball bounds];
    NSRect viewRect = [self bounds];

    if ( ballRect.origin.y < viewRect.origin.y || 
	 (ballRect.origin.y + ballRect.size.height) > viewRect.size.height ) {
	dy = -dy;

	[at release];
	at = [[NSAffineTransform transform] retain];
	[at translateXBy:dx yBy:dy];
    } else if ( ballRect.origin.x < viewRect.origin.x ||
                (ballRect.origin.x + ballRect.size.width) > viewRect.size.width ) {
	dx = -dx;

	[at release];
	at = [[NSAffineTransform transform] retain];
	[at translateXBy:dx yBy:dy];
    }
}

You see in the first two lines that we access the rectangles that define both the bounds of the ball and the bounds of the view. Information about the location and sizes of these two rectangles will prove most important to accomplishing our task. The rest of the method is composed of two logically identical if statements.

In the first, we check to see if the bottom of the ball is below the bottom of the view or if the top edge of the ball is above the top of the view. If so, we set dy equal to negative dy, release at, create a new affine transform, and use our new value of dy to define a new translation for at. The second if statement is no different, except that it applies in the horizontal direction.

With this simple method, we are able to keep the ball in the view indefinitely, and you can stare in wonder at the ball bouncing in the view. Compile it and check it out! If you're interested, you can download my project folder here.

Issues of User Interaction

What we're going to do now is add two sliders to our application’s interface. One slider controls the speed in the x direction, and the other the speed in the y direction. Effectively, the speed in the x and y directions is controlled by changing the magnitude of dx and dy, within the AnimationView object. Rather than having AnimationView handle the action methods of the sliders, we will implement a controller class to interface the AnimationView with the slider controls. Let's go into Interface Builder and add these sliders and set up the controller object.

Pages: 1, 2, 3, 4, 5

Next Pagearrow