12

I am working on a Game like Scrabble on SpriteKit and have been stuck on Zooming and Scrolling the Scrabble Board. First Let me Explain the working behind the game: On my GameScene I Have:

  • A SKNode subclass called GameBoard Layer (named NAME_GAME_BOARD_LAYER) containing following Children:

    A SKNode subclass for Scrabble Board named NAME_BOARD.
    A SKNode subclass for Letters Tile Rack named NAME_RACK.
    

    The Letters Tiles are picked from the Tile Rack and dropped at the Scrabble Board.

The problem here is, I need to mimic the zooming and scrolling which can be achieved by UIScrollView, which I think cant be added on a SKNode. The Features I need to mimic are:

  • Zoom at the precise location where the user has Double-Tapped
  • Scroll around (Tried PanGestures, somehow creates issue with tiles dragging-dropping)
  • Keep the Zoomed SKNode in the Particular Area (Like UIScrollView keeps the zoomed content in the scrollView bounds)

Here is the Code I have used for Zooming, using UITapGestures:

In my GameScene.m

- (void)didMoveToView:(SKView *)view {
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                             action:@selector(handleTapGesture:)];
tapGesture.numberOfTapsRequired = 2;
tapGesture.numberOfTouchesRequired = 1;
[self.scene.view addGestureRecognizer:tapGesture];
}

- (void)handleTapGesture:(UITapGestureRecognizer*)recognizer {
if ([self childNodeWithName:NAME_GAME_BOARD_LAYER]) {
    GameBoardLayer *gameBoardLayer = (GameBoardLayer*)[self childNodeWithName:NAME_GAME_BOARD_LAYER];

    SKNode *node = [Utils nodeAt:[recognizer locationInView:self.view]
                        withName:NAME_BOARD
                   inCurrentNode:gameBoardLayer];

    if ([node.name isEqualToString:NAME_BOARD]) {
        [gameBoardLayer handleDoubleTap:recognizer];
    }

}
}

In my GameBoardLayer Node:

- (void)handleDoubleTap:(UITapGestureRecognizer*)recognizer {
Board *board = (Board*)[self childNodeWithName:NAME_BOARD];
if (isBoardZoomed)
{
    [board runAction:[SKAction scaleTo:1.0f duration:0.25f]];
    isBoardZoomed = NO;
}
else
{
    isBoardZoomed = YES;
    [board runAction:[SKAction scaleTo:1.5f duration:0.25f]];
}
}

Would someone kindly guide me how can i achieve this functionality?

Thanks Everyone.

Haris Hussain
  • 2,531
  • 3
  • 25
  • 38
  • A similar thing can be accomplished with UIViews and UIGestureRecognizers by setting the anchor point of the view when gestures begin and translating the view in the opposite direction to compensate for the change in the anchor point. That way, the anchor point of transformations is under the gesture. The same technique on an SKSpriteNode (which also has a size and anchor point) almost works, but sub-nodes of the transformed sprite node are not correctly translated and everything goes all wrong. Could there be a way around that? – Anthony Mattox Feb 25 '14 at 02:14
  • 1
    This answer http://stackoverflow.com/a/21947549/666588 provides a working solution for this by just calculating a transformation to keep the scene in place. – Anthony Mattox Feb 25 '14 at 23:20

1 Answers1

11

This is how I would do this:

Setup:

  • Create a GameScene as the rootNode of your game. (child of SKScene)
  • Add BoardNode as child to the scene (child of SKNode)
  • Add CameraNode as child to the Board (child of SKNode)
  • Add LetterNodes as children of the Board

Keep Camera node centered:

// GameScene.m
- (void) didSimulatePhysics
{
     [super didSimulatePhysics];
     [self centerOnNode:self.Board.Camera];
}

- (void) centerOnNode:(SKNode*)node
{
    CGPoint posInScene = [node.scene convertPoint:node.position fromNode:node.parent];
    node.parent.position = CGPointMake(node.parent.position.x - posInScene.x, node.parent.position.y - posInScene.y);
}

Pan view by moving BoardNode around (Remember to prevent panning out of bounds)

// GameScene.m
- (void) handlePan:(UIPanGestureRecognizer *)pan
{
    if (pan.state == UIGestureRecognizerStateChanged)
    {
        [self.Board.Camera moveCamera:CGVectorMake([pan translationInView:pan.view].x, [pan translationInView:pan.view].y)];
    }
}

// CameraNode.m
- (void) moveCamera:(CGVector)direction
{
    self.direction = direction;
}

- (void) update:(CFTimeInterval)dt
{
    if (ABS(self.direction.dx) > 0 || ABS(self.direction.dy) > 0)
    {
        float dx = self.direction.dx - self.direction.dx/20;
        float dy = self.direction.dy - self.direction.dy/20;
        if (ABS(dx) < 1.0f && ABS(dy) < 1.0f)
        {
            dx = 0.0;
            dy = 0.0;
        }
        self.direction = CGVectorMake(dx, dy);
        self.Board.position = CGPointMake(self.position.x - self.direction.dx, self.position.y + self.direction.dy);
    }
}

// BoardNode.m
- (void) setPosition:(CGPoint)position
{
    CGRect bounds = CGRectMake(-boardSize.width/2, -boardSize.height/2, boardSize.width, boardSize.height);

    self.position = CGPointMake(
        MAX(bounds.origin.x, MIN(bounds.origin.x + bounds.size.width, position.x)),
        MAX(bounds.origin.y, MIN(bounds.origin.y + bounds.size.height, position.y)));
}

Pinch Zoom by setting the size of your GameScene:

// GameScene.m
- (void) didMoveToView:(SKView*)view
{
    self.scaleMode = SKSceneScaleModeAspectFill;
}

- (void) handlePinch:(UIPinchGestureRecognizer *)pinch
{
    switch (pinch.state)
    {
        case UIGestureRecognizerStateBegan:
        {
            self.origPoint = [self GetGesture:pinch LocationInNode:self.Board];
            self.lastScale = pinch.scale;
        } break;

        case UIGestureRecognizerStateChanged:
        {
            CGPoint pinchPoint = [self GetGesture:pinch LocationInNode:self.Board];
            float scale = 1 - (self.lastScale - pinch.scale);

            float newWidth = MAX(kMinSceneWidth, MIN(kMaxSceneWidth, self.size.width / scale));
            float newHeight = MAX(kMinSceneHeight, MIN(kMaxSceneHeight, self.size.height / scale));

            [self.gameScene setSize:CGSizeMake(newWidth, newHeight)];                
            self.lastScale = pinch.scale;

        } break;

        default: break;
    }
}

What comes to the problem of panning accidentally dragging your LetterNodes, I usually implement a single TouchDispatcher (usually in GameScene class) that registers all the touches. TouchDispatcher then decides which node(s) should respond to the touch (and in which order).

JKallio
  • 903
  • 5
  • 15