In my cocos2d game I have a "Settings" button which launches a modal layer and is meant to lock everything under it. To do this, I use a combination of a menu status method which locks all CCMenuItems and I use a coverlayer; both of which are below in code.
The problem is that neither solution seems to work on CCScrollLayers. When I click the button (which launches the modal) the CCScrollLayer can still be scrolled which is not what i want.
I'd like to:
- Press a button disabled ALL touches and disable all elements including CCScrollLayers
- Launch the modal (allow touches for the modal only)
I've tried:
- Using Touch to swallow all touches using the
CCTargetedTouchDelegate
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
- I've tried
self.isTouchEnabled = NO
on the layer launching the modal
- I've tried adapting the
MenuStatus
method so that it works for CCScrollLayers but it does not seem to work.
I'm not sure what I'm doing wrong. My code now follows.
// My main layer which launches the Settings Modal Layer
#pragma mark - Lock/Unlock layers
-(void) doSettings
{
[self lockLayers];
SettingsModalLayer *sml = [[[SettingsModalLayer alloc] init] autorelease];
[sml showSettingsOnLayer:self closeBlock:^{[self unlockLayers];}];
}
-(void) lockLayers
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
[self MenuStatus:NO Node:self];
}
-(void) unlockLayers
{
[self MenuStatus:YES Node:self];
}
// Disabled/Enable layers
-(void) MenuStatus:(BOOL)_enable Node:(id)_node
{
for (id result in ((CCNode *)_node).children) {
if ([result isKindOfClass:[CCMenu class]]) {
for (id result1 in ((CCMenu *)result).children) {
if ([result1 isKindOfClass:[CCMenuItem class]]) {
((CCMenuItem *)result1).isEnabled = _enable;
}
} // next
}
} // next
}
-(void) registerWithTouchDispatcher {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES];
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"Event: %@", event);
for( UITouch *touch in touches )
{
CGPoint location = [touch locationInView: [touch view]];
location = [[CCDirector sharedDirector] convertToGL: location];
CCLayer *gl = (CCLayer *)[self getChildByTag:4];
[gl setIsTouchEnabled:NO];
}
}
-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
}
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
[self removeFromParentAndCleanup:YES];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
return YES;
}
// Settings Modal Layer
-(void) showSettingsOnLayer:(CCLayer *)layer closeBlock:(void (^)())noBlock
{
CoverLayer *coverLayer = [[CoverLayer alloc] init];
[layer addChild:coverLayer z:1000];
[coverLayer runAction:[CCFadeTo actionWithDuration:kAnimationTime opacity:155]]; // smooth fade-in to dim with semi-transparency
... // Other stuff goes here
}
// CoverLayer
// This is meant to stop all touches, but it doesn't really work on CCScrollLayer
#define kDialogTag 1234
#import "CoverLayer.h"
// class that implements a black colored layer that will cover the whole screen
// and eats all touches except within the dialog box child
@implementation CoverLayer
- (id)init {
self = [super init];
if (self) {
[self initWithColor:ccc4(0,0,0,0)
width:[CCDirector sharedDirector].winSize.width
height:[CCDirector sharedDirector].winSize.height];
self.isTouchEnabled = YES;
}
return self;
}
- (void)dealloc {
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
[super dealloc];
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLocation = [self convertTouchToNodeSpace: touch];
CCNode *dialogBox = [self getChildByTag: kDialogTag];
// eat all touches outside of dialog box
return !CGRectContainsPoint(dialogBox.boundingBox, touchLocation);
}
You just need to understand how priority works with CCTouchDispatcher. Layer which has minimum priority value will receive the touch events first. Now you just need to adjust the priorities accordingly.
Create a blocking layer class and set its priority to minimum when registering with CCTouchDispatcher and override ccTouchBegan and just return YES in it.
If you take a look at the CCMenu class you will notice default priority is kCCMenuTouchPriority = -128, that's the reason menu items have higher touch priority.
The layer that swallows all the touch events needs to be registered with more priority than any underlying controls. Usually these would be menu items, with a default priority of kCCMenuHandlerPriority = -128 (lowest values are handled first).
The swallowing layer then simply handles any touches it receives, do nothing.
Any controls on your popup then need to be prioritised before the swallowing layer, so if you are using menus you need to set the new priority. These touches will then be handled by the item first (and not the swallowing layer).
Relevant functions showing registering the swallowing layer with priority -1024, handling (ignoring) all touches, and adding a menu item with more priority than the swallowing layer:
the poor mans way of doing it : Add a ccmenuitemsprite and ccmenu with the ccsprite opacity set to 0 covering whatever you dont want clicked. works for me when i just dont want to get messy with the touch dispatcher.