With each new iOS device you’ll be sure to hear this argument:
“It doesn’t support Flash! Baaaaah, piece of crap!”
What bothers me most is that this mantra is repeated even by intelligent people, even developers fall into that behavior. But they don’t have an argument other than “I want it, I want it”. No really, it’s actually “I think I want it, I think I want it”. Why?
“Uh … well, Flash games!”
Here’s the problem …
If you like playing Flash games, either some in particular or generally consuming the daily new dose of throwaway games, you’ll be emotionally connected to these games. Of course you would want to play them on any device that seems perfectly capable of running Flash. So I understand the notion of wanting to have Flash on an iOS device. It’s entirely understandable.
What really bothers me, is how little even intelligent people put into what they’re actually demanding. Here’s a smack in the face - think about it!
Other examples include:
“Press any key to continue.”
“Use WASD to control your character, Space to jump, Ctrl to shoot.”
“Move the mouse cursor to the edge of the screen area to scroll in that direction.”
First of all …
All games that make use of the keyboard are out. Add to that the games which require a mousewheel or the distinction of left and right mouse clicks. I bet this will disqualify at least half of all Flash games. I expect that estimate is likely to be much higher if someone were to compile actual statistics of Keyboard and Mouse input use in Flash games.
Next, any game that relies on the concept of “mouse over” won’t work satisfactory, or not at all. There is no “mouse over” on a touchscreen device, there is only “drag & drop”. You have to touch the screen to move the cursor - it’s possible that games will interpret this as some kind of click and click, or drag and drop operation.
Finally, there’s the matter of precision. A mouse cursor has a hotspot that has an exact pixel position. A finger has a touch area, and although you can calculate the center point to be the touch location, it will be quite imprecise. Especially when you consider that users don’t see the exact point they’re touching - their finger is blocking the view. No Flash game has been designed to take into account that the clickable area will have a relatively large range of uncertainty. Many hidden objects games require you to touch locations to almost pixel-perfect accuracy - not to mention the unspeakable horrors of Flash UI Design with small fonts and even smaller buttons.
If you go into details, I’m sure there’ll be plenty more design issues. They may be subtle but overall important enough to make some games less enjoyable, unfair, unplayable or allow for exploits which could skew the highscores towards either mobile or web users. That’s bad.
Technical reasons
There are of course technical reasons why Flash, and specifically Flash games, won’t work well on an iPad or iPhone. The 1 GHz Dual-Core CPU of the iPad 2 is still an order of a magnitude slower than the slowest, Intel-based Mac Mini which clocks in at around 1.8 GHz (also Dual-Core). On older iDevices there’s only a single core ARM CPU with around 500 MHz. And surely you’ve experienced Flash games that didn’t run too well on a Mac mini, right? Imagine how they would perform on a mobile device.
Then we have the memory issue. The iOS devices have at most 512 MB of memory down to 128 MB. Not that bad, unless you consider how much is actually available for Apps. On a 128 MB device it’s just 30-35 MB. Not much you can do with that, and I don’t expect any Flash game to be optimized for this constrained amount of memory. Worse yet, a Flash game won’t receive memory warnings from the device and it’s developer certainly won’t respond to this warning. Plus, since running in the browser, the browser on the iDevice will consume some of the available memory itself. So what you get is some content-heavy Flash games and apps which will simply crash at a certain point, or not run at all because they’re out of memory. That’s not a good user experience.
We also have to consider the issue of screen resolution. Most Flash games run in a window with varying dimensions. On an iOS device, first of all you’d never get the fullscreen experience because the native resolution will almost always be different. Since Flash runs in the browser, the Flash games will have to be zoomed and scaled to fit the screen. In the worst case, the user has to do this manually because double-tapping inside the Flash window probably would be interpreted by Flash as a command, rather than by the browser as the command to zoom and fit the double-tapped content. In any case scaling the content will degrade visual quality while consuming more power because the content needs to be scaled (in most cases: up) by either the CPU or GPU.
What about Loading bars? Do you like loading bars? I’m sure you do. Because on an iDevice, the best you’ll get is a WiFi connection. I suppose that’ll be acceptable. But what if you’re on the road and only get 3G or even Edge? Do you like waiting several minutes for your Flash game to start up every time you run it? I don’t think so. Just try to remember how Flash websites felt back in the 90s when all we had were dial-up connections.
This could be better if …
Yes, if the iOS devices or even the iPads supported Flash, I’m sure Flash developers would take a little more care of the details that they actually can take care of. For example providing a real touch-aware user interface, or designing the whole game with touchscreen devices in mind.
However, only a minority of Flash developers would do so. The low costs of developing Flash apps and games doesn’t make cross-platform compatibility a real possibility for most. They make 90% or more of their revenue from hosting them on ad-supported gaming websites like Kongregate. Any other platform than the web has no relevance to Flash developers, and the iPad simply wouldn’t change that.
Once you do have a successful Flash game, it’s also much more lucrative to simply make an iOS version of it. Take for example Canabalt. It’s free on the web, but it sells for a relatively high price of $2,99 on iTunes. It’s also wildly successful. And it’s also a good example of why many Flash games simply won’t work on an iDevice - the screenshot further up is from the Flash version of Canabalt.
But Flash is good for other things!
Yes, if you like Ads, sure.
Oh, you mean those designer websites that you can’t even figure out how to use on your desktop? No?
What then … don’t tell me Youtube or those other movie sites. For one, there’s an App for that. And trust me, you’ll be too busy doing other stuff with your iPad than watching Youtube videos.
“There’s this site that won’t work …” - Really, you visit www.disney.com every day? You’re adorable!
So far I have not come across a website that’s entirely Flash-driven (eg won’t work without Flash) and that has you coming back. The one that gets closest is github.com - but no point in downloading source code to your iDevice, is there? Most Flash-only websites are once-in-a-lifetime show-off events. A comedy act, if you so will. The second time, it’s just not as much fun. And you can absolutely live without them.
I run a Flashblocker on my browsers, all I’m missing are Ads and rarely a few gizmos that I don’t even notice aren’t there. Almost all websites are intelligent enough to default back to a non-Flash version, which not only works better, it’s also easier to use, loads faster and consumes less memory and CPU time. There you go.
To put it bluntly
Flash has no place on the iPad, or any iDevice.
My real problem with that is the people who repeat the “no Flash support” mantra don’t stop to think about the actual problems in supporting Flash apps and games on a device like the iPad. It just won’t deliver the same experience as it does on the desktop. Those experiences can not be brought over to a mobile device by merely supporting a specific technology.
Moreover, I wish those in favor of Flash support for iPad would actually make an argument, rather than repeating “But that’s what I want.” - you’re like little kids who can literally only be dragged past a candy shop or toy store, crying all along. No toys for you, grow up!
Have a look at the following code, and then answer these questions before reading on:
- Which function will run faster?
- What will be the framerate for each function when run 100 times per frame on an iPhone 3G?
- Will wrapping the 100 calls to function1 in an NSAutoreleasePool show any difference?
[cc lang=”ObjC” height=”465″]
-(void) function1
{
CGPoint pos = [self position];
id x = [NSNumber numberWithFloat:pos.x];
id y = [NSNumber numberWithFloat:pos.y];
id objects = [NSArray arrayWithObjects:x, y, nil];
id keys = [NSArray arrayWithObjects:@”x”, @”y”, nil];
id dict = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
dict; // avoid compiler warning, is a noop
}
-(void) function2
{
CGPoint pos = [self position];
id x = [[NSNumber alloc] initWithFloat:pos.x];
id y = [[NSNumber alloc] initWithFloat:pos.y];
id objects = [[NSArray alloc] initWithObjects:x, y, nil];
id keys = [[NSArray alloc] initWithObjects:@”x”, @”y”, nil];
id dict = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
[x release];
[y release];
[objects release];
[keys release];
[dict release];
}
[/cc]
The Answers
- Which function will run faster? Answer: function1
- What will be the framerate for each function when run 100 times per frame on an iPhone 3G? Answer: 27 fps for function1 and 24 fps for function2.
- Will wrapping the 100 calls to function1 in an NSAutoreleasePool show any difference? Answer: no, but memory of temporary objects is released immediately.
Needless to say, on an iPod (4th Generation) and an iPad these tests all run at 60 fps and give no indication whatsoever that the performance on an iPhone 3G would suffer this much (and neither does the Simulator, of course). All the more reason to test early and often on older devices.
To autorelease or not?
Common wisdom may tell you that alloc/release is faster than autorelease. Even Apple recommends avoiding autorelease, right?
Not quite, because this is often misunderstood: Apple recommends to avoid autorelease but only for functions which create a lot of temporary objects and because of the constrained memory - not because it’s slow or even dangerous - autorelease is not dangerous.
Since memory is so constrained on 1st and 2nd generation iOS devices, it’s best to release that memory as soon as possible and don’t leave it allocated for longer than necessary. To achieve this, you can choose to do two things in this case: use alloc/release or enclose the loop in an NSAutoreleasePool. The latter option is preferred since it will release the memory right away, and not some time later. And autorelease is generally preferable because you will never, ever forget to send a release message to an object - which means it’ll be leaked and forever use up memory.
You can write well-performing, even better-performing code by using autorelease and using NSAutoreleasePool around tight loops creating many temporary autorelease objects.
Innocent looking code kills framerate
Did you expect that creating 100 rather simple NSDictionary instances each frame would drag the framerate down to around 24-27 fps? Me neither. I knew the code wasn’t going to be blazing fast, but I never expected it to have such an impact. However, it can be optimized somewhat since I’m unnecessarily creating two NSArray instances to hold the keys and values respectively before using them to create the NSDictionary. In fact we can get rid of them by using dictionaryWithObjectsAndKeys and doing this in a single step:
[cc lang=”ObjC”]
-(void) function1Optimized
{
CGPoint pos = [self position];
id x = [NSNumber numberWithFloat:pos.x];
id y = [NSNumber numberWithFloat:pos.y];
id dict = [NSDictionary dictionaryWithObjectsAndKeys:x, @”x”, y, @”y”, nil];
dict; // avoid compiler warning, is a noop
}
[/cc]
Sometimes it helps to look around what other ways there are to run the same code. In terms of performance this is an order of a magnitude faster and now clocks in at 42 fps. Still not good enough for realtime rendering obviously but an improvement of over 50% by cutting two NSArray allocations is a very simple and effective optimization.
Just as a general guideline, when I get rid of the two NSNumber instances and simply pass empty strings for x and y the framerate went back up to 60 fps. Of course that’s over-optimizing to the point where the code doesn’t work anymore. It just goes to show how expensive the creation of NSDictionary and NSArray are, as is wrapping simple types in NSNumber or NSValue objects.
If you can avoid allocation and temporary objects, avoid it. If you can’t, at least avoid creating temporary objects every frame. Re-use objects as much as possible. Unfortunately, that’s not an option for NSNumber objects since you can’t change the value of a NSNumber instance.
I have a seemingly simple requirement for my project: each node should have preUpdate and postUpdate methods in addition to the regular update method. Preprocessing and postprocessing before and after all entities have run their “update” logic is a common requirement in game development. Sometimes called the setup stage before processing all entities in the world, and the finalize stage to complete the current game step and possibly resolve collisions or check any win, lose or achievements conditions.
Of course there are alternative and some may argue “better” implementations, after all an experienced game dev might argue that using pre and post update methods are merely signs of a badly designed game update loop (the jury is out on that one). However beginning game developers definitely understand this concept much better and every game-making tool I know implements pre and post updates, so it has its validity. Plus I find it more convenient than “outsourcing” some logic to a central game loop.
On the other hand it’s also bad practice to rely on one node’s update method to be called before or after all others. Especially once you get to prioritizing update method calls among various groups of nodes it’s just going to get you into trouble. If you set the priority wrong just once you can introduce very subtle issues that’ll be hard to track down. Think of some objects not colliding with the player, or some monsters crashing when you shoot at them with a specific weapon. Scheduled methods should never rely on the order they are called for various entities.
Not so easy …
It turns out the system in Cocos2D only allows one update method per node. That means for each node you can only have one update selector, and via the priority parameter you can only control when its update method is called relative to all other nodes’ update method. In that way you can at least ensure that the player’s update method is always called first, or last, depending on your requirements. But it’s bad programming practice to do so.
I thought, why not just schedule a “regular” selector with CCScheduler? Well, they don’t have a priority, and they are always called after any update method, so you could never schedule a “before update” method this way. And those scheduled selectors aren’t meant to be used for selectors that are called every frame due to the additional processing overhead (eg allocating, updating and releasing the CCTimer object, and comparing & updating time elapsed every frame).
So I had to write a class called ScheduleMultiUpdate which creates proxy instances, registers them with CCScheduler to receive an update message of their own, and then they forward that message to the actual node implementing the special update selector. By using the priority you can define if the selector is called before or after the regular update by using a negative (before) respectively a positive priority (after, must be 1 or greater).
Example Code
Here’s an example usage scheduling the beforeUpdate and afterUpdate selectors to be called before and after the regular update method.
[cc lang=”ObjC” height=”550″]
// in interface (.h)
KKScheduleMultiUpdate* multiUpdate;
// in implementation (.m)
-(void) scheduleUpdateMethods
{
multiUpdate = [[KKScheduleMultiUpdate alloc] initWithNode:self];
[multiUpdate scheduleUpdateSelector:@selector(beforeUpdate:) priority:-1];
[self scheduleUpdateWithPriority:priority];
[multiUpdate scheduleUpdateSelector:@selector(afterUpdate:) priority:1];
}
// this has to be cleanup because CCScheduler retains scheduled targets
// we have to give KKScheduleMultiUpdate a chance to unschedule its selectors or it won’t be deallocated
-(void) cleanup
{
[multiUpdate release];
multiUpdate = nil;
[super cleanup];
}
-(void) beforeUpdate:(ccTime)delta
{
}
-(void) update:(ccTime)delta
{
}
-(void) afterUpdate:(ccTime)delta
{
}
[/cc]
Here’s the actual implementation of KKScheduleMultiUpdate and its proxy class. Notice how the KKScheduleMultiUpdate simply retains a list of proxies and creates new ones, while the proxy class registers itself with CCScheduler to schedule its own update method based on the given priority, and then forwards the message to the baseNode. There’s currently no way to unschedule an update selector, but it should be fairly easy to add if you ever need to unschedule an update method while the game is running (I’d rather just use a bool and skip update).
@interface
[cc lang=”ObjC” height=”500″]
#import “cocos2d.h”
/** Allows you to schedule multiple update methods per node. */
@interface KKScheduleMultiUpdate : NSObject
{
CCNode* baseNode;
CCArray* updateProxies;
}
-(id) initWithNode:(CCNode*)node;
-(void) scheduleUpdateSelector:(SEL)selector priority:(int)priority;
@end
/** Since there can be only one update selector scheduled per instance, this proxy will implement
that update method and forward the message to the desired selector. */
@interface KKScheduleMultiUpdateProxy : NSObject
{
id target;
SEL selector;
TICK_IMP impMethod;
}
-(id) initWithTarget:(id)target selector:(SEL)selector priority:(int)priority;
@end
[/cc]
@implementation
[cc lang=”ObjC” height=”500″]
#import “KKScheduleMultiUpdate.h”
@implementation KKScheduleMultiUpdate
-(id) initWithNode:(CCNode*)node
{
if ((self = [super init]))
{
NSAssert(node != nil, @”%@ %@ - node is nil!”, NSStringFromClass([self class]), NSStringFromSelector(_cmd));
baseNode = [node retain];
updateProxies = [[CCArray alloc] initWithCapacity:1];
}
return self;
}
-(void) dealloc
{
CCLOG(@”%@ %@”, NSStringFromSelector(_cmd), self);
[baseNode release];
baseNode = nil;
// must do a cleanup to unschedule CCSelector, this is because CCScheduler retains scheduled targets
[updateProxies makeObjectsPerformSelector:@selector(cleanup)];
[updateProxies release];
updateProxies = nil;
NSAssert([self retainCount] == 1, @”%@ %@ - retainCount on cleanup should be 1 but is %i”, NSStringFromClass([self class]), NSStringFromSelector(_cmd), [self retainCount]);
[super dealloc];
}
-(void) scheduleUpdateSelector:(SEL)selector priority:(int)priority
{
NSAssert([baseNode respondsToSelector:selector], @”%@ %@ - node %@ does not respond to selector”, NSStringFromClass([self class]), NSStringFromSelector(_cmd), baseNode);
// TODO: might want to check here if the selector happens to be already scheduled
KKScheduleMultiUpdateProxy* proxy = [[KKScheduleMultiUpdateProxy alloc] initWithTarget:baseNode selector:selector priority:priority];
[updateProxies addObject:proxy];
[proxy release];
}
@end
@implementation KKScheduleMultiUpdateProxy
-(id) initWithTarget:(id)target_ selector:(SEL)selector_ priority:(int)priority
{
if ((self = [super init]))
{
NSAssert(target_ != nil, @”target is nil”);
NSAssert(selector_ != nil, @”selector is nil”);
target = target_; // weak ref
selector = selector_; // weak ref
impMethod = (TICK_IMP)[target methodForSelector:selector];
NSAssert(impMethod != nil, @”selector is not an instance method of target %@”, target);
[[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:priority paused:NO];
}
return self;
}
-(void) cleanup
{
CCLOG(@”%@ %@”, NSStringFromSelector(_cmd), self);
[[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self];
impMethod = nil;
NSAssert([self retainCount] == 1, @”%@ %@ - retainCount after cleanup should be 1 but is %i”, NSStringFromClass([self class]), NSStringFromSelector(_cmd), [self retainCount]);
}
-(void) dealloc
{
CCLOG(@”%@ %@”, NSStringFromSelector(_cmd), self);
[super dealloc];
}
-(void) update:(ccTime)delta
{
impMethod(target, selector, delta);
}
@end
[/cc]
As always, you’re free to use this code. I release it under the MIT License. You do not need to include a copyright notice, but I’d appreciate any mention or backlink to this website.
I’m now hosting an unofficial Cocos2D (for iOS & Mac OS) API Reference, accessible via the Knowledge Base tab.
What’s so “unofficial” about it?
My version of the API reference includes previously undocumented classes, some are documented but just not in the official documentation, others are completely undocumented but at least now you know they’re there. Currently, there is no additional documentation added other than what’s in the official cocos2d source code files, and as far as I can tell nothing is missing. If there is, please let me know.
Since at least the release of v0.99.5 about a month ago important classes like CCLayer, CCArray and CCDirectorIOS have been missing from the official API reference. Did anyone even take notice, or was it just me? I hope it will be fixed soon and the missing classes added back to the official API reference.
I’d like to call it “unofficial” just to make sure everyone gets the idea that I’m not involved in the development of Cocos2D in any way, and my version of the API reference may contain crucial omissions or mistakes as well.
Angry Birds Tutorial causes slowdown
Current and well-deserved cause of the cocos2d-iphone.org slowdown is the SpaceManager Game Tutorial with source code by the mobile bros. LLC. The tutorial shows you how to make an Angry Birds style of game.
An ongoing discussion about how to correctly send/receive data with Game Kit on Cocos2D Central prompted me to write a demo project to complement the Learn Cocos2D book’s Game Center chapter. The resulting working code is in this post, and also reprinted below.
The Game Kit Data Send/Receive Demo Project (download here) is based on the Learn Cocos2D book code made for the Game Center Chapter. It’s called Tilemap16 just to stay in line with the naming scheme.
You control the little Ninja as usual, but this time anytime you move it, it will also move on all other connected devices because the tile coordinate is sent via Game Kit to all connected players but only when it actually changes. The demo even allows you to move the Ninja on any device, and all others will follow, so it truly works in every direction. In addition, a score variable (int) is transmitted every frame just to show how to send different types of data at different times.
I wish I could have made a movie to show how cool this is but alas, my iPod was busy running the demo, so all I can show as proof is my iPod and iPad running the game with both Ninjas at the same position (mushy image made with my already-ancient iPhone 3G under perfect programming but terrible lighting conditions):
Source Code Example
In summary this is the code that’s used to send/receive data with the help of the GameKitHelper class I wrote for the book (also included in the download and free to use for anyone and any purpose):
[cc lang=”ObjC”]// add this to the header
typedef enum
{
kPacketTypePoint,
} EPacketTypes;
typedef struct
{
EPacketTypes type;
} SPacketInfo;
typedef struct
{
EPacketTypes type;
CGPoint point;
} SPointPacket;
[/cc]
Sending a packet:
[cc lang=”ObjC”]
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint location = [touch locationInView: [touch view]];
SPointPacket packet;
packet.type = kPacketTypePoint;
packet.point = location;
gkHelper = [GameKitHelper sharedGameKitHelper];
[gkHelper sendDataToAllPlayers:&packet length:sizeof(packet)];
}
[/cc]
Receiving the packet:
[cc lang=”ObjC”]
-(void) onReceivedData:(NSData*)data fromPlayer:(NSString*)playerID
{
// first, assume it’s the general SPacketInfo, that way we can access type
SPacketInfo* packet = (SPacketInfo*)[data bytes];
switch (packet->type)
{
case kPacketTypePoint:
{
SPointPacket* pointPacket = (SPointPacket*)packet;
CCLOG(@”received point: %.1f, %.1f”, pointPacket->point.x, pointPacket->point.y);
break;
}
default:
CCLOG(@”received unknown packet type %i”, packet->type);
break;
}
}
[/cc]
Requirements & Setup
You need:
- 2 devices which are Game Center capable, eg they must have the Game Center App installed. If it isn’t, that device is not ready for Game Center. 1st & 2nd Generation devices up to iPhone 3G do not support Game Center. In addition Simulator will not work due to Matchmaking/Invites not being supported on the Simulator.
- 2 Game Center accounts (you can create dummy accounts via the Game Center App)
What you have to do:
- Create a new App on iTunes Connect, enable Game Center for that App, and replace the Bundle ID of your App with the one in the project’s info.plist. Please refer to the Game Center section in the iTunes Connect documentation.
- Make sure Push Notifications are enabled on all devices via Settings App.
- Run the Game Center App on both devices, login and make sure each device is logged in with a unique account. Then invite the other device’s account as friend and accept the friend request, this makes it easier to join each other’s matches.
- Build the Tilemap16 App and deploy it to both devices. Whenever you make a change to the code, you must deploy the App to both devices again, to make sure they run the same code.
- Run the App on one device, wait for the matchmaking screen to appear. Invite your other device’s friend account to join the match.
- Wait on the other device for the match invite to pop up. Tap Accept. The Tilemap16 game will launch.
- Tap Play Now on the first device once the other player has successfully connected and is ready.
- Move the Ninja on either device and watch it move on the other.
Enjoy!