Sprite Kit Tutorial: How To Drag and Drop Sprites

Jean-Pierre Distler
Drag and drop these cute pets with Cocos2D!

Drag and drop these cute pets with Sprite Kit!

Note from Ray: Tutorial Team member Jean-Pierre Distler has ported this tutorial from Cocos2D to Sprite Kit as part of the iOS 7 feast. We hope you enjoy!

In this tutorial, you’re going to learn:

  • The basics of dragging and dropping sprites with touches
  • How to scroll the view itself via touches
  • How to keep coordinates straight in your head
  • How to use gesture recognizers with Sprite Kit for even more cool effects!

To make things fun, you’ll be moving some cute animals around the scene drawn by Ray’s wife Vicki, on a background made by gwebstock.

This tutorial assumes you at least know the basics of Sprite Kit. If you are completely new to Sprite Kit, be sure to check out our Sprite Kit Tutorial for Beginners first.

So without further ado, drag your fingers over to the keyboard and let’s get started!

Getting Started

Before you implement the touch handling, first you’ll create a basic Sprite Kit scene displaying the sprites and artwork.

Open up XCode, go to File\New Project, choose Application\SpriteKit Game, and click “Next“.

Selecting the Sprite Kit Game template

Name the project DragDrop, select iPhone for devices, and click Next. Now save the project to your disc.

Name_Project

Like you did in the Sprite Kit Tutorial for Beginners, you want to make this app landscape only. So select your DragDrop project in the Project Navigator, select your DragDrop target, and make sure only Landscape Left and Landscape Right are checked.

Setting landscape orientation for Sprite Kit

Also the same as you did before, open ViewController.m and replace viewDidLoad with the following:

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];
 
    // Configure the view.
    SKView * skView = (SKView *)self.view;
    if (!skView.scene) {
      skView.showsFPS = YES;
      skView.showsNodeCount = YES;
 
      // Create and configure the scene.
      SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
      scene.scaleMode = SKSceneScaleModeAspectFill;
 
      // Present the scene.
      [skView presentScene:scene];
    }
}

Next, go ahead and download the images you’ll need for this tutorial. Once you download and unzip the file, drag all of the images into your project. Verify that “Copy items into destination group’s folder (if needed)” is checked, and click Finish.

Add_Assets
Once you’ve added the images to your project, open the Classes group in XCode, and select MyScene.m. Add a class extension above the @implementation and declare two properties you’ll need like the following:

@interface MyScene () 
 
@property (nonatomic, strong) SKSpriteNode *background;
@property (nonatomic, strong) SKSpriteNode *selectedNode;
 
@end

You’ll be using these later on to keep track of the background image, the currently selected node/sprite. Now add the following line before the @interface part.

static NSString * const kAnimalNodeName = @"movable";

You will use this String to identify your movable nodes later. Now find the initWithSize: method, and replace it’s content with the following:

- (id)initWithSize:(CGSize)size {
    if (self = [super initWithSize:size]) {
 
        // 1) Loading the background
        _background = [SKSpriteNode spriteNodeWithImageNamed:@"blue-shooting-stars"];
        [_background setName:@"background"];
        [_background setAnchorPoint:CGPointZero];
        [self addChild:_background];
 
        // 2) Loading the images
        NSArray *imageNames = @[@"bird", @"cat", @"dog", @"turtle"];
        for(int i = 0; i < [imageNames count]; ++i) {
          NSString *imageName = [imageNames objectAtIndex:i];
          SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:imageName];
          [sprite setName:kAnimalNodeName];
 
          float offsetFraction = ((float)(i + 1)) / ([imageNames count] + 1);
          [sprite setPosition:CGPointMake(size.width * offsetFraction, size.height / 2)];
          [_background addChild:sprite];
        }
    }
 
    return self;
}

There’s some new stuff in here, so let’s go over it step by step.

1) Loading the Background

The first part of the method loads the background image for the scene (blue-shooting-stars.png). Note that it sets the anchor point of the image to the lower left of the image (0, 0).

In Sprite Kit, when you set the position of a node, you are actually setting where the anchor point of the node is. By default, the anchor point is set to the exact center of the node. However, by setting the anchor point to the lower left corner, when you set the position of the node, you are now setting where the lower left corner is.

The method doesn’t set the position of the background anywhere, so the position of the background defaults to (0,0). Hence, the lower left corner of the image is located at (0,0), and so the image (which is about 800 points long) extends off screen to the right.

2) Loading the Images

The next part of the method loops through the list of images to load. For each image a node is created and placed on the scene. The nodes are distributed along the length of the screen, to have a nice initial layout. It also sets the name to kAnimalNodeName.

Setting the name of a node avoids the need of holding a reference to a node, in case you need it later. After the sprite is created it is added to the _background node.

That’s it! Build & run your code, and you should see a couple cute animals sitting there, just begging to be touched!

DragDrop first run

Selecting Sprites based on Touches

Now you’ll write the code to determine which sprite is selected based on the user’s current touch.

Replace touchesBegan:withEvent: with the following:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint positionInScene = [touch locationInNode:self];
    [self selectNodeForTouch:positionInScene];
}

First you get the touch from the touches set. Then you convert the touches location to the location in a specific node. Here the node is your scene. With this position you call selectNodeForTouch:. This is a new method that you will add now to your scene.

- (void)selectNodeForTouch:(CGPoint)touchLocation {
   //1
   SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
 
      //2
	if(![_selectedNode isEqual:touchedNode]) {
		[_selectedNode removeAllActions];
		[_selectedNode runAction:[SKAction rotateToAngle:0.0f duration:0.1]];
 
		_selectedNode = touchedNode;
		//3
		if([[touchedNode name] isEqualToString:kAnimalNodeName]) {
			SKAction *sequence = [SKAction sequence:@[[SKAction rotateByAngle:degToRad(-4.0f) duration:0.1],
													  [SKAction rotateByAngle:0.0 duration:0.1],
													  [SKAction rotateByAngle:degToRad(4.0f) duration:0.1]]];
			[_selectedNode runAction:[SKAction repeatActionForever:sequence]];
		}
	}
 
}

This method is a helper method that is doing three different things.

  1. It asks your scene (self) for the node that is on the position touchLocation.
  2. After it gets the matching node, the method checks if the node is the same as the previous selected node. In this case there is nothing to do and the method returns. If you touched a new node or you haven‘t touched a node yet. The node should run a „wiggle“ animation so you can see which node you selected. But before you start the animation you remove all running actions from the currently selected node and run a rotateToAngle:duration: action on the current node. This makes sure that only one node is animated and that a node goes to it’s original, unrotated state when another node is selected.
  3. This if-statement checks if the selected node is one of the animatable nodes. You can check this easily with the name property that you have set in your initWithSize: method. If you selected a animatable node you create a sequence of actions that do the „wiggle“ animation (like the one on the home screen when rearranging/deleting apps) and run thos sequence on the selected node. Normally an action runs for the duration you created it with. To avoid the „wiggle“ animation from stopping after it is finsihed you run it as an action that is repeated forever.

Now add the helper function degToRad to the bottom of your file:

float degToRad(float degree) {
	return degree / 180.0f * M_PI;
}

Sprite Kit uses radians for rotation so this function converts an angle given in degree to radians.

Build & Run your code, and you should now be able to tap on the animals, and when you tap them they should wiggle in a particularly cute way to show that they are selected!

Sprites wiggling indicating selection

Moving Sprites and the Layer based on Touches

Time to make these animals move! The basic idea is you’ll implement touchesMoved:withEvent:, and figure out how much the touch has moved since last time. If an animal is selected, it will move the animal by that amount. If an animal is not selected, it will move the entire layer instead, so that the user can scroll the layer from left to right.

Before you add any code though, let’s take a minute to discuss how you can scroll a node in Sprite Kit.

Start by taking a look at the image below:

Scrolling layers with Cocos2D

As you can see, you’ve set up the background so the anchor point (the lower left) is at (0, 0), and the rest extends off to the right. The black area indicates the current visible area (the size of the window).

