Showing posts with label iOS. Show all posts
Showing posts with label iOS. Show all posts

Sunday, May 13, 2012

Background loading a Cocos2d Sprite from URL

Based on Steffen Itterheim's excellent article here, I created a CCSprite class to download itself in the background. You create such a sprite giving it a default image:

 LGNetworkSprite *myImage = [LGNetworkSprite spriteWithFile:@"fbDefault.png"];
 myImage.position = ccp(_screenSize.width * 0.75, _screenSize.height * 0.6);
 [self addChild:myImage];

Then, you can set the URL of the image to download, and it will do the magic in the background:
Note: the local file name should be unique if you download different images.

 [myImage loadFromURLString:imgUrl withLocalFileName:myID];


The code itself is quite simple (once you learn Steffen's teachings...):
//
//  LGNetworkSprite.h
//  CatchTheBall
//
//  Created by Israel Roth on 5/12/12.
//  Based on Steffen Iterheim code:
//  http://www.learn-cocos2d.com/2012/02/cocos2d-webcam-viewer-part-2-asynchronous-texture-loading/
//  Copyright 2012 Labgoo LTD. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "cocos2d.h"

@interface LGNetworkSprite : CCSprite {
}

- (void) loadFromServerAddress: (NSString*) serverAddr fileName: (NSString *) fileName;
- (void) loadFromURLString: (NSString *) urlString withLocalFileName: (NSString *) fileName;

@end


And the m file:
//
//  LGNetworkSprite.m
//  CatchTheBall
//
//  Created by Israel Roth on 5/12/12.
//  Based on Steffen Iterheim code:
//  http://www.learn-cocos2d.com/2012/02/cocos2d-webcam-viewer-part-2-asynchronous-texture-loading/
//  Copyright 2012 Labgoo LTD. All rights reserved.
//

#import "LGNetworkSprite.h"
#import "AsyncFileDownloadData.h"

@implementation LGNetworkSprite

-(NSString*) cacheDirectory
{
 NSString* cacheDirectory = nil;
 NSArray* pathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
                                                                 NSUserDomainMask, YES);
 if ([pathArray count] > 0)
 {
  cacheDirectory = [pathArray objectAtIndex:0];
 }
 return cacheDirectory;
}

- (void) loadFromURLString: (NSString *) urlString withLocalFileName: (NSString *) fileName {
 NSString* localFile = [[self cacheDirectory] stringByAppendingPathComponent:fileName];
 AsyncFileDownloadData* afd = [[[AsyncFileDownloadData alloc] init] autorelease];
 afd.url = [NSURL URLWithString:urlString];
 afd.localFile = localFile;
 [self performSelectorInBackground:@selector(downloadFileFromServerInBackground:) withObject:afd];
}

-(void) logError:(NSError*)error {
 if (error) {
  CCLOG(@"%@: %@", error, [error localizedDescription]);
 }
}

-(void) downloadFileFromServerInBackground:(AsyncFileDownloadData*)afd
{
 NSError* error = nil;
 NSData* data = [NSData dataWithContentsOfURL:afd.url options:NSDataReadingMappedIfSafe error:&error];
 [self logError:error];
 
 [data writeToFile:afd.localFile options:NSDataWritingAtomic error:&error];
 [self logError:error];
 
 // wait until done in this case means that the background thread waits for completion of the task
 // manipulating or creating sprites must be done on the main thread
 [self performSelectorOnMainThread:@selector(updateTexturesWithAsyncData:) withObject:afd waitUntilDone:NO];
}

-(void) updateTexturesWithAsyncData:(AsyncFileDownloadData*)afd
{
 [self updateTexturesFromFile:afd.localFile];
}

-(void) updateTexturesFromFile:(NSString*)file
{
 CCTextureCache* texCache = [CCTextureCache sharedTextureCache];
 [texCache addImageAsync:file 
      target:self
       selector:@selector(asyncTextureLoadDidFinish:)];
}

-(void) asyncTextureLoadDidFinish:(CCTexture2D*)texture
{
 CCTextureCache* texCache = [CCTextureCache sharedTextureCache];
 [texCache removeTexture:self.texture];
 CGSize prevSize = [self contentSize];
 self.texture = texture;
 CGSize size = [texture contentSize];
 [self setTextureRect:CGRectMake(0.0f, 0.0f, size.width,size.height)];
 [self setScale:prevSize.width / size.width];
}

@end

Note: Since I am updating the sprite itself, I do not need to keep the sprite tag. Also, I am using the Library/Caches directory to keep the local file. I think it is the right thing to do (in face, Apple will reject your app if you save to the Documents directory files that should not be there unless you mark them specifically).
Thank you Steffen, and to all of you doing cocos2d - go get yourself some help by purchasing Steffen's book here:

Sunday, March 4, 2012

Custom Transition Animation for Modal View Controllers

Needed to implement a custom animation (push from right/left when presenting a modal view controller, and then pushing out to the other direction when dismissing the dialog).
Yosi found this snippet: http://blog.radi.ws/post/5924267283/custom-modal-uiviewcontroller-transitions
by Evadne Wu. This was so cool, and I think I made it a bit more fun by creating a UIViewController category to do that (I was a bit lazy, so I only implemented the Push in/out transition, but this can be easily extended):


//
//  UIViewController+Transitions.h
//  Labgoo Misc
//
//  Created by Israel Roth on 3/4/12.
//  Copyright (c) 2012 Labgoo LTD. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIViewController(Transitions)

- (void) presentModalViewController:(UIViewController *)modalViewController withPushDirection: (NSString *) direction;
- (void) dismissModalViewControllerWithPushDirection:(NSString *) direction;

@end


and the implementation:

//
//  UiViewController+Transitions.m
//  Labgoo Misc
//
//  Created by Israel Roth on 3/4/12.
//  Copyright (c) 2012 Labgoo LTD. All rights reserved.
//

#import "UIViewController+Transitions.h"

@implementation UIViewController(Transitions)

- (void) presentModalViewController:(UIViewController *)modalViewController withPushDirection: (NSString *) direction {

    [CATransaction begin];
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = direction;
    transition.duration = 0.25f;
    transition.fillMode = kCAFillModeForwards;
    transition.removedOnCompletion = YES;
    
    [[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"transition"];        
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
    [CATransaction setCompletionBlock: ^ {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(transition.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
            [[UIApplication sharedApplication] endIgnoringInteractionEvents];        
        });
    }];
    
    [self presentModalViewController:modalViewController animated:NO];
    
    [CATransaction commit];

}

- (void) dismissModalViewControllerWithPushDirection:(NSString *) direction {

    [CATransaction begin];
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = direction;
    transition.duration = 0.25f;
    transition.fillMode = kCAFillModeForwards;
    transition.removedOnCompletion = YES;

    [[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"transition"];        
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
    [CATransaction setCompletionBlock: ^ {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(transition.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
            [[UIApplication sharedApplication] endIgnoringInteractionEvents];        
        });
    }];
    
    [self dismissModalViewControllerAnimated:NO];
    
    [CATransaction commit];
    
}

@end



to use it, you import "UIViewController+Transitions.h" in your file and call the presentModalViewController:withPushDirection:
the direction parameter receives core animation transition sub-types such as kCATransitionFromRight

Enjoy...




And a message from our advertisers:


Toptal provides remote engineers and designers of high quality. I recommend them. Follow this link (full disclosure: this is my affiliate link):
https://www.toptal.com/#engage-honest-computer-engineers-today



Tuesday, August 30, 2011

OpenGL ES alternate texture renderring

A friend asked me for help with getting the sample here: http://www.mat.ucsb.edu/a.forbes/blog/?p=245 to work on the iPhone.

Since I had no experience with OpenGL ES 2 shaders, I thought it would be a good chance to get my hands dirty and learn something about it. I am still not an expert (far from it), so take it all with healthy skepticism.

This is an interim report, just to let you know the basics are working and provide with the sample code.

So the code is available here: http://iroth.net/prog/PingPong.zip

In addition to the article that was the inspiration of this, I could have not made this work without the help of the great tutorials of Ray Wenderlich and in particular this series of two:
http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial
http://www.raywenderlich.com/4404/opengl-es-2-0-for-iphone-tutorial-part-2-textures

One more thing - there seems to be some black triangle artifact in the upper left corner I still was unable to understand. If any of you find out - please comment below...

And while at it, here are some books that really help me get up to speed on iPhone game programming:






Monday, June 13, 2011

Improved CCSlider included with Cocos2d iPhone Extensions

I was happy to get an email from Stepan Generalov asking for permission to publish an improved version he made of CCSlider under an MIT license, which I approved immediately. Good work Stepan!
You can find the improved CCSlider and other cool stuff here:
http://www.cocos2d-iphone.org/archives/1506

Thursday, March 24, 2011

Pah! is on the app store - the joy of iOS programming...

It is so much fun to create apps for iOS devices. The first time you get your code to run, the first reaction from other people, but the most fun is publishing your product on the app store and watching how the world reacts...

We just published today the first truely voice-activated game for the iPhone - it is called Pah! and it is guaranteed to make you look silly, and laugh hard (or at least make the people around you laugh), and is there a more nobel cause than to make people laugh??

The game idea and design are by Eyal Shahar, and programming was done by Yosi Taguri and myself.
Eyal implemented the game few years ago using Flash, and left it alone for a while, but when he showed us the game we laughed so hard, we knew we must make it into an iPhone game. The first working version was done in few hours, and then it took another two months or so of late night work to make it happen...

The game went up 18 hours ago, and the responses are overwhelming. Here are some:

http://www.mobilecrunch.com/2011/03/24/pah-iphone-game-mouth-sounds/

http://www.theiphoneguru.net/2011/03/24/app-of-the-day-pah-for-iphone-is-voice-activated-and-hilarity-inducing/

http://www.igoiphone.com/?p=1593

http://dailyapp.mobi/index.php/2011/03/pah-for-iphone/

Check our site too: http://ahhhpah.com/

I want to give credit here to the amazing Cocos2d framework that really is fun to work with...

Wednesday, January 5, 2011

Creating a Slider Control in cocos2d

I wanted to add a little slider control to allow the user to set the background music level in a game I am working on. So I created this little CCSliderControl class that I think is cute and useful.

The CCSliderControl is sub-classed from CCLayer. Here is the .h file for it:

 @protocol CCSliderControlDelegate  
 - (void) valueChanged: (float) value tag: (int) tag;  
 @end  
 @interface CCSliderControl : CCLayer {  
      float value;  
      id<CCSliderControlDelegate> delegate;  
      float minX;  
      float maxX;  
 }  
 @property (nonatomic, assign) float value;  
 @property (nonatomic, retain) id<CCSliderControlDelegate> delegate;  
 @end  

In the init of this class instance, I add two sprites - one for the background, and one for the slider thumb:

 -(id) init  
 {  
      if ((self = [super init]))  
      {  
           CCLOG(@"init %@", self);  
           self.isTouchEnabled = YES;  
           value = 0;  
           // add the slider background  
           CCSprite *bg = [CCSprite spriteWithFile:@"slider_background.png"]; //- TODO: add this to some texture atlas  
           [self setContentSize:[bg contentSize]];  
           bg.position = CGPointMake([bg contentSize].width / 2, [bg contentSize].height / 2);  
           [self addChild:bg];  
           // add the slider thumb  
           CGSize thumb_size;  
           CCSprite *thumb = [CCSprite spriteWithFile:@"slider_thumb.png"]; //- TODO: add this to some texture atlas  
           thumb_size = [thumb contentSize];  
           minX = thumb_size.width / 2;  
           maxX = [self contentSize].width - thumb_size.width / 2;  
           thumb.position = CGPointMake(minX, [self contentSize].height / 2);  
           [self addChild:thumb];  
      }  
      return self;  
 }  

