Channels ▼
RSS

Web Development

The iPhone Isn't Easy


The Loop That Isn't and Modes

The master method that drives the app and periodically updates its interfaces is — appropriately enough — called gameLoop; see Listing Five. Note that the method is a loop in name only: it calls housekeeping methods that update the physics model and the screen, and exits. The gameLoop method keeps executing because of the timer.

Listing Five

-(void) gameLoop {
	if (programMode == RUNNING) {
		[self updatePhysics];
		
		[controlPad setNeedsDisplay];
		[infoIcon setNeedsDisplay];
		CGAffineTransform shipRotate = CGAffineTransformMakeRotation(shipHeading / 180.0 * M_PI);
		[shipView setTransform:shipRotate];
		[shipView upDateShip:thrust];
		[shipView setNeedsDisplay];
		
		[self.view setNeedsDisplay];
		return;
	}
	if (programMode == PAUSE) {
		return;
	}

} // end gameLoop

The logic in gameLoop is starkly simple. The code calls doPhysics to update the physics model. doPhysics calculates the spaceship's latest position, velocity, and heading, based on the changes in the variables for rotation and thrust received from the UI. Next, gameLoop has the control pad and info button views redraw themselves. This is done so that the controls can appear with the proper translucent value, depending upon whether the user is touching them or not. Updating the spaceship view is slightly more complicated, but not much. The ShipView's transform property is updated with the ship's latest heading, and then the transformation matrix in this property is applied to the spaceship view, which rotates it. ShipView's upDateShip method is called to load the corresponding image into the view, which depends upon whether the thrust variable is set. Finally, the code has ShipView redraw itself.

The code of doPhysics I dropped in straight from the Android program. I modified its API calls that obtain the absolute time and trigonometric values from Android's to those for Coca Touch (Listing Six). The one hiccup I had was that I couldn't just jam the values for the ship's x and y coordinates into ShipView's center property. This property is a structure of type point and Objective-C syntax doesn't allow you to modify individual elements in a structure if it is an l-value. The easiest solution was to place the coordinates into a point with the CGPointMake() function and write this point into the center property.

Listing Six

-(void) updatePhysics {
	CFTimeInterval now = CFAbsoluteTimeGetCurrent();
	
// Calculate and respond to situation where ship tries
// to go off screen. 
	CGRect rect = [[UIScreen mainScreen] applicationFrame];	

	if (shipX <= kEdgeCondition){
		shipX = kEdgeCondition;
		mDX = -mDX;
	} else if (shipX>>= (rect.size.width - kEdgeCondition)){
		shipX = (rect.size.width - kEdgeCondition);
		mDX = -mDX;
	} // end else
	// Handle top and bottom
	if (shipY <= 10){
		shipY = 10;
		mDY = -mDY;
	} else if (shipY >= (rect.size.height - kEdgeCondition)){
		shipY = (rect.size.height - kEdgeCondition);
		mDY = -mDY;
	} // end else
	
	double elapsed = (now - lastTime) * 10.0;

	// Ship is rotating -- update its heading
	if (shipTurning != 0.0) {
		shipHeading += shipTurning * (SLEW_SEC * elapsed);
		
		// Bring value back into the range 0..360
		if (shipHeading < 0.0) shipHeading += 360.0;
		else if (shipHeading >= 360.0) shipHeading -= 360.0;
		
	} // end if mRotating != 0

	// Base accelerations 
	double ddx = 0.0;
	double ddy = 0.0;
	
	if (thrust) {
		// Taking 0 as up, 90 as to the right
		// cos(deg) is ddy component, sin(deg) is ddx component
		double elapsedFiring = elapsed;
		
		// Have this much acceleration from the engine
		double accel = (FIRE_ACCEL_SEC * elapsedFiring) / 10.0;
		double radians = shipHeading / 180.0 * M_PI;
		ddx = sinf(radians) * accel;
		ddy = -(cosf(radians) * accel);	
	} // end if
	
	double dxOld = mDX;
	double dyOld = mDY;
	
	// figure speeds for the end of the period
	mDX += ddx;
	mDY += ddy;
	
	// figure position based on average speed during the period
	shipX += elapsed * (mDX + dxOld)/2;
	shipY += elapsed * (mDY + dyOld)/2;
	
	CGPoint shipLocation = CGPointMake(shipX, shipY);
	shipView.center = shipLocation;
	
	lastTime = now;
}

Finally, because of the absolute time values returned by the iPhone API were different from those returned by Android, I had the tinker with some constants to get the spaceship's motions working smoothly. Overall, the changes to the physics code were minor and it worked almost "as is" in the app, due to the fact that Java's arithmetic operations are nearly identical to C's. In fact, porting the physics algorithm was probably the easiest part of the project, because I was working with proven code. Porting more sophisticated code from Android or other mobile platforms would require extra work, depending upon how much the algorithms rely on the other platform's APIs.

Touching the information button loads the InfoViewController class, which is a controller view that manages a view displaying help information. There's some nifty code that performs an animation that rotates the help screen over SpaceView as it is added as a subview. I had originally considered making the help screen just another view, but I added a view controller, InfoViewController, since I might add other interactive elements to it in the future. Such elements might be the ability to let you choose where the control pad appears.

Once I got the basic help screen working, I realized I had a problem: the spaceship would keep drifting merrily about while the help screen concealed it. I therefore made a setmode method that modifies a variable, programMode. If programMode was set to the constant value PAUSED, then the gameLoop immediately exits, rather the calling doPhysics and updating the views. With this change, when you switch to the help screen, SpaceView's actions stop until you dismiss it. The little bit of code present in InfoViewController performs the rotating animation back the SpaceView screen, and has the app resume its execution.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV