In Part 1
Of this series of articles we drew some simple graphic primitives on
The iPhone display. In this article we are going to look at how to do
Some simple animation. The goal of this example is to create a 2D ball
That bounces around the iPhone screen.
First of all we need an object to represent a point in our 2 dimen1_coordinate system.
ThePoint2d
Object will take care of this and just keeps trackX
AndY
Values.
@ Interface point2d: nsobject <br/>{< br/> cgfloat X; <br/> cgfloat y; <br/>}</P> <p> @ property (assign) cgfloat X; <br/> @ property (assign) cgfloat y; </P> <p>-(ID) initwithx :( cgfloat) x y :( cgfloat) y; <br/>-(void) addvector :( vector2d *) vector; </P> <p> @ end </P> <p> @ implementation point2d </P> <p> @ synthesize X; <br/> @ synthesize y; </P> <p>-(ID) Init {<br/> If (Self = [Super init]) <br/>{< br/> X = 0.0; <br/> Y = 0.0; <br/>}< br/> return self; <br/>}</P> <p>-(ID) initwithx :( cgfloat) x y :( cgfloat) y; <br/>{< br/> If (Self = [Super init]) <br/> {x = X; <br/> Y = y; <br/>}< br/> return self; <br/>}</P> <p>-(void) addvector :( vector2d *) vector {<br/> X + = vector. endpoint. x; <br/> Y + = vector. endpoint. y; <br/>}</P> <p> @ end
It can be initialized withX
AndY
Value usingInitwithx: Y
Method.Addvector
Method will be used to move our ball around the screen.
Next we need a class that represents a vector. The vector will be
Used to determine the direction that our ball is currently moving in and
Its speed.Vector2d
Class will take of this.
@ Interface vector2d: nsobject <br/>{< br/> cgfloat angle; <br/> cgfloat length; <br/> point2d * <br/> endpoint; <br/>}< br/> @ property (assign) cgfloat angle; <br/> @ property (assign) cgfloat length; <br/> @ property (assign) point2d * endpoint; </P> <p>-(ID) initwithx :( cgfloat) x y :( cgfloat) y; <br/>-(void) setangle :( cgfloat) degrees; <br/> @ end <br/> // geometry constants <br/> # define PI 3.14159 <br/> # define oneeightyoverpi 57.29582 <br/> # define pioveroneeighty 0.01745 </ p> <p> @ implementation vector2d </P> <p> @ synthesize angle; <br/> @ synthesize length; <br/> @ synthesize endpoint; </P> <p>-(ID) init {<br/> If (Self = [Super init]) <br/>{< br/> angle = 0.0; <br/> length = 0.0; <br/> endpoint = [[point2d alloc] init]; <br/>}</P> <p> return self; <br/>}</P> <p>-(ID) initwithx :( cgfloat) x y :( cgfloat) Y <br/>{< br/> If (Self = [Super init]) <br/>{< br/> endpoint = [[point2d alloc] initwithx: x y: y]; </P> <p> // calculate the Angle Based on the end point <br/> angle = atan2 (-endpoint. y, endpoint. x) * <br/> oneeightyoverpi; <br/> If (angle <0) <br/>{< br/> Angle ++ = 360; <br/>}</P> <p> // calculate the length of the vector <br/> length = SQRT (endpoint. x * endpoint. X + endpoint. y * endpoint. y); <br/>}< br/> return self; <br/>}</P> <p>-(void) setangle :( cgfloat) degrees {<br/> angle = degrees; <br/> double radians = angle * pioveroneeighty; <br/> endpoint. X = length * Cos (radians); // cocould speed these up with a lookup table <br/> endpoint. y =-(length * sin (radians); <br/>}</P> <p> @ end
TheVector2d
Class manages the angle of the vector and
Length of the vector. It also keeps track of the end point of the Vector
For convenience since we'll be using the end point to move our ball.
The vector can be initialize with an end point usingInitwithx: Y
Method. The angle and length will automatically be calculated.
The angle is calculated from the end point by usingAtan2
Function. This function returns the angle in radians so we multiply
Result by 180/PI to get the angle in degrees. You'll also notice that
We pass a negative Y coordinate to the atan2 function. This is because
In our screen coordinate system the Y axis starts at zero at the top
The screen and has positive values as your go down the screen.
Geometry functions we are using before CT the opposite so we need
Reverse the Y coordinates.
IfAtan2
Function returns a negative number we add 360 to make it positive.
To get the length of the vector we take the square root of X squared + Y squared.
CallingSetangle
Method will change the angle of
Vector and update it's end point to match the angle. First we convert
The angle in degrees to radians by multiplying the angle by PI/180. We
Get the X end point by multiplying the length of the vector by
Cosine of the angle. We get the y end point by multiplying the length
The vector by the sine of the angle. Notice we used a negative on the Y
Value of the endpoint to fix the coordinate system problem.
Now we need a class to represent the ball we are going to move around the screen. I'm calling itObject2d
Because later this will most likely become a base class for other types of objects. It'll just be a ball for now though.
@ Interface object2d: nsobject <br/>{< br/> point2d * position; <br/> vector2d * vector; <br/> cgsize size; <br/>}</P> <p> @ property (assign) point2d * position; <br/> @ property (assign) vector2d * vector; <br/> @ property (assign) cgsize size; </P> <p>-(ID) initwithposition :( point2d *) POS vector :( vector2d *) VEC; <br/>-(void) Move :( cgrect) bounds; <br/>-(void) bounce :( cgfloat) boundrynormalangle; <br/>-(void) Draw :( cgcontextref) context; </P> <p> @ end </P> <p> // screen edge normals <br/> # define kleftnorm 0.0 <br/> # define klefttopnorm 315.0 <br/> # define ktopnorm 270.0 <br/> # define krighttopnorm 225.0 <br/> # define krightnorm 180.0 <br/> # define krightbottomnorm 135.0 <br/> # define kbottomnorm 90.0 <br/> # define kleftbottomnorm 45.0 </P> <p> # define kdefaultsize 25.0 </P> <p> @ implementation object2d </P> <p> @ synthesize position; <br/> @ synthesize vector; <br/> @ synthesize size; </P> <p>-(ID) init {<br/> If (Self = [Super init]) <br/>{< br/> position = [[point2d alloc] init]; <br/> vector = [[vector2d alloc] init]; <br/> size. width = kdefaultsize; <br/> size. height = kdefaultsize; <br/>}< br/> return self; <br/>}</P> <p>-(ID) initwithposition :( point2d *) pos vector :( vector2d *) VEC <br/>{< br/> If (Self = [Super init]) <br/>{< br/> position = [POS retain]; <br/> vector = [VEC retain]; <br/> size. width = kdefaultsize; <br/> size. height = kdefaultsize; <br/>}</P> <p> return self; <br/>}</P> <p>-(void) Move :( cgrect) bounds {<br/> // move the ball by adding the vector to the position <br/> [position addvector: vector]; </P> <p> // if the ball has hit the edge of the screen bounce it <br/> If (position. x <= bounds. origin. X & position. Y <= <br/> bounds. origin. y) <br/>{< br/> position. X = bounds. origin. x; <br/> position. y = bounds. origin. y; <br/> [self bounce: klefttopnorm]; <br/>}< br/> else if (position. x <= bounds. origin. X & position. Y + <br/> size. height> = bounds. size. height) <br/>{< br/> position. X = bounds. origin. x; <br/> position. y = bounds. size. height-size. height; <br/> [<br/> Self bounce: kleftbottomnorm]; <br/>}< br/> else if (position. X + size. width> = bounds. size. width & <br/> position. Y <= bounds. origin. y) <br/>{< br/> position. X = bounds. size. width-size. width; <br/> position. y = bounds. origin. y; <br/> [self bounce: krighttopnorm]; <br/>}< br/> else if (position. X + size. width> = bounds. size. width & position. Y + size. height> = bounds. size. height) <br/>{< br/> position. X = bounds. size. width-size. width; <br/> position. y = bounds. size. height-size. height; <br/> [self bounce: krightbottomnorm]; <br/>}< br/> else if (position. x <= bounds. origin. x) <br/>{< br/> position. X = bounds. origin. x; <br/> [self bounce: kleftnorm]; <br/>}< br/> else if (position. X + size. width> = bounds. size. width) <br/>{< br/> position. X = bounds. size. width-size. width; <br/> [self bounce: krightnorm]; <br/>}< br/> else if (position. Y <= bounds. origin. y) <br/>{< br/> position. y = bounds. origin. y; <br/> [self bounce: ktopnorm]; <br/>}< br/> else if (position. Y + size. height> = bounds. size. height) <br/>{< br/> position. y = bounds. size. height-size. height; <br/> [self bounce: kbottomnorm]; <br/>}</P> <p>-(void) bounce :( cgfloat) boundrynormalangle {<br/> double angle = vector. angle; <br/> double oppangle = (INT) (angle + 180) % 360; <br/> double normaldiffangle; <br/> If (boundrynormalangle> = oppangle) <br/>{< br/> normaldiffangle = boundrynormalangle-oppangle; <br/> angle = (INT) (boundrynormalangle + ormaldiffangle) % 360; <br/>}< br/> If (boundrynormalangle <oppangle) <br/>{< br/> normaldiffangle = oppangle-boundrynormalangle; <br/> angle = boundrynormalangle-normaldiffangle; <br/> If (angle <0) <br/>{< br/> Angle ++ = 360; <br/>}< br/> // set the new vector angle <br/> [vector setangle: Angle]; <br/>}< br/>-(void) Draw :( cgcontextref) CTX {<br/> cgcontextsetrgbfillcolor (CTX, 255, 0, 0, 1 ); <br/> cgcontextfillellipseinrect (CTX, cgrectmake (position. x, position. y, size. width, size. height); <br/>}
The data stored for our ball is just its current position and its
Current Vector. We also have a size that determines the width and height
Of the ball. If you just callInit
On the object you get a ball at 0, 0 with a zero vector so it won't move. If you callInitwithpostion: Vector
You can pass in the initial position and vector.
TheMove
Method is called to actually move the ball. It adds
The vector to the position then it checks to see if the ball has hit
Any of the screen edges including landing exactly in the corners of
Screen. If it has hit a screen edge the bounce method is called and
Passed the normal angle for the screen edge that was hit.
The bounce method uses the normal angle passed to it to calculate the correct bounce angle for the ball. Then it CILSSetangle
On the vector to update it.
The draw method is passed a graphics context and draws the ball on it.
Now all we need is a view to make this work.
@ Interface graphicsview: uiview <br/>{< br/> object2d * ball; <br/> nstmer * timer; <br/>}</P> <p>-(void) Tick; <br/> @ end </P> <p> @ implementation <br/> graphicsview </P> <p>-(ID) initwithframe :( cgrect) framerect {<br/> Self = [Super initwithframe: framerect]; </P> <p> // create a ball 2D Object in the upper left corner of the screen <br/> // heading down and right <br/> ball = [[ object2d alloc] init]; <br/> ball. position = [[point2d alloc] initwithx: 0.0 Y: 0.0]; <br/> ball. vector = [[vector2d alloc] initwithx: 5.0 Y: 4.0]; </P> <p> // start a timer that will call the tick method of this class <br/> // 30 times per second <br/> timer = [nstimer scheduledtimerwithtimeinterval (1.0/<br/> 30.0) target: Self selector: @ selector (tick) userinfo: Nil repeats: Yes]; </P> <p> return self; <br/>}</P> <p>-(void) Tick {<br/> // update the ballposition <br/> [ball move: Self. bounds]; </P> <p> // tell the view that it needs to re-draw itself <br/> [self setneedsdisplay]; <br/>}</P> <p>-(void) drawrect :( cgrect) rect {<br/> // clear the display and draw the ball <br/> cgcontextref CTX = uigraphicsgetcurrentcontext (); <br/> cgcontextclearrect (CTX, rect ); <br/> [Ball draw: CTX]; <br/>}
The View class derives fromUiview
And will be created by the Application delegate class. it just creates a singleObject2d
To represent our ball and startsNstimer
To move the ball around. We initialize the ball at 0, 0 on the screen
And give it a vector with an X, Y of 5, 4. We then start a timer that will
Be called every 1/30th of a second. The timer will callTick
Method ofGraphicsview
Class.
TheTick
Method callthe ball'sMove
Method and passes in the bounds of the view to use for our bounce testing. It then CILSSetneedsdisplay
To sort y the view that it needs to redraw itself.
TheDrawrect
Method gets the graphics context, clears it and then callthe ball'sDraw
Method to tell it to draw itself.
That's all there is to it. Pretty simple, eh? Next time we'll tie in
The accelerometer and maybe add some physics to simulate gravity.
Here are all of the source files:
Main. m
-Exploringgraphics2appdelegate. h
-Exploringgraphics2appdelegate. m
-Graphicsview. h
-Graphicsview. m
-Object2d. h
-Object2d. m
-Point2d. h
-Point2d. m
-Vector2d. h
-Vector2d. m