So if you want to scroll the image 100 points to the right, you can do that by moving the entire Sprite Kit node 100 points to the left, as you can see in the second image.

You also want to make sure you don’t scroll too far. For example, you shouldn’t be able to move the layer to the right, since there would be a blank spot.

Now that you’re armed with this background information, let’s see what it looks like in code! Add the following new methods to the bottom of your file:

- (CGPoint)boundLayerPos:(CGPoint)newPos {
    CGSize winSize = self.size;
    CGPoint retval = newPos;
    retval.x = MIN(retval.x, 0);
    retval.x = MAX(retval.x, -[_background size].width+ winSize.width);
    retval.y = [self position].y;
    return retval;
}
 
- (void)panForTranslation:(CGPoint)translation {
    CGPoint position = [_selectedNode position];
    if([[_selectedNode name] isEqualToString:kAnimalNodeName]) {
        [_selectedNode setPosition:CGPointMake(position.x + translation.x, position.y + translation.y)];
    } else {
        CGPoint newPos = CGPointMake(position.x + translation.x, position.y + translation.y);
        [_background setPosition:[self boundLayerPos:newPos]];
    }
}

The first method boundLayerPos: is used for making sure you don’t scroll the layer beyond the bounds of the background image. You pass in where you’d like to move the layer, and it modifies what you pass in to make sure you don’t scroll too far.

If you have any troubles understanding what’s going on here, consult the picture above and draw it out on paper.

The next method panForTranslation: first checks if _selectedNode is an animal node and sets the position based on a passed-in translation. If the selected node is the background layer it set‘s also the position but calls boundLayerPos: to make sure that you cannot scroll to far to the left or right.

Now you can implement touchesMoved:withEvent:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
	UITouch *touch = [touches anyObject];
	CGPoint positionInScene = [touch locationInNode:self];
	CGPoint previousPosition = [touch previousLocationInNode:self];
 
	CGPoint translation = CGPointMake(positionInScene.x - previousPosition.x, positionInScene.y - previousPosition.y);
 
	[self panForTranslation:translation];
}

Like you did in touchesBegan:withEvent: you get the touch and convert it‘s position to the position in your scene. To calculate the translation, thus how far you’re finger moved on the screen. You need the previous location of the touch.

With the current and previous location you create the translation by subtracting the current location from the last one. Finally you call panForTransaltion: with the calculated translation.

Give it a shot – compile and run your code, and you should now be able to move the sprites (and the layer!) around by dragging!

Dragging sprites with touch with Cocos2D

How to Use Gesture Recognizers with Sprite Kit

There’s another way to accomplish what you just did with Sprite Kit touch handling – use gesture recognizers instead!

Gesture recognizers are a great way to detect different gestures like taps, double taps, swipes or pans.

Basically, instead of having to write a bunch of crazy looking code to detect the difference between taps, double taps, swipes, pans, or pinches, you simply create a gesture recognizer object for what you want to detect, and add it to the view. It will then give you a callback when that occurs!

They are extremely easy to use, and you can use them with Sprite Kit with no troubles. Let’s see how that works.

First, comment out the touch handling methods, touchesBegan:withEvent: and touchesMoved:withEvent: since you will be using a different method now.

Then add the following method to your scene:

- (void)didMoveToView:(SKView *)view {
    UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanFrom:)];
    [[self view] addGestureRecognizer:gestureRecognizer];
}

This method gets called when the scene is first presented. Here you create a pan gesture recognizer and initialize it with your scene as target and handlePanFrom: as callback. Next you add the gesture recognizer to your scenes presenting view.

Note: You may ask yourself why recognizer is added here and not in the init method of your scene. The answer is simple. SKScene has a property view that holds the SKView that is presenting the scene, but unfortunately this property is first set when the scene is presented on the screen. So the property is nil when your init method gets called. didMoveToView: is like viewDidAppear: from UIKit and gets called after your scene was presented.

Next, add the following to the bottom of your MyScene.m:

- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer {
	if (recognizer.state == UIGestureRecognizerStateBegan) {
 
        CGPoint touchLocation = [recognizer locationInView:recognizer.view];
 
        touchLocation = [self convertPointFromView:touchLocation];
 
        [self selectNodeForTouch:touchLocation];
 
 
    } else if (recognizer.state == UIGestureRecognizerStateChanged) {
 
        CGPoint translation = [recognizer translationInView:recognizer.view];
        translation = CGPointMake(translation.x, -translation.y);
        [self panForTranslation:translation];
        [recognizer setTranslation:CGPointZero inView:recognizer.view];
 
    } else if (recognizer.state == UIGestureRecognizerStateEnded) {
 
        if (![[_selectedNode name] isEqualToString:kAnimalNodeName]) {
            float scrollDuration = 0.2;
            CGPoint velocity = [recognizer velocityInView:recognizer.view];
            CGPoint pos = [_selectedNode position];
            CGPoint p = mult(velocity, scrollDuration);
 
            CGPoint newPos = CGPointMake(pos.x + p.x, pos.y + p.y);
            newPos = [self boundLayerPos:newPos];
            [_selectedNode removeAllActions];
 
            SKAction *moveTo = [SKAction moveTo:newPos duration:scrollDuration];
            [moveTo setTimingMode:SKActionTimingEaseOut];
            [_selectedNode runAction:moveTo];
        }
 
    }
}

This callback gets called when the pan gesture begins, changes (i.e the user continues to drag), and ends. The method switches on each case, and does the appropriate action.

When the gesture begins, it converts the coordinates to node coordinates (note it has to do it the long way because there’s no shortcut method), and calls the selectNodeForTouch: helper you wrote earlier.

When the gesture changes, it needs to figure out the amount the gesture moved. One of the nice things about gesture recognizers it actually stores for you the cumulative translation for the gesture so far! However, you have to reverse the y coordinate to take into effect the difference between UIKit coordinates and Sprite Kit coordinates.

After panning for the translation, it resets the translation on the recognizer to zero, because otherwise the translation is cumulative, and you just want the difference each time.

When the gesture ends, there’s some new and interesting code in here! Another cool thing a UIPanGestureRecognizer gives you is the velocity of the pan movement. You can use this to animate the background node to slide a bit, so the user can flick quickly to get the background to slide a bit, like you’re used to seeing in table views.

So this section contains a bit of code to calculate a point to move based on the velocity, and running a moveTo action (with SKActionTimingEaseOut to make it feel a bit better) for a neat effect.

Before you can Build & Run your code you have to add a last helper function. Add the following function to the bottom:

CGPoint mult(const CGPoint v, const CGFloat s) {
	return CGPointMake(v.x*s, v.y*s);
}

This function just multiplies your velocity with the scroll duration.
Build & Run your code, and you should now be able to slide and move around, all with gesture recognizers!

Moving sprites with UIGestureRecognizers with Cocos2D

Where To Go From Here?

Here is the final project with all of the code from this tutorial.

At this point you should know how to move nodes using touches in your Sprite Kit apps and games, and should know the basics of using gesture recognizers with Sprite Kit.

From here, you could try extending this project with other gesture recognizers, such as perhaps pinch or rotate gesture recognizers. Maybe you can make the cat grow!

If you want to learn more about Sprite Kit, you should check out our book iOS Games by Tutorials. We’ll teach you everything you need to know – from physics, to tile maps, to particle systems, and even making your own level editor.

If you have any questions or comments about this tutorial, please join the discussion below!

Jean-Pierre Distler

Jean-Pierre Distler is an iOS Developer in Braunfels, Germany. He develops apps for 3+ years and is also interested in IT-Security and penetration testing.

When he's not on his computer he enjoys time with his daughters.

Other Items of Interest

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 19 total!

Swift Team

... 16 total!

iOS Team

... 30 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Apple Game Frameworks Team

... 10 total!

Unity Team

... 11 total!

Articles Team

... 11 total!

Resident Authors Team

... 11 total!