02fentym
02fentym

Reputation: 1772

NSNotificationCenter with SpriteKit

I am using NSNotificationCenter to notify when keys are pressed on the keyboard. When moving between scenes, if an additional key is pressed too quickly after the key that causes the scene transition is pressed the app crashes. I'm not sure if it's the previous scene that no longer receives notifications or if the next scene's observer for notifications is not set up. What can I do to stop this from happening? Here's the code for two different scenes and the custom view that handles the notifications. Essentially, I'm posting a notification for key presses in CustomSKView and then I handle the presses in the respective scenes in a method called keyPressed: that is not listed here.

LevelSelectScene.m

@implementation LevelSelectScene

-(void)didMoveToView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyPressed:) name:@"KeyPressedNotificationKey" object:nil];

    //perform scene setup here
    ...
}

-(void)willMoveFromView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:@"KeyPressedNotificationKey"
                                              object:nil];

    //perform additional cleanup before moving to next scene
    ...

}

Menu.m

-(void) didMoveToView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyPressed:) name:@"KeyPressedNotificationKey" object:nil];

    //perform menu setup here
    ...
}


-(void) willMoveFromView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:@"KeyPressedNotificationKey"
                                              object:nil];

    //perform additional cleanup before moving to next scene
    ...
}

CustomSKView.m

#import "CustomSKView.h"

@implementation CustomSKView:SKView {

}

- (id) initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    return self;
}

- (void) keyDown:(NSEvent *)theEvent {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"KeyPressedNotificationKey"
                                                        object:nil
                                                      userInfo:@{@"keyCode" : @(theEvent.keyCode)}];
}

@end

EDIT: Stack Trace

2015-08-15 05:47:08.199 PianoKeyboardTest[21854:4643404] -[NSPathStore2 keyPressed:]: unrecognized selector sent to instance 0x10050d110
2015-08-15 05:47:08.199 PianoKeyboardTest[21854:4643404] -[NSPathStore2 keyPressed:]: unrecognized selector sent to instance 0x10050d110
2015-08-15 05:47:08.200 PianoKeyboardTest[21854:4643404] (
0   CoreFoundation                      0x00007fff8575803c __exceptionPreprocess + 172
1   libobjc.A.dylib                     0x00007fff9227376e objc_exception_throw + 43
2   CoreFoundation                      0x00007fff8575b0ad -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3   CoreFoundation                      0x00007fff856a0e24 ___forwarding___ + 1028
4   CoreFoundation                      0x00007fff856a0998 _CF_forwarding_prep_0 + 120
5   CoreFoundation                      0x00007fff8571445c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
6   CoreFoundation                      0x00007fff85604634 _CFXNotificationPost + 3140
7   Foundation                          0x00007fff83e8e9d1 -[NSNotificationCenter postNotificationName:object:userInfo:] + 66
8   PianoKeyboardTest                   0x000000010001d50e -[CustomSKView keyDown:] + 270
9   AppKit                              0x00007fff8ba1c11b -[NSWindow _reallySendEvent:isDelayedEvent:] + 5452
10  AppKit                              0x00007fff8b3add76 -[NSWindow sendEvent:] + 470
11  AppKit                              0x00007fff8b3aa9b1 -[NSApplication sendEvent:] + 4199
12  AppKit                              0x00007fff8b2d3c68 -[NSApplication run] + 711
13  AppKit                              0x00007fff8b250354 NSApplicationMain + 1832
14  PianoKeyboardTest                   0x0000000100005322 main + 34
15  libdyld.dylib                       0x00007fff8f1ee5c9 start + 1
)

EDIT: Solution

Here are the changes that I made to CustomSKView.

#import "CustomSKView.h"

@implementation CustomSKView:SKView {
    // Add instance variables here

}

- (id) initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        // Allocate and initialize your instance variables here

    }
    return self;
}

- (void) keyDown:(NSEvent *)theEvent {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"KeyPressedNotificationKey"
                                                        object:nil
                                                      userInfo:@{@"keyCode" : @(theEvent.keyCode)}];
}

//overridden version of SKScene's presentScene: transition: method
-(void) presentScene:(SKScene *)scene transition:(SKTransition *)transition {
    [[NSNotificationCenter defaultCenter] removeObserver:self.scene
                                                    name:@"KeyPressedNotificationKey"
                                                  object:nil];
    [super presentScene:scene transition:transition];
}

@end

Upvotes: 2

Views: 266

Answers (2)

0x141E
0x141E

Reputation: 12753

To remove an observer from the current SKScene whenever the game transitions to a new scene, override the presentScene method in your custom view class, remove the observer, and then call the super class's presentScene:

- (void) presentScene:(SKScene *)scene transition:(SKTransition *)transition {
    [[NSNotificationCenter defaultCenter] removeObserver:self.scene
                                                    name:@"KeyPressedNotificationKey"
                                                  object:nil];
    [super presentScene:scene transition:transition];
 }

Upvotes: 1

trojanfoe
trojanfoe

Reputation: 122391

The issue appears to be timing. You need to remove the notification before the transition starts, however SKView does not provide a convenient hook for this.

One possible way of managing this is to subclass SKView to provide the mechanism to add and remove the observers, with the possibility of using multiple notifications; one for keystrokes and the other for transition start/ends. When a transition start notification is fired, this subclass will remove the keystroke observer. When the transition has finished it could be notified to re-observe keystrokes. However this does sound complicated.

Upvotes: 1

Related Questions