BIT-101 [2003-2017]

Gravity Tutorial for iPhone Part 3


See this note.

Here in part three, we are going to do a few small improvements, and add in some gravity. Mid way through part two I realized that I had not set up a way for the ball’s color to get passed through for drawing. I could add another parameter to refresh to pass in a color, but it dawned on me, we might as well just pass in the ball instance itself, which has everything that’s needed.

Also, why not have the ball itself keep track of its rectangle area? It knows where it is and it knows its own radius, so it should calculate the rect based on these. Let’s tackle that first. Add a new method to Ball. First the declaration in Ball.h:

[c]#import <foundation/Foundation.h>

@interface Ball : NSObject {
CGPoint position;
CGPoint velocity;
CGFloat radius;
CGColorRef color;
}

@property CGPoint position;
@property CGPoint velocity;
@property CGFloat radius;
@property CGColorRef color;

– (void)update;
– (CGRect)getRect;

@end[/c]

There’s a getRect method which recturns a CGRect. And the implementation in Ball.m:

[c]- (CGRect)getRect {
return CGRectMake(position.x – radius, position.y – radius, radius * 2.0, radius * 2.0);
}[/c]

Simple enough.

Now let’s change BallView’s refresh method to accept a Ball. And get rid of ballRect in there and just store a Ball. Here’s BallView.h:

[c]#import <uikit/UIKit.h>
#import “Ball.h”

@interface BallView : UIView {
Ball *ball;
}

– (void)refresh:(Ball *)aBall;

@end[/c]

Note that because BallView is now using the Ball class, we need to import that.

And now BallView.m:

[Note – I fixed an earlier error in here in line 30. Should be fine now.]

[c]#import “BallView.h”

@implementation BallView

– (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
}
return self;
}

– (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, ball.color);
CGContextSetLineWidth(context, 1.0);
CGContextAddEllipseInRect(context, [ball getRect]);
CGContextStrokePath(context);
}

– (void)dealloc {
[super dealloc];
}

– (void)refresh:(Ball *)aBall {
ball = aBall;
[self setNeedsDisplay];
}

@end[/c]

A few things going on here. Notice that refresh now takes a pointer to Ball and stores it, then calls setNeedsDisplay. Then in the drawRect method, we use the ball’s color in the CGContextSetSTrokeColorWithColor call, and [ball getRect] in CGContextAddEllipseInRect. Ball has everything we need here.

The last change we need is in the view controller’s onTimer method. Instead of creating a rect and passing that to refresh, we just pass the ball.

[c]- (void)onTimer {
[ball update];
[(BallView *)self.view refresh:ball];
}[/c]

Test that and you should still have a bouncing ball, but now you can change the color of the ball when you create it and this will be reflected in what is drawn in the view.

Now let’s add gravity and bounce. In Ball.h, declare them as CGFloats and properties:

[c]@interface Ball : NSObject {
CGPoint position;
CGPoint velocity;
CGFloat radius;
CGColorRef color;
CGFloat bounce;
CGFloat gravity;
}

@property CGPoint position;
@property CGPoint velocity;
@property CGFloat radius;
@property CGColorRef color;
@property CGFloat bounce;
@property CGFloat gravity;

– (void)update;
– (CGRect)getRect;

@end[/c]

And synthesize these in the implementation .m file:

[c]#import “Ball.h”

@implementation Ball
@synthesize position;
@synthesize velocity;
@synthesize radius;
@synthesize color;
@synthesize bounce;
@synthesize gravity;

…[/c]

Then we change the update method of Ball to add gravity to the y velocity and use bounce instead of the hard coded -1.0.

[c]- (void)update {

velocity.y += gravity;
position.x += velocity.x;
position.y += velocity.y;

if(position.x + radius > 320.0) {
position.x = 320.0 – radius;
velocity.x *= bounce;
}
else if(position.x – radius < 0.0) { position.x = radius; velocity.x *= bounce; } if(position.y + radius > 460.0) {
position.y = 460.0 – radius;
velocity.y *= bounce;
}
else if(position.y – radius < 0.0) { position.y = radius; velocity.y *= bounce; } }[/c] If you test this as is, you’ll see some problems. One, no gravity. Two, no bouncing. That’s because we haven’t initialized the bounce or gravity properties. We could require that the user assigns every property when they make a ball, but you might want some default values in there. You can’t assign values when they are created like CGFloat bounce = -1.0f; unfortunately. So we do this in an init method, which is close to a constructor. [Edit - actually, I just found this reference, which indicates that we should have provided the init method from the start, and should have been calling it right from the start. Oh well, better late than never. 🙂 https://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/ObjectiveC/Articles/chapter_4_section_2.html#//apple_ref/doc/uid/TP30001163-CH22-SW2]

If you just type init in the Ball.m file, you should get a code completion for init Definition. Choose that and it will create an init method for you, with a place to add your initialization code. This is where you set up all your default values, like so:

[c]- (id) init
{
self = [super init];
if (self != nil) {
position = CGPointMake(100.0, 100.0);
velocity = CGPointMake(5.0, 5.0);
radius = 20.0;
color = [UIColor greenColor].CGColor;
bounce = -0.9f;
gravity = 0.5f;
}
return self;
}[/c]

Unfortunately, the init method does not get called by default, so when you allocate the ball, you call it then. This is in the view controller’s viewDidLoad method:

[c]- (void)viewDidLoad {
[super viewDidLoad];
ball = [[Ball alloc] init];
ball.position = CGPointMake(100.0, 100.0);
ball.velocity = CGPointMake(4.0, 3.0);
ball.radius = 20.0;
ball.color = [UIColor blueColor].CGColor;

[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(onTimer) userInfo:nil repeats:YES];
}[/c]

Line 3 shows the change.

Run this and you have a ball that bounces and finally settles to the “floor”. Pretty neat. Note that you can still customize the properties like we are doing for position, velocity, radius and color. You could also leave all those off as long as you call init, and you’ll get a default ball.

In part 4 we will add touch support!

« Previous Post
Next Post »