The setValue method that sets the value property of the instance also updates the thumb sprite position:

- (void) setValue:(float) newValue
{
    if (newValue < 0) newValue = 0;
    if (newValue > 1.0) newValue = 1.0;
    value = newValue;
    CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
    CGPoint pos = thumb.position;
    pos.x = minX + newValue * (maxX - minX);
    thumb.position = pos;
    
}

Next step is to register for touch events:

-(void) registerWithTouchDispatcher
{
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
}


and then handle the touch events:

-(CGPoint) locationFromTouch:(UITouch *)touch
{
    CGPoint touchLocation = [touch locationInView: [touch view]];
    touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
    CGRect bbox = [self boundingBox];
    touchLocation.x -= bbox.origin.x;
    touchLocation.y -= bbox.origin.y;
    return touchLocation;
}

-(bool) isTouchForMe:(CGPoint)touchLocation
{
    CCSprite *bg = (CCSprite *)[[self children] objectAtIndex:0];
    return CGRectContainsPoint([bg boundingBox], touchLocation);
}

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
    CGPoint location = [self locationFromTouch:touch];
    bool isTouchHandled = [self isTouchForMe:location];
    if (isTouchHandled) {
        CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
        thumb.color = ccYELLOW;
        CGPoint pos = thumb.position;
        pos.x = location.x;
        thumb.position = pos;
    }
    return isTouchHandled; // YES for events I handle
}

-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
    CGPoint location = [self locationFromTouch:touch];
    if ((location.x < minX) || (location.x > maxX))
        return;

    CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
    CGPoint pos = thumb.position;
    pos.x = location.x;
    thumb.position = pos;
}

-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
    CCSprite *thumb = (CCSprite *)[[self children] objectAtIndex:1];
    thumb.color = ccWHITE;
    value = (thumb.position.x - minX) / (maxX - minX);
    [delegate valueChanged:value tag:self.tag];
}


And the result looks like this:



You can get the source code from bitbucket by using mercurial:


$ hg clone https://iroth_net@bitbucket.org/iroth_net/ccslider

Saturday, December 25, 2010

Cocos2D support new and old devices textures

Got a new iPhone game project going - an opportunity to learn some new things which is always fun.
I am using the cocos2d framework, which really saves me a lot of work.
I still would like to get deeper into OpenGL ES some time later, but for this project it will be enough to have cocos2d handle the low level details.

One issue I struggled with is that I need to create fairly detailed animation of the scene background.

Due to the limit on OpenGL ES texture size (especially on older devices where the texture is limited to 1024x1024 pixels), I am faced with two options:

  • Limit the number of frames (In a 1024x1024 texture I can get 9 frames of size 320x340 which is the size of my background area in the game).
  • Construct the background area from multiple animations and compose them programmatically. While possible, this makes the design work more cumbersome as the designer needs to supply the needed graphics and their location.
Using a larger texture size of 2048x2048 gives me enough room for 36 frames, which is sufficient for quite a nice background animation loop, but should I give up all older devices (prior to iPhone 3Gs)?

There are millions of those!

So this leads me to the current solution I am going to use, which is:

Use a 2048x2048 texture for newer devices, and substitute that for a smaller 1024x1024 texture on older devices in run time.

But how should I decide which texture to use?

Well, I will save you all my google searches, as the answer was found right under my nose:

Cocos2d CCConfiguration class!

