Friday, November 6, 2015

My Experiments with SpriteKit: Part Three - Textures!

Let's create an interesting sprite from the ground up. We'll do everything!

Start with the code we've written already. Remove the sprite code that we've written, even remove the image from the Assets.xcassets folder. Your GameScene should look like this:
#import "GameScene.h"

@interface GameScene ()
@property CGPoint center;
@property CGFloat height;
@end

@implementation GameScene

-(void)didMoveToView:(SKView *)view {
    self.backgroundColor = [NSColor blackColor];
    _height = CGRectGetHeight(self.frame);
    _center = CGPointMake(CGRectGetMidX(self.frame),
                          CGRectGetMidY(self.frame));
}

-(void)update:(CFTimeInterval)currentTime {
    
}

@end
I've also set the background colour of the scene to black. Build and run this and you'll get an application with a black window.

What does our scene need? It needs a border...

Let's make a nice gold (RED=255, GREEN=215, BLUE=0) border. It should hug the scene frame, have rounded inside corners and be 15 points thick.

SKSpriteKit has a couple of methods we can use to create our sprite: spriteNodeWithImageNamed: and spriteNodeWithTexture:. The first one is great if we already have a named image, for example we may already have an image that we have placed in our Assets.xcassets folder. And the second one is great if we have a Texture of type SKTexture.

Texture objects manage graphics resources that can be applied to our sprites when they are being rendered. Let's use spriteNodeWithTexture: method to create a sprite that we can use as a border to our scene. Sometimes it's necessary to work backwards towards our goal, let's try that.

