Friday, November 12, 2010

Simple Game: Tap Ball (Catch The Ball)

It has been a looong time since I wrote any tutorials / sample codes. Recently a friend asked to create a simple base for a game where there is a character moving around randomly and upon tapping it, the character stops. So I created this tutorial for it.


Initially I just wanted to put a ball as the character, but end up with a weird creature image. It is just an image with a transparent background anyway.



If you are making a game instead of just animations, you should really consider using cocos2d. I have NEVER used cocos2d yet. I design my games from just using normal codings - coregraphics. If you plan to make graphic extensive game, then OpenGL is the only answer.

Ok, to create this simple game, we need to declare some objects and some methods. Obviously we need a UIImageView for the sprite. We also need a timer and a button.


IBOutlet UIImageView *Ball1;

NSTimer *mainTimer;

IBOutlet UIButton *startButton;


To animate objects smoothly in XCode, normally we use the UIView animation routine (that commitAnimations block of code). But the problem with this code is the object's property is set immediately to the final property set, even when the object is still animating. For example,
if we animate a UIImageView such as below:

myball.frame =  CGRectMake(0,0,50,50);

[UIView beginAnimations: @"MovingTheBall" context: nil]; 

[UIView setAnimationDelegate: self];

[UIView setAnimationDuration: 1.0];

[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];

myball.frame =  CGRectMake(100,100,50,50); <---

[UIView commitAnimations];

The moment the iOS runs the code up to the <-- arrow, myball already set to location (100,100) immediately even before it completes animating the movement. So this type of movement animation is useless for a game, because in a game you need to know exactly where the ball is at any instant. Thus we need to use NSTimer and move the UIImageView sprite step by step. And at each step, we will be able to check for collisions/touch location/and so on.

Next we define the methods needed for this project.


-(IBAction)startMove;

-(void)moveBall;

-(BOOL)Intersecting:(CGPoint)loctouch:(UIImageView *)enemyimg;

Then we need a few "Game Variables"
CGPoint Destination; // to be assigned with new destination of the sprite

CGFloat xamt, yamt; // x and y steps to move 

CGFloat speed = 50; // speed 

We need a method to trigger the ball movement when we tap on the button. startMove is the method:
-(IBAction)startMove {

startButton.hidden = YES; // we don't want user to instantiate another timer, so hide the button after it has been tapped.

// assign a random location within the view of size 320x480.

Destination = CGPointMake(arc4random() % 320, arc4random() % 480); 

// calculate steps based on speed specified and distance between the current location of the sprite and the new destination

xamt = ((Destination.x - Ball1.center.x) / speed);

yamt = ((Destination.y - Ball1.center.y) / speed);

// ask timer to execute moveBall repeatedly each 0.2 seconds. Execute the timer. 

mainTimer = [NSTimer scheduledTimerWithTimeInterval:(0.02) target:self selector:@selector(moveBall) userInfo:nil repeats: YES];

} 

Link this method to the Start button. When a user tap the start button, the button will dissappear, a new location destination for the sprite is given, the steps to take all the way to the new destination are calculated and then the move timer is fired up every 0.02 seconds. (the repeats: YES part tells the iOS to repeat calling the method moveBall forever).

Let's take a look at moveBall method (read the comments inside the function):

-(void)moveBall {

 // xdist and ydist is to check the current distance of the sprite to the destination point.

        // we are only interested in the distance so direction (-ve or +ve) does not matter, hence

        // we use fabs() function

 CGFloat xdist = fabs(Destination.x-Ball1.center.x); // fabs gives always +ve value

 CGFloat ydist = fabs(Destination.y-Ball1.center.y);

 

        // Normally, to compare 2 points, we can use CGPointEqualToPoint, but that requires an

        // exact match of points. In our case, since our steps are in CGFloat, each steps make the

        // location to be a non round number, but our destination is always a round number. So

        // we cannot use CGPointEqualToPoint. Instead we just check for the distance, if it is close

        // enough to the destination, then it is time to give it a new destination.

 if ((xdist<5)&&(ydist<5)) {

  Destination = CGPointMake(arc4random() % 320, arc4random() % 480);

  xamt = ((Destination.x - Ball1.center.x) / speed);

  yamt = ((Destination.y - Ball1.center.y) / speed);

 } else {

  //continue to move it if distance is still far

  Ball1.center = CGPointMake(Ball1.center.x+xamt, Ball1.center.y+yamt);

 }

}



Now, we need to check when user touch the screen, and if user does touch it, check whether the touch intersect with the sprite or not, if it touched, then stop animation and show startButton.

To detect touches, use one of the Touches delegate functions:


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

 UITouch *touch = [[event allTouches] anyObject];

 CGPoint location = [touch locationInView:self.view];

 // if user touched ball, stop timer

 if ([self Intersecting:location :Ball1]) {

  [mainTimer invalidate];

  mainTimer = nil;

  startButton.hidden = NO;

 }

} 




As you can see, we use another custom method called Intersecting. This is my custom method that I create to check for UIImageView intersection with a point. Sorry for the names of variables, I just copied it right out of my new game. This is a function that returns a BOOLEAN result. If the ImageView and the point intersects, it will return YES, if not, NO. And yes, you can also use CGRectContainsPoint which does the same thing. :P


-(BOOL)Intersecting:(CGPoint)loctouch:(UIImageView *)enemyimg {
 CGFloat x1 = loctouch.x;

 CGFloat y1 = loctouch.y;

 

 CGFloat x2 = enemyimg.frame.origin.x;

 CGFloat y2 = enemyimg.frame.origin.y;

 CGFloat w2 = enemyimg.frame.size.width;

 CGFloat h2 = enemyimg.frame.size.height;

 

 if ((x1>x2)&&(x1<x2+w2)&&(y1>y2)&&(y1<y2+h2))

  return YES;

 else

  return NO;

 
}


So the TouchesBegan delegate just to get the point of touch, and the check that point whether it intersects with the current location of the sprite. If it intersects, then we just stop the sprite from moving (Invalidate the timer, which will stop it), and show the startButton.

That's all folks.

Wednesday, July 7, 2010

Populating iPhone Simulators With Photos

If you develop apps, most probably you will want a user to load up his/her own photo into the app. You can test the UIImagePickerController function by testing loading a photo from either the "Saved Photos" folder or "Photo Library" folder inside the Photos App. But often, after a new installation, both folders are empty.

To save the photos to Saved Photos album, you can just drag any images onto the iPhone simulator whereby it will opened automatically with Safari. Then you can click and hold on the image and an ActionSheet will slide up and you can save it. Images saved in this way are copied to the Saved Photos album. How about Photos Library folder? See below:

There are 2 ways:

1. To populate the folder Saved Photos: Copy and paste photos to this folder (if not exist, create it by yourself):

[USER]/Library/Application Support/iPhone Simulator/User/Media/DCIM/100Apple/...

2. To populate the folder Photo Library (if not exist, create it by yourself):

[USER]/Library/Application Support/iPhone Simulator/User/Media/Photos/...

Developers On MacBook Beware.

This post is not really a coding tutorial. Instead I will be sharing on how things get pretty gooey if you use a Macbook to develop iPhone apps on. You probably can guess what is the problem, yep, the DVD drive gave away its life too soon.

iPhone SDK 4 is out and Apple makes it a requirement to produce apps/updates compiled with SDK 4. It requires Snow Leopard, but my Macbook was running Leopard still! So I went to an Apple Premium Reseller and bought the Snow Leopard installer DVD. Excited, rushed home and slipped the disc inside the Macbook and it spinned for a while and spit it back out. It kept doing that forever. So it is positive, the drive is malfunctioning.

So I thought.. Great.. what now?

After hours of searching, I finally came across an answer via google - to make a clone of the DVD into a Thumbdrive! Yep. But how to do that if the Macbook DVD drive cannot read the Installer DVD? You are out of luck really. Except if you have another computer, which I have -  another desktop PC running Windows XP. Now if you have another Mac with good disc drive, it would be easy to just fire up the Airport and install remotely. But if you just have a PC, it is going to be a little bit difficult.

Now, if you put the Installer DVD into a Windows PC, the boot sector of that disc is going to hide everything else, except the Windows related programs - which are utility to Remote Install. You can do this if you have a WiFi (ie Airport). My PC only have a bluetooth, so that option is out of the window.
I also do not have a Firewire cable, which is another option to use to do Remote Install.

So, what I did was googled a Free Iso Creator software, downloaded it and generate a disc image of the Installer DVD on my PC's hard disk. The name has *.iso extension on it. rename that to *.dmg (which will be recognized by Mac OS). The size is 7.45GB. For some reason, my so called 8GB Sandisk Cruzer USB drive was a rip off. It is not really 8GB, it is 8million bytes, which is 7.40GB. So, I cannot copy this 7.45GB image file onto my USB. Crap!

Then WinRAR springs to mind. Downloaded the trial version and start to archiving the 7.45GB file into 5 pcs of ~ 1.3GB files. Then transfer them bit by bit to my Mac. Once all the 5 pieces of 1.3GB files are in my Macbook, I need to stitch them up back together.. so I downloaded UnRarX (a cool free extractor for Mac) and start combining back those files into 1 big 7.45GB installer image file.

Next, what I did was open up Disk Utility in my Macbook, plug in my "8GB" thumbdrive and do a Restore using the image file that was retrieved earlier. Remember to format the thumbdrive and partition it as GUID Apple format, which will make it bootable.

Once done, Mac OS immediately recognizes it and mount a virtual drive. Double click it and you will see the installer icon and voila! I could finally install Snow Leopard and SDK 4 on my Mac.