Here's the code:

    NSString *plistFile;
    int numFrames;
    if ([[CCConfiguration sharedConfiguration] maxTextureSize] < 2048)
    {
        plistFile = @"highQualityAnimation.plist"];
        numFrames = 36;
    }
    else
    {
        plistFile = @"highQualityAnimation.plist"];
        numFrames = 9;
    }
   
    // cache the frames to the sharedSpriteFrameCache
   
    CCSpriteFrameCache * cache = [CCSpriteFrameCache sharedSpriteFrameCache];
    [cache addSpriteFramesWithFile:plistFile];

    // create the animation
    CCAnimation * animation = [[CCAnimation alloc] initWithName:@"animation" delay:0.1];    // set the frame rate as needed
    for ( int i=1; i <= numFrames; ++i )
    {
        NSString * fname = [NSString stringWithFormat:@"animation_frame_%i.png", i];
        [animation addFrame:[cache spriteFrameByName: fname]];
    }

    CCSprite *backgroundSprite = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:@"animation_frame_1.png"]];
    id action = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:animation]];
    [backgroundSprite runAction:action];

    // add the sprite to the scene layer

    [self addChild:backgroundSprite];

That's easy enough, gives good animation on newer devices, and still reasonably support for older devices.

Saturday, December 18, 2010

Easily making the different size icons for iPhone and iPad

This is a small tip for scaling your icon to the different sizes requested by iPhone and iPad applications. I used to do that with Photoshop actions in the past, but if that is not available, you can use this tip with no extra software using Mac OS X Automator:
1. I Start with a high quality 512x512 png file (which you will later rename to iTunesArtwork without the png extension) as this is needed for the App Store.
2. In Automator, create a new Service and add the following steps:
Save this service as iPhoneIcon (or icon57, or whatever).
3. Similarly, make services for the different sizes as listed here:
Icon-72.png (72x72)
Icon@2x.png (114x114)
Icon-Small.png (29x29)
Icon-Small-50.png (50x50)
Icon-Small@2x.png (58x58)
4. Now place your 512 icon in a folder, in the finder, right-click the icon file and select from the services sub-menu the different services, and voila! you got the scaled icon with the right name.
5. Add to your project resources and you are ready to go...

Saturday, December 4, 2010

File Names are Case Sensitive!

Urrrrrrgggggghhhh. Spent two days debugging an issue where my program works well on the simulator, but not on a device. I was debugging directory creations, file createions, file permissions in an attempt to find why a dwnloaded file could not be read. I am downloading a zip file from a server, and unzip it into a sub-directory of the Documents directory of my app.
I used PhoneDisk to look at the file system - all hierarchy is created and it seems the file I am trying to read is there too.
After two days of debugging and trying all kind of things, getting file not find errors, I finally realized that the file is named "File.ini" while I was trying to read "file.ini".
This works fine on the simulator, but not on the device where all file names are case sensitive.
I feel stupid, but at least fixed the bug....

Saturday, November 20, 2010

TTURLRequest tracking download progress

OK, I needed to present a progress bar while downloading a fairly large file to my app.
This presented three challanges:
1. Presenting a progress bar. Well, with three20 this is real easy - I created a view controller that is presented modally and used the Three20 ActivityLabel:
- (void) loadView
{
    [super loadView];
    _downloadProgressLabel = [[TTActivityLabel alloc] initWithStyle:TTActivityLabelStyleBlackBezel];
    _downloadProgressLabel.progress = _progress;
    _downloadProgressLabel.text = @"Downloading My File...";
    [_downloadProgressLabel sizeToFit];
    _downloadProgressLabel.center = self.view.center;
    [self.view addSubview:_downloadProgressLabel];
}

- (void) setProgress:(float)inProgress
{
    _progress = inProgress;
    _downloadProgressLabel.progress = _progress;
}

2. Getting the progress from an asynchronous TTURLRequest. Well, the TTURLRequestDelegate protocol has a method called requestDidUploadData, but this is only used by uploads (POST with some body). Well, thought I will implement something, but a quick google search found me that: https://github.com/facebook/three20/pull/288
So, I was not the first to tackle this... thanks Cemal Eker!

3. After implementing this, I still had an issue. I found out that I made a stupid mistake. I was waiting in a loop for the request to finish doing [NSThread sleepForTimeInterval:0.5]. This caused both the URLRequest to not function as well as my view controller to not show up..
So, don't do that, you are going to get a requestDidFinish in your delegate, so no need to sit in a loop...

