Project X Has Begun
In my last post, Something Old or Something New?, I talked about my own mental process for choosing what to do next that would be meaningful, i.e. worth doing and doing well, and ultimately profitable (for a reasonable definition of that word.) I am pleased to say: Work has begun!
I’ve always had a hard time knowing when the design of something was “done,” meaning that I’m never always sure I’ve answered this essential (and perhaps unanswerable) question: Have I thought of everything?
With experience comes some wisdom, so luckily I can recognize this in myself now and can push on, realizing that I have undoubtedly not thought of everything, but if it’s important I eventually will. I saved my spec, printed it, read and re-read it a few times, thought about it, and finally put fingers to keyboard (and mouse) and began the new project. For now, we’ll call it Project X.
What can I tell you about this project? It’s a game. It’ll borrow what I think are solid game mechanics and algorithms from an existing app in my portfolio, and extend them. It’ll be a universal app. It’ll include best practices I have learned and observed in 3+ years doing iOS development (and many more years of software engineering). It’ll also incorporate “obvious” technologies like Game Center, analytics, Twitter, perhaps Facebook… all that stuff.
One “revelation” I’ve already encountered is how easy (so far) it is to support two UI idioms with one code base. Beyond a single conditional to load the right NIB file, the logic in the controller is in fact universal. Imagine that…
A feature of the original app allowed for game board rotation. That is to say, there was a mechanism to rotate the game board on the screen, either to the left or to the right. The UI for this was unnatural, although I didn’t know it at the time. The new game will use a UIGestureRecognizer of course, and it is much nicer.
The underlying code for this UI effect will also change. I always enjoy seeing how code can evolve and be updated. The original app rotated the game board (and its contents) with this code:
[code lang=”objc”]
-(void)rotateView:(CGFloat)degrees
{
[UIView beginAnimations:@"rotate view" context:nil];
[UIView setAnimationDuration:0.5];
theView.transform = CGAffineTransformRotate(theView.transform, degreesToRadians(degrees));
for(UIButton *b in [theView subviews]) {
b.transform = CGAffineTransformRotate(b.transform, degreesToRadians(-degrees));
}
[UIView commitAnimations];
}
-(IBAction)rotateLeft:(id)sender
{
[self rotateView:-90.0];
}
-(IBAction)rotateRight:(id)sender
{
[self rotateView:90.0];
}
[/code]
Serviceable, indeed. But hardly elegant. (Hey– it was my first app. I was thrilled the animation bit was “simple”! And, this was pre-iPhone OS 3, too.) Using the subviews of theView to find the other views I wanted to rotate was my poor man’s IBOutletCollection. (I am not sure if IBOutletCollection was available in 2009, but if it was, I didn’t know about it or didn’t understand it!) Those two actions were connected with buttons on the UI for the user to initiate a rotation.
The updated code, triggered by the gesture recognizer is much more refined. In fact, the gesture recognizer’s handler takes care of everything:
[code lang=”objc”]
-(void)rotateGameBoard:(UIPanGestureRecognizer *)pan
{
static BOOL rotating = NO;
CGFloat degrees = 0.0;
CGPoint point = [pan locationInView:self.view];
CGPoint velocity = [pan velocityInView:self.view];
if (fabsf(velocity.x) < 200 || rotating) return;
// If the touch point is too far away from the container, ignore it
if (point.x > container.frame.origin.x + container.frame.size.width + 20) return;
if (point.x < container.frame.origin.x – 20) return;
if (point.y > container.frame.origin.y + container.frame.size.height + 20) return;
if (point.y < container.frame.origin.y – 20) return;
// NB: In landscape, width & height are reversed. That is, the width is now the height.
CGRect screen = [[UIScreen mainScreen] applicationFrame];
rotating = YES;
if(velocity.x > 0)
{
// right
if (point.y < screen.size.width / 2.0)
degrees = 90.0;
else
degrees = -90.0;
}
else
{
// left
if (point.y < screen.size.width / 2.0)
degrees = -90.0;
else
degrees = 90.0;
}
[UIView animateWithDuration:0.25
animations:^{
container.transform =
CGAffineTransformRotate(container.transform, degreesToRadians(degrees));
for(UIButton *b in contents) {
b.transform =
CGAffineTransformRotate(b.transform, degreesToRadians(-degrees));
}
}
completion:^(BOOL finished) {
rotating = !finished;
}
];
}
[/code]
There’s a couple of things happening here of note. The gesture recognizer handler makes sure the swipe was close enough to the actual game board (‘container’). You might think that would be unnecessary, because you would reasonably assume that the gesture recognizer is associated with the container view itself. But it is not! The reason for that has to do with the fact that swipes ultimately cause the container to be rotated, which also (I discovered) adjusts the coordinates of future swipes on that view. So, the recognizer is actually associated with the main view, which means that for a better user experience, we have to discern where the user touched relative to the game board view.
The actual game board rotation occurs neatly in the animation block. And yes, in this version ‘contents’ is an IBOutletCollection of UIButtons. IBOutletCollections are incredibly handy if you have many UI elements of the same type that are all essentially the same kind of thing that you need to access as a group.
The overall screen layout is pretty much set, although I am not 100% satisfied with it. My vision for the graphics is far beyond my own abilities, so my layout is one step up from a wire frame in that it is functional but ugly. I figure I can make progress on the game mechanics and inner workings while I figure out what to do about the graphics.
There is much more to be done, of course! Hopefully I’ll have more interesting discoveries and improvements to report in the coming weeks.
Comments
Project X Has Begun — No Comments
HTML tags allowed in your comment: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>