Hope this post would help someone in similar situation as mine. Wasted a whole 2 days just to do this. But glad that I found a way. I really don't want to spend hundreds of dollars to swap the drive or to buy external drive. Pheww...

Friday, May 14, 2010

Hacking "More..." Tableview of a TabBar Template Based App

Ok, its been a while since I wrote any tutorials, so I thought I'd write about a basic Tab Bar based template customization.

As most of newbies in iPhone App development, I too started with the basic template of Tab Bar App. It is a great foundation to create a utility like app because you do not have to care (much) about how to switch views within the app using a tab bar. But, this also means you are stuck with the layout pretty much forever.

** NO SOURCE CODE FOR THIS TUTORIAL **

After a while, my app that was using this Tab Bar template (iTronixPal - Electronics Box), has grown to be a rather extensive app with many tools which are useful for electronics enthusiasts. So in each update I felt compelled to improve upon the graphics, and just to make it better and better, as a gesture of thanks to those who bought it.

Then came the boring More... table view. Seriously? Have u looked at it? White background, black text, no shadows, no eye candy whatsoever. Blerghh.....!!

So I did a search on the Apple Docs, and found a lil something about the moreNavigationController.
Read through it, and found that you can access it as a view. Bada-bing! Once you can access a view, you can basically do anything to the view. And in particular of interest, add a subview.

So, I created a transparent image background for the tableview in More tab. I have lots of tabs, therefore my image is rather long, but the width is fixed to 320pixels. Such as below:


I arranged the icons nicely in my graphics software (I use GIMP) based on a screen capture of the original More table view. Remember to set the background to transparent. That is Alpha channel = 0.0 in the graphics software.

Then, I just add this image to the More.. tableview view by using the following codes in AppDelegate.m file inside the applicationDidFinishLaunching method. Such as below:

  //create a uiimageview object;
UIImageView *tmp = [[UIImageView alloc]  initWithImage:[UIImage imageNamed:@"MoreBg.png"]];
 //add the uiimageview as a subview of moreNavigationController
 [tabBarController.moreNavigationController.topViewController.view addSubview:tmp];

//send the imageview to back (though i think it is not sent to back)

 [tabBarController.moreNavigationController.topViewController.view sendSubviewToBack:tmp];

The code should be self explanatory. Next, you need to open the TabBarController in your Window.xib file (in interface builder), and add some spaces in the beginning of the title for each tabs (so that it does not overlap with your icons). Or if you want to customize all the titles as images , then just delete all the titles).

Here is how mine looks like (below). I even changed the background color of the More navigation controller view to gray.


Hope this helps for anyone who is looking for an easy way to do this.

This tutorial does not accompany a downloadable sample code since it is pretty simple.

Thursday, March 4, 2010

How To Use UIImagePickerController In Landscape App




My app just got rejected for displaying UIImagePickerController in the landscape mode. As Apple doc stated, UIImagePickerController (will be refered as UIIPC from now on) only works in portrait mode. For SDK 3.0+, this is no problem since i think it will auto display in portrait mode (correct me if im wrong). But for me, who is using 2.2.1 still, becomes a problem. I know there are quite a few posts about this issue with solutions but I tried all those and proves to be either "not working" or "too difficult to implement.". Especially those that suggest to apply transform to all the views object.



The problem is that the UIIPC launched in landscape mode causes the thumbnails to be stretched in devices, and appears funny in simulator. Such as below:


After few hours of banging my head on my MacBook, I came up with a simple hack that I think works ok. Here's one way how to solve this problem - very easy way without needing to apply transform to all of your objects and views. This is a sample app i created. From launch, the app starts from landscape.  

Now when I press a button to load a photo (calling UIIPC - either savedphotosalbum/photolib), the app display a "transition view" that halts the operation until the user rotates the device. Put a message/text on it to ask the user to rotate the device to proceed. At the time of this view displaying, also set a GLOBAL BOOL FLAG, (named WANTSPORTRAIT for me), to YES. (initially set to NO)

Then, when the user rotates it, detect the orientation (by the delegate function of  - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation). In this delegate function, detect the flag and if it is set, call the UIIPC and return UIInterfaceOrientationPortrait. Voila, now your UIIPC is in Portrait.

The next thing to do is to handle the selected image/photo. Once the image/photo is selected, the appropriate delegate function will be called (for 4.0, it is - - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info. In this function set WANTSPORTRAIT to NO and again, display the transition view with a message asking the user to rotate to landscape. (You can customize these messages, and even easily apply simple transform on them to make it appear nice). 

And when the user rotates it again back to landscape, the delegate detects it and return the orientation back to landscape. The project sample probably is not perfect (eg if user rotates the other way or etc, you need to modify it to handle all of orientation)

This method is accepted by Apple Reviewer. 2 of my apps are using this method and appears to be accepted by users.