Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Beginning iOS5 Development.pdf
Скачиваний:
7
Добавлен:
09.05.2015
Размер:
15.6 Mб
Скачать

CHAPTER 19: Whee! Gyro and Accelerometer!

669

[self setNeedsDisplayInRect:CGRectUnion(currentImageRect, previousImageRect)];

}

- (void)update {

static NSDate *lastUpdateTime;

if (lastUpdateTime != nil) { NSTimeInterval secondsSinceLastDraw =

-([lastUpdateTime timeIntervalSinceNow]);

ballYVelocity = ballYVelocity + -(acceleration.y * secondsSinceLastDraw);

ballXVelocity = ballXVelocity + acceleration.x * secondsSinceLastDraw;

CGFloat xAcceleration = secondsSinceLastDraw * ballXVelocity * 500;

CGFloat yAcceleration = secondsSinceLastDraw * ballYVelocity * 500;

self.currentPoint = CGPointMake(self.currentPoint.x + xAcceleration, self.currentPoint.y + yAcceleration);

}

// Update last time with current time lastUpdateTime = [[NSDate alloc] init];

}

@end

The first thing to notice is that one of our properties is declared as @synthesize, yet we have implemented the mutator method for that property in our code. That’s OK. The @synthesize directive will not overwrite accessor or mutator methods that you write; it will just fill in the blanks and provide any methods that you do not write.

Calculating Ball Movement

We are handling the currentPoint property manually, since, when the currentPoint changes, we need to do a bit of housekeeping, such as making sure that the ball has not rolled off the screen. We’ll look at that method in a moment. For now, let’s look at the first method in the class, initWithCoder:.

Recall that when you load a view from a nib, that class’s init or initWithFrame: methods will never be called. Nib files contain archived objects, so any instances loaded from the nib will be initialized using the initWithCoder: method. If we need to do any additional initialization, we must do it in that method.

In this view, we do have some additional initialization, so we’ve overridden initWithCoder:. First, we load the ball.png image. Second, we calculate the middle of the view and set that as our ball’s starting point, and we set the velocity on both axes to 0.

self.image = [UIImage imageNamed:@"ball.png"];

self.currentPoint = CGPointMake((self.bounds.size.width / 2.0f) + (image.size.width / 2.0f), (self.bounds.size.height / 2.0f) +

www.it-ebooks.info

670

CHAPTER 19: Whee! Gyro and Accelerometer!

(image.size.height / 2.0f));

ballXVelocity = 0.0f; ballYVelocity = 0.0f;

Our drawRect: method couldn’t be much simpler. We just draw the image we loaded in initWithCoder: at the position stored in currentPoint. The currentPoint accessor is a standard accessor method. The setCurrentPoint: mutator is another story, however.

The first things we do in setCurrentPoint: is to store the old currentPoint value in previousPoint and assign the new value to currentPoint.

previousPoint = currentPoint; currentPoint = newPoint;

Next, we do a boundary check. If either the x or y position of the ball is less than 0 or greater than the width or height of the screen (accounting for the width and height of the image), then the acceleration in that direction is stopped.

if (currentPoint.x < 0) { currentPoint.x = 0; ballXVelocity = 0;

}

if (currentPoint.y < 0){ currentPoint.y = 0; ballYVelocity = 0;

}

if (currentPoint.x > self.bounds.size.width - image.size.width) { currentPoint.x = self.bounds.size.width - image.size.width; ballXVelocity = 0;

}

if (currentPoint.y > self.bounds.size.height - image.size.height) { currentPoint.y = self.bounds.size.height - image.size.height; ballYVelocity = 0;

}

TIP: Do you want to make the ball bounce off the walls more naturally, instead of just stopping? It’s easy enough to do. Just change the two lines in setCurrentPoint: that currently read ballXVelocity = 0; to ballXVelocity = - (ballXVelocity / 2.0);. And change the two lines that currently read ballYVelocity = 0; to ballYVelocity = - (ballYVelocity / 2.0);. With these changes, instead of killing the ball’s velocity, we reduce it in half and set it to the inverse. Now, the ball has half the velocity in the opposite direction.

After that, we calculate two CGRects based on the size of the image. One rectangle encompasses the area where the new image will be drawn, and the other encompasses the area where it was last drawn. We’ll use these two rectangles to ensure that the old ball is erased at the same time the new one is drawn.

CGRect currentImageRect = CGRectMake(currentPoint.x, currentPoint.y, currentPoint.x + image.size.width,

currentPoint.y + image.size.height);

www.it-ebooks.info

CHAPTER 19: Whee! Gyro and Accelerometer!

671

CGRect previousImageRect = CGRectMake(previousPoint.x, previousPoint.y, previousPoint.x + image.size.width,

currentPoint.y + image.size.width);

Finally, we create a new rectangle that is the union of the two rectangles we just calculated and feed that to setNeedsDisplayInRect: to indicate the part of our view that needs to be redrawn.

[self setNeedsDisplayInRect:CGRectUnion(currentImageRect, previousImageRect)];

The last substantive method in our class is update, which is used to figure out the correct new location of the ball. This method is called in the accelerometer method of its controller class after it feeds the view the new acceleration object. The first thing this method does is declare a static NSDate variable that will be used to keep track of how long it has been since the last time the update method was called. The first time through this method, when lastUpdateTime is nil, we don’t do anything because there’s no point of reference. Because the updates are happening about 60 times a second, no one will ever notice a single missing frame.

static NSDate *lastUpdateTime; if (lastUpdateTime != nil) {

Every other time through this method, we calculate how long it has been since the last time this method was called. We negate the value returned by timeIntervalSinceNow because lastUpdateTime is in the past, so the value returned will be a negative number representing the number of seconds between the current time and lastUpdateTime.

NSTimeInterval secondsSinceLastDraw = -([lastUpdateTime timeIntervalSinceNow]);

Next, we calculate the new velocity in both directions by adding the current acceleration to the current velocity. We multiply acceleration by secondsSinceLastDraw so that our acceleration is consistent across time. Tipping the phone at the same angle will always cause the same amount of acceleration.

ballYVelocity = ballYVelocity + -(acceleration.y * secondsSinceLastDraw);

ballXVelocity = ballXVelocity + acceleration.x * secondsSinceLastDraw;

After that, we figure out the actual change in pixels since the last time the method was called based on the velocity. The product of velocity and elapsed time is multiplied by 500 to create movement that looks natural. If we didn’t multiply it by some value, the acceleration would be extraordinarily slow, as if the ball were stuck in molasses.

CGFloat xAcceleration = secondsSinceLastDraw * ballXVelocity * 500;

CGFloat yAcceleration = secondsSinceLastDraw * ballYVelocity * 500;

Once we know the change in pixels, we create a new point by adding the current location to the calculated acceleration and assign that to currentPoint. By using self.currentPoint, we use that accessor method we wrote earlier, rather than assigning the value directly to the instance variable.

self.currentPoint = CGPointMake(self.currentPoint.x +

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]