Since getting my Mac earlier this year, I've been on the lookout for a decent Photoshop replacement. Why replace Photoshop? Well, there are a number of reasons:
It's expensive. Even if I'm able to write it off on my taxes, CS5 runs $700. The features that I use don't justify that price tag.
It cements Adobe's monopoly. Buying CS5 means that I help keep the system in place - a world where there aren't that many great alternatives to Photoshop.
It's bloaty and slow. Photoshop has notoriously bad performance on Macs. Adobe has been dragging its' heels with any sort of performance enhancements.
I figure that these reasons are good enough to pursue alternatives. The question is, what alternatives are there? Well, right now there are two popular "independent" image editing programs for Mac: Pixelmator and Acorn. I tried both, and ended up choosing Acorn. My reasoning:
Acorn has a more standard UI. Acorn looks like a regular Mac application. While some developers try to differentiate their apps by using non-standard chrome, I think that following Apple's UI guidelines is a big plus. Pixelmator uses a black background for all its' windows, which I've never seen in an OS X app before. The result is that conflicts horribly with other windows that you might have open, and looks very out of place.
Acorn's toolset is a suitable replacement for Photoshop. There are some minor things that Acorn doesn't have (such as an option for aliased text), but on the whole it has all the functionality that I need. Acorn can even do stuff like having two windows open that display the same document at different zoom levels.
Acorn is cheaper. Hah, OK, I'm nitpicking here, but Acorn is $10 cheaper than Pixelmator (and $650 cheaper than Photoshop).
Acorn is shareware, but you can still use a stripped down version after your trial expires. It's nice that the developers still allow you to use a portion of Acorn's functionality, even after the demo expires. Much nicer than totally disabling the app. Since I don't have a chance to work with a new program every day, sometimes my shareware trials expire, and I have to search around for whatever preference file stores the "first run" date on my system, and delete it. Or I just delete the program, because I wasn't favorably impressed enough.
In the previous tutorial I wrote about scenes and layers, the basic structural classes of a cocos2d app. In this next installment, I'm going to talk about putting some actual content into your scenes and responding to user input.
If we're interested in getting user input, first we should create something that responds to that input. In most cases, this will be a sprite object. A sprite is usually a representation of an entity in your game system, such as the player, an enemy, or a power-up. When you create a new sprite, you give the sprite an image as a parameter. After you add the sprite to a containing layer, that image then appears on the screen, and you can move it around by manipulating properties of the sprite object. A simple example:
This is the easiest way to get an image onto the screen. Once you've created a sprite, you can transform it in various ways.
[mySprite setPosition:ccp(30, 30)]; // Change position
[mySprite setScale:2.0]; // Make 2x bigger
[mySprite setRotation:180.0]; // Rotate
Wait, this post was supposed to be about detecting user touches, right? OK, let's create an example app that will use the ubiquitous "pinch" gesture to scale a sprite that's displayed in the center of the screen.
Create a new project using the regular cocos2d template. I named mine "TouchExample." Open up the Classes group in the Groups & Files sidebar, click on the HelloWorldScene.h file, then modify the class declaration as follows:
@interface HelloWorld : CCLayer
{
CCSprite *mySprite;
}
@property (nonatomic, retain) CCSprite *mySprite;
// returns a Scene that contains the HelloWorld as the only child
+(id) scene;
@end
Basically what you're doing here is creating a reference to a sprite object that can be accessed from any method in the HelloWorld layer. That means you can create the sprite in your "init" method, then reference it and move it around in a different method later. Next, open the HelloWorldScene.m file. Navigate to the "init" method, and replace the code there:
// Creates "setters" and "getters" for this sprite
@synthesize mySprite;
- (id)init
{
if ((self = [super init]))
{
// ask director the the window size
CGSize size = [[CCDirector sharedDirector] winSize];
// Set layer to respond to touch events
[self setIsTouchEnabled:TRUE];
// Create sprite and add to layer
mySprite = [CCSprite spriteWithFile:@"Icon.png"];
[mySprite setPosition:ccp(size.width / 2, size.height / 2)];
[self addChild:mySprite];
}
return self;
}
What we're doing here is telling the layer that we want it to respond to touches. Then we create a sprite using the app icon and slap it in the center of the screen. Next, add the following methods after "init." When you called the "setIsTouchEnabled" method of the layer in the init method, the following three methods are available to be overridden with your own logic.
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"Touches began!");
}
// Override the "ccTouchesMoved:withEvent:" method to add your own logic
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// This method is passed an NSSet of touches called (of course) "touches"
// "allObjects" returns an NSArray of all the objects in the set
NSArray *touchArray = [touches allObjects];
// Only run the following code if there is more than one touch
if ([touchArray count] > 1)
{
// We're going to track the first two touches (i.e. first two fingers)
// Create "UITouch" objects representing each touch
UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = [touchArray objectAtIndex:1];
// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];
CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];
// The touch points are always in UIKit coordinates
// You will need to convert them to OpenGL coordinates (which have an inverted y-axis)
pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];
pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];
// Get the distance between the touch points
float distance = sqrt(pow(pointOne.x - pointTwo.x, 2.0) + pow(pointOne.y - pointTwo.y, 2.0));
// Scale the distance based on the overall width of the screen (multiplied by a constant, just for effect)
float scale = distance / [CCDirector sharedDirector].winSize.width * 5;
// Set the scale factor of the sprite
[mySprite setScale:scale];
}
}
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"Touches ended!");
}
You can see that there are three phases we can use to get info about user touches: touchesBegan, touchesMoved, and touchesEnded. Right now we're only concerned about what happens when touches move around the screen, so the other two methods just have logging statements in them to prove that they're firing.
The ccTouchesMoved: method is automatically given an NSSet of UITouch objects when it is called. You can see in the commented code how those objects are converted down into CGPoint structs that contain regular Cartesian coordinates, which can then be used in a meaningful way. If your game is set up to run in landscape mode (or can toggle between portrait and landscape), you'll need to send the CGPoint coordinates to the CCDirector to be converted to the current orientation. However, if your game is portrait-only (like, *ahem*,Nonogram Madness), you can omit that step.
Once the x/y values of the two touch points are obtained, they get plugged into the Pythagorean theorem to find the distance between them. That distance is scaled by the total screen width, then applied to the sprite as a scaling factor.
The last step that needs to be taken is that we need to tell the app that it should recognize multiple touches. To do that, open up TouchExampleAppDelegate.m and find where the OpenGL view is created (do a search for "EAGLView *glView"). After the long initialization, type:
[glView setMultipleTouchEnabled:TRUE];
Build & run the project in the iOS simulator, and hold down the Option key to make two touch points with your mouse. You can see that the sprite scales up and down based on the distance between the touches.
Congrats! You have the basics of getting user input in your cocos2d app. Play around with the project and see what other sorts of ways you can manipulate the sprite. One problem with this example is that the sprites' scale factor is reset each time you touch the screen again. An interesting reader exercise might be to retain the scale factor, so that the interaction feels more "natural." I've attached my solution (which is probably needlessly complex) in an Xcode project file.
Hey, so a new version of Nonogram Madness is out! I'd been wanting to do an update since the first version was released, since I boned the keyword list for the app when I first submitted it (you can't change keywords unless you upload a new version). This update makes Nonogram Madness an "universal" app, which means it has both iPhone/iPod touch and iPad versions in the same app. To make the iPad version, I upscaled all my art assets by 2x, and introduced some new controls since the screen is so much larger. Instead of manipulating the crosshairs to mark blocks, you can just directly touch the puzzle grid. I also tweaked the "mark" behavior, so that it makes it a bit easier to mark lots of blocks at the same time, and added 10 new levels just to round out the update. Oh yeah, there's a new icon too. I'm not sure how I feel about the icon, but I think it depicts the game more accurately than the generic "N" logo. If you've already got the game, make sure to update! Otherwise, you can grab it on the App Store.
To be honest, I feel that cocos2d-iphone doesn't really require that much knowledge of Objective-C, at least in my experience so far. I know the basics of the language, but really have yet to delve deeply. However, I realize that I've been posting cocos2d tutorials without actually doing an intro to Objective-C. From a practical standpoint, the big differences between Objective-C and C++ are syntax (obviously) and memory management.
With cocos2d, memory management has not been an issue for me, since all cocos2d objects are "autoreleased," that is, you don't have to deallocate their memory when you're finished using them. Basically you can "fire and forget." The Objective-C syntax is slightly wonky, but once you look through some example code and work through some tutorials, you'll get the hang of it.
I'm not going to go super in-depth and make an Obj-C tutorial right now, but I figured I'd share some links that have helped me out a lot.