-(void)didMoveToView:(SKView *)view {
    self.backgroundColor = [NSColor blackColor];
    _height = CGRectGetHeight(self.frame);
    _center = CGPointMake(CGRectGetMidX(self.frame),
                          CGRectGetMidY(self.frame));
    
    SKSpriteNode* border;
    border = [SKSpriteNode spriteNodeWithTexture:<#(nullable SKTexture *)#>];
    border.position = self.center;
    [self addChild:border];
}
So we need a SKTexture. Let's add one of those:
SKSpriteNode* border;
SKTexture* texture = [SKTexture textureWithImage:<#(nonnull NSImage *)#>];
border = [SKSpriteNode spriteNodeWithTexture:texture];
border.position = self.center;
[self addChild:border];
Right, now we need an NSImage:
SKSpriteNode* border;
NSImage* image;
image = [[NSImage alloc] initWithSize:self.frame.size];
// ???
SKTexture* texture = [SKTexture textureWithImage:image];
border = [SKSpriteNode spriteNodeWithTexture:texture];
border.position = self.center;
[self addChild:border];
OK, we're back to a build-able and run-able project. But it's still a black window. We have a sprite, having a texture that contains a image that has the same size as the window. Maybe... Let's find out by drawing the border.

In order to draw in the image we need to obtain a graphics context for the image, this turns out to be pretty easy with the NSImage method lockFocus.  When we're done drawing our image, we'll call unlockFocus.
NSImage* image;
image = [[NSImage alloc] initWithSize:self.frame.size];
[image lockFocus];
// ???
[image unlockFocus];
We just need to figure out how to draw the border. It's easy: use NSBezierPath to describe the outline of our border and then fill it in.
[image lockFocus];
NSBezierPath* path = [NSBezierPath bezierPathWithRect:self.frame];
[path appendBezierPathWithRoundedRect:CGRectInset(self.frame, 15, 15)
                              xRadius:25
                              yRadius:25];
// ???
[image unlockFocus];
That defines the path we want to fill: a large rect that is the size of view, and a smaller rounded rect with the same centre 15 points inside of the large rect. Now, we need to apply the path, with the gold colour to the image.
[image lockFocus];
NSBezierPath* path = [NSBezierPath bezierPathWithRect:self.frame];
[path appendBezierPathWithRoundedRect:CGRectInset(self.frame, 15, 15)
                              xRadius:25
                              yRadius:25];
[[NSColor colorWithRed:(255.0/255.0)
                 green:(215.0/255.0)
                  blue:(0.0/255.0)
                 alpha:1.0] set];
path.windingRule = NSEvenOddWindingRule;
[path fill];
[image unlockFocus];
the set method, called on an NSColor, sets the drawing fill and stroke colour to that of the receiver. The windingRule applies to the fill method that we use to actually draw the path and fill it with the gold colour. I chose the NSEvenOddWindingRule simply because it caused the fill to work correctly, inside the two rectangles as opposed to filling both rectangles (actually, there's a good reason to use NSEvenOddWindingRule, I explain why in another post).

Finally, call the fill method on our path. Here's the application:


A very nice border. Let's add one more sprite to set us up for the next blog post in this series. But first, a bit of refactoring. The didMoveToView: method is getting a bit ungainly:
-(void)didMoveToView:(SKView *)view {
    self.backgroundColor = [NSColor blackColor];
    _height = CGRectGetHeight(self.frame);
    _center = CGPointMake(CGRectGetMidX(self.frame),
                          CGRectGetMidY(self.frame));
    
    SKSpriteNode* border;
    NSImage* image;
    image = [[NSImage alloc] initWithSize:self.frame.size];
    [image lockFocus];
    NSBezierPath* path = [NSBezierPath bezierPathWithRect:self.frame];
    [path appendBezierPathWithRoundedRect:CGRectInset(self.frame, 15, 15)
                                  xRadius:25
                                  yRadius:25];
    [[NSColor colorWithRed:(255.0/255.0)
                     green:(215.0/255.0)
                      blue:(0.0/255.0)
                     alpha:1.0] set];
    path.windingRule = NSEvenOddWindingRule;
    [path fill];
    [image unlockFocus];
    
    SKTexture* texture = [SKTexture textureWithImage:image];
    border = [SKSpriteNode spriteNodeWithTexture:texture];
    border.position = self.center;
    [self addChild:border];
}
So we should pull the border creation code out into it's own method.
-(void)addBorderofThickness:(CGFloat)t andCornerRadius:(CGFloat)cr {
    SKSpriteNode* border;
    NSImage* image;
    image = [[NSImage alloc] initWithSize:self.frame.size];
    [image lockFocus];
    NSBezierPath* path = [NSBezierPath bezierPathWithRect:self.frame];
    [path appendBezierPathWithRoundedRect:CGRectInset(self.frame, t, t)
                                  xRadius:cr
                                  yRadius:cr];
    [[NSColor colorWithRed:(255.0/255.0)
                     green:(215.0/255.0)
                      blue:(0.0/255.0)
                     alpha:1.0] set];
    path.windingRule = NSEvenOddWindingRule;
    [path fill];
    [image unlockFocus];
    
    SKTexture* texture = [SKTexture textureWithImage:image];
    border = [SKSpriteNode spriteNodeWithTexture:texture];
    border.position = self.center;
    [self addChild:border];
}
Now we can create the border in didMoveToView with just one line.

On to the new sprite! I want a retro game style spaceship... Here's the drawing code:
NSImage* image;
image = [[NSImage alloc] initWithSize:CGSizeMake(30, 30)];
[image lockFocus];
NSBezierPath* path = [NSBezierPath bezierPath];
path.lineWidth = 2.5;
[path moveToPoint:CGPointMake(0, 0)];
[path lineToPoint:CGPointMake(15, 30)];
[path lineToPoint:CGPointMake(30, 0)];
[path lineToPoint:CGPointMake(15, 10)];
[path closePath];
[[NSColor colorWithRed:(255.0/255.0)
                 green:(215.0/255.0)
                  blue:(0.0/255.0)
                 alpha:1.0] set];
[path stroke];
[image unlockFocus];
 Go ahead and make the sprite, you'll get something like:



In part four we'll let the user rotate the space ship and move it around.

Wednesday, November 4, 2015

My Experiments with SpriteKit: Part Two - Movement!


At the end of Part One, we had a cute little kitten, or some other image, displayed right in the middle of our scene.

Your GameScene.m implementation should look like this:
#import "GameScene.h"

@implementation GameScene

-(void)didMoveToView:(SKView *)view {
    CGFloat midX = CGRectGetMidX(self.frame);
    CGFloat midY = CGRectGetMidY(self.frame);
    CGPoint center = CGPointMake( midX, midY);
    SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithImageNamed:@"cat"];
    sprite.position = center;
    [self addChild:sprite];
}

-(void)update:(CFTimeInterval)currentTime {  
}
@end
You might have a different image name.

The grey background is a bit dull, so lets brighten it up a bit with sky blue, RGB=(135, 206, 235). SKGameScene has a backgroundColor property of type NSColor. We can set that property in didMoveToView:
-(void)didMoveToView:(SKView *)view {
    self.backgroundColor = [NSColor colorWithRed:(135.0/255.0)
                                           green:(206.0/255.0)
                                            blue:(235.0/255.0)
                                           alpha:1.0];
    CGFloat midX = CGRectGetMidX(self.frame);
    CGFloat midY = CGRectGetMidY(self.frame);
    ...
The colorWithRed:green:blue:alpha: takes CGFloat values in the range of 0 to 1.0 as parameters, so we need to scale the RGB values.

This puts a nice blue background behind our kitty:


Let's move our kitty around a bit now. Recall that our kitty sprite has a position property of type CGPoint, so we can change that variable to move it.

The update: method of our GameScene is called once per frame. Based on the debug info in the lower left of the scene, we are getting about 60 frames per second. The parameter being passed to update is the current system time, a NSTimeInterval type. As a simple experiment we can use that time value to move our sprite around the centre of scene.

First, we need a way to access our sprite from the update: method. One way is to make use of the childNodeWithName: method of SKNode. Since SKGameScene is-a SKNode, and we have added our sprite as a child of our GameScene, we can get our sprite. All we need to do is set the name property of our sprite in the didMoveToView: method:
-(void)didMoveToView:(SKView *)view {
    self.backgroundColor = [NSColor colorWithRed:(135.0/255.0)
                                           green:(206.0/255.0)
                                            blue:(235.0/255.0)
                                           alpha:1.0];
    CGFloat midX = CGRectGetMidX(self.frame);
    CGFloat midY = CGRectGetMidY(self.frame);
    CGPoint center = CGPointMake( midX, midY);
    SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithImageNamed:@"cat"];
    sprite.name = @"cat";
    sprite.position = center;
    [self addChild:sprite];
}
In the update: method we can access our sprite, and make it move:
-(void)update:(CFTimeInterval)currentTime {
    SKSpriteNode* sprite = (SKSpriteNode*)[self childNodeWithName:@"cat"];
    CGFloat midX = CGRectGetMidX(self.frame);
    CGFloat midY = CGRectGetMidY(self.frame);
    sprite.position = CGPointMake(midX + 200 * cos(currentTime), midY + 200 * sin(currentTime));
}
There's a bit of code duplication, so lets refactor things a bit by adding a CGPoint property for the centre of the scene in an interface extension:
#import "GameScene.h"

@interface GameScene ()
@property CGPoint center;
@end

@implementation GameScene

-(void)didMoveToView:(SKView *)view {
    self.backgroundColor = [NSColor colorWithRed:(135.0/255.0)
                                           green:(206.0/255.0)
                                            blue:(235.0/255.0)
                                           alpha:1.0];
    _center = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
    SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithImageNamed:@"cat"];
    sprite.name = @"cat";
    sprite.position = self.center;
    [self addChild:sprite];
}

-(void)update:(CFTimeInterval)currentTime {
    SKSpriteNode* sprite = (SKSpriteNode*)[self childNodeWithName:@"cat"];
    sprite.position = CGPointMake(self.center.x + 200 * cos(currentTime),
                                  self.center.y + 200 * sin(currentTime));
}
@end
And finally, lets make our kitty sprite a property as well, then we can remove the call to childNodeWithName:, which may make things a bit quicker:
#import "GameScene.h"

@interface GameScene ()
@property CGPoint center;
@property SKSpriteNode* kitty;
@end

@implementation GameScene

-(void)didMoveToView:(SKView *)view {
    self.backgroundColor = [NSColor colorWithRed:(135.0/255.0)
                                           green:(206.0/255.0)
                                            blue:(235.0/255.0)
                                           alpha:1.0];
    _center = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
    _kitty = [SKSpriteNode spriteNodeWithImageNamed:@"cat"];
    self.kitty.name = @"cat";
    self.kitty.position = self.center;
    [self addChild:self.kitty];
}

-(void)update:(CFTimeInterval)currentTime {
    self.kitty.position = CGPointMake(self.center.x + 200 * cos(currentTime),
                                  self.center.y + 200 * sin(currentTime));
}
@end
There, now we have a kitty, or whatever, sprite moving around the centre of a game scene!

Next time, lets look at generating textures for our sprites, then we can add a border and a background to our scene.

Tuesday, November 3, 2015

My Experiments with SpriteKit: Part One - Sprites!


Yay! Sprites! I remember, vaguely, creating sprites on my C64 back in the '80's. Long before *you* were born! Back then we had peek and poke and do some sort of nerd wizard pickery-pokery magic to make a blocky image that could be moved on our 320x200 pixel screen...

And we were happy! And then we walked to school, in the snow, uphill...

Ok, lets see what we can do on OS X.

The goal of this experiment is to create a scene that contains a sprite that we have created all by ourselves!!!

For this experiment lets open Xcode (Version 7.1) and create a new project.

     File->New->Project

which opens the template dialog where you can select Application from the OS X section, and then the Game template. And press Next.

For Product Name I chose SimpleSprite, Objective-C  for Language and SpriteKit for Game Technology. No Unit or UI tests. Press Next and Create on the following Dialog and you have a runnable project! Run it and you get a game scene with the text Hello, World!,  mouse clicking on the scene gets you a spinning jet plane sprite..

Let's delete the template code we don't need, so we can learn how to create sprites. Open GameScene.m and delete everything except empty didMoveToView and update methods:
#import "GameScene.h"

@implementation GameScene

-(void)didMoveToView:(SKView *)view {
}

-(void)update:(CFTimeInterval)currentTime {
}

@end
 
Go ahead and run, you will see an application with an empty scene save for the debug info at the bottom right. We'll leave the debug info for now.

Sprites, in sprite kit, are represented by the SKSpriteNode class which is a SKNode class. SKNodes are the building blocks of the sprite kit api. The GameScene class  is a SKScene. SKScene is the root of the SKNodes that provide the content for the scene we are creating. So... SKScene holds a tree of SKNodes, and we will be instantiating and adding SKNodes.

We're going to add our sprite in the didMoveToView method which, according to the Xcode Quick Help, is Called immediately after a scene is presented by a view. Here's the code
-(void)didMoveToView:(SKView *)view {
    CGFloat midX = CGRectGetMidX(self.frame);
    CGFloat midY = CGRectGetMidY(self.frame);
    CGPoint center = CGPointMake( midX, midY);
    SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithColor:[NSColor redColor]
                                                        size:CGSizeMake(20.0, 20.0)];
    sprite.position = center;
    [self addChild:sprite];
}
we expect this to place a red square right in the middle of the application screen:


Great! We can add a sprite to the scene! Look at what we did. First we identified the centre of the scene using the CGRectGetMid functions to get the x and y coordinates of the centre of the screen from the CGRect self.frame. We could have also used (self.frame.size.width/2.0).

Then, we instantiated a SKSpriteNode using the factory class method spriteWithColor:size:. We could have also used the initWithColor:size: method, as long as we called alloc.

Sprites are initialized with a position of CGPointZero, so we need to set the position to the centre of the view. Note that the anchor point of our sprite is the centre of the sprite, more about this in a later post.

The final step is to add our sprite to the scene using the addChild method.

Let's do something more interesting than a red square. Find a nice image that would make a good sprite and drag it into the Assets.xcassets folder in your Project.



I found a nice cat photo that I cropped and gave a transparent background. Now we can rewrite our didMoveToView method:

-(void)didMoveToView:(SKView *)view {
    CGFloat midX = CGRectGetMidX(self.frame);
    CGFloat midY = CGRectGetMidY(self.frame);
    CGPoint center = CGPointMake( midX, midY);
    SKSpriteNode* sprite = [SKSpriteNode spriteNodeWithImageNamed:@"cat"];
    sprite.position = center;
    [self addChild:sprite];
}
which gives us:



All it needs is a nice background! Perhaps in part two..