Sprite Kit is one of the most exciting new technologies available with the iOS 7 SDK and Xcode 5, but how does it compare against an established game engine like Cocos2D? This tutorial will provide a brief introduction to Sprite Kit before taking a comprehensive look at how it stacks up against Cocos2D.
Introducing Sprite Kit
Of all the available games in the App Store, many of the most downloaded and most profitable are 2D games. Some iconic titles in this category include Angry Birds, Tiny Wings, and Cut the Rope. Driving the success of these games are several common characteristics: beautiful graphics, particle effects, a physics engine, seamless animation, and compelling sound effects.
Before the release of the iOS 7 SDK, building games like these was only possible with the use of third-party frameworks and engines. Now, with the introduction of Sprite Kit, developers need look no further than the native SDK to find everything they need to be able to build great 2D and 2.5D games. The functionality provided by Sprite Kit includes Sprites, Shapes, Particles (e.g. fire, smoke, etc.), Animations, Physics Simulation, Audio, Video, and Visual Effects. Xcode 5 now also provides support for texture packs and particle design.
Sprite Kit can be logically abstracted into the following three parts:
- Scenes - As in Cocos2D, Scenes are the visual layer of the game. It is where you manage the background, the objects (like trees, cars, airplanes, avatars, etc).
- Actions - Smooth animations are a vital part to any game. Apple designed the action system in an intuitive fashion, and it allows you to do almost anything. Some of the most common actions presented are: move, fade, scale, resize, rotate, animate with textures, and group actions. Further, if a particular action is not defined, you can always create a custom block of code to form your own action and manipulate that object.
- Physics - If you want a game that behaves realistically, you need to add a physics engine. You don't want a bullet that does not follow a specific trajectory, a ball that does not jump when it hits the ground, and other such amateurish effects. Fortunately, Sprite Kit comes with a physics engine baked in.
Why Sprite Kit?
There are some very solid benefits to having a 2D and 2.5D game platform provided and maintained by Apple. Consider the following points:
Native Performance
Native development and native tools are all about performance.
Despite the fact that developers typically want their games to run on as many different platforms as possible, a native game will almost always have better performance than a non-native game. Plus, if the tools to develop those games are native, one can ensure that the code is integrated with the platform ecosystem.
Platform Integration
As mentioned above, Sprite Kit and Xcode 5 combine many of the essential components to building great games. This means that development can be more streamlined and tools will be more reliable and effective.
Future Proof Development
Writing a game using a third-party framework or game engine is always a two-edged sword. We never know if the tools will be compatible with future platform updates, or even if the game will run well after an update. When things do break, it's uncertain how long it will take the community to fix the bugs.
Cocos2D is an example of an open-source project that must deal with this problem. The code is constantly evolving, and at every new release several safety steps need to be performed in order to guarantee that applications built with Cocos2D will still run on the newest version of iOS and latest hardware.
With Sprite Kit, Apple has provided a set of tools to ensure that game code will work on every compatible device without any trouble. Note that Sprite Kit is not just an iOS framework. Developers can start building Sprite Kit games for OS X as well, and it's a safe bet that Sprite Kit games will run on any future iOS devices as well.
Developer Friendly
Ease of use was a major factor behind the success of game engines like Cocos2D. Generally speaking, developers found Cocos2D much easier to implement than other native alternatives like OpenGL ES. With Cocos2D, all the low level API calls were transformed in simple methods.
Sprite Kit follows this approach and offers hundreds of methods that makes the game development process a lot easier. Sprite Kit is friendly as well. It has the custom, well-designed Apple API and comes with a complete structured documentation. Apple has done an outstanding job of sharpening this tool for third party devleopers to use. The biggest advantage of all is that it comes fully loaded with every resource that you need to create a game. Physics, sound effects, particle effects, textures, scene management -everything is included in a single bundle.
Note that, at the initial presentation of Sprite Kit, Ricardo Quesada, the lead developer of Cocos2D said the following on Twitter:
Sprite Kit is very good. With less features than Cocos2D, but better. I like the physics integration.
This is high praise coming from one of the leading minds behind Cocos2D!
Sprite Kit & Cocos2D Features
Feature | Sprite Kit | Cocos2D |
Open Source | No | Yes |
Objective-C Native Support | Yes | Yes |
Graphics Engine | Yes | Yes |
Animations | Yes | Yes |
Physics Simulation | Yes (Integrated) | No (Requires Box2D or Chipmunk) |
Particle Effects | Yes | Yes |
Xcode Native Integration | Yes | No |
Automatic Atlas Creation | Yes | No |
Built-In Particle Editor | Yes | No |
Shaders | No | Yes |
Camera | No | Yes |
Project Comparison
So, what do projects with each game engine actually look like? To answer this question, the authors have included the complete source code for both a Sprite Kit and a Cocos2D project. You can use these projects as a high-level comparison of each game engine.
Source Code Comparison
In this section we'll actually breakdown common tasks and concepts, showing how to implement them in both Cocos2D and Sprite Kit.
CClayer vs. SKScene
CCLayer or SkScene is the main object used to draw other objects. You can think of this as the default view that will receive all the objects, animations, and touch events.
The transition between scenes in Cocos2D is done with the following steps:
GameScene* gameScene = [[GameScene alloc] init]; [[CCDirector sharedDirector] replaceScene:gameScene];
Note that the GameScene.h
file must be of the CCLayer
category and have the specific initializer available.
@interface GameScene : CCLayer {} +(CCScene *) scene;
In GameScene.m
, the initial implementation is:
+(CCScene *)scene { CCScene *scene = [CCScene node]; GameScene *layer = [GameScene node]; [scene addChild: layer]; return scene; } -(id) init{ if( (self=[super init] )) { // Your code here } return self; }
In Sprite Kit the transition is similar:
GameScene* gameScene = [[GameScene alloc] initWithSize:CGSizeMake(1024, 768)]; [self.scene.view presentScene:gameScene]; }
The GameScene
must be of the SKScene
category, and the -(id)initWithSize:(CGSize)size
is the custom initializer. A simple example:
-(id)initWithSize:(CGSize)size { if (self = [super initWithSize:size]) { // Your code } return self; }
CCSprite vs. SKSpriteNode
Sprite objects are normally used to display some kind of image. It can have several properties, such as: rotation, scale, position, frames, ids, and more. The implementation of both Cocos2D and Sprite Kit are similar. The Cocos2D implementation is:
CCSprite* aSprite; aSprite = [CCSprite spriteWithFile:@"player.png"]; aSprite.scale = .5; aSprite.position = ccp(_size.width/1.30, _size.height/1.25); [self addChild:aSprite];
While in Sprite Kit the implementation is:
SKSpriteNode* planeShadow = [SKSpriteNode spriteNodeWithImageNamed:@"player.png"]; planeShadow.scale = 0.5; planeShadow.position = CGPointMake(CGRectGetMidX(self.frame)+100,CGRectGetMidY(self.frame)+200); [self addChild:planeShadow];
CCLabelTTF vs. SKLabelNode
Label objects are used to display text. It can have several properties, including text, text size, text color, position, and many others. The implementation of both Cocos2D and Sprite Kit are similar. The Cocos2D implementation is:
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64]; // ask director for the window size CGSize size = [[CCDirector sharedDirector] winSize]; label.position = ccp( size.width /2 , size.height/2 ); [self addChild: label];
The Sprite Kit implementation is:
SKLabelNode* gameScene = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"]; [gameScene setText:@"New Game"]; [gameScene setFontSize:18]; gameScene setPosition:CGPointMake(CGRectGetMidX(self.frame)+5,CGRectGetMidY(self.frame)-40)]; [self addChild:gameScene];
CCMenu and CCMenuItem vs. Sprite Kit Menu
In Cocos2D, the menus are created using two objects: CCMenu
and CCMenuItem
. The following example presents a menu with 2 options in Cocos2D:
CGSize size = [[CCDirector sharedDirector] winSize]; [CCMenuItemFont setFontSize:28]; CCMenuItem *itemNewGame = [CCMenuItemFont itemWithString:@"New Game" block:^(id sender) { // Your code }]; CCMenuItem *itemOptions = [CCMenuItemFont itemWithString:@"Options" block:^(id sender) { NSLog(@"Second item"); }]; CCMenu *menu = [CCMenu menuWithItems:itemNewGame, itemOptions, nil]; [menu alignItemsHorizontallyWithPadding:20]; [menu setPosition:ccp( size.width/2, size.height/2 - 50)]; [self addChild:menu];
SpiteKit does not include any type of menu specific object. You need to create an event handler to a specific object in order to activate it for user input. So, in order to "create" a menu you must use a UIKit object or a Sprite Kit object.
The following example uses a SKLabelNode
as a menu item. First, we define the SKLabelNode
:
SKLabelNode* gameScene = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"]; [gameScene setText:@"New Game"]; [gameScene setFontSize:18]; [gameScene setPosition:CGPointMake(CGRectGetMidX(self.frame)+5,CGRectGetMidY(self.frame)-40)]; [self addChild:gameScene];
Inside the -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method we will create the event handler that will intercept the touch event:
for (UITouch *touch in touches) { CGPoint location = [touch locationInNode:self]; if ([gameScene containsPoint:location]) { // Scene Transition Animation SKTransition* reveal = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:1]; GameScene* gameScene = [[GameScene alloc] initWithSize:CGSizeMake(1024, 768)]; [self.scene.view presentScene:gameScene transition:reveal]; NSLog(@"Touched gameScene!!!!"); } }
The aforementioned code does several things:
- Activates the touch events.
- Converts the tap location to the internal location.
- Tests if the tap location is inside the gameScene
SKLabelNode
object. - Creates a transition animation.
- Changes the scene.
Action vs. SKAction
The main difference between Action and SKAction is that SKAction is a complex object with several properties. Action in Cocos2D is only an action that the programmer must define, call, and treat.
With Sprite Kit, SKAction offers several options to the developers, such as rotation, resize, scale, repeat, fade, play sound, and more. SKaction can be seen as an abstract object that deals with any kind of action, from sound, to sprites, to nodes.
We'll focus for the moment on movement actions.
In Cocos2D we need to define a scheduler to call a custom method:
[self schedule:@selector(addSprite:) interval:1];
And then define the custom method to do the custom animation.
- (void) addSprite:(ccTime)dt { CCSprite* aMovableSprite = [CCSprite spriteWithFile:@"frankenstein.png"]; aMovableSprite.scale = .8; [self addChild:aMovableSprite]; CGSize winSize = [CCDirector sharedDirector].winSize; int minX = aMovableSprite.contentSize.width / 2; int maxX = winSize.width - aMovableSprite.contentSize.width/2; int rangeX = maxX - minX; int actualY = (arc4random() % rangeX) + minX; CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) { NSLog(@"Sprite free!"); }]; NSMutableArray *arrayBezier = [[NSMutableArray alloc] init]; ccBezierConfig bezier; id bezierAction1; float splitDuration = 6 / 6.0; for(int i = 0; i< 6; i++){ if(i % 2 == 0){ bezier.controlPoint_1 = ccp(actualY+100,winSize.height-(100+(i*200))); bezier.controlPoint_2 = ccp(actualY+100,winSize.height-(100+(i*200))); bezier.endPosition = ccp(actualY,winSize.height-(200+(i*200))); bezierAction1 = [CCBezierTo actionWithDuration:splitDuration bezier:bezier]; } else{ bezier.controlPoint_1 = ccp(actualY-100,winSize.height-(100+(i*200))); bezier.controlPoint_2 = ccp(actualY-100,winSize.height-(100+(i*200))); bezier.endPosition = ccp(actualY,winSize.height-(200+(i*200))); bezierAction1 = [CCBezierTo actionWithDuration:splitDuration bezier:bezier]; } [arrayBezier addObject:bezierAction1]; } [arrayBezier addObject:actionMoveDone]; id seq = [CCSequence actionsWithArray:arrayBezier]; [aMovableSprite runAction:seq]; }
In Sprite Kit, we can use SKAction to control what happens to an object at the beginning and at the end of movement. The next lines show how to move any object in a straight line:
SKSpriteNode* playerSprite = [SKSpriteNode spriteNodeWithImageNamed:@"player.png"]; [playerSprite setScale:0.4]; SKAction *movement =[SKAction moveTo:CGPointMake(900, 500) duration:5]; SKAction *remove = [SKAction removeFromParent]; [playerSprite runAction:[SKAction sequence:@[movement,remove]]]; [self addChild:playerSprite];
However, we can define a custom action and use SKAction to activate that action. The following example exemplifies a Bézier movement (similar to the Cocos2D version of Action). Note that we must define a scheduler to call a custom method.
SKAction *wait = [SKAction waitForDuration:1]; SKAction *callEnemies = [SKAction runBlock:^{ [self sendNewSKSpriteNode]; }]; SKAction *updateSKSpriteNodeOnScreen = [SKAction sequence:@[wait,callEnemies]]; [self runAction:[SKAction repeatActionForever:updateSKSpriteNodeOnScreen]];
The method sendNewSKSpriteNode
will handle the custom object movement.
-(void) sendNewSKSpriteNode{ CGRect screenRect = [[UIScreen mainScreen] bounds]; // Custom SKAction SKSpriteNode* enemy = [SKSpriteNode spriteNodeWithImageNamed:@"frankenstein.png"]; enemy.scale = 0.6; CGMutablePathRef cgpath = CGPathCreateMutable(); //random values float xStart = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ]; float xEnd = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ]; //ControlPoint1 float cp1X = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ]; float cp1Y = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.height ]; //ControlPoint2 float cp2X = [self getRandomNumberBetween:0+enemy.size.width to:screenRect.size.width-enemy.size.width ]; float cp2Y = [self getRandomNumberBetween:0 to:cp1Y]; CGPoint s = CGPointMake(xStart, 1024.0); CGPoint e = CGPointMake(xEnd, -100.0); CGPoint cp1 = CGPointMake(cp1X, cp1Y); CGPoint cp2 = CGPointMake(cp2X, cp2Y); CGPathMoveToPoint(cgpath,NULL, s.x, s.y); CGPathAddCurveToPoint(cgpath, NULL, cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y); SKAction *planeDestroy = [SKAction followPath:cgpath asOffset:NO orientToPath:YES duration:5]; [self addChild:enemy]; SKAction *remove2 = [SKAction removeFromParent]; [enemy runAction:[SKAction sequence:@[planeDestroy,remove2]]]; CGPathRelease(cgpath); }
CCParticleExplosion vs. Emitter
Cocos2D does not have any kind of particle editor. One must use an external app to create the particle and then use specific CCParticleExplosion
properties to change its behavior. After you have the particle in your Xcode project you can call it using:
CCParticleExplosion* _particleExplosion; particleExplosion = [[CCParticleExplosion alloc] initWithTotalParticles:800]; particleExplosion.texture = [[CCTextureCache sharedTextureCache] addImage:@"texture.png"]; particleExplosion.life = 0.0f; particleExplosion.lifeVar = 0.708f; particleExplosion.startSize = 40; particleExplosion.startSizeVar = 38; particleExplosion.endSize = 14; particleExplosion.endSizeVar = 0; particleExplosion.angle = 360; particleExplosion.angleVar = 360; particleExplosion.speed = 243; particleExplosion.speedVar = 1; CGPoint g = CGPointMake(1.15, 1.58); particleExplosion.gravity = g; ccColor4F startC = {0.89f, 0.56f, 0.36f, 1.0f}; particleExplosion.startColor = startC; ccColor4F endC = {1.0f,0.0f,0.0f,1.0f}; particleExplosion.endColor = endC; [self addChild:_particleExplosion]; particleExplosion.position = ccp(_size.width/5, _size.height/5); [particleExplosion resetSystem];
Emitters are used within Sprite Kit for particle generation. In order to use them, you need to add a particle to your project. Go to New -> File -> Resource -> Sprite Kit Particle File
. Next you should name it and choose any type of particle (fire, magic, smoke, snow, among others). Now you will see that two new files will appear on your Xcode project. You'll implement them with:
SKEmitterNode* smokeTrail; NSString *smokePath = [[NSBundle mainBundle] pathForResource:@"MyParticle" ofType:@"sks"]; smokeTrail = [NSKeyedUnarchiver unarchiveObjectWithFile:smokePath]; smokeTrail.position = CGPointMake(CGRectGetMidX(self.frame)+40,CGRectGetMidY(self.frame)-100); [self addChild:smokeTrail];
The SKEmitterNode class is extensive and contains several properties. We advise you to read it in order to learn every property that an emitter node can have.
SimpleAudioEngine vs. Sprite Kit Sound
Sound is an active part of any game or multimedia application. In Cocos2D, we can achieve that with two steps. The first is to include the SimpleAudioEngine
header file.
#import "SimpleAudioEngine.h"
Then you use the following lines to call the music file inside our project:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"sound.caf" loop:YES]; [[SimpleAudioEngine sharedEngine] setEffectsVolume:0.4f];
Sometimes, Xcode does no automatically include the music file in the "Copy Bundle Resources". If that happens you should add it manually.
With Sprite Kit, the inclusions of sounds is straightforward:
SKAction* soundAction = [SKAction playSoundFileNamed:@"preview.mp3" waitForCompletion:NO]; [self runAction:soundAction];
Note that to accomplish this with Sprite Kit you once again used the SKAction object.
Conclusion
As you can see from the above analysis, Cocos2D and Sprite Kit have many similarities. Cocos2D uses several layers for each object, while Sprite Kit encapsulate more objects and uses the NSObject super class to achieve certain objectives (like buttons or menus).
In terms of user friendliness, Sprite Kit really shines when you want to use the Particle system or the Action performer. However, when working with more general objects, both frameworks are at about the same level of difficulty.
Nonetheless, building a game with Sprite Kit brings many key benefits, including a fully integrated physics engine, streamlined workflow tools in Xcode 5, compatibility with both iOS and OS X, and official maintenance by Apple.
The question is: which will you use for your next 2D game project? Let us know in the comments.
Comments