doublea
doublea

Reputation: 2576

How should SKNode subclasses communicate with parent Scenes and sibling nodes?

How should a SKNode subclasses notify parent Scenes when events have taken place?

I have a SKNode subclass that handles touch events by redrawing or removing the Node as appropriate, based on the game logic. What are the iOS best practices for notifying parent scenes, (or other sibling SKNode instances) that the event has taken place? Should I be using NSNotification center? Some form of delegation? I want to avoid tight coupling. I can think of a couple ways to get it done but wondering if there are standard practices, as there seem to be for non-game MCV iOS designs (e.g. NSNotification for communication between models, or model -> controller, and delegation or target-action for view -> controller, etc.)

Conceptually, I like the idea of the SKNode subclass essentially serving the role of a 'View', and interpreting the touch event, then communicating 'semantic' game information to the containing scene or other nodes.

// SampleScene.m

#include "SampleScene.h"
#include "SampleBallNode.h"

@implementation SampleScene
- (void)didMoveToView:(SKView *)view {
  if (!self.contentCreated) {
    [self createSceneContents];
    self.contentCreated = YES;
  }
}

- (void)createSceneContents {
  self.backgroundColor = [SKColor whiteColor];
  self.scaleMode = SKSceneScaleModeAspectFit;
  [self addBall];
}

- (void)addBall {
  SampleBallNode *ball = [[SampleBallNode alloc] init];
  ball.position = CGPointMake(100, 100);
  [self addChild:ball];
}

@end

// SampleBallNode.m

#include "SampleBallNode.h"

- (id)init {
  if ([super init]) {
    self.name = @"ball";
    self.userInteractionEnabled = YES;
    [self drawSelf];
  }
  return self;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  [self removeFromParent];

  // TELL SCENE THAT I'VE DIED HERE... TO UPDATE SCORE OR SOMETHING...
}

- (void)drawSelf {
  UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect ...

  SKShapeNode *tokenBall = [[SKShapeNode alloc] init];
  tokenBall.path = ovalPath.CGPath;
  [self addChild:tokenBall];
}

@end

Upvotes: 1

Views: 2666

Answers (3)

93sauu
93sauu

Reputation: 4117

This it is an example using an SKSpriteNode subclass. Its delegate BallDelegate has 4 optional methods. If you want some required method, you can add the method without optional

@objc protocol BallDelegate {
    @objc optional func ball(ball:Ball, touchesBegan touches: Set<UITouch>, withEvent event: UIEvent?)
    @objc optional func ball(ball:Ball, touchesMoved touches: Set<UITouch>, withEvent event: UIEvent?)
    @objc optional func ball(ball:Ball, touchesEnded touches: Set<UITouch>, withEvent event: UIEvent?)
    @objc optional func ball(ball:Ball, touchesCancelled touches: Set<UITouch>?, withEvent event: UIEvent?)
}

class Ball: SKSpriteNode {

   var delegate:BallDelegate?


   override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?){
        self.delegate?.ball?(self, touchesBegan: touches, withEvent: event)
   }

   override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
        self.delegate?.ball?(self, touchesMoved: touches, withEvent: event)
    }

   override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        self.delegate?.ball?(self, touchesEnded: touches, withEvent: event)
   }

   override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
       self.delegate?.ball?(self, touchesCancelled: touches, withEvent: event)
   }
}

If you want more information you can see the Swift's documentation:

The Swift Programming Language (Swift 2.2)

Upvotes: 0

AwDogsGo2Heaven
AwDogsGo2Heaven

Reputation: 972

I'm unaware of anything in the documentation, however I personally use NSNotification center because it has a very loose coupling, and I separate my game logic and view. This worked out very well for me because I originally began my project in cocos2d and moved it to spritekit.(I'll leave the reasons why out of here). But the transition was very easy because of my use of it.

I had my nodes 'watch' for certain posted notifications from the game model, and have them respond or update accordingly. For using interaction, I would have it post back, and the model would watch for the corresponding notifications.

Upvotes: 1

CodeSmile
CodeSmile

Reputation: 64477

For something like touches and input in general I would avoid handling those in each individual instance. Not just because it's inefficient, you also have no easy way to decide who receives a touch and how touches should propagate to other nodes.

Simple case: two overlapping nodes - do you only want the topmost to be removed, or just any one that's touched, or all at the location of the touch? In this case it's better to receive input at a central location and then iterate over "touchable" nodes.

Other than that:

  • use delegation when otherwise you would have tight coupling by sending a message directly to another object
  • use notification if you have multiple potential delegates who might want to react to the event, and that event isn't happening too often (ie definitely not every frame)
  • use singletons when you're like totally lazy and don't care

Conceptually you'll find good examples and solutions in Kobold Kit via behaviors, which are a form of game components. Also includes a per-node model storage class KKModel, which allows you to store things like score in any node without having to add a subclass just for variables.

For example:

[self.scene.model setDouble:score forKey:@"score"];

You can then just use the update method in a label class to get the score:

double score = [self.scene.model doubleForKey:@"score"];

Avoids the need for messaging, delegation, notification while compromising (a little) on efficiency.

Upvotes: 7

Related Questions