Thursday, October 21, 2010

Sizing a launcher item image in Three20

Recently I got deeper into iPhone (or iOS) programming, doing some projects for fun and for fee.
My friend Reuven wanted me to use a framework called Three20 in the context of a project I am doing for XMPie, but the person who really pushed me into it is Yosi Taguri of fiddme.com who gave me a crash course on Three20.
So, I am working now on converting the XMPie project to Three20, and I spent the last couple of hours on trying to get the launcher view work with some images that are of unknown sizes. I initially thought I will have to sub-class the launcher view and all its related classes (TTLauncherItem, TTLauncherButton), but this is of course the wrong way of doing things in Three20...
There is a better way which is to use the stylesheet mechansim of Three20. I read the tutorial by Matt Vague (Three20 stylesheets tutorial), but it was not enough to get what I wanted.
What really helped me figure this one out is a tool named TTStyleBuilder by a guy named Keith Lazuka.
finally I was able to create the following style in my stylesheet to get the images to behave:

- (TTStyle*)launcherButtonXM:(UIControlState)state {
    return
    [TTPartStyle styleWithName:@"image" style: TTSTYLESTATE(launcherButtonImageXM:, state) next:
     
     [TTTextStyle styleWithFont:[UIFont boldSystemFontOfSize:11] color:RGBCOLOR(180, 180,180)
                minimumFontSize:11 shadowColor:nil shadowOffset:CGSizeZero
                  textAlignment:UITextAlignmentCenter verticalAlignment:UIControlContentVerticalAlignmentBottom
                  lineBreakMode:UILineBreakModeClip numberOfLines:1 next:nil]];
}

// Our launcher button image style
- (TTStyle*)launcherButtonImageXM:(UIControlState)state {

    TTStyle* style =
     [TTShapeStyle styleWithShape:[TTRoundedRectangleShape
                                   shapeWithRadius:8] next:
      [TTBoxStyle styleWithMargin:UIEdgeInsetsMake(0, 0, 0, 0)
                          padding:UIEdgeInsetsMake(22, 22, 22, 22)
                          minSize:CGSizeMake(0, 0)
                         position:TTPositionStatic next:
          [TTImageStyle styleWithImageURL:nil defaultImage:nil contentMode:UIViewContentModeScaleAspectFit
                                     size:CGSizeMake(57, 57) next:nil
      ]]];

    if (state == UIControlStateHighlighted || state == UIControlStateSelected) {
        [style addStyle:
         [TTBlendStyle styleWithBlend:kCGBlendModeSourceAtop next:
          [TTSolidFillStyle styleWithColor:RGBACOLOR(0,0,0,0.5) next:nil]]];
    }
   
    return style;
}


You use this style in the launcher by setting the style property of the launcher item:

        launcherItem.style = @"launcherButtonXM:";
        [_launcherView addItem:launcherItem animated:YES];

Three20 is cool!

[Edit] Based on some questions I got, I want to clarify that the style methods above are placed in my XMDefaultStyleSheet which is defined as an inherited class of TTDefaultStyleSheet. I create an instance of this class and set it as the default style sheet in the App Delegate applicationDidFinishLaunching method like that:

    // register our stylesheet
    [TTStyleSheet setGlobalStyleSheet:[[[XMPDefaultStylesheet alloc] init] autorelease]];

Tuesday, July 20, 2010

Startup weekend Tel Aviv Jaffa

Just got back from three days of fun at the startup weekend event in the Peres Center for Peace in Jaffa. It was nice to see a nice Palestinian group joining together their fellow Israeli entrepreneurs and working together to create cool and useful start ups. Compared to the previous event, I felt this time there were more "real" ideas and very high level execution and presentation. I joined a team where we developed an iPhone game for blind people. The game "son of zato" allows blind people to aim and shoot at virtual monsters using audio directional signals. It was great fun!




- Posted using BlogPress from my iPad